第8章 下半部和推后执行的工作

8.3 tasklet

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

选择到底是用软中断还是tasklet其实很简单:通常应该用tasklet。软中断的使用者屈指可数,只在执行频率很高和连续性要求很高的情况下才需要使用。而tasklet用途广泛。大多数情况下用tasklet效果都不错,而且它们还非常容易使用。

8.3.1 tasklet的实现

tasklet是通过软中断实现的,本身也是软中断。tasklet由两类软中断代表:HI_SOFTIRQ和TASKLET_SOFTIRQ。这两者之间唯一的实际区别在于,HI_SOFTIRQ类型的软中断先于TASKLET_SOFTIRQ类型的软中断执行。

1、tasklet结构体

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

struct tasklet_struct
{
    struct tasklet_struct *next;
    unsigned long state;
    atomic_t count;
    void (*func)(unsigned long);//tasklet处理函数
    unsigned long data;// 给tasklet处理函数的参数
};

结构体中的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_hi_vec。这两个数据结构都是由tasklet_struct结构体构成的链表。链表中的每个tasklet_struct代表一个不同的tasklet。

/*
 * Tasklets
 */
struct tasklet_head
{
    struct tasklet_struct *head;
    struct tasklet_struct **tail;
};

static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);// 普通的tasklet
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);// 高优先级的tasklet

tasklet由tasklet_schedule()和tasklet_hi_schedule()进行调度,它们接受一个指向tasklet_struct结构的指针作为参数。

8.3.2 使用tasklet

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

1、声明tasklet

既可以静态地创建tasklet,也可以动态地创建tasklet。选择哪种方式取决于到底是有一个对tasklet的直接引用还是间接引用。如果准备静态地创建一个tasklet,使用<linux/interrupt.h>中定义的两个宏中的一个:
struct tasklet_struct
{
    struct tasklet_struct *next;
    unsigned long state;
    atomic_t count;
    void (*func)(unsigned long);
    unsigned long data;
};

#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被调度以后,给定的函数func会被执行,它的参数由data给出。这两个宏之间的区别在于引用计数器的初始值设置不同。DECLARE_TASKLET宏把创建的tasklet的引用计数器设置为0,该tasklet处于激活状态。DECLARE_TASKLET_DISABLED宏把引用计数器设置为1,所以该tasklet处于禁止状态。例如:

DECLARE_TASKLET(my_tasklet, mytasklet_handler, dev);

等价于

struct tasklet_struct my_tasklet= { NULL, 0, ATOMIC_INIT(0), mytasklet_handler, dev};

这就创建一个名为my_tasklet,处理程序为mytasklet_handler并且是已被激活的tasklet。当处理程序被调用时,dev就会被传递给它。

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

<linux/interrupt.h>

extern void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);//动态创建

kernel/softirq.c

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);

2、编写tasklet处理程序

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

void tasklet_handler(unsigned long data)

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

3、调度tasklet

通过调用tasklet_schedule()并传递给它相应的tasklet_struct的指针,该tasklet就会被调度以便执行:

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

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

通过调用tasklet_disable()来禁止某个指定的tasklet。如果该tasklet当前正在执行,这个函数会等到它执行完毕再返回。也可以调用tasklet_disable_nosync()来禁止指定的tasklet,不过它无须在返回前等待tasklet执行完毕。这么做不太安全,因为无法估计该tasklet是否仍在执行。调用tasklet_enable()可以激活一个tasklet,如果希望激活DECLARE_TASKLET_DISABLED宏创建的tasklet,也得调用这个函数,如:

tasklet_disable(&my_tasklet);//tasklet现在被禁止

tasklet_enable(&my_tasklet);//tasklet现在被激活

通过调用tasklet_kill()从挂起的队列中去掉一个tasklet。定义在<linux/interrupt.h>文件中,

void tasklet_kill(struct tasklet_struct *t);

实现在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)) {
        do {
            yield();
        } while (test_bit(TASKLET_STATE_SCHED, &t->state));
    }
    tasklet_unlock_wait(t);
    clear_bit(TASKLET_STATE_SCHED, &t->state);
}

EXPORT_SYMBOL(tasklet_kill);

该函数的参数是一个指向某个tasklet的tasklet_struct的指针。在处理一个经常重新调度它自身的tasklet时,从挂起的队列中移去已调度的tasklet会很有用。这个函数首先等待该tasklet执行完毕,然后再将它移去。由于该函数可能会引起休眠,所以禁止在中断上下文中使用。

4、ksoftirqd

每个处理器都有一组辅助处理软中断的内核线程。当内核中出现大量软中断时,这些内核线程就会辅助处理它们。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值