linux内核源码分析之中断tasklet
目录
前言
中断服务例程ISR
tasklet
注册tasklet
执行tasklet
前言
硬件中断(hardware interrupt): 由系统自身和与之连接的外设自动产生。它们用于支持更高效地实现设备驱动程序,也用于引起处理器自身对异常或错误的关注软中断(SoftIRQ): 用于有效实现内核中的延期操作。同步中断和异常:
这些由CPU自身产生,针对当前执行的程序 触发原因 1)运行时发生的程序设计错误(典型的例子是除0) ;2)出现了异常的情况或条件。
异步中断: 这是经典的中断类型,由外部设备产生,可能发生在任意时间。
中断服务例程ISR
在处理程序执行期间,发生了其他中断。尽管可以通过在处理程序执行期间禁用中断来防止,但这会引起其他问题,如遗漏重要的中断,因而只能短时间使用。1. 注册IRQ 由设备驱动程序动态注册ISR的工作
int request_irq(unsigned int irq,irqreturn_t handler, unsigned long irqflags, const char *devname, void *dev_id)
内核首先生成一个新的irqaction实例,然后用函数参数填充其内容,特别重要的是处理程序函数handler 如果安装的处理程序是该IRQ编号对应链表中的第一个,则调用handler->startup初始化函数。如果该IRQ此前已经安装了处理程序,则没有必要再调用该函数。 register_irq_proc在proc文件系统中建立目录/proc/irq/NUM。而register_handler_proc生成proc/irq/NUM/name
在设备请求一个系统中断,而中断控制器通过引发中断将该请求转发到处理器的时候,内核 将调用该处理程序函数
ISR必须满足:尽可能少的代码,以支持快速处理;不能彼此干扰。 那么延时较长的处理在什么地方执行呢? 内核中提供了下半部软中断、tasklet、工作队列软中断类型为
enum{HI_SOFTIRQ=0,TIMER_SOFTIRQ,NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,BLOCK_SOFTIRQ,IRQ_POLL_SOFTIRQ,TASKLET_SOFTIRQ,SCHED_SOFTIRQ,HRTIMER_SOFTIRQ,RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */NR_SOFTIRQS};
ISR 软中断 tasklet之间的关系
tasklet
- 更易于扩展,因而更适合于设备驱动程序
- 同一个tasklet只能在一个CPU上运行,不同的tasklet可以在不同的CPU上运行
- tasklet使用了软中断枚举的TASKLET_SOFTIRQ和HI_SOFTIRQ
struct tasklet_struct{struct tasklet_struct *next; //TASKLET_STATE_SCHED:在tasklet注册到内核,等待调度执行 //TASKLET_STATE_RUN:当前正在执行unsigned long state;atomic_t count;//原子计数void (*func)(unsigned long);//tasklet的函数执行体unsigned long data;};
使用 tasklet_init 函数初始化 tasklet
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data){t->next = NULL;t->state = 0;atomic_set(&t->count, 0);t->func = func;t->data = data;}EXPORT_SYMBOL(tasklet_init);
注册tasklet
tasklet_schedule将一个tasklet注册到系统中tasklet_schedule在什么时候调用呢? 在ISR中调用
irqreturn_t xxx_handler(int irq, void *dev_id) { ...... /* 调度 tasklet */ tasklet_schedule(&testtasklet); ......}
tasklet_schedule做了什么工作?tasklet_schedule->__tasklet_schedule->__tasklet_schedule_common
static void __tasklet_schedule_common(struct tasklet_struct *t, struct tasklet_head __percpu *headp, unsigned int softirq_nr){struct tasklet_head *head;unsigned long flags;local_irq_save(flags);head = this_cpu_ptr(headp);t->next = NULL;*head->tail = t;head->tail = &(t->next);raise_softirq_irqoff(softirq_nr);local_irq_restore(flags);}
- 如果设置了TASKLET_STATE_SCHED标志位,则结束注册过程,因为该tasklet此前已经注册了。
- 否则,将该tasklet置于一个链表的起始,其表头是特定于CPU的变量tasklet_vec。
- 该链表包含了所有注册的tasklet,使用next成员作为链表元素。
- 在注册了一个tasklet之后,tasklet链表即标记为即将进行处理。
执行tasklet
tasklet执行体的注册,基于软中断
softirq_init->open_softirq->tasklet_action->tasklet_action_common->while循环执行
void __init softirq_init(void){int cpu;for_each_possible_cpu(cpu) {per_cpu(tasklet_vec, cpu).tail =&per_cpu(tasklet_vec, cpu).head;per_cpu(tasklet_hi_vec, cpu).tail =&per_cpu(tasklet_hi_vec, cpu).head;}open_softirq(TASKLET_SOFTIRQ, tasklet_action);open_softirq(HI_SOFTIRQ, tasklet_hi_action);}
static __latent_entropy void tasklet_action(struct softirq_action *a){tasklet_action_common(a, this_cpu_ptr(&tasklet_vec), TASKLET_SOFTIRQ);}static __latent_entropy void tasklet_hi_action(struct softirq_action *a){tasklet_action_common(a, this_cpu_ptr(&tasklet_hi_vec), HI_SOFTIRQ);}
tasklet占用了了软中断的两个中断类型(TASKLET_SOFTIRQ和HI_SOFTIRQ),优先级有高低之分,分别对应tasklet_action()和tasklet_hi_action(),需要执行的tasklet保存在tasklet_vec和tasklet_hi_vec链表中
循环执行的过程
static void tasklet_action_common(struct softirq_action *a, struct tasklet_head *tl_head, unsigned int softirq_nr){struct tasklet_struct *list; //保持中断状态寄存器并关闭本地CPU中断local_irq_disable();list = tl_head->head;tl_head->head = NULL;tl_head->tail = &tl_head->head; //恢复中断寄存器并开本地中断local_irq_enable();while (list) { //循环执行tasklet链表上每个tasklet的处理函数struct tasklet_struct *t = list;list = list->next; //如果tasklet没被执行,执行设备tasklet的state字段为RUNNINGif (tasklet_trylock(t)) { //如果tasklet的锁计数器为0,执行if (!atomic_read(&t->count)) {if (!test_and_clear_bit(TASKLET_STATE_SCHED,&t->state))BUG();t->func(t->data);//*****函数的执行***** //如果不为0 则表示禁用,清楚RUNNING状态tasklet_unlock(t);continue;}tasklet_unlock(t);} //关本地中断,并把没有处理的tasklet 重新挂载到tasklet_vec上local_irq_disable();t->next = NULL;*tl_head->tail = t;tl_head->tail = &t->next; //把本地CPU上的TASKLET_SOFTIRQ标记为挂起,并使能中断__raise_softirq_irqoff(softirq_nr);local_irq_enable();}}
在while循环中执行tasklet,类似于处理软中断使用的机制。 因为一个tasklet只能在一个处理器上执行一次,但其他的tasklet可以并行运行,所以需要特定于 tasklet 的锁。 state状态用作锁变量。在执行一个 tasklet 的处理程序函数之前,内核使用tasklet_trylock检查tasklet的状态是否为TASKLET_STATE_RUN
static inline int tasklet_trylock(struct tasklet_struct *t) { return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state); }
除了普通的tasklet之外,内核还使用了另一种tasklet,它具有“较高”的优先级。除以下修改之 外,其实现与普通的tasklet完全相同。
- 使用HI_SOFTIRQ作为软中断,相关的action函数tasklet_hi_action。
- 注册的tasklet在CPU相关的变量tasklet_hi_vec中排队。这是使用tasklet_hi_schedule完成的。
上半部与下半部选择建议
- 如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
- 如果要处理的任务对时间敏感,可以放到上半部。
- 如果要处理的任务与硬件有关,可以放到上半部。
- 除了上述三点以外的其他任务,优先考虑放到下半部。
(内核免费课程链接:https://ke.qq.com/course/4032547?flowToken=1042391)