07、中断下半部:softirq软中断
📢本篇章将详细介绍软中断。
一、软中断的定义与背景
在操作系统中,中断机制是硬件和系统内核之间沟通的重要方式。中断主要分为两种:
- 硬中断:由硬件设备直接触发,比如网卡收到数据或键盘按下时,会立刻通知CPU有紧急任务需要处理。这类中断必须快速响应,但处理时会暂时关闭其他中断,避免任务被打断。
- 软中断:是操作系统自己设计的机制。它的作用是把硬中断里复杂耗时的工作"先记下来",等合适的时机再慢慢处理。比如收到网络数据包时,硬中断先记录"有新数据",然后让软中断在后面整理数据内容。
软中断的出现解决了硬中断的两大问题:
- 硬中断处理期间系统无法响应其他中断,容易造成"堵车"
- 复杂任务卡在硬中断里会拖慢系统整体反应速度
Linux系统很早就引入了软中断机制。简单来说,就是把任务分成"紧急处理"(硬中断)和"后续处理"(软中断)两部分。这样既能及时响应硬件信号,又能把耗时工作放到不影响系统实时性的环境里完成,让电脑运行得更快更稳定。
举个例子:当你下载文件时,网卡通过硬中断告诉CPU"有新数据来了",然后软中断负责把数据整理到存储空间。这样即使有大量数据涌入,系统也不会因为处理数据而错过新的网络信号。
1.1、软中断的实现原理
软中断的核心思想是通过一种轻量级的任务调度机制,在硬中断上下文之外完成那些不需要立即执行但又不能完全延后的任务。为了实现这一目标,Linux内核定义了一张软中断向量表(softirq vector table),其中每个软中断类型对应一个唯一的编号以及一个具体的处理函数。这些处理函数通过softirq_action
结构体进行封装,其定义如下:
struct softirq_action {
void (*action)(struct softirq_action *);
};
2
3
从代码中可以看出,softirq_action
结构体非常简单,仅包含一个函数指针action
,该指针指向具体实现的软中断处理函数。这种设计不仅简化了软中断的注册和调用过程,还使得每种软中断可以独立地定义自己的行为逻辑。
软中断向量表的定义如下:
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
其中,NR_SOFTIRQS
表示系统支持的软中断总数,目前内核定义了10种软中断类型。每种软中断都有一个固定的编号,这些编号不仅标识了软中断的种类,还隐含了它们之间的优先级关系。编号越小的软中断优先级越高,这意味着在多个软中断同时待处理的情况下,优先级高的软中断会被优先执行。
1.2、软中断的种类及其应用场景
目前,Linux内核定义了以下10种软中断类型,每种类型都针对特定的应用场景进行了优化:
- HI_SOFTIRQ 高优先级的小任务软中断,主要用于处理那些需要快速响应的任务。例如,某些硬件驱动可能会使用此软中断来完成紧急的数据处理。
- TIMER_SOFTIRQ 定时器软中断,用于处理内核定时器相关的任务。定时器是操作系统中非常重要的机制,许多周期性任务(如定时唤醒、超时检测等)都依赖于定时器软中断。
- NET_TX_SOFTIRQ 网络栈发送报文的软中断,负责将网络数据包从内核缓冲区发送到网络设备。由于网络传输通常涉及大量的数据拷贝和协议处理,将其放在软中断中可以避免阻塞硬中断上下文。
- NET_RX_SOFTIRQ 网络栈接收报文的软中断,负责从网络设备接收数据并将其传递给上层协议栈。与
NET_TX_SOFTIRQ
类似,这种软中断也旨在减少硬中断上下文中的处理负担。 - BLOCK_SOFTIRQ 块设备软中断,用于处理块设备(如硬盘、SSD)的I/O请求。这类软中断通常与块设备驱动程序密切相关。
- IRQ_POLL_SOFTIRQ 支持I/O轮询的块设备软中断,主要用于高性能存储设备(如NVMe SSD)。通过轮询机制,可以显著降低I/O延迟,提升存储性能。
- TASKLET_SOFTIRQ 低优先级的小任务软中断,通常用于处理那些不需要立即执行但又不适合完全交由用户态线程完成的任务。Tasklet是一种基于软中断的任务调度机制,广泛应用于内核子系统中。
- SCHED_SOFTIRQ 调度软中断,用于在多处理器系统中实现负载均衡。当某个CPU上的任务队列过载时,调度软中断会尝试将部分任务迁移到其他空闲的 CPU上执行,从而优化系统的整体资源利用率。这种软中断在多核架构下尤为重要,它能够动态调整任务分布,避免某些CPU过载而其他CPU闲置的情况。
- HRTIMER_SOFTIRQ 高精度定时器软中断,用于处理对时间敏感的任务。与传统的
TIMER_SOFTIRQ
相比,高精度定时器提供了更细粒度的时间控制能力,适用于需要微秒甚至纳秒级别精度的场景,例如实时音频处理和工业控制。 - RCU_SOFTIRQ RCU(Read-Copy-Update)软中断,主要用于实现高效的并发数据结构管理。RCU是一种无锁同步机制,允许读者在不加锁的情况下访问共享数据,同时通过延迟销毁旧版本数据来保证一致性。
RCU_SOFTIRQ
负责在适当的时机清理这些不再使用的数据,从而避免内存泄漏。
1.3、软中断的执行流程
软中断的执行流程可以分为以下几个阶段:
- 触发阶段
软中断通常由硬中断处理程序触发。当硬中断处理程序完成其关键操作后,如果存在需要延迟处理的任务,则会调用raise_softirq()
函数标记相应的软中断类型为“待处理”。例如,网络设备驱动在接收到数据包时,可能会触发NET_RX_SOFTIRQ
以通知内核后续的数据处理逻辑。
void raise_softirq(unsigned int nr);
上述函数会将指定编号的软中断加入到当前CPU的软中断挂起队列中,并设置一个标志位以指示该软中断需要被调度执行。
- 调度阶段
软中断的实际执行依赖于内核的软中断调度机制。在Linux内核中,软中断的执行通常发生在以下两种情况下:
- 硬中断返回时:当硬中断处理完成后,内核会检查当前CPU是否有待处理的软中断。如果有,则直接调用
do_softirq()
函数进行处理。这种方式能够在最短时间内响应软中断请求,从而提高系统效率。 - ksoftirqd线程:如果当前CPU负载较高或软中断数量过多,可能无法及时处理所有软中断。此时,内核会唤醒专门的内核线程
ksoftirqd
来接管剩余的软中断任务。每个CPU都有一个对应的ksoftirqd
线程,确保即使在高负载场景下,软中断也能得到妥善处理。
- 执行阶段
do_softirq()
是软中断的核心执行入口,其主要逻辑如下:
- 检查当前CPU是否有挂起的软中断。
- 如果存在挂起的软中断,则遍历软中断向量表,依次调用对应类型的处理函数。
- 在处理过程中,软中断之间遵循优先级顺序,编号较小的软中断优先执行。
- 如果在处理某个软中断期间又触发了新的软中断,内核会重新评估挂起队列并继续处理,直到所有软中断都完成。
需要注意的是,软中断的执行环境介于硬中断上下文和用户态进程之间。它既拥有比用户态更高的权限,又不像硬中断那样完全禁用抢占。因此,软中断可以在一定程度上使用阻塞操作,但仍需尽量避免长时间占用CPU,以免影响系统的实时性。
二、注册软中断的处理函数
在操作系统内核中,软中断(softirq)是一种重要的机制,用于处理那些需要延迟执行但又对系统性能影响较大的任务。软中断的设计目标是高效且灵活,能够在多处理器环境下实现并行处理,同时尽量减少对硬件中断的干扰。为了实现这一目标,Linux 内核提供了 open_softirq()
函数,用于注册软中断的处理函数,并将其与特定的软中断编号关联起来。
2.1. **open_softirq()**
函数的定义与作用
open_softirq()
函数的核心功能是为指定的软中断编号设置对应的处理函数。其函数原型如下:
void open_softirq(int nr, void (*action)(struct softirq_action *));
参数解析:
nr
:表示软中断的编号,通常是一个整数值,用于标识具体的软中断类型。例如,NET_TX_SOFTIRQ
和NET_RX_SOFTIRQ
分别对应网络数据包的发送和接收软中断。action
:指向软中断处理函数的指针。该函数接受一个struct softirq_action *
类型的参数,用于传递与软中断相关的上下文信息。
功能描述:
- 该函数将指定的处理函数
action
注册到软中断向量表(softirq vector table)中,与编号nr
相关联。当内核触发编号为nr
的软中断时,对应的处理函数会被调用以完成特定的任务。
- 该函数将指定的处理函数
2.2. 软中断处理函数的设计要求
由于软中断可以在多个处理器上同时执行,因此其处理函数必须满足以下设计要求:
- 可重入性:软中断处理函数可能会被多个 CPU 并发调用,因此必须是可重入的。这意味着函数内部的状态不能依赖于全局变量或共享资源,除非这些资源受到适当的保护。
- 临界区保护:如果处理函数需要访问共享资源(如全局数据结构或硬件设备),则必须使用锁机制(如自旋锁或互斥锁)来保护临界区,以避免并发访问导致的数据不一致或竞争条件。
- 高效性:软中断的设计初衷是为了提高系统的响应速度和吞吐量,因此处理函数应尽可能简洁高效,避免长时间占用 CPU 或阻塞其他任务。
2.3. 实际应用场景
软中断机制广泛应用于 Linux 内核的多个子系统中,以下是一些典型的应用场景:
- 网络子系统:网络数据包的接收和发送任务通常通过软中断机制来处理。例如,当网卡接收到数据包时,会触发
NET_RX_SOFTIRQ
软中断,对应的处理函数负责将数据包从网卡缓冲区移动到 内核的网络协议栈中,从而实现高效的数据包处理。类似地,当内核需要向网卡发送数据包时,会触发NET_TX_SOFTIRQ
软中断,其处理函数负责将数据包从内核缓冲区传递到硬件设备。 - 块设备子系统:在块设备 I/O 操作中,软中断被广泛用于完成异步 I/O 请求。例如,当磁盘控制器完成一个读写操作后,会通过软中断通知内核,相应的处理函数则负责将数据交付给上层应用程序或将其写入缓存。
- 定时器子系统:软中断还被用于处理高精度定时器事件。通过
TIMER_SOFTIRQ
,内核能够在多个 CPU 上并行执行定时器回调函数,从而满足实时性和高并发场景下的需求。 - RCU(Read-Copy-Update)机制:RCU 是一种高效的同步机制,常用于读多写少的场景。软中断在 RCU 中扮演了重要角色,通过
RCU_SOFTIRQ
,内核能够延迟执行资源回收任务,从而避免阻塞关键路径上的操作。
三、触发软中断
函数raise_softirq用来触发软中断,参数是软中断编号。
void raise_softirq(unsigned int nr){unsigned long flags;local_irq_save(flags);raise_softirq_irqoff(nr);local_irq_restore(flags);}
该函数调用raise_softirq_irqoff(),raise_softirq_irqoff()是在已经禁止中断的情况下调用函数来触发软中断。
inline void raise_softirq_irqoff(unsigned int nr){__raise_softirq_irqoff(nr);/*
* If we're in an interrupt or softirq, we're done
* (this also catches softirq-disabled code). We will
* actually run the softirq once we return from
* the irq or softirq.
*
* Otherwise we wake up ksoftirqd to make sure we
* schedule the softirq soon.
*/if (!in_interrupt() && should_wake_ksoftirqd())wakeup_softirqd();}
2
3
4
5
6
7
8
9
调用__raise_softirq_irqoff函数,如下:
void __raise_softirq_irqoff(unsigned int nr){lockdep_assert_irqs_disabled();trace_softirq_raise(nr);or_softirq_pending(1UL << nr);}
把宏or_softirq_pending展开以后是:
irq_stat[smp processor_id()].softirq_pending |= (1UL << nr);
- raise_softirq_irqoff函数设定本CPU上的softirq_pending的某个bit等于1,具体的bit是由soft irq number(nr参数)指定的。
- 如果在中断上下文,我们只要set __softirq_pending的某个bit就OK了,在中断返回的时候自然会进行软中断的处理。但是,如果在context上下文调用这个函数的时候,我们必须要调用wakeup_softirqd函数用来唤醒本CPU上的softirqd这个内核线程。
这样softirq就相当于准备好了,在合适的时机将会调用softirq的处理函数。
四、执行软中断
内核执行软中断的地方如下。
- 在中断处理程序的后半部分执行软中断,对执行时间有限制:不能超过2毫秒,并且最多执行10次。
- 每个处理器有一个软中断线程,调度策略是SCHED_NORMAL,优先级是120。
- 开启软中断的函数local_bh_enable()。
如果开启了强制中断线程化的配置宏CONFIG_IRO_FORCED_THREADING,并且在引导内核的时候指定内核参数“threadirqs”,那么所有软中断由软中断线程执行
2.1、中断处理程序执行软中断
在中断处理程序的后半部分,调用函数irq_exit()以退出中断上下文,处理软中断,其代码如下:
<kernel/softirq.c>
void irq_exit(void)
{
__irq_exit_rcu();
rcu_irq_exit();
/* must be last! */
lockdep_hardirq_exit();
}
...
static inline void __irq_exit_rcu(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
local_irq_disable();
#else
lockdep_assert_irqs_disabled();
#endif
account_hardirq_exit(current);
preempt_count_sub(HARDIRQ_OFFSET);
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();
tick_irq_exit();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
如果in_interrupt()为真,表示在不可屏蔽中断、硬中断或软中断上下文,或者禁止软中断。如果正在处理的硬中断没有抢占正在执行的软中断,没有禁止软中断,并且当前处理器的待处理软中断位图不是空的,那么调用函数invoke_softirq()来处理软中断。
函数invoke_softirq的代码如下:kernel/softirq.c
static inline void invoke_softirq(void)
{
/* 1. 如果软中断线程处于就绪状态或运行状态,那么让软中断线程执行软中断 */
if (ksoftirqd_running(local_softirq_pending()))
return;
/* 2. 如果没有强制中断线程化,那么调用函数_do_softirq()执行软中断 */
if (!force_irqthreads || !__this_cpu_read(ksoftirqd)) {
#ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK
/*
* We can safely execute softirq on the current stack if
* it is the irq stack, because it should be near empty
* at this stage.
*/
__do_softirq();
#else
/*
* Otherwise, irq_exit() is called on the task stack that can
* be potentially deep already. So call softirq in its own stack
* to prevent from any overrun.
*/
do_softirq_own_stack();
#endif
} else {
/* 3. 如果强制中断线程化,那么唤醒软中断线程执行软中断*/
wakeup_softirqd();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
函数_do_softirq是执行软中断的核心函数,其主要代码如下:
<kernel/softirq.c>
asmlinkage __visible void __softirq_entry __do_softirq(void)
{
unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
unsigned long old_flags = current->flags;
int max_restart = MAX_SOFTIRQ_RESTART;
struct softirq_action *h;
bool in_hardirq;
__u32 pending;
int softirq_bit;
current->flags &= ~PF_MEMALLOC;
/* 1. 把局部变量pending设置为当前处理器的待处理软中断位图 */
pending = local_softirq_pending();
/* 2. softirq_handle_begin调用__local_bh_disable_ip 把抢占计数器的软中断计数加1 */
softirq_handle_begin();
in_hardirq = lockdep_softirq_start();
account_softirq_enter(current);
restart:
/* 3. 把当前处理器的待处理软中断位图重新设置为0 */
set_softirq_pending(0);
/* 4. 开启硬中断 */
local_irq_enable();
h = softirq_vec;
/* 5. 从低位向高位扫描待处理软中断位图,针对每个设置了对应位的转中断编号,执行软中断的处理函数*/
while ((softirq_bit = ffs(pending))) {
unsigned int vec_nr;
int prev_count;
h += softirq_bit - 1;
vec_nr = h - softirq_vec;
prev_count = preempt_count();
kstat_incr_softirqs_this_cpu(vec_nr);
trace_softirq_entry(vec_nr);
/* 调用action */
h->action(h);
trace_softirq_exit(vec_nr);
if (unlikely(prev_count != preempt_count())) {
pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
vec_nr, softirq_to_name[vec_nr], h->action,
prev_count, preempt_count());
preempt_count_set(prev_count);
}
h++;
pending >>= softirq_bit;
}
if (!IS_ENABLED(CONFIG_PREEMPT_RT) &&
__this_cpu_read(ksoftirqd) == current)
rcu_softirq_qs();
/* 6. 禁止硬中断 */
local_irq_disable();
/* 7. 如果软中断的处理函数又触发软中断,处理如下 */
pending = local_softirq_pending();
if (pending) {
/* 8. 如果软中断的执行时间小于2毫秒,不需要重新调度进程,并口且软中断的执行次数没超过10,那么跳转到restart继续执行软中断 */
if (time_before(jiffies, end) && !need_resched() &&
--max_restart)
goto restart;
/* 9. 唤醒软中断线程执行软中断 */
wakeup_softirqd();
}
account_softirq_exit(current);
lockdep_softirq_end(in_hardirq);
/* 10. 把抢占计数器的软中断计数减1 */
softirq_handle_end();
current_restore_flags(old_flags, PF_MEMALLOC);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
上面就是软中断的调用流程。
2.2、软中断线程
每个处理器有一个软中断线程,名称是“ksofirqd/”后面跟着处理器编号,调度策略SCHED_NORMAL,优先级是120。软中断线程的核心函数是run_ksoftirqd(),其代码如下
<kernel/softirq.c>
static void run_ksoftirqd(unsigned int cpu)
{
ksoftirqd_run_begin();
if (local_softirq_pending()) {
/*
* We can safely run softirq on inline stack, as we are not deep
* in the task stack here.
*/
__do_softirq();
ksoftirqd_run_end();
cond_resched();
return;
}
ksoftirqd_run_end();
}
...
static struct smp_hotplug_thread softirq_threads = {
.store = &ksoftirqd,
.thread_should_run = ksoftirqd_should_run,
.thread_fn = run_ksoftirqd,
.thread_comm = "ksoftirqd/%u",
};
static __init int spawn_ksoftirqd(void)
{
cpuhp_setup_state_nocalls(CPUHP_SOFTIRQ_DEAD, "softirq:dead", NULL,
takeover_tasklets);
BUG_ON(smpboot_register_percpu_thread(&softirq_threads));
return 0;
}
early_initcall(spawn_ksoftirqd);
...
static int __smpboot_create_thread(struct smp_hotplug_thread *ht, unsigned int cpu)
{
struct task_struct *tsk = *per_cpu_ptr(ht->store, cpu);
struct smpboot_thread_data *td;
...
tsk = kthread_create_on_cpu(smpboot_thread_fn, td, cpu,
ht->thread_comm);
if (IS_ERR(tsk)) {
kfree(td);
return PTR_ERR(tsk);
}
kthread_set_per_cpu(tsk, cpu);
...
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
这里创建一个线程,然后线程中执行run_ksoftirqd函数,run_ksoftirqd函数里执行__do_softirq()函数。
五、抢占计数器
让我们用更简单的方式解释这个概念:
每个进程都有一个"抢占计数器"(preempt_count),它就像一个开关:
- 当计数器是0时,进程可以被其他更紧急的任务打断
- 当计数器大于0时,进程不会被中断
为什么需要这个计数器? 当进程在执行重要操作(比如修改关键数据)时,我们不希望它突然被其他任务打断。这时会把计数器加1(禁止抢占),等操作完成后减回0(允许抢占)。
具体流程是这样的:
- 进程在执行系统任务时,如果计数器是0,可能会被更高优先级的进程打断
- 当中断处理完成后,系统会检查当前进程的计数器
- 如果计数器是0,系统会判断是否有其他更紧急的任务需要运行
- 如果计数器不为0,进程会继续执行直到计数器归零
内核对计数器的每一位都做了特殊用途划分,这样能更精确地管理不同场景下的抢占状态。比如:
- 进入中断时会标记特定位
- 执行关键代码段时标记其他位
- 每个标记都有对应的开启/关闭操作
简单总结:这个计数器就像交通灯,控制着进程执行时是否允许被其他任务"截停",确保关键操作能完整执行。
其中第0(7位是抢占计数,第8)15位是软中断计数,第16~19位是硬中断计数第20位是不可屏蔽中断(Non Maskable Interrupt,NMI)计数。
#define PREEMPT_MASK (__IRQ_MASK(PREEMPT_BITS) << PREEMPT_SHIFT)
#define SOFTIRQ_MASK (__IRQ_MASK(SOFTIRQ_BITS) << SOFTIRQ_SHIFT)
#define HARDIRQ_MASK (__IRQ_MASK(HARDIRQ_BITS) << HARDIRQ_SHIFT)
#define NMI_MASK (__IRQ_MASK(NMI_BITS) << NMI_SHIFT)
...
#define PREEMPT_BITS 8
#define SOFTIRQ_BITS 8
#define HARDIRQ_BITS 4
#define NMI_BITS 4
2
3
4
5
6
7
8
9
各种场景分别利用各自的位禁止或开启抢占。
- 普通场景(PREEMPT_MASK):对应函数preempt_disable()和preempt_enable() 。
- 软中断场景(SOFTIRO_MASK):对应函数local_bh_disable()和local_bh_enabe() 。
- 硬中断场景(HARDIRQ_MASK):对应函数 _irq_enter()和_irq_exit()。
- 不可屏蔽中断场景(NMI MASK):对应函数nmi_enter()和nmi_exit() 。
反过来,我们可以通过抢占计数器的值判断当前处在什么场景:
<include/linux/preempt.h>
/*
* Macros to retrieve the current execution context:
*
* in_nmi() - We're in NMI context
* in_hardirq() - We're in hard IRQ context
* in_serving_softirq() - We're in softirq context
* in_task() - We're in task context
*/
#define in_nmi() (nmi_count())
#define in_hardirq() (hardirq_count())
#define in_serving_softirq() (softirq_count() & SOFTIRQ_OFFSET)
#define in_task() (!(in_nmi() | in_hardirq() | in_serving_softirq()))
/*
* The following macros are deprecated and should not be used in new code:
* in_irq() - Obsolete version of in_hardirq()
* in_softirq() - We have BH disabled, or are processing softirqs
* in_interrupt() - We're in NMI,IRQ,SoftIRQ context or have BH disabled
*/
#define in_irq() (hardirq_count())
#define in_softirq() (softirq_count())
#define in_interrupt() (irq_count())
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- in_irq()表示硬中断场景,也就是正在执行硬中断。
- in_softirq()表示软中断场景,包括禁止软中断和正在执行软中断。
- in_interrupt()表示正在执行不可屏蔽中断、硬中断或软中断,或者禁止软中断。
- in_serving_softirq()表示正在执行软中断。
- in_nmi()表示不可屏蔽中断场景。
- in_task()表示普通场景,也就是进程上下文。
六、禁止/开启软中断
如果进程和软中断可能访问同一个对象,那么进程和软中断需要互斥,进程需要禁止软中断。禁止软中断的函数是local_bh_disable(),注意:这个函数只能禁止本处理器的软中断,不能禁止其他处理器的软中断。该函数把抢占计数器的软中断计数加2,其代码如下:
static inline void local_bh_disable(void)
{
__local_bh_disable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);
}
...
#define SOFTIRQ_DISABLE_OFFSET (2 * SOFTIRQ_OFFSET)
2
3
4
5
6
调用__local_bh_disable_ip函数:
static __always_inline void __local_bh_disable_ip(unsigned long ip, unsigned int cnt)
{
preempt_count_add(cnt);
barrier();
}
2
3
4
5
开启软中断的函数是local_bh_enable(),该函数把抢占计数器的软中断计数减2。为什么禁止软中断的函数local_bh_disable()把抢占计数器的软中断计数加2,而不是加 1呢?目的是区分禁止软中断和正在执行软中断这两种情况。执行软中断的函数__do_sofir()把抢占计数器的软中断计数加1。如果软中断计数是奇数,可以确定正在执行软中断。