Linux内核——中断上下文

跟着简叔学的,可以B站搜索 简说linux

1.上下文

1.1中断的种类如上下文判断方式

中断可分为软件中断和硬件中断。或者BH ,这个BH是什么东西去查了查,说是软中断的实现方式,已经被弃用了。

之前讲过内核中的进程描述方式struct task_struct 中有一个thread_info,他之中保留着不同架构的对于进程的描述信息。其中有一个属性是下面这个。 32bit的变量

1678170873661

红色部分描述当前硬中断嵌套的深度,0的话就是不是中断,不允许中断嵌套执行的话就只有0,1两种。

绿色部分第8位是软中断,1的话就是软中断的处理上下文,9~15大于0也表示软中断。蓝色部分表示当前进程是否可以被抢占如果是0就是可以被抢占反之就是不能被抢占。

include/linux/preempt.h

1678171398820

通过这些函数来确定出狱生么上下问之中,可以看到都是在用上面说的preemtycount做位运算。通过current->threadinfo得到threadinfo 然后就能得到preemtycount ,这样就能直到当前处于什么上下文

1.2 调用上面函数的案例

arm中 handle_irq是处理中断的总入口,arch/arm/kernel/irq.c

1678172574530

/kernel/irq/irqdesc.c。

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
#ifdef CONFIG_HANDLE_DOMAIN_IRQ
/**
* __handle_domain_irq - Invoke the handler for a HW irq belonging to a domain
* @domain: The domain where to perform the lookup
* @hwirq: The HW irq number to convert to a logical one
* @lookup: Whether to perform the domain lookup or not
* @regs: Register file coming from the low-level handling code
*
* Returns: 0 on success, or -EINVAL if conversion has failed
*/
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
bool lookup, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
unsigned int irq = hwirq;
int ret = 0;

irq_enter();//中断陷入

#ifdef CONFIG_IRQ_DOMAIN
if (lookup)
irq = irq_find_mapping(domain, hwirq);
#endif

/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (unlikely(!irq || irq >= nr_irqs)) {
ack_bad_irq(irq);
ret = -EINVAL;
} else {
generic_handle_irq(irq);
}

irq_exit(); //中断退出
set_irq_regs(old_regs);
return ret;
}
#endif

上面 中断陷入中断退出之间就是中断上下文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* Enter an interrupt context.
*/
void irq_enter(void)
{
rcu_irq_enter();
//如果当前进程是一个空闲进程并且不是在终端上上下文
if (is_idle_task(current) && !in_interrupt()) {
/*
* Prevent raise_softirq from needlessly waking up ksoftirqd
* here, as softirq will be serviced on return from interrupt.
*/
local_bh_disable();
tick_irq_enter();
_local_bh_enable();
}

__irq_enter();
}

2.中断上下文进入时机

2.1硬件中断进入时机

当外设产生中断,会给cpu发送中断信号,cpu收到中断信号之后会调用(以arm为例,arch/arm/kernel/irq.c)

1678175449036

然后又回到上部分的代码

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
/*
* Enter an interrupt context.
*/
void irq_enter(void)
{
rcu_irq_enter();
//如果当前进程是一个空闲进程并且不是在终端上上下文
if (is_idle_task(current) && !in_interrupt()) {
/*
* Prevent raise_softirq from needlessly waking up ksoftirqd
* here, as softirq will be serviced on return from interrupt.
*/
local_bh_disable();
tick_irq_enter();
_local_bh_enable();
}

__irq_enter();
}
/*
* It is safe to do non-atomic ops on ->hardirq_context,
* because NMI handlers may not preempt and the ops are
* always balanced, so the interrupted value of ->hardirq_context
* will always be restored.
*/
#define __irq_enter() \
do { \
account_irq_enter_time(current); \
preempt_count_add(HARDIRQ_OFFSET); \
trace_hardirq_enter(); \
} while (0)

可以看到__irq_enter()有一个操作是对preempt_count的操作,相当于把 preempt_count代表硬中断的位置为真,从此开始就进入了硬件中断的上下文。之前那些判断上下文的函数返回值就会显示是硬件中断。与irq_enter相反的操作irq_exit也是将硬件中断的位置为0;

2.2软件中断进入时机

1678176007129

可以看到软中断这块有两个范围,一个是比特8,一个是比特9~15。当我们调用 BHdisable这种函数,9-15就会被置1,还有就是正常处理软中断 比特8会置为1.

在/kernel/softirq.c中,

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
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;

/*
* 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();

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;
}

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();
}

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);
}

轮询所有软中断,如果某个软中断有需要处理的数据,就会调用相应的软中断的handle。处理软中断前后会调用_local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);__local_bh_enable(SOFTIRQ_OFFSET);这两个函数的作用是会操作preemty_cnt,点进去查看会发现他操作的位是,比特8.可以说这两个函数可以对应硬中断的__irq_enter()__irq_exit()。所以,在这两条函数中间,调用那些处理软函数中,是在软中断上下文环境中,这个环境中是不允许休眠和调度的。