博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
下半部和推后执行的工作--tasklet
阅读量:5954 次
发布时间:2019-06-19

本文共 7346 字,大约阅读时间需要 24 分钟。

(一):tasklet


tasklet是利用软中断实现的一种下半部机制,他和进程没有任何关系,他在本质上和软中断是相似的,行为表现也很相近.但是他的接口很简单,锁保护也要求较低.

1:tasklet的实现


tasklet有两类软中断代表:HI_SOFTIRQ和TASKLET_SOFTIRQ.这两者之间唯一的实际区别在于,HI_SOFTIRQ类型的软中断先于TASKLET_SOFTIRQ类型的软中断执行.

1):tasklet结构体

tasklet由tasklet_struct结构表示.每个结构体单独代表一个tasklet,他在linux/interrupt.h中定义:

/* Tasklets --- multithreaded analogue of BHs.   BHS的多线程模拟   Main feature differing them of generic softirqs: tasklet   is running only on one CPU simultaneously.   他们和软中断不同的主要特征为:tasklet只能同时运行在一个cpu上.   Main feature differing them of BHs: different tasklets   may be run simultaneously on different CPUs.   他们和BHS不同的主要特征为:不同的tasklet可能同时运行在不同的CPU上   Properties:   特性:   * If tasklet_schedule() is called, then tasklet is guaranteed     to be executed on some cpu at least once after this.     如果tasklet_schedule()函数被调用,tasklet一定会运行在     一些CPU上,至少一次   * If the tasklet is already scheduled, but its excecution is still not     started, it will be executed only once.     如果tasklet已经被调度了,但是他的运行依然没有开始,他将仅仅被执行一次.   * If this tasklet is already running on another CPU (or schedule is called     from tasklet itself), it is rescheduled for later.     如果该tasklet已经运行在另外一个CPU上了(或这是对tasklet本身执行调度了),     他将会在后面被调度.   * Tasklet is strictly serialized wrt itself, but not     wrt another tasklets. If client needs some intertask synchronization,     he makes it with spinlocks.     和锁相关 */struct tasklet_struct{    struct tasklet_struct *next;    unsigned long state;    atomic_t count;    void (*func)(unsigned long);    unsigned long data;};

结构体中func成员是tasklet的处理程序.data是他唯一的参数.state成员只能在0,TASKLET_STATE_SCHED和TASKLET_STATE_RUN之间取值.TASKLET_STATE_SCHED 表明tasklet已被调度,正准备投入运行,TASKLET_STATE_RUN表明该tasklet正在运行.TASKLET_STATE_RUN只有在多处理器上才会作为一种优化来使用,单处理器系统任何时候都清楚单个tasklet是不是正在运行.

count成员是tasklet的引用计数器.如果他不为0,则tasklet被禁止,不允许执行;只有当他为0的时候,tasklet才被激活,并且设置为挂起状态,该tasklet才能够执行.

2):调度tasklet

已调度的tasklet(等同于被触发的软中断)存放在两个单处理器数据结构:tasklet_vec(普通tasklet)和task_hi_vec(高优先级的tasklet).这两个数据结构都是由tasklet_struct构成的链表.链表中每一个tasklet_struct代表一个不同的tasklet.

tasklet由tasklet_schedule()和tasklet_hi_schedule()函数进行调度.他们接受一个指向tasklet_struct结构的指针作为参数.两个函数非常相似(区别在于一个使用TASKLET_SOFTIRQ 而另外一个使用HI_SOFTIRQ). 现在我们看一下tasklet_schedule()函数.

static inline void tasklet_schedule(struct tasklet_struct *t){    if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))        __tasklet_schedule(t);}

1:首先检查tasklet的状态是否为TASKLET_STATE_SCHED.如果是,说明tasklet已经被调度过了,函数立即返回

2:调用__tasklet_schedule()

void __tasklet_schedule(struct tasklet_struct *t){    unsigned long flags;    local_irq_save(flags);    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_restore(flags);}

3:保存中断状态,然后禁止本地中断.我们在执行tasklet代码的时候,这么做能够保证当tasklet_schedule()处理这些tasklet的时候,处理器上的数据不会被弄乱.

4:把需要调度的tasklet加到每个处理器的tasklet_vec链表或task_hi_vec链表的表头上去.

5:唤起TASKLET_SOFTIRQ或HI_SOFTIRQ软中断,这样下一次调用do_softirq()的时候就会执行该tasklet.

6:恢复中断到原状态并返回

下面我们来看一下相应的软中断处理程序,tasklet_action()和tasklet_hi_action().这两个函数的处理过程基本上是相同的,我们来看其中一个:

static void tasklet_action(struct softirq_action *a){    struct tasklet_struct *list;    local_irq_disable();    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;        if (tasklet_trylock(t)) {            if (!atomic_read(&t->count)) {                if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))                    BUG();                t->func(t->data);                tasklet_unlock(t);                continue;            }            tasklet_unlock(t);        }        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_vec或者是tasklet_hi_vec.

2:将当前处理器上的该链表设置为NULL,达到清空效果.
3:允许响应中断.没有必要再恢复他们到原来状态,因为这段代码本身就是作为软中断处理程序调用的,所以中断是应该被允许的
4:循环遍历获得连表上的每一个待处理的tasklet
5:如果是多处理器系统,通过检查TASKLET_STATE_RUN来判断这个tasklet是否在其他处理器上运行,如果他正在运行,那么现在就不要执行,跳到下一个待处理的tasklet去.
6:如果当前这个tasklet没有执行,将其状态设置为TASKLET_STATE_RUN,这样别的处理器就不会再去执行他了.
7:检查count是否为0,确保tasklet没有被禁止.如果tasklet被禁止了,则跳到下一个挂起的tasklet中去.
8:我们已经清楚的知道这个tasklet没有在其他地方执行,并且被我们设置成执行状态,这样他在其他部分就不会被执行,并且引用计数为0,现在可以执行tasklet的处理程序了.
9:tasklet执行完毕,清除tasklet的state域的TASKLET_STATE_RUN状态标志.
10:重复执行下一个tasklet,直至没有剩余的等待处理的tasklet.

2:使用tasklet


大多数情况下,为了控制一个寻常的硬件设备,tasklet机制都是实现自己的下半部的最佳选择.tasklet可以动态创建,使用方便,执行起来也还算快.

1):声明自己的tasklet

你既可以静态的创建tasklet,也可以动态的创建他.选择哪种方式取决于你到底是有一个对tasklet的直接引用还是间接引用.如果你准备静态的创建一个tasklet(也就是有一个他的直接引用),使用下面linux/interrupt.h中定义的两个宏的一个:

#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 }

这两个宏都能根据给定的名称静态创建一个tasklet_struct结构.当该tasklet被调度以后,给定的函数fun会被执行,他的参数由data给出.这两个宏之间的区别在于引用计数器的初始值不同.前面一个宏把创建的tasklet的引用计数器设置为0,该tasklet处于激活状态.另一个把引用计数器设置为1,所以该tasklet处于禁止状态,下面是一个例子:

DECLARE_TASKLET(my_tasklet,my_tasklet_hadnler,dev);//等价于struct tasklet_struct my_tasklet = { NULL, 0 , ATOMIC_INIT(0), my_tasklet_handler,dev};

这样就创建了一个名为my_tasklet.处理程序为tasklet_handler并且是已被激活的tasklet.当处理程序被调用的时候,dev就被传递给他.

还可以通过将一个间接引用(一个指针)赋给一个动态创建的tasklet_struct结构的方式来初始化一个tasklet_init():

tasklet_init(t,tasklet_handler,dev); /* 动态而不是静态创建 */

2):编写你自己的tasklet处理程序

tasklet处理程序必须符合规定的函数类型:

void tasklet_handler(unsigned long data);

因为是靠软中断实现,所以tasklet不能睡眠.这意味着你不能在tasklet中使用信号量或者其他什么阻塞式的函数.由于tasklet运行的时候允许响应中断,所以你必须做好预防工作(如屏蔽中断然后获取一个锁),如果你的tasklet和中断处理程序之间共享了某些数据的话.两个相同的tasklet绝不会同时执行,这点和软中断不同-尽管两个不同的tasklet可以在两个处理器上同时执行.如果你的tasklet和其他的tasklet或者是 软中断共享了数据,你必须进行适当的锁保护.

3):调度你自己的tasklet

通过调用task_schedule()函数并传递给他相应的task_struct的指针,该tasklet就会被调度以便执行:

tasklet_schdule(&my_tasklet);  /* 把my_tasklet标记为挂起 */

在tasklet被调度以后,只要有机会他就会尽可能早的运行.在他还没有得到运行机会之前,如果有一个相同的tasklet又被调度了,那么他仍然只会运行一次.而如果这时他已经开始运行了,比如说在另外一个处理器上,那么这个新的tasklet会被重新调度并再次运作.作为一种优化措施,一个tasklet总在调度他的处理器上执行--这是更好的利用处理器的高速缓存.

你可以调用tasklet_disable()函数来禁止某个指定的tasklet.如果该tasklet当前正在执行,这个函数会等到他执行完毕后再返回.也可以使用tasklet_disable_nosync()函数,他也用来禁止指定的tasklet,不过他无须在返回前等待tasklet执行完毕.调用tasklet_enable()函数可以激活一个tasklet,如果希望激活DECLARE_TASKLET_DISABLE()创建的tasklet,你也得调用这个函数.如:

tasklet_disable(&my_tasklet);  /* tasklet现在被禁止 *//* 我们现在毫无疑问的知道tasklet不能运行 */tasklet_enable(&my_tasklet); /* tasklet现在激活 */

你也可以使用tasklet_kill()函数从挂起的队列中去掉一个tasklet.这个函数的参数是一个指向某个tasklet的tasklet_struct的长指针.在处理一个经常调度他自身的tasklet的时候,从挂起的队列中移去已调度的tasklet会很有用,这个函数首先等待该tasklet执行完毕,然后再将他移除.当然,没有什么可以阻止其他地方的代码重新调度该tasklet,由于该函数可能会引起休眠,所以禁止在中断上下文中使用它.

4:ksoftirqd


当有大量软中断出现的时候,立即处理软中断或者是不立即处理软中断都会导致一些问题,那么就需要有一种折中的方法,那就是,当大量软中断出现的时候,内核会唤醒一组内核线程来处理这些负载,这些线程在最低的优先级上运行,这样能够避免他们跟重要的任务抢夺资源.所以这个方案能够保证在软中断负担很重的时候,用户程序不会因为得不到处理时间而处于饥饿状态,相应的,也能够保证”过量”的软中断终究会得到处理.

每个处理器都有一个这样的线程,所有线程的名字都叫做ksoftirqd/n,区别在于n.他对应的是处理器的编号.在一个双CPU的机器上就有两个这样的线程,分别叫做ksoftirqd/0和ksoftirqd/1;为了保证只要有空闲的处理器,他们就会软中断,所有给每个处理器都分配一个这样的线程.一旦该线程被初始化,他就会执行类似下面的死循环.

for (;;) { if (!softirq_pending(cpu)) schedule(); set_current_state(TASK_RUNNING); while (softirq_pending(cpu)) { do_softirq(); if (need_resched()) schedule(); } set_current_state(TASK_INTERRUPTIBLE); }

转载于:https://www.cnblogs.com/bobo1223/p/7287526.html

你可能感兴趣的文章
线程互互斥锁
查看>>
KVM虚拟机&openVSwitch杂记(1)
查看>>
win7下ActiveX注册错误0x80040200解决参考
查看>>
《.NET应用架构设计:原则、模式与实践》新书博客--试读-1.1-正确认识软件架构...
查看>>
2013 Linux领域年终盘点
查看>>
linux学习之查看程序端口占用情况
查看>>
相逢在栀枝花开的季节
查看>>
linux下git自动补全命令
查看>>
Ubuntu14.04LTS更新源
查看>>
Linux报“Unknown HZ value! (288) Assume 100”错误
查看>>
mysql多实例实例化数据库
查看>>
我的友情链接
查看>>
golang xml和json的解析与生成
查看>>
javascript 操作DOM元素样式
查看>>
Android 内存管理 &Memory Leak & OOM 分析
查看>>
【查找算法】基于存储的查找算法(哈希查找)
查看>>
JavaWeb网上图书商城完整项目--day02-10.提交注册表单功能之页面实现
查看>>
记录一下这次web实训的两个网站
查看>>
POJ-1830 开关问题 高斯消元
查看>>
HDU-4366 Successor 线段树+预处理
查看>>