Linux tasklet 分析笔记(转载) 原文作者:不详 Chapter 1: 驱动程序在初始化时,通过函数task_init建立一个tasklet,然后调用函数tasklet_schedule将这个tasklet
放在
tasklet_vec链表的头部,并唤醒后台线程ksoftirqd。当后台线程ksoftirqd运行调用__do_softirq时,会执行在中断
向量表softirq_vec里中断号TASKLET_SOFTIRQ对应的tasklet_action函数,然后tasklet_action遍历
tasklet_vec链表,调用每个tasklet的函数完成软中断操作。
下面对函数tasklet_init和tasklet_schedule分析:
函数tasklet_init初始化一个tasklet,其参数t是tasklet_struct结构描述的tasklet,参数
(*func)是软中断响应函数。
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; } 驱动程序调用函数tasklet_schedule来运行tasklet。
static inline void tasklet_schedule(struct tasklet_struct *t)
{ if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) __tasklet_schedule(t); } 函数__tasklet_schedule得到当前CPU的tasklet_vec链表,并执行TASKLET_SOFTIRQ软中断。
void fastcall __tasklet_schedule(struct tasklet_struct *t)
{ unsigned long flags; local_irq_save(flags);
t->next = __get_cpu_var(tasklet_vec).list; __get_cpu_var(tasklet_vec).list = t; raise_softirq_irqoff(TASKLET_SOFTIRQ); local_irq_restore(flags); } 函数raise_softirq_irqoff设置软中断nr为挂起状态,并在没有中断时唤醒线程ksoftirqd。函数
raise_softirq_irqoff必须在关中断情况下运行。
inline fastcall void raise_softirq_irqoff(unsigned int nr)
{ __raise_softirq_irqoff(nr); /*
* If we're in an interrupt or softirq, we're done * (this also catches softirq-disabled code). We will * actually run the softirq once we return from * the irq or softirq. * * Otherwise we wake up ksoftirqd to make sure we * schedule the softirq soon. */ if (!in_interrupt()) wakeup_softirqd(); } 下面是tasklet_struct和softirq_action的定义。
struct tasklet_struct
{ struct tasklet_struct *next; unsigned long state; atomic_t count; void (*func)(unsigned long); unsigned long data; }; struct softirq_action
{ void (*action)(struct softirq_action *); void *data; }; 摘录于《Linux内核分析及编程>
http://hi.baidu.com/ryderlee/blog/item/ceeec316e8d1f318962b431a.html 1.Tasklet 可被hi-schedule和一般schedule,hi-schedule一定比一般shedule早运行; 2.同一个Tasklet可同时 被hi-schedule和一般schedule; 3.同一个Tasklet若被同时hi-schedule多次,等同于只hi-shedule 一次,因为,在tasklet未 运行时,hi- shedule同一tasklet无意义,会冲掉前一个tasklet; 4. 对于一般shedule, 同上。 5.不同的tasklet不按先后shedule顺序运行,而是并行运行。 6.Tasklet的运行时 间: a.若在中断中schedule tasklet, 中断结束后立即运行; b.若CPU忙,在不在此次中断后立即运行; c.不在中断中shedule tasklet; d.有软或硬中断在运行; e.从系统调用中返回;(仅当process闲时) f.从异常中返回; g.调试程序调度。(ksoftirqd运行时,此时CPU闲) 7.Taskelet的hi-schedule 使用softirq 0, 一般schedule用softirq 30; 8.Tasklet的运行时间最完在下一次time tick 时。(因为最外层中断一定会运行使能的softirq, 面不在中断中便能或shedule的softirq在下一定中断后一定会被调用。) 综上: Tasklet 能保证的运行时间是(1000/HZ)ms,一般是10ms。Tasklet在CPU闲或中断后被调用。 Chapter 2:
Tasklet机制是一种较为特殊的软中断。
Tasklet一词的原意是“小片任务”的意思,这
里是指一小段可执行的代码,且通常以函数的形式出现。软中断向量HI_SOFTIRQ和 TASKLET_SOFTIRQ 均是用tasklet机制来实现的。 从某种程度上讲,tasklet机制是Linux内核对BH机制的一种扩展。在2.4内核引入了 softirq 机制后,原有的BH机制正是通过tasklet机制这个桥梁来纳入softirq机制的整体 框架中的。正是由于这种历史的延伸关系,使得 tasklet机制与一般意义上的软中断有所 不同,而呈现出以下两个显著的特点: 1. 与一般的软中断不同,某一段tasklet代码在某个时刻只能在一个CPU上运行,而不像 一般的软中断服务函数(即softirq_action 结构中的action函数指针)那样——在同一 时刻可以被多个CPU并发地执行。 2. 与BH机制不同,不同的tasklet代码在同一时刻可以在多个CPU上并发地执行,而不像 BH机制那样必须严格地串行化执行(也即在同一时刻系 统中只能有一个CPU执行BH函 数)。 Linux用数据结构tasklet_struct来描述一个tasklet。该 数据结构定义在 include/linux/interrupt.h头文件中。如下所示: struct tasklet_struct { struct tasklet_struct *next; unsigned long state; atomic_t count; void (*func)(unsigned long); unsigned long data; }; 各成员的含义如下: (1)next 指针:指向下一个tasklet的指针。 (2)state:定义了这个tasklet的当前状态。这一个32位的无符号长整数,当前只使用 了 bit[1]和bit[0]两个状态位。其中,bit[1]=1表示这个tasklet当前正在某个 CPU上被执行,它仅对SMP系统才有意义, 其作用就是为了防止多个CPU同时执行一个 tasklet的情形出现;bit[0]=1表示这个tasklet已经被调度去等待执行了。对这两个 状 态位的宏定义如下所示(interrupt.h): enum { TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */ TASKLET_STATE_RUN /* Tasklet is running (SMP only) */ }; (3)原子计数count:对这个tasklet的引用计数值。NOTE!只有当 count等于0时, tasklet代码段才能执行,也即此时tasklet是被使能的;如果count非零,则这个 tasklet是被 禁止的。任何想要执行一个tasklet代码段的人都首先必须先检查其count成 员是否为0。 (4)函数指针func:指向以函数形式 表现的可执行tasklet代码段。 (5)data:函数func的参数。这是一个32位的无符号整数,其具体含义可供func函数自 行 解释,比如将其解释成一个指向某个用户自定义数据结构的地址值。 Linux在interrupt.h头文件中又定义了两个用来定义 tasklet_struct结构变量的辅助 宏: #define DECLARE_TASKLET(name, func, data) / struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data } #define DECLARE_TASKLET_DISABLED(name, func, data) / struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data } 显然,从上述 源代码可以看出,用DECLARE_TASKLET宏定义的tasklet在初始化时是被使 能的(enabled),因为其count成员为0。 而用DECLARE_TASKLET_DISABLED宏定义的 tasklet在初始时是被禁止的(disabled),因为其count等于 1。 在这里,tasklet状态指两个方面:(1)state成员所表示的运行状态;(2)count成员 决定的使能/ 禁止状态。 (1)改变一个tasklet的运行状态 state成员中的bit[0]表示一个tasklet是否已被调度去等待执 行,bit[1]表示一个 tasklet是否正在某个CPU上执行。对于state变量中某位的改变必须是一个原子操作,因 此可以用定义 在include/asm/bitops.h头文件中的位操作来进行。 由于bit[1]这一位(即TASKLET_STATE_RUN)仅仅对于 SMP系统才有意义,因此Linux在 Interrupt.h头文件中显示地定义了对TASKLET_STATE_RUN位的操作。如下所示: #ifdef CONFIG_SMP #define tasklet_trylock(t) (!test_and_set_bit(TASKLET_STATE_RUN, &(t)- >state)) #define tasklet_unlock_wait(t) while (test_bit(TASKLET_STATE_RUN, &(t)- >state)) { /* NOTHING */ } #define tasklet_unlock(t) clear_bit(TASKLET_STATE_RUN, &(t)->state) #else #define tasklet_trylock(t) 1 #define tasklet_unlock_wait(t) do { } while (0) #define tasklet_unlock(t) do { } while (0) #endif 显然,在SMP系统 同,tasklet_trylock()宏将把一个tasklet_struct结构变量中的state 成员中的bit[1]位设置成1,同时还 返回bit[1]位的非。因此,如果bit[1]位原有 值为1(表示另外一个CPU正在执行这个tasklet代码),那么 tasklet_trylock()宏将返 回值0,也就表示上锁不成功。如果bit[1]位的原有值为0,那么 tasklet_trylock()宏 将返回值1,表示加锁成功。而在单CPU系统中,tasklet_trylock()宏总是返回为1。 任 何想要执行某个tasklet代码的程序都必须首先调用宏tasklet_trylock()来试图对这 个tasklet进行上锁(即设置 TASKLET_STATE_RUN位),且只能在上锁成功的情况下才能 执行这个tasklet。建议!即使你的程序只在CPU系统上运行,你也 要在执行tasklet之前 调用tasklet_trylock()宏,以便使你的代码获得良好可移植性。 在SMP系统 中,tasklet_unlock_wait()宏将一直不停地测试TASKLET_STATE_RUN位的值, 直到该位的值变为0(即一直等待 到解锁),假如:CPU0正在执行tasklet A的代码,在 此期间,CPU1也想执行tasklet A的代码,但CPU1发现tasklet A的TASKLET_STATE_RUN位 为1,于是它就可以通过 tasklet_unlock_wait()宏等待tasklet A被解锁(也即 TASKLET_STATE_RUN位被清零)。在单CPU系 统中,这是一个空操作。 宏tasklet_unlock()用来对一个tasklet进行解锁操作,也即将TASKLET_STATE_RUN位 清 零。在单CPU系统中,这是一个空操作。 (2)使能/禁止一个tasklet 使能与禁止操作往往总是成对地被调用 的,tasklet_disable()函数如下 (interrupt.h): static inline void tasklet_disable(struct tasklet_struct *t) { tasklet_disable_nosync(t); tasklet_unlock_wait(t); } 函 数tasklet_disable_nosync()也是一个静态inline函数,它简单地通过原子操作将 count成员变量的值减1。如下所 示(interrupt.h): static inline void tasklet_disable_nosync(struct tasklet_struct *t) { atomic_inc(&t->count); } 函数 tasklet_enable()用于使能一个tasklet,如下所示(interrupt.h): static inline void tasklet_enable(struct tasklet_struct *t) { atomic_dec(&t->count); } 函 数tasklet_init()用来初始化一个指定的tasklet描述符,其源码如下所示 (kernel/softirq.c): void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data) { t->func = func; t->data = data; t->state = 0; atomic_set(&t->count, 0); } 函数tasklet_kill()用来 将一个已经被调度了的tasklet杀死,即将其恢复到未调度的状 态。其源码如下所示(kernel/softirq.c): void tasklet_kill(struct tasklet_struct *t) { if (in_interrupt()) printk("Attempt to kill tasklet from interrupt/n"); while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) { current->state = TASK_RUNNING; do { current->policy |= SCHED_YIELD; schedule(); } while (test_bit(TASKLET_STATE_SCHED, &t->state)); } tasklet_unlock_wait(t); clear_bit(TASKLET_STATE_SCHED, &t->state); } 多个tasklet可以通过tasklet描述符中的next成员指针链接成 一个单向对列。为此, Linux专门在头文件include/linux/interrupt.h中定义了数据结构tasklet_head来描 述 一个tasklet对列的头部指针。如下所示: struct tasklet_head { struct tasklet_struct *list; } __attribute__ ((__aligned__(SMP_CACHE_BYTES))); 尽管tasklet机制是特定于软中断向量HI_SOFTIRQ和 TASKLET_SOFTIRQ的一种实现,但是 tasklet机制仍然属于softirq机制的整体框架范围内的,因此,它的设计与实现仍然必 须 坚持“谁触发,谁执行”的思想。为此,Linux为系统中的每一个CPU都定义了一个 tasklet对列头部,来表示应该有各个CPU负责执行的 tasklet对列。如下所示 (kernel/softirq.c): struct tasklet_head tasklet_vec[NR_CPUS] __cacheline_aligned; struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned; 其中,tasklet_vec[]数组用于软中断向 量TASKLET_SOFTIRQ,而tasklet_hi_vec[] 数组则用于软中断向量HI_SOFTIRQ。也即,如果 CPUi(0≤i≤NR_CPUS-1)触发了软中断 向量TASKLET_SOFTIRQ,那么对列tasklet_vec[i]中的每一个 tasklet都将在CPUi服务 于软中断向量TASKLET_SOFTIRQ时被CPUi所执行。同样地,如果 CPUi(0≤i≤NR_CPUS- 1)触发了软中断向量HI_SOFTIRQ,那么队列tasklet_vec[i]中的每一个tasklet 都将 CPUi在对软中断向量HI_SOFTIRQ进行服务时被CPUi所执行。 队列tasklet_vec[I]和 tasklet_hi_vec[I]中的各个tasklet是怎样被所CPUi所执行 的呢?其关键就是软中断向量TASKLET_SOFTIRQ 和HI_SOFTIRQ的软中断服务程序—— tasklet_action()函数和tasklet_hi_action()函数。下面我们就来 分析这两个函数。 Linux为软中断向量TASKLET_SOFTIRQ和HI_SOFTIRQ实现了专用的触发函数和软中断服务 函 数。其中,tasklet_schedule()函数和tasklet_hi_schedule()函数分别用来在当前 CPU上触发软中断向量 TASKLET_SOFTIRQ和HI_SOFTIRQ,并把指定的tasklet加入当前CPU 所对应的tasklet队列中去等待执行。而 tasklet_action()函数和tasklet_hi_action() 函数则分别是软中断向量TASKLET_SOFTIRQ和 HI_SOFTIRQ的软中断服务函数。在初始化函 数softirq_init()中,这两个软中断向量对应的描述符 softirq_vec[0]和softirq_vec [3]中的action函数指针就被分别初始化成指向函数 tasklet_hi_action()和函数 tasklet_action()。 (1)软中断向量 TASKLET_SOFTIRQ的触发函数tasklet_schedule() 该函数实现在include/linux /interrupt.h头文件中,是一个inline函数。其源码如下所 示: static inline void tasklet_schedule(struct tasklet_struct *t) { if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) { int cpu = smp_processor_id(); unsigned long flags; local_irq_save(flags); t->next = tasklet_vec[cpu].list; tasklet_vec[cpu].list = t; __cpu_raise_softirq(cpu, TASKLET_SOFTIRQ); local_irq_restore(flags); } } 该函数的参数t指向要在 当前CPU上被执行的tasklet。对该函数的NOTE如下: ①调用test_and_set_bit()函数将待调度的tasklet的 state成员变量的bit[0]位(也 即TASKLET_STATE_SCHED位)设置为1,该函数同时还返回 TASKLET_STATE_SCHED位的原有 值。因此如果bit[0]为的原有值已经为1,那就说明这个tasklet已经被调度到另一个 CPU 上去等待执行了。由于一个tasklet在某一个时刻只能由一个CPU来执行,因此 tasklet_schedule()函数什么也不做就直接返 回了。否则,就继续下面的调度操作。 ②首先,调用local_irq_save()函数来关闭当前CPU的中断,以保证下面的步骤在当前 CPU 上原子地被执行。 ③然后,将待调度的tasklet添加到当前CPU对应的tasklet队列的首部。 ④接着,调用 __cpu_raise_softirq()函数在当前CPU上触发软中断请求 TASKLET_SOFTIRQ。 ⑤最后,调用 local_irq_restore()函数来开当前CPU的中断。 (2)软中断向量TASKLET_SOFTIRQ的服务程序 tasklet_action() 函数tasklet_action()是tasklet机制与软中断向量TASKLET_SOFTIRQ的联系 纽带。正是 该函数将当前CPU的tasklet队列中的各个tasklet放到当前CPU上来执行的。该函数实现 在 kernel/softirq.c文件中,其源代码如下: static void tasklet_action(struct softirq_action *a) { int cpu = smp_processor_id(); struct tasklet_struct *list; local_irq_disable(); list = tasklet_vec[cpu].list; tasklet_vec[cpu].list = NULL; local_irq_enable(); while (list != NULL) { struct tasklet_struct *t = list; list = list->next; if (tasklet_trylock(t)) { if (atomic_read(&t->count) == 0) { clear_bit(TASKLET_STATE_SCHED, &t->state); t->func(t->data); /* * talklet_trylock() uses test_and_set_bit that imply * an mb when it returns zero, thus we need the explicit * mb only here: while closing the critical section. */ #ifdef CONFIG_SMP smp_mb__before_clear_bit(); #endif tasklet_unlock(t); continue; } tasklet_unlock(t); } local_irq_disable(); t->next = tasklet_vec[cpu].list; tasklet_vec[cpu].list = t; __cpu_raise_softirq(cpu, TASKLET_SOFTIRQ); local_irq_enable(); } } 注释如下: ①首先,在当前 CPU关中断的情况下,“原子”地读取当前CPU的tasklet队列头部指针, 将其保存到局部变量list指针中,然后将当前CPU的 tasklet队列头部指针设置为NULL, 以表示理论上当前CPU将不再有tasklet需要执行(但最后的实际结果却并不一定如此, 下 面将会看到)。 ②然后,用一个while{}循环来遍历由list所指向的tasklet队列,队列中的各个元素就 是将在当前CPU上执 行的tasklet。循环体的执行步骤如下: l 用指针t来表示当前队列元素,即当前需要执行的tasklet。 l 更新list指针为list->next,使它指向下一个要执行的tasklet。 l 用tasklet_trylock()宏试图对当前要执行的tasklet(由指针t所指向)进行加锁,如 果加锁成功(当前没有任何其他CPU正 在执行这个tasklet),则用原子读函数 atomic_read()进一步判断count成员的值。如果count为0,说明这个 tasklet是允许执行 的,于是:(1)先清除TASKLET_STATE_SCHED位;(2)然后,调用这个tasklet的可执 行 函数func;(3)执行barrier()操作;(4)调用宏tasklet_unlock()来清除 TASKLET_STATE_RUN 位。(5)最后,执行continue语句跳过下面的步骤,回到while循环 继续遍历队列中的下一个元素。如果count不为0,说明这个 tasklet是禁止运行的,于是 调用tasklet_unlock()清除前面用tasklet_trylock()设置的 TASKLET_STATE_RUN位。 l 如果tasklet_trylock()加锁不成功,或者因为当前tasklet的count值非0而不允许执 行时,我们必须将这个 tasklet重新放回到当前CPU的tasklet队列中,以留待这个CPU下 次服务软中断向量TASKLET_SOFTIRQ时再执行。为此 进行这样几步操作:(1)先关CPU中 断,以保证下面操作的原子性。(2)把这个tasklet重新放回到当前CPU的tasklet队列 的 首部;(3)调用__cpu_raise_softirq()函数在当前CPU上再触发一次软中断请求 TASKLET_SOFTIRQ;(4)开 中断。 l 最后,回到while循环继续遍历队列。 (3)软中断向量HI_SOFTIRQ的触发函数 tasklet_hi_schedule() 该函数与tasklet_schedule()几乎相同,其源码如下 (include/linux /interrupt.h): static inline void tasklet_hi_schedule(struct tasklet_struct *t) { if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) { int cpu = smp_processor_id(); unsigned long flags; local_irq_save(flags); t->next = tasklet_hi_vec[cpu].list; tasklet_hi_vec[cpu].list = t; __cpu_raise_softirq(cpu, HI_SOFTIRQ); local_irq_restore(flags); } } (4)软中断向量 HI_SOFTIRQ的服务函数tasklet_hi_action() 该函数与tasklet_action()函数几乎相同,其源码如下 (kernel/softirq.c): static void tasklet_hi_action(struct softirq_action *a) { int cpu = smp_processor_id(); struct tasklet_struct *list; local_irq_disable(); list = tasklet_hi_vec[cpu].list; tasklet_hi_vec[cpu].list = NULL; local_irq_enable(); while (list != NULL) { struct tasklet_struct *t = list; list = list->next; if (tasklet_trylock(t)) { if (atomic_read(&t->count) == 0) { clear_bit(TASKLET_STATE_SCHED, &t->state); t->func(t->data); tasklet_unlock(t); continue; } tasklet_unlock(t); } local_irq_disable(); t->next = tasklet_hi_vec[cpu].list; tasklet_hi_vec[cpu].list = t; __cpu_raise_softirq(cpu, HI_SOFTIRQ); local_irq_enable(); } } Bottom Half机制在新的softirq机制中被保留下来,并作为softirq框架的一部分。其实 现也似乎更为复杂些,因为它是通过tasklet机 制这个中介桥梁来纳入softirq框架中 的。实际上,软中断向量HI_SOFTIRQ是内核专用于执行BH函数的。 原有的32 个BH函数指针被保留,定义在kernel/softirq.c文件中: static void (*bh_base[32])(void); 但 是,每个BH函数都对应有一个tasklet,并由tasklet的可执行函数func来负责调用相 应的bh函数(func函数的参数指定调用哪 一个BH函数)。与32个BH函数指针相对应的 tasklet的定义如下所示(kernel/softirq.c): struct tasklet_struct bh_task_vec[32]; 上述tasklet数组使系统全局的,它对所有的CPU均可见。由于在 某一个时刻只能有一个 CPU在执行BH函数,因此定义一个全局的自旋锁来保护BH函数,如下所示 (kernel/softirq.c): spinlock_t global_bh_lock = SPIN_LOCK_UNLOCKED; 在softirq机制的初始化函数 softirq_init()中将bh_task_vec[32]数组中的每一个 tasklet中的func函数指针都设置为指向同一个函数 bh_action,而data成员(也即func 函数的调用参数)则被设置成该tasklet在数组中的索引值,如下所示: void __init softirq_init() { …… for (i=0; i<32; i++) tasklet_init(bh_task_vec+i, bh_action, i); …… } 因此,bh_action()函数将负责相应地调用参数所指定的bh函数。该函数是连接 tasklet 机制与Bottom Half机制的关键所在。 该函数的源码如下(kernel/softirq.c): static void bh_action(unsigned long nr) { int cpu = smp_processor_id(); if (!spin_trylock(&global_bh_lock)) goto resched; if (!hardirq_trylock(cpu)) goto resched_unlock; if (bh_base[nr]) bh_base[nr](); hardirq_endlock(cpu); spin_unlock(&global_bh_lock); return; resched_unlock: spin_unlock(&global_bh_lock); resched: mark_bh(nr); } 对 该函数的注释如下: ①首先,调用spin_trylock()函数试图对自旋锁global_bh_lock进行加锁,同时该函数 还将返 回自旋锁global_bh_lock的原有值的非。因此,如果global_bh_lock已被某个CPU 上锁而为非0值(那个CPU肯定在执 行某个BH函数),那么spin_trylock()将返回为0表示 上锁失败,在这种情况下,当前CPU是不能执行BH函数的,因为另一个CPU 正在执行BH函 数,于是执行goto语句跳转到resched程序段,以便在当前CPU上再一次调度该BH函数。 ②调用 hardirq_trylock()函数锁定当前CPU,确保当前CPU不是处于硬件中断请求服务 中,如果锁定失败,跳转到 resched_unlock程序段,以便先对global_bh_lock解锁,在 重新调度一次该BH函数。 ③此时,我们已经可以放心 地在当前CPU上执行BH函数了。当然,对应的BH函数指针 bh_base[nr]必须有效才行。 ④从BH函数返回后,先调用 hardirq_endlock()函数(实际上它什么也不干,调用它只是 为了保此加、解锁的成对关系),然后解除自旋锁 global_bh_lock,最后函数就可以返 回了。 ⑤resched_unlock程序段:先解除自旋锁 global_bh_lock,然后执行reched程序段。 ⑥resched程序段:当某个CPU正在执行BH函数时,当前CPU就不能通过 bh_action()函 数来调用执行任何BH函数,所以就通过调用mark_bh()函数在当前CPU上再重新调度一 次,以便将这个 BH函数留待下次软中断服务时执行。 (1)init_bh()函数 该函数用来在bh_base[]数组登记一个指定的 bh函数,如下所示 (kernel/softirq.c): void init_bh(int nr, void (*routine)(void)) { bh_base[nr] = routine; mb(); } (2)remove_bh() 函数 该函数用来在bh_base[]数组中注销指定的函数指针,同时将相对应的tasklet杀掉。 如下所示 (kernel/softirq.c): void remove_bh(int nr) { tasklet_kill(bh_task_vec+nr); bh_base[nr] = NULL; } (3)mark_bh()函数 该函数用来向当前CPU标记由一个BH函数等待去执行。它实际上通过调 用 tasklet_hi_schedule()函数将相应的tasklet加入到当前CPU的tasklet队列 tasklet_hi_vec[cpu] 中,然后触发软中断请求HI_SOFTIRQ,如下所示 (include/linux/interrupt.h): static inline void mark_bh(int nr) { tasklet_hi_schedule(bh_task_vec+nr); } 在 32个BH函数指针中,大多数已经固定用于一些常见的外设,比如:第0个BH函数就固定 地用于时钟中断。Linux在头文件 include/linux/interrupt.h中定义了这些已经被使用 的BH函数所引,如下所示: enum { TIMER_BH = 0, TQUEUE_BH, DIGI_BH, SERIAL_BH, RISCOM8_BH, SPECIALIX_BH, AURORA_BH, ESP_BH, SCSI_BH, IMMEDIATE_BH, CYCLADES_BH, CM206_BH, JS_BH, MACSERIAL_BH, ISICOM_BH }; |
|
来自: langhuayipian > 《linux内核》