中断服务程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU就不能及时响应其他的中断请求,从而造成中断的丢失。因此,内核的目标就是尽可能快的处理完中断请求,尽其所能把更多的处理向后推迟。例如,假设一个数据块已经到达了网线,当中断控制器接受到这个中断请求信号时,Linux内核只是简单地标志数据到来了,然后让处理器恢复到它以前运行的状态,其余的处理稍后再进行(如把数据移入一个缓冲区,接受数据的进程就可以在缓冲区找到数据)。因此,内核把中断处理分为两部分:上半部(top half)和下半部(bottom half),上半部(就是中断服务程序)内核立即执行,而下半部(就是一些内核函数)留着稍后处理。
首先,一个快速的“上半部”来处理硬件发出的请求,它必须在一个新的中断产生之前终止。通常,除了在设备和一些内存缓冲区(如果你的设备用到了DMA,就不止这些)之间移动或传送数据,确定硬件是否处于健全的状态之外,这一部分做的工作很少。下半部运行时是允许中断请求的,而上半部运行时是关中断的,这是二者之间的主要区别。
但是,内核到底什时候执行下半部,以何种方式组织下半部?这就是我们要讨论的下半部实现机制,这种机制在内核的演变过程中不断得到改进,在以前的内核中,这个机制叫做bottom half(简称bh),在2.4以后的版本中有了新的发展和改进,改进的目标使下半部可以在多处理机上并行执行,并有助于驱动程序的开发者进行驱动程序的开发。下面主要介绍常用的小任务(Tasklet)机制及2.6内核中的工作队列机制。除此之外,还简要介绍2.4以前内核中的下半部和任务队列机制。
1、小任务机制
这里的小任务是指对要推迟执行的函数进行组织的一种机制。其数据结构为tasklet_struct,每个结构代表一个独立的小任务,其定义如下:
struct tasklet_struct {
struct tasklet_struct *next; /*指向链表中的下一个结构*/
unsigned long state; /* 小任务的状态 */
atomic_t count; /* 引用计数器 */
void (*func) (unsigned long); /* 要调用的函数 */
unsigned long data; /* 传递给函数的参数 */
};
结构中的func域就是下半部中要推迟执行的函数 ,data是它唯一的参数。state域的取值为TASKLET_STATE_SCHED或TASKLET_STATE_RUN。TASKLET_STATE_SCHED表示小任务已被调度,正准备投入运行,TASKLET_STATE_RUN表示小任务正在运行。TASKLET_STATE_RUN只有在多处理器系统上才使用,单处理器系统什么时候都清楚一个小任务是不是正在运行(它要么就是当前正在执行的代码,要么不是)。count域是小任务的引用计数器。如果它不为0,则小任务被禁止,不允许执行;只有当它为零,小任务才被激活,并且在被设置为挂起时,小任务才能够执行。
2、声明和使用小任务
大多数情况下,为了控制一个寻常的硬件设备,小任务机制是实现下半部的最佳选择。小任务可以动态创建,使用方便,执行起来也比较快。我们既可以静态地创建小任务,也可以动态地创建它。选择那种方式取决于到底是想要对小任务进行直接引用还是一个间接引用。如果准备静态地创建一个小任务(也就是对它直接引用),使用下面两个宏中的一个:
DECLARE_TASKLET(name, func, data)
DECLARE_TASKLET_DISABLED(name, func, data)
这两个宏都能根据给定的名字静态地创建一个tasklet_struct结构。当该小任务被调度以后,给定的函数func会被执行,它的参数由data给出。这两个宏之间的区别在于引用计数器的初始值设置不同。第一个宏把创建的小任务的引用计数器设置为0,因此,该小任务处于激活状态。另一个把引用计数器设置为1,所以该小任务处于禁止状态。例如:
DECLARE_TASKLET(my_tasklet, my_tasklet_handler, dev);
这行代码其实等价于
struct tasklet_struct my_tasklet = { NULL, 0, ATOMIC_INIT(0),tasklet_handler, dev};
这样就创建了一个名为my_tasklet的小任务,其处理程序为tasklet_handler,并且已被激活。当处理程序被调用的时候,dev就会被传递给它。
3、编写自己的小任务处理程序
小任务处理程序必须符合如下的函数类型:
void tasklet_handler(unsigned long data)
由于小任务不能睡眠,因此不能在小任务中使用信号量或者其它产生阻塞的函数。但是小任务运行时可以响应中断。
4、调度自己的小任务
通过调用tasklet_schedule()函数并传递给它相应的tasklt_struct指针,该小任务就会被调度以便适当的时候执行:
tasklet_schedule(&my_tasklet); /*把 my_tasklet 标记为挂起 */
在小任务被调度以后,只要有机会它就会尽可能早的运行。在它还没有得到运行机会之前,如果一个相同的小任务又被调度了,那么它仍然只会运行一次。可以调用tasklet_disable()函数来禁止某个指定的小任务。如果该小任务当前正在执行,这个函数会等到它执行完毕再返回。调用tasklet_enable()函数可以激活一个小任务,如果希望把以DECLARE_TASKLET_DISABLED()创建的小任务激活,也得调用这个函数,如:
tasklet_disable(&my_tasklet); /* 小任务现在被禁止,这个小任务不能运行 */
tasklet_enable(&my_tasklet); /* 小任务现在被激活 */
也可以调用tasklet_kill()函数从挂起的队列中去掉一个小任务。该函数的参数是一个指向某个小任务的tasklet_struct的长指针。在小任务重新调度它自身的时候,从挂起的队列中移去已调度的小任务会很有用。这个函数首先等待该小任务执行完毕,然后再将它移去。
5、tasklet的简单用法
下面是tasklet的一个简单应用, 以模块的形成加载。
#include
#include
#include
#include
#include
#include
#include
static struct tasklet_struct my_tasklet;
static void tasklet_handler (unsigned long data)
{
printk(KERN_ALERT "tasklet_handler is running.\n");
}
static int __init test_init(void)
{
tasklet_init(&my_tasklet, tasklet_handler, 0);
tasklet_schedule(&my_tasklet);
return 0;
}
static void __exit test_exit(void)
{
tasklet_kill(&my_tasklet);
printk(KERN_ALERT "test_exit running.\n");
}
MODULE_LICENSE("GPL");
module_init(test_init);
module_exit(test_exit);
原文:
http://hi.baidu.com/caosicong/blog/item/bcb91dd92656702a11df9b91.html
Linux tasklet 分析笔记(转载)2008-07-30 10:31原文作者:不详
原文地址:不详
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
};
in。。。。。searching。。。。。。