文章目录
中断和中断处理
中断上下文:被内核调用来响应中断的中断处理程序(中断服务例程)
注册或注销中断处理程序
request_irq的参数说明:
- irq:表示分配的中断号
- handler:一个指针,指向这个中断实际处理程序
- flags:可以为0,也可以是多个标志的位掩码,其定义在<linux/interrupt.h>
- name:与中断相关的设备ASCII文本,会被/proc/irq和proc/interrupts文件使用
- dev:用于共享中断线,当一个中断处理程序需要释放时,dev将提供唯一的标志信息,如果无需共享中断号,可以设置为NULL,在应用中,往往用于传递驱动程序的设备结构体,并且由于指针唯一性,有可能在中断处理程序内使用。
flags主要标志:
- IRQF_DISABLED:若该标志被设置,则内核处理中断处理程序期间,要禁止所有的其他中断,如果不设置,中断处理程序可以与其他中断同时运行
- IRQF_SAMPLE_RANDOM:表示该设备产生的中断对内核熵池有贡献,而内核熵池负责提供随机事件导出的真正随机数。如果指定该标志,来自设备的中断间隔时间就会作为熵填充到熵池
- IRQF_TIMER:为系统定时器的中断处理程序而设计
- IRQF_SHARED:表示该中断号可以在多个中断处理程序之间共享,在同一个中断号注册的每个中断处理程序必须指定该标志,否则该中断号只能注册一个中断处理程序
#include <linux/interrupt.h>
/*分配指定的中断号*/
typedef int irqreturn_t;
typedef irqreturn_t (*irq_handler_t)(int irq, void *dev);
/*成功返回0,失败返回非0值,如-EBUSY表示中断号已被使用,或没有设置IRQF_SHARED*/
/*request_irq肯能会睡眠,不能在中断上下文或其他不允许阻塞的地方使用,在注册过程中,
内核需要在/proc/irq文件中创建一个与中断对应的项,函数proc_mkdir就是用于创建新的
procfs项,而proc_mkdir通过调用proc_create对新的profs项进行设置,而proc_create
调用函数kmalloc请求内存分配,函数kmalloc是可以睡眠的*/
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);
/*如果指定的中断号是非共享的,那么该函数删除中断处理程序的同时将禁用该中断号*/
/*如果指定的中断号是共享的,则仅删除dev所对应的中断处理程序,只有该中断号删除了最后一个处理程序时才被禁用*/
void free_irq(unsigned int irq, void *dev);
中断处理程序
中断处理程序参数:
- irq:与request_irq中的irq一致,指示该中断处理程序响应的中断号
- dev:在中断处理程序注册时传递给request_irq的参数必须一致,可以用来区分共享同一中断号的多个设备,也可以用于指向中断处理程序使用的一个设备数据结构
中断处理程序的返回:
- IRQ_NONE:当中断处理程序检测到一个中断,但该中断对应的设备并不是注册处理函数指定的中断源
- IRQ_HANDLED:当中断处理程序被正确调用,且确实是对应设备产生了中断
/*中断处理程序的声明*/
/*Linux的中断处理程序无需可重入,当一个给定的中断处理程序执行时,相应的中断号被处理器被屏蔽,以防止在同一中断号上接收新的中断*/
static irqreturn_t irq_handler(int irq, void *dev);
中断处理机制的实现
在内核中,中断的处理开始预定义入口点,类似于系统调用通过预定义的异常句柄进入内核。对于每个中断号,处理器都会跳转到一个唯一的位置,内核就可以获得所接收的IRQ号。初始入口点只是在中断处理程序栈中保存这个IRQ号,并存放当前寄存器的值(属于被中断的任务),然后内核调用函数do_IRQ()。
/*C的调用惯例把函数参数置于栈的顶部,因此pt_regs结构包含原始寄存器的值,由调用的汇编入口保存*/
/*得出中断号后,do_IRQ对所有接收的中断进行应答,禁止该中断号的中断传递*/
unsigned int do_IRQ(struct pt_regs regs);
do_IRQ需要确保该中断号有一个有效的处理程序,而且这个处理程序已经启动,但是当前并没有执行,如果如此,do_IRQ就会调用handle_IRQ_event来执行注册该中断号的中断处理程序。handle_IRQ_event方法被定义在文件<kernel/irq/handler.c>
/*回到do_IRQ,该函数做清理工作并返回初始入口点,然后再从这个入口点跳转到ret_from_intr*/
irqreturn_t hanlde_IRQ_event(unsigned int irq, struct irqaction *action)
{
irqreturn_t ret, retval = IRQ_NONE;
unsigned int status = 0;
/*因为处理器禁止中断,这里把它们打开,就必须在处理程序注册时指定IRQF_DISABLED标志*/
if (!(action->flags & IRQF_DISABLED))
local_irq_enable_in_hardirq();
/*每个潜在的处理程序在循环中依次执行,如果非共享,第一次执行后iu退出循环*/
do {
strace_irq_handler_entry(irq, action);
ret = action->handler(irq, action->dev_id);
strace_irq_handler_exit(irq, action, ret);
switch (ret) {
case IRQ_WAKE_THREAD:
//把返回值设置为已处理,以便可疑检查不再触发
ret = IRQ_HANDLED;
//捕获返回值为WAKE_THREAD的驱动程序,但是并不创建一个线程函数
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}
//为这次中断唤醒线程,如果线程崩溃且被杀死,仅仅假装已经处理该中断
if (likely(!test_bit(IRQTF_DIED, &action->thread_flags))) {
set_bit(IRQTF_RUNTHREAD, action->thread_flags);
wake_up_process(action->thread);
}
//Fall throug to add to randomness
case IRQ_HANDLED:
status |= action->flags;
break;
default:
break;
}
retval |= ret;
action = action->next;
} while(action);
/*如果注册指定IRQF_SAMPLE_RANDOM标志,还要调用add_interrupt_randomness函数使用中断间隔时间作为随机数产生熵*/
if (status & IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
/*do_IRQ期望中断一直时禁止的,所有将中断禁止,函数返回*/
local_irq_disable();
return retval;
}
中断控制
#include <asm/system.h>
#include <asm/irq.h>
禁止和激活中断
//禁止或激活当前处理器上的本地中断
local_irq_disable();
local_irq_enable();
//禁止中断之前保存中断系统的状态,以及激活中断恢复
unsigned long flags;//flags不能传递给另一个函数,必须驻留再同一栈帧中
local_irq_save(flags);
local_irq_restore(flags);
禁止指定中断号
/*只有当前正在执行的所有处理程序完成后才返回*/
void disable_irq(unsigned int irq);
/*不会等待当前中断处理程序执行完毕即可返回*/
void disable_irq_nosync(unsigned int irq);
/*disable_irq和disable_irq_nosync的每次调用,都需要相应调用一次enable_irq*/
void enable_irq(unsigned int irq);
/*等待一个特定的中断处理程序的退出,如果该程序正在执行,那么该函数必须退出后才能返回*/
void synchronize_irq(unsigned int irq);
中断系统的状态
#include <asm/system.h>
//如果本地处理器上的中断系统被禁止,则返回非0,否则返回0
irqs_disable()
#include <linux/hardirq.h>
//如果内核处于任何类型的中断处理中,返回非0,说明内核此刻正在执行中断处理程序,或者正在执行下半部处理程序
//如果在进程上下文中,则返回0
in_interrupt();
//只有内核确实正在执行中断处理程序时才返回非0,否则返回0
in_irq();
下半部和推后执行的工作
软中断
软中断的实现
- 软中断的代码位于kernel/softirq.c文件中
- 软中断是在编译期间静态分配的
- 一个软中断不会抢占另外一个软中断,唯一能抢占软中断的是中断处理程序
- 其他软中断(相同类型的软中断)可以在其他处理器上同步执行
- 软中断由softirq_action结构体表示,定义在<linux/interrupt.h>
struct softirq_action {
void (*action)(struct softirq_action *action);
};
/*kernel/softirq.c定义一个包含32个结构体的数组,每个被注册的软中断占用该数组的一项*/
static struct softirq_action softirq_vec[NR_SOFTIRQS];
软中断处理程序函数原型:
/*当内核运行一个软中断处理程序时,根据其唯一的参数所指向softirq_vec数组的某项,然后执行struct softirq_action的成员处理函数*/
void softirq_handler(struct softirq_action *action);
执行软中断:一个注册的软中断必须被标记后才会执行(触发软中断raising the softing),通常中断处理程序会在返回前触发它的软中断,以便稍后执行。在一下情况,待处理的软中断被检查和执行:
- 从一个硬件中断代码处返回时
- 在ksoftirqd内核线程中
- 在那些显式和执行待处理的软中断的代码中,如网络子系统中
不管以何方式唤起,软中断都要在do_softirq()中执行,如果有待处理的软中断,do_softirq()会循环遍历每一个,调用它们的处理程序,简化的do_softirq()核心部分如下:
u32 pending = 0;
struct softirq_action *h = NULL;
pending = local_softirq_pending();
if (pending) {
//重设待处理的位图
set_softirq_pending(0);
h = softirq_vec;
do {
if (pending & 1)
h->action(h);
h++;
pending >>= 1;
} while (pending);
}
使用软中断
软中断保留给系统中对时间要求最严格以及最重要的下半部使用,目前只有两个子系统(网络和SCSI)直接使用软中断。此外,内核定时器和tasklet都是建立在软中断上的。
- 分配索引
在编译期间,通过<linux/interrupt.h>中定义一个枚举类型静态地声明软中断。内核使用从0开始的索引表示相对优先级,索引号小的软中断在索引大的软中断之前执行。HI_SOFTIRQ通常作为第一项,RUC_SOFTIRQ作为最后一项,新添加的枚举尽可能插在BLOCK_SOFTIRQ和TASKLET_SOFTIRQ之间。
软中断枚举 | 优先级 | 软中断描述 |
---|---|---|
HI_SOFTIRQ | 0 | 优先级高的软中断 |
TIMER_SOFTIRQ | 1 | 定时器的下半部 |
NET_TX_SOFTIRQ | 2 | 发送网络数据包 |
NET_RX_SOFTIRQ | 3 | 接收数据网络包 |
BLOCK_SOFTIRQ | 4 | BLOCK装置 |
TASKLET_SOFTIRQ | 5 | 正常优先权的tasklets |
SCHED_SOFTIRQ | 6 | 调度软中断 |
HRTIMER_SOFTIRQ | 7 | 高分辨率定时器 |
RUC_SOFTIRQ | 8 | RCU锁定 |
- 注册软中断处理程序
在运行期间通过调用open_softirq()注册软中断处理程序,该函数有两个参数:软中断的索引号和处理函数,如网络系统,在net/coreldev.c通过以下方式注册软中断
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
//软中断处理程序执行的时候,允许响应中断,但是不能休眠
- 触发软中断
raise_softirq()函数可以将一个软中断设置为挂起状态,让在下次调用do_softirq()函数时执行,如网络子系统中断处理程序中会调用
raise_softirq(NET_TX_SOFTIRQ);
//该函数在触发一个软中断之前先禁止中断,触发后再恢复原来的状态
//如果中断本来就已经禁止了,那么可以调用另一个函数
raise_softirq_irqoff(NET_TX_SOFTIRQ);
tasklet
tasklet可以动态创建
tasklet的实现
tasklet是通过软中断实现的,由两类软中断代表:HI_SOFTIRQ和TASKLET_SOFTIRQ,这两者的区别在于HI_SOFTIRQ类型的软中断优先于TASKLET_SOFTIRQ类型的软中断执行。
- tasklet结构体:每个结构体单独代表一个tasklet,在<linux/interrupt.h>中定义。
struct tasklet_struct{
struct tasklet_struct *next; //链表中下一个tasklet
unsigned long state; //tasklet的状态
atomic_t count; //引用计数
void (*func)(unsigned long); //tasklet处理函数
unsigned long data; //给tasklet处理函数的参数
};
/*
state成员只能在0,TASKLET_STATE_SCHED和TASKLET_STATE_RUN之间取值
TASKLET_STATE_SCHED:表明tasklet已被调度,正准备投入允许
TASKLET_STATE_RUN:表示该tasklet正在运行,只有在多处理器的系统上才会使用
count成员是tasklet的引用计数器,如果不为0,则tasklet被禁止,不允许执行
只有count为0时,tasklet才被激活,并且在被设置为挂起状态时,该tasklet才被执行
*/
- 调度tasklet
1、已调度的tasklet(等同于被触发的软中断)存放在两个当处理器数据结构:tasklet_vec(普通tasklet)和tasklet_ji_vec(高优先级的tasklet),这两个数据结构都是由tasklet_struct结构体构成的链表,链表中的每个tasklet_struct代表一个不同的tasklet。
2、tasklet由tasklet_schedule()和tasklet_hi_schedule()函数进行调度,接收一个指向tasklet_struct结构体的指针作为参数,区别在于一个使用TASKLET_SOFTIRQ,另一个使用HI_SOFTIRQ。
3、tasklet_schedule()的执行步骤
1)检查tasklet的状态是否为TASKLET_STATE_SCHED,如果是,说明已经被调度,函数立即返回
2)调用_tasklet_schedule()
3)保存中断状态,然后禁止本地中断,从而保证处理tasklet时,处理器上的数据完整性
4)把需要调度的tasklet加到每个处理器一个的tasklet_vec链表或tasklet_hi_vec链表中
5)唤起TASKLET_SOFTIRQ或HI_SOFTIRQ软中断,以便在下一次调用do_softirq()时执行该tasklet
6)恢复中断到原状态并返回
4、tasklet_action()和tasklet_hi_action()这两个软中断处理函数是tasklet处理核心,处理过程如下:
1)禁止中断(没有必要保存其状态,因为这里的代码总是作为软中断被调用,而且中断总是被激活的),并为当前处理器检索tasklet_vec和tasklet_hi_vec链表
2)将当前处理器上的该链表设置为NULL,达到清空的效果
3)允许响应中断,没有必要恢复原状态,因为这段程序本身就是作为软中断处理程序被调用,所以中断是应该被允许的
4)循环遍历获得链表上的每一个待处理的tasklet
5)如果是多处理器系统,通过检查TASKLET_STATE_RUN来判断这个tasklet是否正在其他处理器上运行,如果它正在运行,那么现在就不要执行,跳到下一个待处理的tasklet上(同一时间,相同类型的tasklet只能有一个执行)
6)如果当前这个tasklet没有执行,将其状态设置为TASKLET_STATE_RUN,这样别的处理器就不会执行它
7)检查count值是否为0,确保tasklet没有被禁止,如果tasklet被禁止了,则跳到下一个挂起的tasklet上
8)满足count为0,且状态为TASKLET_STATE_RUN,则执行该tasklet处理程序
9)tasklet运行完毕,清除tasklet的state域TASKLET_STATE_RUN状态标志
10)重复执行下一个tasklet,直至没有剩余的等待处理的tasklet
使用tasklet
- 声明tasklet
#include <linux/interrupt.h>
//两个宏都可以静态创建一个tasklet
//根据给定的name静态的创建一个tasklet_struct结构
//当tasklet被调度后,绑定的func就会被执行,参数由data给出
DECLARE_TASKLET(name, func, data);//创建的tasklet的引用计数设置为0,处于激活状态
DECLARE_TASKLET_DISABLED(name, func, data);//创建的tasklet引用计数为1,处于禁止状态
//动态创建一个tasklet
struct tasklet_struct t;
void tasklet_init(struct tasklet_struct *t, void (*tasklet_handler)(unsigned long data), unsigned long data);
- 编写tasklet处理程序
//tasklet处理程序必须符合规定的函数类型
void (*tasklet_handler)(unsigned long data);
//因为tasklet依靠软中断实现,所以tasklet处理程序不允许睡眠
//由于tasklet运行时允许响应中断,如果tasklet和中断处理程序共享数据,则应该屏蔽中断然后获取一个锁
//两个相同的tasklet绝不会同时执行
//如果tasklet处理程序与另一个tasklet处理程序或软中断共享了数据,则进行锁保护
- 调度tasklet
//通过调用tasklet_schedule()函数并传递给它相应的tasklet_struct指针,该tasklet就会被调度执行
static inline void tasklet_schedule(struct tasklet_struct *t);
//如果没有得到运行机会之前,有一个相同的tasklet被调度,那么仍然会运行一次
//如果此时该tasklet已经开始运行,如在另外一个处理器上,那么这个新的tasklet会被重新调度并再次运行
//可以调用tasklet_disable()函数禁止某个指定的tasklet,如果该tasklet当前正在执行,这个函数就会等待它执行完毕再返回
static inline void tasklet_disable(struct tasklet_struct *t);
//也可以调用tasklet_disable_nosync()函数,用来禁止指定的tasklet,不过无需再返回前等待tasklet执行完毕,这样做法不安全,无法预料tasklet是否仍在执行
static inline void tasklet_disable_nosync(struct tasklet_struct *t);
//tasklet_enable()函数激活一个tasklet
//如果希望激活DECLARE_TASKLET_DISABLED()创建的tasklet,也可以调用该函数
static inline void tasklet_enable(struct tasklet_struct *t);
//通过调用tasklet_kill()函数从挂起的队列中剔除一个tasklet,先等待tasklet执行完毕,然后再剔除
//由于该函数可能引起休眠,所以禁止在中断上下文中使用
void tasklet_kill(struct tasklet_struct *t);
- ksoftirqd
1、每个处理器都有一组辅助处理软中断(和tasklet)的内核线程,当内核中出现大量软中断的时候,这些内核进程就会辅助处理
2、每个处理器都有一个这样的线程(最低的优先级上运行nice = 19),所有线程的名字叫做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(),以便让更重要的进程得到处理
schedule();
}
//当所有需要执行的操作都完成,让内核线程将自己设置为TASK_INTERRUPTIBLE状态
//唤起调度程序选择其他可执行进程
set_current_state(TASK_INTERRUPTIBLE);
}
工作队列
- 工作队列将工作交由一个内核线程去执行(也就是说下半部总是在进程上下中执行)
- 通过工作队列执行的程序占有进程上下文优势,并且允许工作队列重新调度和睡眠
- 工作队列子系统是一个用于创建内核线程(工作者线程)的接口,通过它创建的进程负责执行由内核其他部分分排到队列的任务
- 工作队列可以在驱动程序中创建一个专门的工作者线程来处理需要下半部执行的工作
- 缺省的工作者线程叫做events/n,n是处理器编号,每个处理器对应一个线程,许多内核驱动程序都把下半部交给缺省的工作者线程执行
工作队列的实现
- 表示工作队列的数据结构
每个工作者线程类型关联一个workqueue_struct,在结构体内,给每个线程分配一个cpu_workqueue_struct,也就是说每个处理器都有一个该类型的工作者线程
//外部可见的工作队列是由每个cpu的工作队列组成的数组
struct workqueue_struct{
//cpu_workqueue_struct结构定义在kernel/workqueue.c中
//数组中每一项对应系统的一个处理器
//由于系统每个处理器对应一个工作者线程,所以每个工作者线程对应由一个cpu_workqueue_struct结构体
struct cpu_workqueue_struct cpu_wq[NR_CPUS];
struct list_head list;
const char *name;
int sinqletthread;
int freezeable;
int rt;
}
struct cpu_workqueue_struct{
spinlock_t lock;//锁保护
struct list_head worklist;//工作列表
wait_queue_head_t more_work;
struct work_struct *current_struct;
struct workqueue_struct *wq;//关联工作队列结构
tasK_t *thread;//关联工作者线程
}
- 表示工作的数据结构
所有的工作者线程都是普通的内核线程实现的,都有执行worker_thread()函数,初始化完成后,执行一个死循环并开始休眠。当有操作被插入到队列时,线程就会被唤醒,执行操作,若没有剩余队列,继续休眠
#include <linux/workqueue.h>
//每个处理器上的每种类型的队列都对应这样结构体组成的列表
//当工作者线程被唤醒,执行链表上所有的工作,执行完成后,将相应的work_struct对象从链表移除,当链表没有对象时,便进入休眠
struct work_struct{
atomic_long_t data;
struct list_head entry;
work_func_t func;
}
worker_thread()函数的核心流程
for(;;) {
prepare_to_wait(&cwq->more_work, &wait, TASK_INTERRUPTIBLE);
if (list_empty(&cwq->worklist))
schedule();
finish_wait(&cwq->more_work, &wait);
run_workqueue(cwq);
}
- 线程将自己设置为休眠状态(state被设置成TASK_INTERRUPTIBLE),并把自己加入到等待队列中
- 如果工作链表为空,线程调用shedule()函数进入睡眠状态
- 如果链表有对象,线程不会睡眠,相反,将自己设置成TASK_RUNNING,脱离等待队列
- 如果链表非空,调用run_workqueue()函数执行推后的工作
由run_workqueue()函数实际完成被推后的工作,该函数循环遍历链表上每个待处理的工作,执行链表每个节点上的workqueue_struct中的func成员函数
struct work_struct *work;
work_func_t f;
void *data;
while(!list_empty(&cwq->worklist)) {
work = list_entry(cwq->worklist.next, struct work_struct, entry);
f = work->func;
list_del_init(cwq->worklist.next);
work_clear_pending(work);
f(work);
}
- 当链表不为空时,选取下一个节点对象
- 获取希望执行的函数func及其参数
- 把该节点从链表上移除,将待处理标志位pending清零
- 调用函数
- 重复执行
- 工作队列实现机制的总结
work、工作队列和工作者线程之间的关系
- 位于最高一层的是工作者线程,系统允许有多种类型的工作者线程存在
- 对于指定的一个类型,系统每个cpu都有一个该类的工作者线程
- 大部分驱动程序都是用默认工作者线程
使用工作队列
- 向缺省的events任务队列添加工作程序程序
//静态创建名为name,处理函数为func,参数为data的work_struct工作结构体
DECLARE_WORK(name, void (*func)(void *), void *data);
//动态创建工作结构体
INIT_WORK(struct work_struct *work, void (*func)(void *), void *data);
- 工作队列处理函数
- 该函数由一个工作者线程执行,并且运行在进程上下文中
- 默认情况下,允许响应中断,并且不持有任何锁,如果需要,函数可以睡眠,但是不能访问用户空间,因为内核线程在用户空间没有相关的内存映射
- 通常在发生系统调用时,内核会代表用户空间的进程运行,此时才能访问用户空间,也只有此时才会映射用户空间的内存
- 在工作队列使用锁,与内核其他部分使用锁机制一致
void work_handler(void *data);
- 对工作队列进行调度
//如果把下部分工作的处理函数提交给缺省的events工作线程,那么调度使用schedule_work()
//work会马上调度,一旦其所在的处理器的工作线程被唤醒,就会被执行
schedule_work(&work);
//如果希望延迟在执行,可调用
schedule_delayed_work(&work, delay);
//取消延迟执行的工作,即取消任何与work_struct相关的挂起工作
int cancel_delayed_work(struct work_struct *work);
- 刷新操作
//该函数一直等待,直到队列中所有对象都被执行以后才返回
//在等待所有待处理的工作执行的时候,该函数会进入休眠状态,所有只能在进程上下文中使用
//该函数不取消任何延迟执行的工作,也就是说通过schedule_delayed_work调度的工作不因调用flush_scheduled_work而被刷新
void flush_scheduled_work(void);
- 创建新的工作队列
//如果创建新的工作队列和与之对应的工作者线程,就会在每个处理器上创建一个工作者线程,name参数用于该内核线程的命名
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);
禁止下半部
- 如果进程上下文和一个下半部共享数据,在访问这些数据之前,需要禁止下半部的处理并得到锁的使用权,是为了防止本地和SMP的保护并且防止死锁的出现
- 任何在工作队列中被共享的数据需要使用锁机制
- 为了保障共享数据的安全,先得到锁然后再禁止下半部的处理
- 如果编写内核的核心代码,可能仅需要禁止下半部即可
#include <asm/softirq.h>
//禁止、激活本地处理器的软中断和tasklet的处理
//可以嵌套使用,如果local_bh_disable被调用三次,那么第三次调用local_bh_enable时,软中断或tasklet处理才被重新激活
//函数通过preemt_count为每个进程维护一个计数器,当计数器为0时,下半部才能被激活
//这些函数并不能禁止工作队列的执行,因为工作队列运行在进程上下文
void local_bh_disable(void)
{
struct thread_info *t = current_thread_info();
t->preemt_count += SOFTIRQ_OFFSET;
}
void local_bh_enable(void)
{
struct thread_info *t = current_thread_info();
t->preemt_count -= SOFTIRQ_OFFSET;
//preemt_count是否为0,另外是否由挂起的下半部,如果都满足,则执行待执行的下半部
if (unlikely(!t->prermt_count && softirq_pending(smp_processor_id()))
do_softirq();
}