LKD:下半部

  1. 在2.6版本中,内核提供了三种下半部实现机制:软中断、tasklets和工作队列。
  2. 内核定时器:把操作推迟到某个确定的时间段之后执行
  3. 软中断

<a>软中断由softirq_action表示,softirq.c中定义了包含有32个该结构体的数组。

struct softirq_action{

           void (*action)(struct softirq_action *)

}

static struct softirq_action softirq_vec[NR_SOFTIRQS];

 

<b>软中断处理程序action的函数原型如下:

void softirq_handler(struct softirq_action *)

如果my_softirq指向softirq_vec数组的某项,那么内核会用如下方式调用软中断处理程序中的函数:

my_softirq->action(my_softirq);

注意:这里把整个结构体都传递给软中断处理程序而不是仅仅传递数据值,这个小技巧可以保证将来在结构体中加入新的域时,无需对所有软中断处理程序都进行变动。

注意:一个软中断不会抢占另一个软中断,唯一可以抢占软中断的是中断处理程序。不过,其他软中断可以在其他处理器上同时执行。

 

<c>执行软中断

在以下地方,待处理的软中断会被检查和执行:

  • 从一个硬件中断代码处返回
  • 在ksoftirqd内核线程中
  • 显式检查和执行待处理软中断的代码

注意:不管用什么方法唤起,软中断都要在do_softirq()中执行。

 

<d>使用软中断

分配索引:在interrupt.h中定义的一个枚举类型来静态声明软中断,按照希望的优先级添加自己的软中断;

注册处理程序:open_softirq(NET_TX_SOFTIRQ, net_tx_action);

注意:软中断处理程序执行时,允许响应中断,但它自己不能休眠;在一个处理程序运行时,当前处理器上的软中断被禁止。但其他处理器仍可以执行别的软中断,这就意味着共享数据加锁的问题。引入软中断是因为其可扩展性,可以扩展到多个处理器。

触发软中断:raise_softirq(NET_TX_SOFTIRQ)将一个软中断设置为挂起状态,内核在下次调用do_softirq()时投入运行。该函数在触发一个软中断前先要禁止中断。

 

  1. tasklet

<a>tasklet的实现

tasklet由两类软中断代表:HI_SOFTIRQ和TASKLET_SOFTIRQ,前者先于后者执行。

tasklet有tasklet_struct结构表示:

struct tasklet_struct{

struct tasklet_struct *next;

unsigned long state; //只能在0、TASKLET_STATE_SCHED和TASKLET_STATE_RUN之间取值;TASKLET_STATE_SCHED表明tasklet已被调度,正准备投入运行;TASKLET_STATE_RUN表明该tasklet正在运行,只有在多处理器系统上才会作为一种优化来使用,单处理器系统任何时候都清楚单个tasklet是不是正在运行。

           atomic_t count; //引用计数器,如果它不为0,则tasklet被禁止

           void (*func)(unsigned long);

           unsigned long data;

}

 

<b>调度tasklet

已调度的tasklet存放在两个链表中:tasklet_vec和tasklet_hi_vec。tasklet由tasklet_schedule()函数进行调度,主要就是把需要调度的tasklet加到每个处理器一个的tasklet_vec链表,然后唤起软中断。

总结:raise_softirq()和tasklet_schedule()在触发软中断和调度tasklet前都要先保存中断状态,然后禁止本地中断,这么做能够保证当tasklet_schedule()处理这些tasklet时,处理器上的数据不会弄乱。

注意:同一时间,相同类型的tasklet只能有一个执行(通过TASKLET_STATE_RUN状态标志)。

 

<c>使用tasklet

静态创建:DECLARE_TASKLET(name, func, data)

                     DECLARE_TASKLET_DISABLED(name, fun, data);  等价于:

                     struct tasklet_struct my_tasklet = {NULL, 0, ATOMIC_INIT(0), fun, data};

动态创建:tasklet_init(t, tasklet_handler, dev);

编写自己的tasklet处理程序:

void tasklet_handler(unsigned long data)

注意:

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

 

<d >调度你自己的tasklet

tasklet_schedule(&my_tasklet);

禁止指定的tasklet:tasklet_disable(&my_tasklet);

激活指定的tasklet:tasklet_enable(&my_tasklet);

从挂起的队列中去掉一个tasklet:tasklet_kill();

 

<e>ksoftirqd

作用:辅助处理软中断(和tasklet)的内核线程。

  • 当大量软中断出现的时候内核会唤醒一组内核线程来处理这些负载(只要do_softirq()函数发现已经执行过的内核线程重新出发了它自己,软中断内核线程就会被唤醒)
  • 这些线程在最低的优先级上运行(nice值是19),这能避免它们跟其他重要的任务抢夺资源。
  • 每个处理器都有一个这样的线程。名字叫做ksoftirqd/n,n对应的是处理器编号。

 

  1. 工作队列

<a>工作队列由内核线程执行,在进程上下文中执行。

<b>工作队列允许重新调度甚至是睡眠

<c>在工作队列和软中断/tasklet中做出选择非常容易。如果推后执行的任务需要睡眠,就选择工作队列。不需要睡眠,就选择软中断或tasklet

<d>唯一能在进程上下文中运行、睡眠的下半部机制。在需要获得大量内存、需要获取信号量、需要执行阻塞式的I/O操作时,工作队列会非常有用。

<e>工作队列的实现

  • 工作者线程:创建的内核线程负责执行由内核其他部分排到队列里的任务
  • 缺省的工作者线程叫做events/n,这里的n是处理器编号
  • 表示线程的数据结构

工作者线程用workqueue_struct结构标识

struct workqueue_struct{

           struct cpu_workqueue_struct cpu_wq[NR_CPUS];

         …

}

         每个处理器,每个工作者线程对应一个cpu_workqueue_struct结构体。

         struct cpu_workqueue_struct {

                   spinlock_t lock;

                   struct workqueue_struct *wq;   //关联工作队列结构

}

工作用work_struct结构体表示

struct work_struct{

           atomic_long_t data;

           struct list_head entry;

           work_fun_t func;

  • 使用工作队列

缺省的events工作队列

 

<a>创建推后的工作:

编译时静态创建  DECLARE_WORK(name, void (*func) (void *), void *data);

动态创建  INIT_WORK(struct work_struct *work, void (*func) (void *), void *data);

<b>工作队列处理函数:

void work_handler(void *data)

<c>对工作进行调度:

schedule_work(&work);

schedule_delayed_work(&work, delay);

<d>刷新操作:在继续下一步工作之前,必须保证一些操作已经执行完毕。

void flush_scheduled_work(void);

函数会一直等待,知道队列中的所有对象都被执行才返回。该函数不取消任何延迟执行的工作。取消延迟执行工作应调用:

Int cancel_delayed_work(struct work_struct *work);

 

<f>创建新的工作队列

    • 创建自己的工作队列

struct workqueue_struct *create_workqueue(const char *name);

    • 对工作进行调度

int queue_work(struct workqueue_struct *wq, struct work_struct *work)

int queue_delayed_work(struct workqueue_struct *wq, struct work_struct *work, unsigned long delay)

    • 刷新指定队列

Flush_workqueue(struct workqueue_struct *wq);

 

  1. 下半部机制的选择

下半部

上下文

顺序执行保障

软中断

中断

没有

tasklet

中断

同类型不能同时执行

工作队列

进程

没有(和进程上下文)

         如果你需要休眠,那么只能选择工作队列,否则最好用tasklet。

 

  1. 在下半部直接加锁

<a>如果进程上下文和一个下半部共享数据,在访问这些数据之前,你需要禁止下半部的处理并得到锁的使用权。为了本地和SMP的保护并且防止死锁的出现

<b>如果中断上下文和一个下半部共享数据,在访问这些数据之前,你需要禁止下半部的处理并得到锁的使用权。

 

  1. 禁止下半部

<a>为了保证共享数据的安全,驱动程序通常的做法:先得到一个锁然后再禁止下半部的处理。

<b>禁止本地处理器的软中断和tasklet:void local_bh_disable()

<c>激活本地处理器的软中断和tasklet:void local_bh_enable()

<d>这些函数不能禁止工作队列的执行。因为工作队列是在进程上下文运行的,不涉及异步执行的问题。由于软中断和tasklet是异步发生的(就是说在中断处理返回的时候),所以内核必须禁止它们。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值