精华内容
下载资源
问答
  • Tasklet
    2019-02-20 15:30:45

    tasklet是一种软中断,是基于softirq的基础演变而来的,但是由于资源的限制,softirq是在内核里面预定义好的,无法改变其种类,若要改变的话,就要修改内核代码,这对于驱动开发来说不是很合适.因此基于softirq发展出的tasklet来动态的改变中断的种类,这些中断使用链表连接起来,一旦发生了TASKLET_SOFTIRQ中断(tasklet对应的软中断类型),就按照链表的顺序依次全部执行,执行完毕就断开链表.

    使用tasklet方法

    //首先调用tasklet_init函数,对tasklet_struct 结构体进行初始化
    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;
    }
    
    struct tasklet_struct
    {
    	struct tasklet_struct *next; //链表指向的下一个目标
    	unsigned long state; 
    	atomic_t count;
    	void (*func)(unsigned long); //中断函数
    	unsigned long data; //传递给中断函数的参数
    };
    //然后触发tasklet中断,引起调度
    static inline void tasklet_schedule(struct tasklet_struct *t)
    {
    	if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
    		__tasklet_schedule(t);
    }
    

    因为tasklet不同于softirq,softirq允许同一个中断函数在不同的CPU上面执行,但是要保证中断函数的可重入性.tasklet就正好相反,本身就带有锁机制,也就是tasklet_struct的state域,用于同步中断函数的执行,自然就不能同时在多个CPU上面执行了,这样就简化了我们中断函数的编写,不需要考虑到额外的同步问题.至于tasklet_struct的count域是用于屏蔽掉当前tasklet中断,这可以从tasklet_disable这个函数的内容看出.

    tasklet既然是一种softirq,那就一定要注册相应的软中断函数:

    void __init softirq_init(void)
    {
    	int cpu;
    	/*因为是哪个处理器触发的软中断就由哪个处理器处理中断函数
    	* 因此,每个处理器都要添加相应的结构
    	*/
    	for_each_possible_cpu(cpu) {
    		int i;
    
    		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;
    		for (i = 0; i < NR_SOFTIRQS; i++)
    			INIT_LIST_HEAD(&per_cpu(softirq_work_list[i], cpu));
    	}
    
    	register_hotcpu_notifier(&remote_softirq_cpu_notifier);
    
    	open_softirq(TASKLET_SOFTIRQ, tasklet_action);
    	open_softirq(HI_SOFTIRQ, tasklet_hi_action);
    }
    

    上面代码显示tasklet_action就是相应的中断函数

    static void tasklet_action(struct softirq_action *a)
    {
    	struct tasklet_struct *list;
    
    	local_irq_disable();
    	/*每个cpu都有一个关于tasklet链表的头和尾,用于中 tasklet中断的处理*/
    	list = __get_cpu_var(tasklet_vec).head;
    	__get_cpu_var(tasklet_vec).head = NULL;
    	__get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head;
    	local_irq_enable();
    
    	while (list) {
    		struct tasklet_struct *t = list;
    
    		list = list->next;
    		/*检查是否已经被正在执行,即检查(t)->state的TASKLET_STATE_RUN位是否被置位
    		* 若已置位说明这个中断函数正在被其他的cpu执行
    		*/
    		if (tasklet_trylock(t)) {
    			//检查当前tasklet是否被disable掉了
    			if (!atomic_read(&t->count)) {
    				//检查当前的tasklet是否是pending状态,如果是的话,就执行中断函数
    				if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
    					BUG();
    				t->func(t->data);
    				tasklet_unlock(t);
    				continue;
    			}
    			tasklet_unlock(t);
    		}
    		/*因为上面有一个continue语句,因此对于运行到这里的函数说明上面的判断
    		* 语句有不满足的情况,第一种情况:当前函数被其他的cpu执行;第二种情况:
    		* 当前中断被disable了;对于这两种情况都会把他们重新加入到链表的尾部
    		* 然后重新唤醒TASKLET_SOFTIRQ,等到有空再次执行这些中断函数
    		*/
    		local_irq_disable();
    		t->next = NULL;
    		*__get_cpu_var(tasklet_vec).tail = t;
    		__get_cpu_var(tasklet_vec).tail = &(t->next);
    		__raise_softirq_irqoff(TASKLET_SOFTIRQ);
    		local_irq_enable();
    	}
    }
    
    更多相关内容
  • 1. Tasklet机制分析  上面我们介绍了软中断机制,linux内核为什么还要引入tasklet机制呢?主要原因是软中断的pending标志位也32位,一般情况是不随意增加软中断处理的。而且内核也没有提供通用的增加软中断的接口...
  • #NAME tasklet - 用于协作任务调度的 Java 库#描述##Tasklet Tasklet 是与 tasklet 调度程序合作来决定如何(同步或异步)以及何时执行它们的小型工作单元。 Tasklet 实现了一个task()方法,调度程序调用该方法来...
  • 在编写设备驱动时, tasklet 机制是一种比较常见的机制,通常用于减少中断处理的时间,将本应该是在中断服务程序中完成的任务转化成软中断完成。  为了最大程度的避免中断处理时间过长而导致中断丢失,有时候我们...
  • Tasklet是一个简单的任务管理框架,主要用于将一个大任务分割成许多较小的子任务,并管理这些子任务的执行。Tasklet主要有以下4个类: Task : 任务/子任务,多个Task组成一个完整的任务/功能。 ...
  • linux-tasklet机制--转载

    2021-06-06 17:07:04
    Tasklet机制是一种较为特殊的软中断。Tasklet一词的原意是“小片任务”的意思,这里是指一小段可执行的代码,且通常以函数的形式出现。软中断向量HI_SOFTIRQ和TASKLET_SOFTIRQ均是用tasklet机制来实现的。从某种程度...

    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

    };

    展开全文
  • ISR 软中断 tasklet之间的关系 tasklet 更易于扩展,因而更适合于设备驱动程序 同一个tasklet只能在一个CPU上运行,不同的tasklet可以在不同的CPU上运行 tasklet使用了软中断枚举的TASKLET_SOFTIRQ和HI_SOFTIRQ ...

    目录

     

    前言

    中断服务例程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)

     

    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Li65LqG57u05oqk5LiW55WM5ZKM5bmzXw==,size_14,color_FFFFFF,t_70,g_se,x_16

     

     
     
            内核首先生成一个新的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
    };
     
     
    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Li65LqG57u05oqk5LiW55WM5ZKM5bmzXw==,size_20,color_FFFFFF,t_70,g_se,x_16

                                                    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字段为RUNNING
    		if (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. 除了上述三点以外的其他任务,优先考虑放到下半部。

     

    展开全文
  • Chapter 2:Tasklet机制是一种较为特殊的软中断。Tasklet一词的原意是“小片任务”的意思,这里是指一小段可执行的代码,且通常以函数的形式出现。软中断向量HI_SOFTIRQ和TASKLET_SOFTIRQ均是用tasklet机制来实现的。...

    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_STATE_RUN

    };

    (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)) { }

    #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来执行,因此let_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);

    #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

    };

    展开全文
  • Linux 中断子系统之tasklet 1 前言 中断服务程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU就不能及时响应其他的...
  • 因此期望让中断处理程序运行得快,并想让它完成的工作量多,这两个目标相互制约,诞生——顶/底半部机制,本文主要介绍tasklet的执行过程。 读者可根据情况了解以下知识: 软中断过程总结 中断机制详细总结 由于软...
  • Linux tasklet

    2022-05-01 15:25:34
    1.声明自己的tasklet 既可以静态地创建tasklet,也可以动态地创建它。如果静态地创建一个tasklet(直接引用),使用<linux/interrupt.h>中的两个宏: #define DECLARE_TASKLET(name, func, data) / struct ...
  • 文章目录linux中断管理—tasklet一、tasklet简介二、如何使用tasklet三、tasklet源码分析(3-1)在linux启动过程中tasklet的初始化(3-2)tasklet_schedule()函数分析四、总结 一、tasklet简介 tasklet是利用软中断...
  • 文章目录一、中断函数1、设计原则2、根源二、中断上下半部机制1、上半部2、下半部三、软中断1、...init()函数tasklet_struct 结构体tasklet相关的apitasklet_init()函数tasklet_schedule()函数tasklet_kill()函数 一
  •   硬件的中断处理函数处于中断上半部分,在CPU关中断的状态下执行,中断线程、软中断(softirq)及小任务(tasklet)属于中断的下半部分(bottom half),在CPU开中断的状态下执行。小任务基于软中断实现,实质是...
  • Tasklet

    2016-09-08 17:59:04
    Tasklet 有的时候在驱动程序中需要延迟某些操作的进行,最典型的操作就是在驱动程序的中断处理函数延迟操作。比如在DMA驱动中,当数据传输完毕之后会触发中断的,通常这时候会启动一个tasklet来完成耗时的操作,也...
  • 3)DEFINE_PER_CPU(struct tasklet_head,tasklet_vec)为每个CPU都分了 tasklet_head结构,该结构用来维护 struct tasklet_struct 链表,需要放到该CPU上运行的tasklet将会添加到该结构的链表中,内核...
  • 目录 一、takslet 1.1、数据结构 1.2、初始化 1.3、执行tasklet 1.4、启用和禁用tasklet 1.5、销毁tasklet sample code: 二、workqueue 2.1、创建工作队列 2.2、调度 2.3、取消调度 2.4、销毁workqueue sample code...
  • Linux把中断分为两个部分,一个是上半部分,一个是下半部分,在上半部分只处理紧急的事情,同时调用tasklet来启动中断下文,比较耗时的就放到下文来处理,调用tasklet以后,tasklet绑定的函数并不会立刻执行,而是...
  • Linux内核-tasklet微任务

    2022-03-14 16:43:23
    tasklet用于处理一些不那么紧急的任务,可以延后在将来的某个时间点来执行,linux通过中断来响应处理一些任务,这类任务通常是很紧急的需要立即处理,此类任务通常运行于interrupt context(中断上下文),存在较多限制...
  • tasklet与workqueue区别

    2020-03-30 14:19:00
    一、中断处理的tasklet(小任务)机制 中断服务程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU就不能及时响应其他...
  • 那么我们就把复杂但不那么紧急的事情放在中断的下半部tasklet里面 中断和tasklet是 多对一的关系 怎么描述呢,描述不出来 就像下面 我按钮按了三下的话,就会循环三次,跑到100 在跑到100之前,发生中断,还是继续先跑完...
  • 硬件的中断处理函数处于中断上半部分,在CPU关中断的状态下执行,中断线程、软中断(softirq)及小任务(tasklet)属于中断的下半部分(bottom half),在CPU开中断的状态下执行。小任务基于软中断实现,实质是对软...
  • 一、前言 对于中断处理而言,linux将其分成了两个部分,一个叫做中断handler(top half),属于不那么...对于第一种情况,内核中的机制包括softirq机制和tasklet机制。第二种情况是属于softirq机制的一种应用场景(time
  • 前言:这篇文章不会对系统软中断、tasklet、工作队列work queue的内核实现机制进行深入分析,仅仅是谈一下这几种机制的不同以及简单的使用。有描述不对的地方,欢迎大家指出。 说明:在分析具体代码时候,用I.MX6Q...
  • 使用tasklet⌛销毁tasklet(下半部)⚡️实战篇:重点代码分析♐终极篇: 完整代码演示 ????中断的概念 中断是整个系统的核心,用来事件的切换或者响应。在Linux内核下又分为上半部中断和下半部中断,他们都遵循以下...
  • Linux 内核的 tasklet 机制的分析和理解
  • 一、tasklet概述 tasklet是利用软中断实现的一种下半部机制。我们之前提到过,它和进程没有任何关系。tasklet和软中断在本质上很相似,行为表现也相近,但是,它的接口更简单,锁保护也要求较低 选择到底是用软...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,962
精华内容 4,384
关键字:

Tasklet

友情链接: checkgroup.rar