您的位置:首页 > 其它

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

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();


假如在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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐