1.中断下半部
1.1.中断上半部与中断下半部
为保证系统的实时性,中断服务程序必须足够简短。但实际应用中,中断处理过程就是需要消耗一定的时间。比如:
SOC在中断服务函数中接收网络数据,还需要对网络数据进行解析处理,解析数据不可避免需要消耗时间;触摸屏通过中断通知SOC有触摸事件产生,如果SOC与触摸屏是IIC通信(IIC通信速度本来就慢),则在中断中通过IIC读取数据也得消耗时间。如果在中断服务程序中消耗时间,则会严重降低系统的实时性。基于这个原因,Linux系统中提出一个概念:把中断处理过程划分为俩部分:中断上半部和中断下半部。
中断上半部:
即中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。
中断下半部:
如果中断处理过程比较耗时,那么将这些比较耗时的代码提出来,交给下半部去执行。
因此,Linux内核将中断分为上半部和下半部的主要目的就是实现中断处理函数的快进快出。中断上半部完成尽可能少的比较紧急的功能,它往往只是简单的读取寄存器中的中断状态并清除中断标志后就启动中断下半部。中断下半部来完成剩下的所有工作。而中断下半部是由内核线程来执行,因此中断下半部即可以被中断打断,也不会影响系统的实时性。
注意:
是否所有的中断程序都需要分成两部分实现呢?
不一定要这么做,如果你发生中断要做事情很少,不会影响系统的实时性,则就不必分成俩部分来实现。
注意:
1、如果一个任务对时间十分敏感,将其放在上半部。
2、如果一个任务和硬件有关,将其放在上半部。
3、如果一个任务要保证不被其他中断打断,将其放在上半部。
4、其他所有任务,考虑放在下半部
1.2.中断下半部实现机制
中断上半部就是中断处理函数,对于中断下半部的实现,Linux内核提供了多种机制:
1.softirq(软中断,使用不灵活,不常用)
2.tasklet(小任务,软中断的封装)
3.workqueue(工作队列)
2.tasklet机制
2.1.tasklet简介
tasklet是一种软中断,是基于softirq的基础演变而来的。但是由于资源的限制,softirq是在内核里面预定义好的,无法改变其种类(softirq定义了10种软中断类型);若要改变的话,就要修改内核代码,这对于驱动开发来说不是很合适。
而基于softirq发展出的tasklet使用方法简单、灵活,自带有锁机制可以防止多个CPU同时运行,是中断处理下半部分最常用的一种方法,通过执行中断处理程序来快速完成上半部分的工作,接着通过调用tasklet使得下半部分的工作得以完成。tasklet执行过程中是可以被硬件中断所中止的,这样不会影响系统实时性。是一种将任务推后执行的一种机制。
2.2.tasklet核心数据结构
Linux内核使用tasklet_struct结构体描述tasklet对象,该结构体定义在include\linux\interrupt.h文件中。
struct tasklet_struct
{
struct tasklet_struct *next; //用来实现多个tasklet_struct结构链表
unsigned long state; //当前这个tasklet是否已经被调度
atomic_t count; //值为0的时候用户才可以调度(0使能,1禁能)
void (*func)(unsigned long); //指向tasklet绑定的任务函数的指针
unsigned long data; //传递能tasklet绑定的任务函数的参数
};
2.3.tasklet使用步骤
2.3.1.构造tasklet对象
**静态初始化:**
DECLARE_TASKLET(name, func, data);
头文件:#include <linux/interrupt.h>
宏原型:
作用: 定义一个名字为name的task_struct结构变量,并且初始化这个结构。func就是tasklet的处理函数,data是传递给func函数的参数。
动态初始化:
头文件: #include <linux/interrupt.h>
函数原型:void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long),unsigned long data);
作用:初始化一个tasklet_struct结构变量
参数:t为要初始化的tasklet对象,func为tasklet的处理函数,data是要传递给func函数的参数。
注意: 使用该API函数之前,先为tasklet开辟空间,直接定义一个tasklet_struct结构体变量即可。
2.3.2.调度tasklet
头文件: #include <linux/interrupt.h>
函数原型:void tasklet_schedule(struct tasklet_struct *t);
作用:tasklet_struct结构中绑定了一个函数,当用户需要执行这个函数的时候,需要自己调用tasklet_schedule函数去通知内核帮我们调度所绑定的函数。
注意:
tasklet被调用之后,其绑定的处理函数不会被马上运行,需要在合适的时机去运行!在tasklet被调度以后,只要有机会它就会尽可能早的运行,在它还没有得到运行机会之前,如果一个相同的tasklet又被调度了,那么它仍然只会运行一次。本质上tasklet链表不能存在相同的tasklet对象。
2.3.3.释放tasklet对象
调度tasklet实际上就是将tasklet添加到一个链表中,内核线程会根据这个链表去执行tasklet。只要tasklet对象还在链表中,就可以重新被调度。如果我们不想让tasklet被执行,可以调用tasklet_kill将指定的tasklet对象从链表中释放。
头文件: #include <linux/interrupt.h>
函数原型:void tasklet_kill(struct tasklet_struct *t);
作用:如果中途不想使用tasklet,则可以调用该函数释放它,不能在tasklet回调函数调用。和初始化函数作用相反。
注意: 这个函数首先等待该tasklet执行完毕,然后再将它释放。
2.4.tasklet机制使用示例
使用过程示例:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
struct tasklet_struct mytasklet;
void tasklet_handler(unsigned long data)
{
printk("tasklet_handler is run\r\n");
}
static int __init xxx_module_init(void)
{
printk("xxx_module is init !\n");
tasklet_init(&mytasklet, tasklet_handler,100);
tasklet_schedule(&mytasklet);
return 0;
}
static void __exit xxx_module_exit(void)
{
printk("xxx_module is exit !\n");
tasklet_kill(&mytasklet);
}
module_init(xxx_module_init);
module_exit(xxx_module_exit);
MODULE_LICENSE("GPL");
3.workqueue机制
3.1.workqueue简介
工作队列是另外一种中断下半部执行的方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许休眠或重新调度。所以如果你要推后的工作要执行睡眠动作就可以选择工作队列。否则,只能选择tasklet或softirq。
注意:
tasklet机制是在中断上下文执行,所以在tasklet中不可以执行休眠动作。
关于workqueue与tasklet这两个机制的选择,看具体的工作过程有没有休眠动作!
3.2.workqueue核心数据结构
Linux内核使用work_struct结构体描述一个工作,该结构体在include\linux\workqueue.h文件中定义。
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作队列处理函数 */
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
这些工作组织成工作队列,工作队列使用workqueue_struct结构体来表示,该结构体在include\linux\workqueue.h文件中定义。
struct workqueue_struct {
unsigned int flags; /* W: WQ_* flags */
union {
struct cpu_workqueue_struct __percpu *pcpu;
struct cpu_workqueue_struct *single;
unsigned long v;
} cpu_wq; /* I: cwq's */
struct list_head list; /* W: list of all workqueues */
struct mutex flush_mutex;/* protects wq flushing */
int work_color; /* F: current work color */
int flush_color; /* F: current flush color */
atomic_t nr_cwqs_to_flush; /* flush in progress */
struct wq_flusher *first_flusher; /* F: first flusher */
struct list_head flusher_queue; /* F: flush waiters */
struct list_head flusher_overflow; /* F: flush overflow list */
mayday_mask_t mayday_mask; /* cpus requesting rescue */
struct worker *rescuer; /* I: rescue worker */
int nr_drainers; /* W: drain in progress */
int saved_max_active; /* W: saved cwq max_active */
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
char name[]; /* I: workqueue name */
};
对于workqueue_struct结构体,我们不用做过多关注!
3.3.workqueue使用步骤
3.3.1.构造work对象
静态初始化工作队列:DECLARE_WORK(n, f)
宏功能:定义一个名字为n的work_struct结构变量,并且初始化它,工作是f
参数: n:要定义的work_struct结构变量名
f:工作函数,就是要延后执行的代码
宏原型:
动态初始化工作结构:INIT_WORK(_work, _func)
宏功能:运行期间动态初始化一个work_struct结构
参数: _work:要构造的work_struct结构变量(工作对象)地址
_func:指定工作对象的工作函数,就是要延后执行的代码
注意:使用该函数之前,要先为工作结构体开辟空间
宏原型:
3.3.2.调度work
调度工作:schedule_work(struct work_struct *work)
函数功能:把一个work_struct结构添加到工作队列中,成为一个工作节点
参数: work:要调度的work_struct结构变量地址
返回值:成功返回0,失败返回其它值。
函数原型:
3.3.3.workqueue使用注意事项
当调用了schedule_work后很快会执行工作函数,工作函数执行完毕后work对象便自动从工作队列中移除,所以就不需要用户开发中在驱动卸载函数中手动移除work对象了。
work工作函数的参数就是work对象的首地址!一般是把这个work对象嵌入到自定义的结构体中,这样在工作函数就可以通过形式参数访问自定义结构体中的其他成员。
如果自定义的结构中工作结构不是在第一个成员,如何找到其他的成员?很显然,如果结构体中工作结构定义不是在开头地方,则不能直接转换成自定义的数据结构来访问其他成员。
实际上内核给我们提供了一个宏container_of(ptr, type, member),让我们能通过结构体任何一个成员的地址找到其它所在结构体变量的首地址
头文件:#include <linux/kernel.h>
宏原型:container_of(ptr, type, member)
宏功能:根据结构体变量中的一个成员地址计算这个成员所在的结构的首地址
参数: ptr:已知的结构体变量成员地址
type:已知结构体变量成员所在的结构体变量的类型
member:已知结构体变量成员所在的结构体类型中的成员名
宏原型:
3.4.workqueue使用示例
使用过程示例:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/workqueue.h>
struct work_struct mywork;
void workqueue_handler(struct work_struct *work)
{
printk("workqueue_handler is run\r\n");
printk("work addres = %d\r\n",work);
}
static int __init xxx_module_init(void)
{
printk("xxx_module is init !\r\n");
INIT_WORK(&mywork,workqueue_handler);
printk("mywork address = %d\r\n",&mywork);
schedule_work(&mywork);
return 0;
}
static void __exit xxx_module_exit(void)
{
printk("xxx_module is exit !\r\n");
}
module_init(xxx_module_init);
module_exit(xxx_module_exit);
MODULE_LICENSE("GPL");