-
Notifications
You must be signed in to change notification settings - Fork 45
/
softirq 与 ksoftirqd 内核线程
466 lines (368 loc) · 24.8 KB
/
softirq 与 ksoftirqd 内核线程
1
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
-----------------------------------------
1. ksoftirqd内核线程
-----------------------------------------
每个处理器都有一组辅助处理器【软中断】的内核线程(tasklet也是基于软中断)。当内核中出现【大量软中断】的时候,这些内核进程就会辅助处理它们。
-----------------------
引入ksoftirq内核线程的原因:
-----------------------
对软中断,内核会选择在【几个特殊时机】进行处理, 而在中断处理程序【返回】时处理是最常见的。
软中断被触发的频率,有时可能很高,更不利的是,处理函数有时还会自行重复触发,那么就会导致【用户空间进程】无法获得足够的处理时间,因而处于饥饿状态。
对于单纯的重新触发的软中断,采取不立即处理的策略,也让人无法接受。
-------------
最初的解决方案:
-------------
方案1)只要还有被触发并等待处理的软中断,“本次执行”就要负责处理,重新触发的软中断也在“本次执行”返回前被处理。
这样做可以保证对内核的软中断采取即时处理的方式,关键在于,对重新触发的软中断也会立即处理。
当负载很高的时候,此时若有大量被触发的软中断,而它们本身又会重复触发, 系统可能会一直处理软中断,根本不能完成其他任务。
方案2)不处理重新触发的软中断。
在从【中断返回】的时候,内核和平常一样,也会检查【所有挂起的软中断】并处理他们。但是,任何自行重新触发的软中断不会马上处理,
它们被放到【下一个软中断执行时机】去处理。
而这个时机,通常也就是下一次中断返回的时候。可是,在比较空闲的系统中,立即处理软中断才是比较好的做法。
尽管它能保证用户空间不处于饥饿状态,但它却让软中断忍受饥饿的痛苦,而根本没有好好利用闲置的系统资源。
改进:
最终,在内核中实现的方案,是不会立即处理重新触发的软中断。
而作为改进,当大量软中断出现的时候,内核会【唤醒一组内核线程】来处理这些负载。这些线程在最低的优先级上运行(nice值是19),
这能避免它们跟其他重要的任务抢夺资源。但它们最终肯定会被执行,所以这个折中方案能够保证在【软中断负担很重】的时候用户程序也不会因为得不到
处理时间处于饥饿状态。相应的,也能保证”过量“的软中断终究会得到处理。
每个处理器都有一个这样的线程。所有线程的名字都叫做ksoftirq/n,区别在于n,它对应的是处理器的编号。
在一个双CPU的机器上就有两个这样的线程,分别叫做ksoftirqd/0和ksoftirqd/1。
为了保证【只要有空闲的处理器,它们就会处理软中断】,所以给每个处理器都分配一个这样的线程。
一旦该线程被初始化,它就会执行类似下面这样的【死循环】:
在<Softirq.c(kernel)>中
static int ksoftirqd(void * __bind_cpu)
{
set_user_nice(current, 19);
current->flags |= PF_NOFREEZE;
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
preempt_disable();
if (!local_softirq_pending()) { //负责发现 有没有待处理的软中断
preempt_enable_no_resched();
schedule(); //如果有必要,每次迭代后都会调用schedule(),以便让更重要的进程得到处理机会
preempt_disable();
}
__set_current_state(TASK_RUNNING);
while (local_softirq_pending()) { //负责发现 有没有待处理的软中断
/* Preempt disable stops cpu going offline.
If already offline, we'll be on wrong CPU:
don't process */
if (cpu_is_offline((long)__bind_cpu))
goto wait_to_die;
do_softirq(); //去处理待处理的软中断
preempt_enable_no_resched();
要有待处理的软中断(由softirq_pending函数负责发现),ksoftirqd就会调用do_softirq去处理它们。 通过重复执行这样的操作,
重新触发的软中断也会被执行。
如果有必要,每次迭代后都会调用schedule(),以便让更重要的进程得到处理机会。
当所有需要执行的操作都完成以后,该内核线程,将自己设置为【TASK_INTERRUPTIBLE】状态,唤起调度程序,选择其他可执行进程投入运行。
只要do_softirq()函数发现已经执行过的内核线程重新触发了它自己,软中断内核线程就会被唤醒。
※<2.8 进程间通讯>
与进程同步一样,进程间通信(IPC)也分成【集中式】与【分布式】两种情况。下面介绍【集中式】系统中的进程间通信问题。
进程间交换信息称为通信。
有些情况下进程之间交换的信息量很少,例如,仅交换某个状态信息,某资源忙或闲状态。
有些情况下进程间交换大批数据,例如传送一批消息或整个文件。
进程同步和互斥,仅通过修改【信号量的值】向对方传递信息,从这个意义上看,进程同步和互斥是一种低级的进程通信。
当今操作系统中,使用的进程间通信方式有: 软中断、 共享存储、 共享文件、 消息传递、 信箱
前3种用于集中式系统;
后2种既可用于集中式,也可用于分布式系统;
1、软中断
UN1X系统提供软中断机制作为进程通信的一种手段。软中断是通过发送规定的信号到指定进程,对方进程定时地查询有无外来信号,若有则按约定进行处理,处理完毕,返回断点继续执行原来的指令。可见,软中断是对硬中断的一种模拟。软中断存在较大的时延,不象硬中断能获得及时响应。例如,对方进程若处在阻塞队列,那么只有等到该进程执行时才能查询软中断信号。显然,从软中断信号发出到对方响应,时间可能拖得很长。此外,软中断处理程序运行在用户态,硬中断处理程序则运行在核心态。
各种UNIX版本设置的软中断信号一般不超过32种,其中部分信号已规定了它们的意义,另一部分留给用户自己定义。信号由键盘产生或由进程中的错误(例如不合理的存储访问)或由许多异步事件(例如时钟或来自C-Shell的作业控制)产生。
1)主要数据结构
软中断通信涉及进程的proc和user结构,有关部分介绍如下:
(1)p_clktim
它是报警时钟时间计数器,由系统调用alarm来设置它,时间一到就发送14号信号。
(2)p_sigign
它是忽略信号标记,共32位,每位对应一个信号。当进程需要忽略某些信号时,就把p_sigign中与这些信号对应的位置上1。
(3)p_sig
它是本进程接收信号的地方,共32位,正好对应32种不同的信号(因为信号只有27种,所以有5位未用)。第0位对应信号 1,第1位对应信号2,…,当本进程收到一个信号时,就在p_sig的对应位上置1。
(4)u_signal[NSIG]
它是一个有32个元素的一维数组,每个元素占32位,正好存放一个地址值。此地址为软中断处理程序的入口。当u_signal[i](i =1 , 2 , ... , 32)的值为非零偶数时,表明它是信号软中断处理程序入口地址,本进程按该处理程序来响应软中断;当u_signal[i]的值为0时,则终止进程本身;当u_signal[i]的值为非零奇数时,该软中断不起作用,本进程忽略它,不予处理。
2)信号发送
供用户进程发送软中断信号的系统调用是kill(pid, sig),其中pid为对方进程的标识号,sig为信号名称。如果pid为正整数,则把sig发给pid 号进程;如果pid 为0,则把sig发给同一进程组内的所有进程;如果pid 为-1,则把sig发送给自己或发送给除进程0和进程1之外的每个进程(用户是超级用户时)。具体发送工作由程序_psignal(&proc, sig)和_signal(p_pgrp, sig)完成。
_psignal(&proc, sig)中的参数&proc是对方进程的proc首址,sig为信号名。当对方进程未忽略sig时,就在对方进程的p_sig中相应的位上设置sig 。为尽快处理软中断信号,当对方进程处于睡眠态SSLEEP且它的优先数p_pri大于25时,则唤醒它,并把它排入就绪队列中。
_signal(p_pgrp, sig)中的第1参数p_pgrp为同组进程的组标识号,sig为信号名。该程序把信号发给同组其他进程,其实现比较简单:查找所有proc数组,凡其中proc结构中p_pgrp与第1参数相同者,就调用_psignal程序将sig发送给它。
3)信号接收与处理
UN1X系统V有一条系统调用signal(sig, func)用于软中断信号的接收与处理。正在执行的进程遇到时钟中断或核心态转至用户态或进入睡眠态之前或它退出低优先级睡眠之时,总要执行signal(sig, func)。
signal(sig, func)中的第1参数sig为信号名,第2参数func为对该信号的处理方式。当func为 1时,忽略该信号;当func为0 时,终止本进程;当func非奇数,非零的正整数时,按u_signal[sig]中的入口地址转软中断处理程序。软中断处理程序必须预先设计好。
当进程处于核心态时,即使收到软中断信号也不予理睬,只有当它返回用户态后,才处理收到的软中断信号。对于3号和12号软中断,则在调用_exit自我终止(因func=0)之前,还需要调用_core程序,将进程的数据段转贮到文件core中,转贮成功时置成功标记,该标记连同信号一起作为参数提供给_exit程序,由该程序负责返回给接受进程。
4)27种软中断信号
下列27种软中断信号的注释部分即为它的功能说明
#define SIGHUP 1 /* hangup */
#define SIGINT 2 /* interrupt */
#define SIGQUIT 3 /* quit */
#define SIGILL 4 /* illegal instruction (not reset when caught ) */
#define SIGTRAP 5 /* trace trap (not reset when caught ) */
#define SIGIOT 6 /* IOT instruction */
#define SIGEMT 7 /* EMT instr uction */
#define SIGFPE 8 /* floating point exception */
#define SIGKILL 9 /* kill (cannot be caught or ignored) */
#define SIGBUS 10 /* bus error */
#define SIGSEGV 11 /* segmentation violation */
#define SIGSYS 12 /* bad argument to system call */
#define SIGPIPE 13 /* write on a pipe with no one to read it */
#define SIGALAM 14 /* alarm clock */
#define SIGTERM 15 /* software termination signal from kill */
#define SIGSTOP 17 /* sendoble stop signal not from tty */
#define SIGTSTP 18 /* stop signal from tty */
#define SIGCONT 19 /* continue a stopped process */
#define SIGCHLD 20 /* to parent on child stop or exit */
#define SIGTTIN 21 /* to readers pgrp upon background tty read */
#define SIGTTOOU 22 /* like TTIN for output it (tp→t-local &LTOSTOP) */
#define SIGTINT 23 /* to pgrp on every input character if LINTRUP */
#define SIGXCPU 24 /* exceeded CPU time limit */
#define SIGXFSZ 25 /* exceeded file size limit */
#define SIGSTAT 26 /* status update requsted */
#define SIGMOUS 27 /* mouse interrupt */
#define NSIG 32
上述信号中,中断信号SIGNT(信号2)通常是从终端键盘上打入^c字符所产生的,该信号常用于停止一条未完成的命令。
退出信号SIGQUIT通常由打入^\字符所产生,该信号还要使得有关的进程把它当前的存储器映象写入当前目录下的称之为core 的文件中, 以便于诊断程序使用。信号SIGSTOP和SIGCONT用于C-Shell的作业控制中停止和重新启动某个进程;信号SIGILL是由非法指令所产生的,SIGSEGV是由访问的存储器超出了进程的合法虚拟存储器空间所产生的。
在下列地方,会执行软中断:
1.从一个硬件中断代码中返回。
2.在ksoftirqd内核线程中。
3.在显示得检查和执行待处理的软中断代码中,如网络子系统。
(UN1X系统V有一条系统调用signal(sig, func),用于软中断信号的接收与处理。
正在执行的进程,遇到【时钟中断】或【核心态转至用户态】或【进入睡眠态之前】或【它退出低优先级睡眠】之时,总要执行signal(sig, func)。)
/////////////////////////////////////////////////
2. softirq 与 ksoftirqd 内核线程
/////////////////////////////////////////////////
本文是对《Understanding Linux Network Internals》笔记。
中断,简单的说就是外部设备通知kernel的一种方式。在之前的文章里也介绍了,可以通过request irq的方法注册一个中断号以及对应的irq。
当一个数据包到达网卡的时候,网卡就会触发对应的中断号,然后内核根据这个中断号找到对应的handle,由handler对这个数据包进行处理。
注意,当中断的handler在进行处理的时候,中断是被关闭的。也就是说在handler执行期间,新的中断是不会被接收的。
因此中断的handler只适合做一些非常快速的活,否则会阻塞其它中断的进行。那么对于网络协议来说要对其进行二层、三层等处理是很复杂很耗时的,
但有时候数据包又会很多,咋办呢?于是内核里有下半区中断的说法。其实很简单,在handler里我只做最简单的操作,并在内存里保存足够的信息,
然后设置一个flag说:“hi 内核,有空的时候来处理下我这边的事情哈”,然后handler就结束了。之后当内核空闲的时候,其会去检查有没有flag被标记为有事情要做,
如果有的话内核就会去进行处理。
/////////////////////////////////////////////////
目前主要有三种下半区的方法:
/////////////////////////////////////////////////
1.old-style bottom half:即便是多核处理器,同一时间只能运行一种下半区任务
2.tasklet:对于多核处理器,同一时间多个核可以同时运行不同种类的下半区任务,但是对于同一种类的下半区任务不能同时运行
3.softirq:对于多核处理器,同一时间多个和可以同时运行多个下半区任务,并且任务的种类可以相同。不过同一时间相同的核上不能运行相同的任务
下面主要介绍第三种: softirq
来看下handler和下半区(下面会简称BH)的联系。在内核boot up的时候,start_kernel是被调用的入口:
asmlinkage __visible void __init start_kernel(void)
{
......
init_IRQ();
......
softirq_init();
......
time_init();
......
rest_init();
}
static noinline void __init_refok rest_init(void)
{
......
kernel_thread(kernel_init, NULL, CLONE_FS);
......
}
static int __ref kernel_init(void *unused)
{
......
kernel_init_freeable();
......
}
static noinline void __init kernel_init_freeable(void)
{
......
do_basic_setup();
......
}
static void __init do_basic_setup(void)
{
cpuset_init_smp();
usermodehelper_init();
shmem_init();
driver_init();
init_irq_proc();
do_ctors();
usermodehelper_enable();
do_initcalls();
random_int_secret_init();
}
static void __init do_initcalls(void)
{
int level;
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}
//subsys_initcall(net_dev_init)会的注册net_dev_init,所以其中的一个level会的执行net_dev_init。
可以看到,start_kernel会调用net_dev_init,BH就是在这里绑定BH的处理handler的(至少网络部分是在这里)。来看下softirq的BH种类:
enum {
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
SCSI_SOFTIRQ,
TASKLET_SOFTIRQ
};
这些类型通过open_softirq和对应的handler关联: static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;
void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
softirq_vec[nr].data = data;
softirq_vec[nr].action = action;
}
可以看到关联关系是放在softirq_vec这个数组里的。那么softirq在哪些时候会的被调用呢? 有下面几种情况:
1.do_IRQ的结尾
2.系统调用的返回
3.local_bh_enable调用后
4.ksoftirqd这个内核线程
当softirq可以被调用的时候,入口为do_softirq。
首先再来说下此时的背景:
一个数据包到达网卡,网卡根据中断号调用中断的handler,中断的handler做了必要的处理,比如从网卡取出数据放到skb_buf中、设置有BH要处理的flag等。
之后中断结束(do_IRQ)的时候,允许软中断的执行,此时do_softirq就上场了。
来看代码:
asmlinkage __visible void do_softirq(void)
{
__u32 pending;
unsigned long flags;
if (in_interrupt())
return;
local_irq_save(flags);
pending = local_softirq_pending();
if (pending)
do_softirq_own_stack();
local_irq_restore(flags);
}
首先如果在中断里就退出了(无论是硬中断还是软中断)。然后会保存下中断flag,之后看下local_softirq_pending:
#define local_softirq_pending() this_cpu_read(irq_stat.__softirq_pending)
在这之后的do_softirq_own_stack会调用__do_softirq。来看下__do_softirq:
asmlinkage __visible void __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;
/*
* Mask out PF_MEMALLOC s current task context is borrowed for the
* softirq. A softirq handled such as network RX might set PF_MEMALLOC
* again if the socket is related to swap
*/
current->flags &= ~PF_MEMALLOC;
pending = local_softirq_pending();
account_irq_enter_time(current);
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
in_hardirq = lockdep_softirq_start();
可以看到当前CPU的softirq pending被保存了。同时当前CPU上的bh被停止,不允许其余的bh执行。
restart:
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0);
local_irq_enable();
h = softirq_vec;
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);
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;
}
然后通过set_softirq_pending,当前CPU的softirq的pending状态被清零了。
话说现有的pending的请求都要被这个__do_softirq处理。之后的while循环就是一次处理pending中的每类softirq。通过h->action(h)调用对应的方法。
rcu_bh_qs();
local_irq_disable();
pending = local_softirq_pending();
if (pending) {
if (time_before(jiffies, end) && !need_resched() &&
--max_restart)
goto restart;
wakeup_softirqd();
}
由于我们已经开中断了,所以可能有新的softirq pending上来,__do_softirq目前还没有打开BH,所以其会在max_restart内都去循环执行上面的处理。
但是如果次数达到了max_restart,那么会通过ksoftirqd的内核线程继续处理。
lockdep_softirq_end(in_hardirq);
account_irq_exit_time(current);
__local_bh_enable(SOFTIRQ_OFFSET);
WARN_ON_ONCE(in_interrupt());
tsk_restore_flags(current, old_flags, PF_MEMALLOC);
}
最后BH重新enable。
/////////////////////////////////////////////////
为什么要有ksoftirqd呢?? 为什么不都在 __do_softirq 里都处理了呢?
/////////////////////////////////////////////////
原因很简单:
我们要给用户态CPU时间。
如果都在内核态__do_softirq里,并且只有硬件中断可以抢占它,那么当数据包很多的时候__do_softirq就不能返回了,于是用户态的进程就没有机会使用CPU了。
/////////////////////////////////////////////////
那为什么不都交给内核线程呢?
/////////////////////////////////////////////////
原因应该是在包很少的时候,通过直接的去处理可以加速处理速度吧。只有包很多造成大量软中断产生的时候才需要内核线程来帮忙。
换句话说当ksoftirqd的CPU使用率很高的时候,可能就是软中断产生过多了。
此时怎么调优呢?比如可以看下网卡支不支持多队列,支持的话绑定不同的核让软中断分散开来。
///////////////////////////
来看下ksoftirqd这个内核线程:
///////////////////////////
static void run_ksoftirqd(unsigned int cpu)
{
local_irq_disable();
if (local_softirq_pending()) {
/*
* We can safely run softirq on inline stack, as we are not deep
* in the task stack here.
*/
__do_softirq();
local_irq_enable();
cond_resched_rcu_qs();
return;
}
local_irq_enable();
}
其也就是调用了下__do_softirq(),只不过是在内核线程中执行罢了。
注意,__do_softirq可是会disable BH的,所以如果软中断很高的话,BH就都会由这个内核线程处理而不是由其它地方(如上面中断后调用的do_softirq)
进行处理。
---------------------------------------------------
3.构成软中断机制的核心元素
---------------------------------------------------
构成软中断机制的核心元素包括:
软中断状态寄存器 (soft interrupt state(irq_stat)
软中断向量表 (softirq_vec)
软中断守护进程daemon
软中断的工作模拟了实际的中断处理过程:
1、当某一软中断时间发生后,首先需要设置对应的中断标记位,触发中断事务(raise_softirq()设置软中断状态bitmap,触发软中断事务)
2、然后唤醒守护线程去检测中断状态寄存器(在Linux中 软中断daemon线程函数为do_softirq())
3、如果通过查询发现某一软中断事务发生之后,那么通过软中断向量表调用软中断服务程序action()。
这就是软中断的过程,与硬件中断唯一不同 的地方是从中断标记到中断服务程序的映射过程。
在CPU的硬件中断发生之后,CPU需要将硬件中断请求通过向量表映射成具体的服务程序,这个过程是硬件自 动完成的,
但是软中断不是,其需要守护线程去实现这一过程,这也就是软件模拟的中断,故称之为软中断。
一个软中断不会去抢占另一个软中断,只有硬件中断才可以抢占软中断,所以软中断能够保证对时间的严格要求。
软中断守护daemon是软中断机制的实现核心,其实现过程也比较简单,通过查询软中断状态irq_stat来判断事件是否发生,
如果发生,那么映 射到软中断向量表,调用执行注册的action函数就可以了。从这一点分析可以看出,软中断的服务程序的执行上下文为软中断daemon。
常用的软中断函数列表如下:
1、 Open_softirq,注册一个软中断,将软中断服务程序注册到软中断向量表。
void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
open_softirq()会填充softirq_vec[nr],将action和data设为传入的参数。
TASKLET_SOFTIRQ填充为tasklet_action(NULL),HI_SOFTIRQ填充为tasklet_hi_action(NULL),
在do_softirq()函数中,这两个函数会被调用,分别启动tasklet_vec[cpu]和tasklet_hi_vec[cpu]链上的tasklet运行。
2、 Raise_softirq,设置软中断状态bitmap,触发软中断事务。
static inline void __cpu_raise_softirq(int cpu, int nr)
这个函数用来激活软中断,实际上就是第cpu号CPU的第nr号软中断的active位置1。在do_softirq()中将判断这个active位。tasklet_schedule()和tasklet_hi_schedule()都会调用这个函数。
///////////////////////////////
do_softirq()的4个执行时机:
///////////////////////////////
从系统调用中返回(arch/xxx/kernel/entry.S::ENTRY(ret_from_sys_call))、
从异常中返回(arch/xxx/kernel/entry.S::ret_from_exception标号)、
调度程序中(kernel/sched.c::schedule()),
处理完硬件中断之后(kernel/irq.c::do_IRQ())。
它将遍历所有的softirq_vec,依次启动其中的action()。
需要注意的是,软中断服务程序,不允许在硬中断服务程序中执行,也不允许在软中断服务程序中嵌套执行,但允许多个软中断服务程序同时在多个CPU上并发。