中断子系统
一、异常
在执行代码时产生,而且由代码本身产生,也就是说这个操作是处理器在执行代码的时候发生的,所以又称作同步中断
二、中断
与处理器当前处理的代码无关,处理器并不知道什么时候中断到来,所以中断是异步的
三、中断处理机制的实现:
设备产生中断--->中断控制器--->处理器打断正在执行的代码--->跳到相应中断线的初始入口地址--->保存当前中断号和寄存器的值--->内核调用do_IRQ()函数--->开启中断系统并禁止该中断线上的中断---> do_IRQ()调用handle_IRQ()来执行这条中断线上注册过的中断--->中断返回,如果当时中断的是用户态程序,则在中断返回时schedule()被调用重新调度进程,如果中断的是内核本生则反回中断处继续执行
四、/proc/interrupts显示中断有关的信息
五、中断上下文
上下文就是代码所执行时的环境,进程处在进程上下文中,中断处理程序处在中断上下文中,只有处在进程上下文中的代码才可以被重新调度,处在中断上下文中的代码不能被重新调度,所以在中断上下文中不能睡眠,否则中断处理将永远不返回,in_interrupt()返回0代表在进程上下文中,返回非0代表在中断上下文中
六、中断线和中断号的相互转换
根据分析,6410支持的中断线如下:
0~15: 为ISA设备预留
16~31: 为UART0到UART3准备
32~95: 为6410的64组中断源各分配了一条中断线
96~100: Timer0~4
101~128: 外部IO group 0对应的28个外部中断的中断线
129~228: 外部group1~9对应的99个外部中断的中断线
NR_IRQS: 229,linux中支持的中断号的总数量
七、申请中断
int request_irq(unsigned int irq, irqreturn_t (*handle)(int, void *), unsigned long irqflags, const char *devname, void *dev_id)
irq:要申请的中断线
handle:要注册的中断处理函数 该函数的第一个参数是触发中断后的中断线号,第二个是一个通用指针,当中断线共享的时候要传入一个唯一的值供注销中断的时候用,一般来说传入的是设备结构体的指针
irqreturn_t:中断处理程序的返回值,中断处理程序正常执行返回该值应该返回IRQ_HANDLED否则该值应该返回IRQ_NONE(当检测到的中断不是想要的中断的时候要返回该值)
irqflags:对中断的一些特殊要求,比如上升沿等等
devname:用来在/proc/interrupts 下显示的中断的名字
dev_id:这个参数便是传入到handle当中的第二个参数,一般用于中断的共享,如果不共享一般传NULL
返回值:当该函数调用成功则返回0,如果错误返回一个负的错误码,一般是-EBUSY表示该中断线已经被注册了,而且没有被共享
注意:这个函数可能会睡眠,在创建/proc下和中断相关的文件的时候要用kmalloc分配内存
五、注销中断:
void free_irq(unsigned int irq, void *dev_id)
irq:要释放的中断线,和注册中断时候传入的一样
dev_id:同一个中断线上不同共享中断处理函数的标志,如果中断是非共享的,则在注销中断处理函数的时候也把中断线禁止掉,如果是共享的,则dev_id就是指定要注销的那个处理函数,直到注销最后一个处理函数的时候才会禁止掉这条中断线
六、中断处理程序一般不需要可重入:因为在中断处理函数在执行的时候最好的情况是禁止当前中断线,最不好的情况是禁止所有的本地中断
七、中断控制
禁止当前处理器的中断系统
local_irq_disable()
打开当前处理器的中断系统
local_irq_enable()
保存中断状态并且禁止中断
unsigned long flags
local_irq_save(flags)
恢复中断状态
local_irq_restore(flags)
禁止指定的中断线
void disable_irq(unsigned int irq)
void disable_irq_nosync(unsigned int irq)
void enable_irq(unsigned int irq)
void synchronize(unsigned int irq)
注意:前两个函数的功能是一样的,都是禁止掉irq线上的中断传递,第一个函数只有等到中断处理程序返回时候才会结束,第二个则不会等待。第四个函数用来等待特定的中断线处理程序的退出,这些函数可以嵌套使用,例如使用了两次disable则要使用两次enable才能再次激活该中断线,这四个函数基本上已经不再使用
八、上半部分和下半部分
为什么要分上下两部分:
1.中断处理程序处在中断上下文中,不能睡眠
2.中断时候会禁止其他的中断,中断处理程序太长会应影响系统的响应速率
3.操作硬件的时候有严格的时间限制,所以要求响应要迅速
要求立即处理的代码放到上部分
和硬件相关的代码放到上部分
要求不能被其他代码打断的代码放到上部分
其他基本上放到下部分
上部分:中断处理此程
下部分:BH、任务队列、软中断、tasklet、工作队列
BH、任务队列已经被淘汰掉了
1.软中断
软中断是一种需要用静态定义实现的下半部接口,软中断处理函数处在中断上下文中,所以在软中断处理函数不能睡眠或者阻塞,软中断最多有32个,软中断可同时在不同的处理器上运行,即使是相同的软中断也能同时在不同的处理器上运行。
在内核中用一个结构体表示一个软中断:
struct softirq_action{
void (*action)(struct softirq_action *);
void *data;//在2.6.28以后的内核中已经去掉
};
在内核中用一个拥有32个元素的结构体来表示这三十二个软中断,每注册一个软中断就占用该数组的一项
struct softirq_action softirq_vec[];
使用软中断:
(1)在内核中添加自己的软中断
(2)注册软中断open_softirq(n, void(*actions)(struct softirq_action *)),其实就是把数组当中的第n个元素的action = actions
(3)触发软中断,一般在中断处理函数返回的时候触发
raise_softirq(n)。其实就是把一个32为的变量pending的第n为设置1
(4)在中断函数一返回就开始执行do_softirq,这个函数会按顺序检查pending中的每一位,那位是1就去执行相应的软中断处理函数,优先级是从0到31
注意:执行raise_softirq(n)的时候默认是要关中断的,完事后再打开中断,如果已经知道中断已经关了可以调用raise_softirq_irqoff(n)优化
2.tasklet
tasklet是用过软中实现的,所以实质上它还是软中断,任然具有软中断的属性,只不过是把软中断又封装了一层,其实用的软中断中0和软中断5,但同一个tasklet不能同时运行
在内核中用一个结构体表示一个tasklet:
struct tasklet_struct{
struct tasklet_struct *next;//链表中的下一个tasklet
unsigned long state;状态:0代表该tasklet没有运行也没有被调度
TASKLET_STATE_SCHED 代表已经调度了
TASKLET_STATE_RUN 代表正在执行
atomic_t count;引用计数,只有为0的时候才能执行
void (*func)(unsigned long);处理函数
unsigned long data;给处理函数的参数
};
调度tasklet:对于已经调用的tasklet存放在两个tasklet_struct类型的链表中,一个是tasklet_vec,用的是软中断的5,用tasklet_schedule(tasklet_struct *)调度,另一个tasklet_hi_vec,用的是软中断的0用tasklet_hi_schedule(tasklet_struct *)调度
使用tasklet:
(1)建立tasklet
静态定义并初始化一个tasklet
DECLARE_TASKLET(name, func, data)
= struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data } DECLARE_TASKLET_DISABLED(name, func, data)
= struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data } 禁止了tasklet但可以激活
初始化一个tasklet
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
(2)调度tasklet
void tasklet_schedule(struct tasklet_struct *t)
void tasklet_hi_schedule(struct tasklet_struct *t)
其实做得就是把相应的tasklet结构体插入到相应的链表中
(3)中断处理程序退出后就执行tasklet_action和tasklet_hi_action来执行调度的tasklet
(4)其他API
void tasklet_disable(struct tasklet_struct *t)//会等待taseklet执行完成,所以肯能会睡眠
void tasklet_disable_nosync(struct tasklet_struct *t)//不会睡眠
以上两个函数其实就是把count字段++
void tasklet_enable(struct tasklet_struct *t)
void tasklet_hi_enable(struct tasklet_struct *t)
以上两个函数其实就是把count字段--
tasklet_kill(struct tasklet_struct *t)//可能会睡眠,因为要等待tasklet执行完
注意:被disable的tasklet能够被调度,但是不能被执行,如果这个时候还是用tasklet_kill来等待的话那么它会一直睡眠
3.工作队列
工作队列用内核线程实现的,工作队列默认创建了一个叫做event的工作者线程来执行被退后的工作,工作队列处在进程上下文中,所以可以阻塞,睡眠,在内核中用一个结构体表示于一个工作:
struct work_struct {
atomic_long_t data;
struct list_head entry;工作队列链表
work_func_t func;处理函数
struct lockdep_map lockdep_map;
};
struct delayed_work {
struct work_struct work;
struct timer_list timer;
};
这些结构体被连接成链表,在每个处理器上对应这样一个链表。当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。当工作完毕时, 他会将相应的work_struct对象从链表中移去
使用工作队列:
(1)创建工作
静态创建并初始化一个工作
DECLARE_WORK(work_struct n, void (*f)(void *)) n是工作的名字, f是处理函数
DECLARE_DELAYED_WORK(work_struct n, void (*f)(void *)) n是工作的名字, f是处理函数
初始化一个工作
INIT_WORK(work_struct *, void (*func)(void *))
INIT_DELAYED_WORK(work_struct *, void (*func)(void *))
(2)对工作调度
int schedule_work(struct work_struct *)
int schedule_delayed_work(struct delayed_work *dwork, unsigned long delay)
第二个函数调度的工作会延时delay个滴答数后执行
(3)中断程序退出后event线程调用run_workqueue就开始执行链表上的工作
(4)其他API
void flush_scheduled_work(void) //用来以睡眠的方式等待所有的work执行完才退出,一般在模块卸载的时候用来检查
int cancle_delayed_work(struct work_struct *work) //用来取消延时执行的操作
创建一个属于自己的工作队列:
(1)创建一个新的工作队列和相应的工作者线程,名字是name
struct workqueue_struct *create_workqueue(const char *name)
(2)向新的工作队列添加任务
int queue_work(struct workqueue_struct *wq, struct work_struct *work)
int queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *work, unsigned long delay);
(3)刷新工作队列
void flush_workqueue(struct workqueue_struct *wq)
(4)销毁工作队列
void destroy_workqueue(struct workqueue_struct *wq)