软中断与软件中断
Posted 贺二公子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了软中断与软件中断相关的知识,希望对你有一定的参考价值。
原文地址:https://zhuanlan.zhihu.com/p/360683396
前言
软中断?软件中断?仿佛真假美猴王,在源码面前才能让他们现出原型!
本文阐述的两个概念分别是:
- 软中断(softIRQ),即中断下半部机制。ISR运行时间不易过长,linux将中断中的一部分逻辑推后执行,这就是softIRQ,它完全由软件实现;
- 软件中断(Software Interrupt),从软件中断指令而来。在32位x86中,为了实现linux用户态到内核态的切换,linux使用软中断指令“int 0x80”来触发异常,切换CPU特权级,实现系统调用。
实现系统调用的“软中断”和中断下半部的“软中断”并不是一回事!二者是完全不同的实现机制,只是翻译的时候同名导致混淆。下面会结合一些官方描述和linux kernel 源码来具体分析二者的区别,让同样被迷惑的读者认清真相!
传统的系统调用实现原理
调用系统调用的传统方法是使用汇编指令"int",0x80是中断向量号。在内核初始化期间调用的函数trap_init()中,在中断描述符表(IDT)上设置系统调用门:
void __init trap_init(void)
… …
idt_setup_traps();
… …
void __init idt_setup_traps(void)
idt_setup_from_table(idt_table, def_idts, ARRAY_SIZE(def_idts), true);
static const __initconst struct idt_data def_idts[] =
… …
#if defined(CONFIG_IA32_EMULATION)
SYSG(IA32_SYSCALL_VECTOR, entry_INT80_compat),
#elif defined(CONFIG_X86_32)
SYSG(IA32_SYSCALL_VECTOR, entry_INT80_32),
#endif
;
/* System interrupt gate */
#define SYSG(_vector, _addr) \\
G(_vector, _addr, DEFAULT_STACK, GATE_INTERRUPT, DPL3, __KERNEL_CS)
#define IA32_SYSCALL_VECTOR 0x80
IA32_SYSCALL_VECTOR 值为 0x80。用户空间的lib库函数会调用软件中断指令"int 0x80"触发中断,然后硬件根据向量号"0x80"在 IDT 中找到对应的表项,即中断描述符,进行特权级检查,发现 DPL = CPL = 3 ,允许调用。然后硬件将切换到进程内核栈 (tss.ss0 : tss.esp0)。软件执行函数是“entry_INT80_32”,其定义在 arch/x86/entry/entry_32.S:
SYM_FUNC_START(entry_INT80_32)
ASM_CLAC
pushl %eax /* pt_regs->orig_ax */
SAVE_ALL pt_regs_ax=$-ENOSYS switch_stacks=1 /* save rest */
movl %esp, %eax
call do_int80_syscall_32
1)将eax寄存器值入栈保存。寄存器eax中保存系统调用号,对应sys_call_table[]的下标,用于索引kernel中的系统调用函数;
2)SAVE_ALL 将其他寄存器的值压入栈中进行保存,系统调用涉及到的参数情况如下:
* Arguments:
* eax system call number
* ebx arg1
* ecx arg2
* edx arg3
* esi arg4
* edi arg5
* ebp arg6
3)将当前栈指针保存到 eax ,调用 do_int80_syscall_32函数,该函数在arch/x86/entry/common.c 中定义:
/* Handles int $0x80 */
__visible noinstr void do_int80_syscall_32(struct pt_regs *regs)
unsigned int nr = syscall_32_enter(regs);
/*
* Subtlety here: if ptrace pokes something larger than 2^32-1 into
* orig_ax, the unsigned int return value truncates it. This may
* or may not be necessary, but it matches the old asm behavior.
*/
nr = (unsigned int)syscall_enter_from_user_mode(regs, nr);
instrumentation_begin();
do_syscall_32_irqs_on(regs, nr);
instrumentation_end();
syscall_exit_to_user_mode(regs);
/*
* Invoke a 32-bit syscall. Called with IRQs on in CONTEXT_KERNEL.
*/
static __always_inline void do_syscall_32_irqs_on(struct pt_regs *regs,
unsigned int nr)
if (likely(nr < IA32_NR_syscalls))
nr = array_index_nospec(nr, IA32_NR_syscalls);
regs->ax = ia32_sys_call_table[nr](regs);
可见最后是根据eax传入的syscall number,在sys_call_table 中调用对应位置处的sys_xxx。
基于软件中断指令"int"实现的传统系统调用方式,属于同步中断(异常),工作在进程上下文且可被中断。
下面看下软中断(softIRQ)的原理。
软中断(softIRQ)
由于ISR运行时间不易过长,且不能睡眠,linux将中断中的一部分逻辑推后执行,于是诞生了软中断机制。实际上出现在内核代码中的术语“软中断(softirq)”常常表示可延迟函数的所有种类,即tasklet、softirq、work queue都可以统称为“软中断”,为了不产生混淆,我们使用更加广泛的统称:中断下(底)半部。
软中断(softIRQ)自内核2.3引入,是最基本、最优先的中断下半部机制。
【1】软中断数据结构
struct softirq_action
void (*action)(struct softirq_action *);
;
softirq_action用于描述一个软中断,action是软中断的处理函数。softirq不能动态分配,都是静态定义的。内核中使用“softirq_vec”表示所有支持的软中断:
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
enum
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
;
【2】软中断的触发时机
-
irq_exit
在硬中断退出时,会检查local_softirq_pending和preemt_count,如果都符合条件,则执行软中断。 -
local_bh_enable
使用此函数开启软中断时,会检查local_softirq_pending,如果都符合条件,则执行软中断。 -
raise_softirq
主动唤起一个软中断。内核提供了软中断处理线程“ksoftirq”,在适当时机会唤醒ksoftirq后台线程进行执行,处理软中断。
【3】软中断执行
软中断执行核心API是“__do_softirq”。
软中断类似硬件中断机制,是“异步中断”,在同一个CPU上资源不可重入,工作在中断上下文,因此不能睡眠。不过可以在多个处理器上同时执行。
总结
本文通过对两个“软中断”实现机制的分析,我们论证了系统调用的"软中断" 和中断下半部的“软中断”并不是一个概念。前者被翻译成“软件触发的中断”更加合理!
使用时需要注意的是软中断工作在中断上下文,需要注意原子操作。
参考
《深入理解linux内核》
注:所有kernel源码均来自linux 5.12-rc3
以上是关于软中断与软件中断的主要内容,如果未能解决你的问题,请参考以下文章