[笔记分享] [中断] 中断申请释放以及上下半部

来源:互联网 发布:安装包 制作软件 编辑:程序博客网 时间:2024/05/21 10:18

1.1 介绍
Linux将中断分为中断上半部和下半部。上半部用来处理紧急的和硬件操作相关的,此时所有当前中断线都被禁止,包括其它CPU。下半部用来处理能够被允许推迟完成的中断处理部分,此时中断是开启的。上下半部之间的界限依情况划分。
而异常和中断不同,必须考虑时钟的同步,也称同步中断,如除0、缺页等。这里我们只讨论异步中断。
1.2 中断注册/释放
1.2.1 注册函数 request_irq()
这里写图片描述
irq:要分配的中断号
handler: 中断处理函数,中断触发时会被调用
flags:处理中断标志,比较常用的是IRQF_DISABLED、IRQF_SHARED,IRQF_ONESHOT这几种,IRQF_ONESHOT后面再解释。IRQF_DISABLED表示进入中断处理程序时禁止任何中断,包括其它CPU,默认只是屏蔽当前对应所有CPU的中断线。IRQF_SHARED表示中断线是共享的,也就是说同一个中断线上会对应多个中断处理程序,在中断处理程序中,通过第5个参数dev来判断到底是哪个发生了中断。。
name:中断名。会被/proc/irq和/proc/interrupt使用。
dev: 中断共享时用到,用来判断是哪个中断触发。

request_irq()会导致睡眠,因为其在注册过程中调用了kmalloc(),而这个函数是会引起睡眠的。因此不能用于中断上下文中或者其他不允许阻塞的地方。
1.2.2 注册函数 request_threaded_irq()
考虑到硬中断占用时间问题以及软中断/tasklet的使用死锁及debug问题,内核又新增了一个带线程处理功能的中断接口。它将不重要的工作放到线程处理函数中作了(有点像work queue不是吗)。在未来,有可能tasklet 会被其替代掉。
接口定义:
这里写图片描述
除了thread_fn,其他的参数和request_irq()中的意义一样。
handler: 中断处理函数,不要可以传NULL。
thread_fn: 中断线程处理函数,不需要可以传NULL。

handler 和 thread_fn 两个可以同时定义,或者只用其中一个。当同时定义时,中断处理函数必须要返回值为IRQ_WAKE_THREAD,thread_fn才能被调用到。thread_fn可以返回IRQ_NONE或者IRQ_HANDLED。
之前有提到一个flag: IRQF_ONESHOT。它表示当中断线程处理函数执行完成以后才开启中断。另外,IRQF_ONESHOT和IRQF_SHARED是不能共用的,因为IRQF_SHARED不能关闭中断。
当中断一直被触发时,中断线程处理函数会得不到运行,因此flag可以加上IRQF_ONESHOT。

1.2.3 释放函数 free_irq()
这里写图片描述
如果中断是共享的,则仅删除dev_id所对应的处理程序,直到最后一个中断被删除的时候才禁用此中断线。
1.2.4 中断处理函数
格式如下:
这里写图片描述
其返回类型为irqreturn_t,有如下三种:
这里写图片描述
当检测到中断正常时返回IRQ_HANDLED,否则返回IRQ_NONE,如果需要wakeup中断线程时,返回IRQ_WAKE_THERAD。
另外,中断处理程序是不用考虑重入的,当一个中断处理程序运行时,相同中断线上的中断都会被屏蔽。不过这时其他线上的中断是被打开的,这样使得更高优先级的中断能被处理。
1.3 中断上下文
和进程上下文类似,当内核在执行一个中断处理程序或者下半部时,内核处于中断上下文。在进程上下文中,进程可以通过current宏关联到当前进程,也可以睡眠,也可以调度程序。但是在内核上下文中,和进程没什么关系,和current宏也不相关,不能睡眠,不然谁又知道什么时候能唤醒如何唤醒呢?因此那些会引起休眠的函数如printk是不能放在中断上下文中的!
进程有各自的内核栈,一般为两页大小,以前中断和其共用内核栈,必须要非常节省。现在进程的内核栈缩小为一页,而中断又有了自己的中断栈(全部中断共用一页),平均比使用内核栈要大得多。但是要注意的是在内核中要尽量少使用栈,否则可能导致益处。
1.4 中断处理机制实现
设备产生中断发送电信号给中断控制器  中断控制器对应中断线激活的话把中断发给处理器  处理器跳到中断向量表定义的位置,中断号被保存在寄存器中  调到内核的do_IRQ()判断该中断线是否有注册中断处理程序,有就执行
1.5 中断控制
1.5.1 禁止本地中断
当我们需要控制中断内的数据要同步时,我们可以控制中断的禁止或者禁止内核抢占来实现。关于数据的同步,SMP的数据保护问题我们将在后面内核同步章节中讲到,这里我们只讨论如何禁止中断。
禁止和使能本地中断函数如下:
这里写图片描述
宏的实现和平台相关。注意,调用这两个函数有潜在的危险,如下例子:

local_irq_disable();/*禁止中断时执行的代码*/local_irq_enable();

假如在local_irq_disable()之前中断是被禁止的,之后又调用了local_irq_enable(),结果中断被enable了!
因此,我们用保存中断以前的中断状态的方法。在禁止中断之前保存中断系统的状态,然后在激活中断时,将中断恢复到原来的状态就可以了。函数如下:
这里写图片描述
这两个函数也和体系结构相关。注意,对local_irq_save()和local_irq_restore()的调用必须在同一函数中进行(flags包含中断系统的状态,不能传递给另外一个函数)。

1.5.2 禁止指定中断线
上述禁止本地CPU所有中断,有时候可能只需要禁止某条中断线就可以了。
这里写图片描述
一般使用enable_irq/disable_irq这两个函数,最好成对调用,如果调用了两次disable_irq(),那么enable_irq()两次才能enable中断。
如果是在中断处理函数中调用,请一定要使用disable_irq_nosync()函数,否则会导致死掉。
disable_irq: 当前中断处理程序执行完之后才能返回。
disable_irq_nosync: 直接关闭中断不等中断处理程序执行完就返回。
1.5.3 判断中断状态
有时我们需要判断当前是否处于中断状态,这时下面这几个宏可以派上用场了:
这里写图片描述
in_irq():判断是否在执行中断处理程序。
in_softirq():判断是否处于下半部
in_interrupt():判断是否正在执行中断处理程序或下半部
其实本质都是通过preemmpt_count来判断,看下面实现就明白了。注意preempt_count还有一部分是用来对内核是否可抢占进行计数的。
这里写图片描述

1.6 中断下半部
相对来说,下半部处理可往后推迟的事情。如网卡驱动在硬中断部分从网卡接收数据,然后在下半部对网卡数据进行处理。
有三种机制实现下半部: 软中断、tasklet和工作队列。tasklet通过软中断实现,而工作队列和它们完全不同。
a) 软中断
软中断是在编译时静态分配的,不能像tasklet那样进行动态分配。可以在所有处理器上同时执行,即使两个类型相同也可以。由softirq_action 结构表示,如下:
这里写图片描述
action()为软中断处理函数,另外还定义了一个包含32个该结构体的数组:
这里写图片描述
软中断类型列表如下:
这里写图片描述
0优先级最先运行,依次排列。注册方法如下:
这里写图片描述
其实也就是将软中断处理函数加入到softirq_vec中去。举例:
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
每个被注册的软中断占据一项。
一个注册好的软中断必须要触发才能执行,通常在中断处理程序返回前标记软中断(rasie_softirq()),使其稍后被处理。待处理的软中断在以下地方被检查:
1. 从一个硬件中断代码处返回时
2. 在ksoftirqd内核线程中
3. 显示检查中
不管用什么方法,软中断最终都需要调用do_softirq()执行。

一个软中断不会抢占另一个软中断,唯一可抢占它的只有中断处理程序,因为在处理软中断处理程序的时候,中断是开启的,但它和中断处理程序一样不能休眠。本地软中断被禁止,但是其他处理器上可执行软中断,甚至是同类的中断,那么处理函数就会被执行两次,数据就会遭到破坏了。因此如果没必要,我们就用tasklet, 它的同一个处理函数不会在两个处理器上同时运行,这样也就避免了加锁的麻烦。
b) tasklet
tasklet基于软中断,虽然很相似,但接口更简单,锁保护要求也低,通常我们选择使用tasklet。只有那些执行频率很高的或者连续性要求很高的才用软中断。
Tasklet由两类软中断代表: HI_SOFTIRQ和 TASKLET_SOFTIRQ。两者区别在于前者优先级高先执行而已。Tasklet结构如下:
这里写图片描述
Next:将注册的tasklet给链接起来。
State:有三种,TASKLET_STATE_SCHED表明已被调度,准备投入运行;TASKLET_STATE_RUN表明正在运行。
Count:是tasklet引用计数器,0表示被激活,设置为挂起,这样才能运行。不为0表示被禁止,不许执行。

tasklet只有一个同一类别的tasklet会被执行,甚至是在不同处理器上,但是允许不同类别的tasklet执行。
使用tasklet之前,需要创建一个task_struct类型变量,内核提供了静态和动态两种方法。其实都一个意思,静态是内核给你分配了一个task_struct变量,而动态是要你自己分配一个task_struct变量,然后传给task_init()接口去初始化。
使用举例:

struct tasklet_struct mytasklet;void mytasklet_handler(unsigned long data){    printk(“this is tasklet handler!\n”);}irqreturn_t myirq_hanlder(int irq, void *dev){    /*do sth*/tasklet_schedule(&mytasklet);}void driver_init(void param){    tasklet_init(&mytasklet, mytasklet_handler, NULL);}

c) work queue
工作队列相对前两者具备的优势是可以睡眠,因为它是在进程上下文工作。从本质上来说,工作队列是由专门的work thread去完成的,因此其造成的开销也是最大,因为要牵扯到内核线程甚至上下文切换。
所以,当你的工作任务是需要休眠的,那就选择work queue,否则就选择tasklet。
对于work queue的原理,可以参考另外一份文档。(work_queue_in_linux2.3.36.doc)
work_struct定义:
这里写图片描述
data:传递给函数的参数,现在的机制是直接传递真个struct work_struct,所以data 也不需要再单独传了。
entry: 连接所有工作链表,链接在一起的链表形成工作队列,work thread被唤醒时执行链表上的所有工作,执行完后就删除相应的work struct。
func: 处理函数

针对work queue,系统有两种工作方法:
1. 默认使用共享work thread来执行工作,这种情况下,每个工作任务需要尽可能快地完成任务,否则其他任务会在队列上等待而得不到执行。
2. 创建自己的工作队列并添加任务。本质上是创建了自己的work thread来执行任务。

work queue的初始化也分静态和动态两种,和tasklet一个效果。
共享工作队列使用举例:

struct work_struct my_work_struct;void my_work_struct_handler(struct work_struct *work){    printk(“work struct handler\n”);}void driver_init(void *param){INIT_WORK(&my_work_struct, my_work_struct_handler);}irqreturn_t myirq_hanlder(int irq, void *dev){    /*do sth*/schedule_work(&my_work_struct);

或者

schedule_delayed_work(&my_work_struct);

单独工作队列使用举例:

struct work_struct my_work_struct;struct workqueue_struct *my_work_queue;void my_work_struct_handler(struct work_struct *work){    printk(“work struct handler\n”);}void driver_init(void *param){    my_work_queue = create_workqueue(“my_work_queue”);INIT_WORK(&my_work_struct, my_work_struct_handler);}irqreturn_t myirq_hanlder(int irq, void *dev){    /*do sth*/queue_work(my_work_queue, &my_work_struct);}

1.7 dts中添加中断
如果要想使用dts来获取中断号,可以参考如下例子使用方法:
这里写图片描述
其中, interrupt-parent表明参数按照msmgpio的设置, interrupts指明中断num和flags, 看了下code, flags一般都是直接code中指定。
上面这两个property是必须的。
然后你在code中获取irq gpio number,这里是通过taos,irq-gpio这个property获得。
msmgpio interrupt controller定义:
这里写图片描述
定义说明:

这里写图片描述

参考:
http://www.ibm.com/developerworks/cn/linux/l-tasklets/
《Linux内核设计与实现》
http://blog.chinaunix.net/uid-9688646-id-4052595.html
http://lwn.net/Articles/302043/
http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=21977330&id=3755609
http://www.zhihu.com/question/20055228/answer/14061827
http://blog.csdn.net/lzy_gym/article/details/7672193

                                                    Kris Fei                                                    2014/04/24
阅读全文
1 0
原创粉丝点击