[笔记分享] [中断] 中断申请释放以及上下半部
2017-08-20 23:21
260 查看
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(),结果中断被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()接口去初始化。
使用举例:
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一个效果。
共享工作队列使用举例:
或者
单独工作队列使用举例:
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
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
相关文章推荐
- 中断上下半部原理以及实践
- 中断上下半部原理以及实践
- 5-1 Linux内核中断机制(中断的申请和释放、中断低半部tasklet队列和workqueue队列、中断共享的实现)
- C 笔记 free()释放一个非malloc() 申请的内存
- 读王爽老师汇编语言笔记---int指令、端口、外中断以及键盘的输入
- /MT、/MD编译选项,以及可能引起在不同堆中申请、释放内存的问题
- /MT、/MD编译选项,以及可能引起在不同堆中申请、释放内存的问题
- php内核探索笔记-内存的申请与释放
- linux中断流程以及下半部机制
- /MT、/MD编译选项,以及可能引起在不同堆中申请、释放内存的问题
- /MT、/MD编译选项,以及可能引起在不同堆中申请、释放内存的问题
- /MT、/MD编译选项,以及可能引起在不同堆中申请、释放内存的问题
- /MT、/MD编译选项,以及可能引起在不同堆中申请、释放内存的问题
- iOS学习笔记:ios申请真机调试 以及证书配置
- 动态链表的创建、节点内存空间申请以及释放
- ATECC508A芯片开发笔记(六):产生CSR以及申请证书(X.509)流程及其内容分析
- Linux Kernel Development 笔记(六)中断以及中断处理
- 个人学习笔记--linux中断下半部之软中断
- C#开发学习笔记:C#中实现两个GridControl之间的数据拖拽以及同一个GridControl中的数据行上下移动
- 学习笔记 什么是中断向量以及配置中断服务函数的原理