linux内核中的软中断的实现

最近在阅读linux内核源码,把自己的一些理解发上来,一方面看到的朋友可以帮我指正我理解偏差的地方,别一方面也算是做一个简单的总结。

首先调用open_softirq()函数来初始化软件中断处理函数,将软件中断处理函数根据软中断的下标号插入到softirq_vec数组中,实现过程很简单如下:

1
2
3
4
void
 open_softirq(
int
 nr,
 void
 (
*
action)
(
struct
 softirq_action *
)
)

{
softirq_vec[ nr] .action = action;
}

softirq_vec数据有32个元素,对应的是可以有32个软件中断,但实际上linux只是使用了其中的6个软中断,相应的每一个CPU都会 有一个对应的32位的掩码__softirq_pending描述挂起的软中断,每一位对应一个软件中断,__soctirq_penging在 irq_cpustat_t中定义,如下:

@include/asm/hardirq.h

1
2
3
4
5
6
typedef
 struct
 {

unsigned int __softirq_pending;
unsigned long idle_timestamp;
unsigned int __nmi_count; /* arch dependent */
unsigned int __irq_count; /* arch dependent */
} ____cacheline_aligned irq_cpustat_t;

其中local_softirq_pending()宏用于选择当前CPU所对应的__softirq_penging掩码。相关的宏如下:
@include/linux/irq_cpustat.h

1
2
3
4
#define local_softirq_pending() /
__IRQ_STAT(smp_processor_id() , __softirq_pending)

 
#define __IRQ_STAT(cpu , member) (irq_stat[cpu].member)

接着调用raise_softirq()函数来激活软中断,函数如下:

@kernel/softirq.c

1
2
3
4
5
6
7
8
9
void
 raise_softirq(
unsigned
 int
 nr)

{
unsigned long flags;
/* 将eflags寄存器的IF标志保存到flags,并且禁用了本地CPU上的中断 */
local_irq_save( flags) ;
raise_softirq_irqoff( nr) ;
/* 根据flags变量的值恢复eflags寄存器的IF标志,重新打开本地CPU上的中断 */
local_irq_restore( flags) ;
}

raise_softirq_irqoff()函数必须是在禁止中断的情况下执行的,它首先调用__raise_softirq_irqoff() 宏激活软件中断,其实也就是设置当前CPU所对应的__softirq_pending所对应的软中断的位,以表示该软中断已激活。如果当前正处于中断或 者软中断当中,那么raise_softirq_irqoff执行结束,否则的话就调用wakeup_softirqd()函数激活 ksoftirqd/n内核线程来处理软中断。

@kernel/softirq.c

1
2
3
4
5
6
7
inline
 void
 raise_softirq_irqoff(
unsigned
 int
 nr)

{
__raise_softirq_irqoff( nr) ;
 
if ( ! in_interrupt( ) )
wakeup_softirqd( ) ;
}

__rarse_softirq_irqoff()宏在/include/linux/interput.h中定义:

1
2
3
4
#defome __raise_softirq_irqoff(nr) /
do{ or_softirq_pending(1UL << (nr)); } while(0)

 
#define or_softirq_pending(x) (local_softirq_pending() |= (x))

其中local_softirq_pending()宏已经在上面列出来了,我不太明白的是__raise_softirq_irqoff()这个宏为什么要放到一个循环里面,它也只是执行了一次,不过linux既然这样写就一定有它的道理,慢慢再去研究吧。

这样软中断已经激活,其处理函数已经加入到了softirq_sec数组中,并且相应的标志位已经合适地设置,系统会周期性地检查已经挂起的软中断,当在 local_softirq_pending()中的某个位检测到挂起的软中断的时候,就会调用do_softirq()函数对软中断进行处理。

do_softirq()函数定义如下:
@ kernel/softirq.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
asmlinkage 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( ) ;
 
local_irq_restore( flags) ;
}

如果在中断上下文中调用了do_softirq函数或者禁用了软件中断,那么in_interrupt函数返回1,这时候do_softirq() 不做任何事情,否则,它会把eflags寄存器IF标志保存在flags中并禁用中断,然后将local_softirq_pending()的值保存在 pending变量中,检测pending的值,如果pending不为0,则表示pending的某一位被设置,当前CPU就有对应的挂起的软中断需要 进行处理,调用__do_softirq()对挂起的软中断进行处理,处理完成之后再根据flags变量的值恢复eflags寄存器并打开中断。

接下来看__do_softirq()函数:

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
asmlinkage void
 __do_softirq(
void
)

{
struct softirq_action * h;
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;
 
pending = local_softirq_pending( ) ;
 
/* 增加preempt_count字段中软中断计数器的值,
* 以此来禁用软中断,因为软件中断的可延迟函数
* 一般是在开中断的情况下执行的,这样就可能在__do_softirq 执行
* 的过程中有别一个__do_softirq的实例正在执行
* ,这样就会影响可延迟函数的串行化执行*/

__local_bh_disable( ( unsigned long ) __builtin_return_address( 0 ) ) ;
 
restart:
set_softirq_pending( 0 ) ;
 
local_irq_enable( ) ;
 
h = softirq_vec;
 
do {
if ( pending & 1 ) {
int prev_count = preempt_count( ) ;
/* 执行软件中断处理函数 */
h-> action( h) ;
if ( unlikely( prev_count != preempt_count( ) ) ) {
printk( KERN_ERR "huh, entered softirq %td %p"
"with preempt_count %08x,"
" exited with %08x?/n " , h - softirq_vec,
h-> action, prev_count, preempt_count( ) ) ;
preempt_count( ) = prev_count;
}
}
h++;
pending >>= 1 ;
} while ( pending) ;
/* 禁用本地软中断 */
local_irq_disable( ) ;
 
pending = local_softirq_pending( ) ;
if ( pending && -- max_restart)
goto restart;
 
if ( pending)
wakeup_softirqd( ) ;
 
_local_bh_enable( ) ;
}

内核循环地检测每一个pending的位,以处理当前位挂机的软件中断,为了防止内核在执行__do_softirq()的过程中不停地有新的 softirq产生,以导致内核无暇顾及其它,__do_softirq()在循环检测的时候设置了外层循环的最大次数为10次,对pending的每一 位检测10次之后如果pending的值仍不为0,则表示当前CPU仍有未处理的挂起的软件中断,这时候__do_softirq不再对它进行处理,而是 唤醒内核线程ksoftirqd/n对它进行处理。
在函数开始的时候,先将软件中断的位图__softirq_pending保存在pending局部变量中,然后调用set_softirq_pending(0)以清空软中断位图,从而允许新的软中断的到来。接着调用local_irq_enable()来激活软中断。

原创文章,转载请注明: 转载自basic coder

本文链接地址: http://basiccoder.com/kernel-softirq.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值