一、基础知识 中断控制:是计算机发展中一种重要的技术。最初他是为克服对I/O接口控制采用的程序查询所带来的处理器效率低而产生的的;
中断系统:外部中断(硬件中断)----------------对于外部设备而言
内部中断(异常中断)----------------为了解决机器运行时所出现的某些随机事件发生。又分为故障和陷进,他们共同的特点是既不使用中断控制器,也不能屏蔽中断。
Intelx86系列微机共支持256种向量中断,为使处理器较容易识别每种中断源,将他们从0--255进行编号,该编号叫做中断类型号。中断的入口地址叫做中断向量。
linux 对于256个响亮的分配如下:
- 从0-31的向量对应于异常和非屏蔽中断
- 从32-47的向量分配给屏蔽中断
- 剩余的从48-255的向量用来表示标识软中断。
cat /proc/interrupts 查看当前系统中各种外设的IRQ命令。
二、外设可屏蔽中断和中断描述符表
intel x86通过两片中断控制器8259A来响应15个外部中断源,每个8259A可管理8个中断源。第一级的第二个中断控制器相连的每条线叫做中断线,要使用中断线就要进行申请,也就是IRQ,因此也常把申请一条中断线称为申请一个IRQ或者申请一个中断号。
在实模式下,CPU将0开始的1KB用来存储中断向量表。表中的每个表项占用4个字节,由两个字节的段地址和两个字节的偏移量组成。但是对于保护模式,中断向量表中的表项由8个字节构成。中断向量表也叫做中断描述符表IDT。每个表项叫做一个门:中断门、陷阱门、系统门
中断门:其类型码:110,中断门包含了一个中断或异常处理程序所在段的选择符和段内的偏移量。当控制权通过中断门进入中断处理程序时,处理器清IF标志,即关中断。以避免中断发生。中断门中的请求特权级DPL为0,因此用户态进程不能访问Intel的中断门,所有的中断处理程序都由中断门激活,并且全部限制在内核态
陷阱门:其类型标志:111,与中断门类似。其唯一的区别是:控制权通过陷阱门进入处理程序时维持I标志位不变,即不关中断
系统门:门描述符的特权级为3,系统调用是通过系统门进入内核态的。
三、中断请求队列
由于硬件的限制,很多外部设备不得不共享中断线,例如:一个PC配置可以把同一条中断线分配给网卡和图形卡。在Linux中,专门为每一个中断请求IRQ设置了一条队列,这就是所谓的中断请求队列。
1、 中断服务程序与中断处理程序:在Linux中有15条中断处理程序,其名:IRQ0x00_interrupt()...., 中断处理程序相当于某个中断向量的总处理程序。
- typedef irqreturn_t (*irq_handler_t)(int,void *)
- struct irqaction
- {
- irq_handler_t handler ;
- unsigned long flags;
- cpumask_t mask;
- const char * name;
- void *dev_id;
- struct irqaction *next;
- int irq;
- }
2、注册中断服务程序
在中断描述符表初始化完成之后,每个中断服务队列还是空。因此即使打开某个中断且某个外设真的发生中断也得不到实际的服务。因为具体的中断服务程序并没有挂入中断请求队列。所以,在设备驱动程序的初始化阶段,不需通过request_irq()将响应的中断服务程序挂如中断请求队列。即进行注册。
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char * devname , void *dev_id)
irq:表示要分配的中断号
handler:指向处理这个中断的实际中断服务程序。只要操作系统一接受到中断,该函数会被调用。
irqflags:为0、IRQF_SAMPLE_RANDOM、IRQF_SHARED、IRQF_DISABLED 这几个标志位的掩码。
devname:与中断相关的设备名。
dev_id:主要用于共享中断线。
3、注销中断服务程序:卸载驱动时,需要注销相应的中断服务程序,并且释放中断线。使用void free_irq( unsigned int irq, void * dev_id) 来释放中断线。
interrupt.c
- #include<linux/module.h>
- #include<linux/init.h>
- #include<linux/interrupt.h>
- #include<linux/kernel.h>
- #include<linux/moduleparam.h>
- static int irq;
- static char *interface;
- static int count=0;
- module_param(irq,int,0644);
- module_param(interface,charp,0644);
- static irqreturn_t interrupt_handle(int irq, void * dev_id)
- {
- static long interval=0;
- if(count==0)
- interval = jiffies;
- interval = jiffies - interval;
- printk(KERN_EMERG"The interval between two interrupt is %ld\n",interval);
- interval = jiffies;
- count++;
- return IRQ_NONE;
- }
- static int __init interrupt_init(void)
- {
- if(request_irq(irq,&interrupt_handle,IRQF_SHARED,interface,&irq)){
- printk(KERN_ERR"Fail to register IRQ %d\n",irq);
- return -EIO;
- }
- printk(KERN_EMERG" %sRequest on IRQ %d succeeded\n",interface,irq);
- return 0;
- }
- static void __exit interrupt_exit(void)
- {
- printk(KERN_EMERG"the %d interrupts happened on irq %d",count,irq);
- free_irq(irq,&irq);/*释放中断线*/
- printk(KERN_EMERG"free IRQ %d, bye\n",irq);
- }
- MODULE_LICENSE("GPL");
- module_init(interrupt_init);
- module_exit(interrupt_exit);
- obj-m:=interrupt.o
- KER:=/usr/src/linux-headers-$(shell uname -r)/
- all:
- make -C $(KER) M=$(shell pwd) modules
- clean:
- make -C $(KER) M=$(shell pwd) clean
四、中断的下半部处理机制 中断服务成一般都在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,不能关中断太长时间,CPU就不能及时处理其他的中断请求,从而造成中断的丢失,因此内核的目标就是尽可能的快处理完中断请求,尽可能的把更多的处理向后推迟。因此内核将中断分为:上半部和下半部。
1、小任务机制:是只对于要推迟执行的函数进行组织的一种机制。数据结构:
- struct tasklet_struct {
- struct tasklet_struct *next;
- unsigned long state ;
- atomic_t count;
- void(*func)(unsigned long);
- unsigned long data;
- }
批注:func域的就是下半部中要推迟执行的函数,data是他传递的参数
state域的取值为TASK_STATE_SCHED或TASK_STATE_RUN。其中第一个表示小任务已经被调度,等待执行。对于第二个表示小任务正在执行,他主要针对于多处理器(SMP)使用的。
count表示小任务的引用计数器,如果不是0则小任务禁止,不允许执行;只有当它为0时,小任务才能被激活,并且在被设置为挂起时,小任务才能够执行。
- 声明和使用小任务机制: 对于小任务来讲可以动态创建也可以静态创建
静态创建:DECLARE_TASKLET(name,func,data) 对于count的初值为0,该任务处于激活状态
DECLARE_TASKLET_DISABLE(name,func,data) 对于count的初值为1,该任务处于禁止状态 - 编写自己的小任务处理程序:函数格式满足 void function_name(unisgned long data)。由于小函数不能够睡眠,因此不能在小任务中使用信号量或者其他的产生阻塞的操作
- 调度小任务:通过使用函数tasklet_schedule进行调度小任务 :tasklet_schedule(tasklet_struct * ),其中如果希望小任务被禁止:tasklet_disable(tasklet_struct * tasklet ) 其中如果希望小任务被激活:tasklet_enable(tasklet_struct * tasklet )
小任务的简单应用:- #include<linux/module.h>
- #include<linux/fs.h>
- #include<linux/cdev.h>
- #include<linux/kdev_t.h>
- #include<linux/kernel.h>
- #include<linux/interrupt.h>
- #include<linux/init.h>
- static struct tasklet_struct my_tasklet;
- static void tasklet_handler(unsigned long data)
- {
- printk(KERN_ALERT"tasklet_handler is running\n");
- }
- static int __init test_init(void)
- {
- tasklet_init(&my_tasklet,tasklet_handler,0);
- tasklet_schedule(&my_tasklet);
- return 0;
- }
- static void __exit test_exit(void)
- {
- tasklet_kill(&my_tasklet);
- printk(KERN_ALERT"test_exit running\n");
- }
- MODULE_LICENSE("GPL");
- module_init(test_init);
- module_exit(test_exit);
- obj-m:=newtasklt.o
- KDIR:=/usr/src/linux-headers-$(shell uname -r)/
- all:
- make -C $(KDIR) M=$(shell pwd) modules
- clean:
- make -C $(KDIR) M=$(shell pwd) clean
2、工作队列 :是另一种将工作推后执行的工作形式,它和前面讨论的所有其他形式有所不同。工作队列可以把工作推后,交由一个内核线程进行执行。工作队列允许重新调度甚至睡眠。
- 工作:我们将推后执行的任务叫做工作。描述他结构体是work_struct,这些工作以队列的形式组织成工作队列。其数据结构为workqueue_structs,而工作线程就是负责执行就绪队列中的工作。系统默认工作线程是events,自己也可以创建
在linux/workqueue.h中的数据结构:work_struct
- struct work_struct{
- unsigned long pengding;/*这个工作队列正在等待处理吗*/
- struct list_head entry;/*工作的链表*/
- void (*func)(void*);/*要执行的函数*/
- void *data;/*传递函数的参数*/
- void *wq_data;/*内部使用*/
- struct timer_list timer;/*延迟的工作队列所用到的寄存器*/
- }
这些结构体被连接成链表。当一个工作线程被唤醒时,它会执行它的链表上的所有工作。工作执行完后,相应的结点会被删除,当链表中不再有对象时,工作线程会继续睡眠。
- 创建推后工作:DECLARE_WORK(name,void(*func)(void *), void *data)----静态创建一个名为 name,待执行函数为func,参数为data的work_struct结构。INIT_WORK(name, void (*func)(void *) ,void *data) -----动态创建一个推后工作
- 工作队列中待执行的函数: void work_handler(void * data);该函数由工作线程进行执行,因此函数运行在进程的上下文中。默认情况下允许中断、并且不持有任何锁。如果需要,函数可以睡眠。批注:尽管该函数运行在进程的上下文中,但是他不能够访问用户空间,因为内核线程在用户空间没有相应的映射。通常在系统调用发生时,内核代表用户空间的进程运行,此时他才能访问用户空间。对于线程来讲mm_struct =NULL ,但是对于线程或者进程一旦调度他的PCB中有一个字段--struct mm_struct * mm * active不能为空。进程的active=mm,线程的active则借鉴了前一个调度的进程的active;
- 对工作队列的调度:schedule_work(&work);
schedule_delayed_work(&work);/*经过一段延迟后再执行*/
工作队列的简单应用:
- #include<linux/module.h>
- #include<linux/init.h>
- #include<linux/workqueue.h>
- static struct workqueue_struct *queue = NULL;
- static struct work_struct work;
- static void work_handler(struct work_struct *data)
- {
- printk(KERN_ALERT"work handler function \n");
- }
- static int __init test_init(void)
- {
- queue = create_singlethread_workqueue("hellow world");
- if(!queue)
- goto err;
- INIT_WORK(&work,work_handler);
- schedule_work(&work);
- return 0;
- err:
- return -1;
- }
- static void __exit test_exit(void)
- {
- destroy_workqueue(queue);
- }
- MODULE_LICENSE("GPL");
- module_init(test_init);
- module_exit(test_exit);
- obj-m:=Workqueue.o
- KDIR:=/usr/src/linux-headers-$(shell uname -r)/
- all:
- make -C $(KDIR) M=$(shell pwd) modules
- clean:
- make -C $(KDIR) M=$(shell pwd) clean
理解:为什么小任务机制不能够睡眠,而工作队列可以睡眠和阻塞?
对于小任务被调度以后,它还没有得到运行机会之前,如果有一个相同的任务又被调度了,那么他仍然只能够运行一次。
对于工作队列,它可以把工作推后--主要是由另一个线程执行。
阅读(281) | 评论(0) | 转发(0) |