> 文档中心 > linux内核源码分析之中断tasklet

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成的。

上半部与下半部选择建议

  1. 如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
  2. 如果要处理的任务对时间敏感,可以放到上半部。
  3. 如果要处理的任务与硬件有关,可以放到上半部。
  4. 除了上述三点以外的其他任务,优先考虑放到下半部。

(内核免费课程链接:https://ke.qq.com/course/4032547?flowToken=1042391)

多事通报价网