LogoLogo
  • 简介
  • 引导
    • 从引导加载程序内核
    • 在内核安装代码的第一步
    • 视频模式初始化和转换到保护模式
    • 过渡到 64 位模式
    • 内核解压缩
  • 初始化
    • 内核解压之后的首要步骤
    • 早期的中断和异常控制
    • 在到达内核入口之前最后的准备
    • 内核入口 - start_kernel
    • 体系架构初始化
    • 进一步初始化指定体系架构
    • 最后对指定体系架构初始化
    • 调度器初始化
    • RCU 初始化
    • 初始化结束
  • 中断
    • 中断和中断处理第一部分
    • 深入 Linux 内核中的中断
    • 初步中断处理
    • 中断处理
    • 异常处理的实现
    • 处理不可屏蔽中断
    • 深入外部硬件中断
    • IRQs的非早期初始化
    • Softirq, Tasklets and Workqueues
    • 最后一部分
  • 系统调用
    • 系统调用概念简介
    • Linux 内核如何处理系统调用
    • vsyscall and vDSO
    • Linux 内核如何运行程序
    • open 系统调用的实现
    • Linux 资源限制
  • 定时器和时钟管理
    • 简介
    • 时钟源框架简介
    • The tick broadcast framework and dyntick
    • 定时器介绍
    • Clockevents 框架简介
    • x86 相关的时钟源
    • Linux 内核中与时钟相关的系统调用
  • 同步原语
    • 自旋锁简介
    • 队列自旋锁
    • 信号量
    • 互斥锁
    • 读者/写者信号量
    • 顺序锁
    • RCU
    • Lockdep
  • 内存管理
    • 内存块
    • 固定映射地址和 ioremap
    • kmemcheck
  • 控制组
    • 控制组简介
  • SMP
  • 概念
    • 每个 CPU 的变量
    • CPU 掩码
    • initcall 机制
    • Linux 内核的通知链
  • Linux 内核中的数据结构
    • 双向链表
    • 基数树
    • 位数组
  • 理论
    • 分页
    • ELF 文件格式
    • 內联汇编
    • CPUID
    • MSR
  • Initial ram disk
  • 杂项
    • Linux 内核开发
    • 内核编译方法
    • 链接器
    • 用户空间的程序启动过程
    • 书写并提交你第一个内核补丁
  • 内核数据结构
    • 中断描述符表
  • 有帮助的链接
  • 贡献者
由 GitBook 提供支持
在本页
  • 深入Linux内核中的中断和异常处理
  • 为中断堆栈设置Stack Canary值
  • 禁用/使能本地中断
  • 内核初始化过程中的早期 trap 初始化
  • 总结
  • 链接
  1. 中断

深入 Linux 内核中的中断

上一页中断和中断处理第一部分下一页初步中断处理

最后更新于1年前

深入Linux内核中的中断和异常处理

在 中我们学习了中断和异常处理的一些理论知识,在本章节中,我们将深入了解Linux内核源代码中关于中断与异常处理的部分。之前的章节中主要从理论方面描述了Linux中断和异常处理的相关内容,而在本章节中,我们将直接深入Linux源代码来了解相关内容。像其他章节一样,我们将从启动早期的代码开始阅读。本章将不会像 中那样从Linux内核启动的 几行代码读起,而是从与中断与异常处理相关的最早期代码开始阅读,了解Linux内核源代码中所有与中断和异常处理相关的代码。

如果你读过本书的前面部分,你可能记得Linux内核中关于 x86_64架构的代码中与中断相关的最早期代码出现在 文件中,该文件首次配置了 (IDT)。对IDT的配置在go_to_protected_mode函数中完成,该函数首先调用了 setup_idt函数配置了IDT,然后将处理器的工作模式切换为 :

void go_to_protected_mode(void)
{
        ...
        setup_idt();
        ...
}

setup_idt函数在同一文件中定义,它仅仅是用 NULL填充了中断描述符表:

static void setup_idt(void)
{
        static const struct gdt_ptr null_idt = {0, 0};
        asm volatile("lidtl %0" : : "m" (null_idt));
}

其中,gdt_ptr表示了一个48-bit的特殊功能寄存器 GDTR,其包含了全局描述符表 Global Descriptor的基地址:

struct gdt_ptr {
        u16 len;
        u32 ptr;
} __attribute__((packed));

显然,在此处的 gdt_prt不是代表 GDTR寄存器而是代表 IDTR寄存器,因为我们将其设置到了中断描述符表中。之所以在Linux内核代码中没有idt_ptr结构体,是因为其与gdt_prt具有相同的结构而仅仅是名字不同,因此没必要定义两个重复的数据结构。可以看到,内核在此处并没有填充Interrupt Descriptor Table,这是因为此刻处理任何中断或异常还为时尚早,因此我们仅仅以NULL来填充IDT。

protected_mode_jump(boot_params.hdr.code32_start,
                            (u32)&boot_params + (ds() << 4));
GLOBAL(protected_mode_jump)
        ...
        ...
        ...
        .byte   0x66, 0xea              # ljmpl opcode
2:      .long   in_pm32                 # offset
        .word   __BOOT_CS               # segment
...
...
...
ENDPROC(protected_mode_jump)

其中 in_pm32包含了对32-bit入口的跳转语句:

GLOBAL(in_pm32)
        ...
        ...
        jmpl    *%eax // %eax contains address of the `startup_32`
        ...
        ...
ENDPROC(in_pm32)
  • arch/x86/boot/compressed/head_32.S.

  • arch/x86/boot/compressed/head_64.S;

vmlinux-objs-y := $(obj)/vmlinux.lds $(obj)/head_$(BITS).o $(obj)/misc.o \
...
...
ifeq ($(CONFIG_X86_32),y)
...
        BITS := 32
else
        BITS := 64
        ...
endif
movl    $MSR_GS_BASE,%ecx
movl    initial_gs(%rip),%eax
movl    initial_gs+4(%rip),%edx
wrmsr
#define MSR_GS_BASE             0xc0000101

由此可见,MSR_GS_BASE定义了 model specific register的编号。由于 cs, ds, es,和 ss在64-bit模式中不再使用,这些寄存器中的值将会被忽略,但我们可以通过 fs和 gs寄存器来访问内存空间。model specific register提供了一种后门 back door来访问这些段寄存器,也让我们可以通过段寄存器 fs和 gs来访问64-bit的基地址。看起来这部分代码映射在 GS.base域中。再看到 initial_gs函数的定义:

GLOBAL(initial_gs)
        .quad   INIT_PER_CPU_VAR(irq_stack_union)
#define INIT_PER_CPU(x) init_per_cpu__##x = x + __per_cpu_load
INIT_PER_CPU(irq_stack_union);
DECLARE_INIT_PER_CPU(irq_stack_union);

#define DECLARE_INIT_PER_CPU(var) \
       extern typeof(per_cpu_var(var)) init_per_cpu_var(var)

#define init_per_cpu_var(var)  init_per_cpu__##var
#define PER_CPU_VAR(var)        %__percpu_seg:var

其中:

#ifdef CONFIG_X86_64
    #define __percpu_seg gs
endif
extern char __per_cpu_load[], __per_cpu_start[], __per_cpu_end[];
...
...
...
ffffffff819ed000 D __init_begin
ffffffff819ed000 D __per_cpu_load
ffffffff819ed000 A init_per_cpu__irq_stack_union
...
...
...

现在我们终于知道了 initial_gs是什么,回到之前的代码中:

movl    $MSR_GS_BASE,%ecx
movl    initial_gs(%rip),%eax
movl    initial_gs+4(%rip),%edx
wrmsr
for (i = 0; i < NUM_EXCEPTION_VECTORS; i++)
        set_intr_gate(i, early_idt_handlers[i]);

load_idt((const struct desc_ptr *)&idt_descr);
for (i = 0; i < NUM_EXCEPTION_VECTORS; i++)
        set_intr_gate(i, early_idt_handler_array[i]);

load_idt((const struct desc_ptr *)&idt_descr);

如你所见,这段代码与之前相比唯一的区别在于中断服务程序入口点数组的名称现在改为了 early_idt_handler_array:

extern const char early_idt_handler_array[NUM_EXCEPTION_VECTORS][EARLY_IDT_HANDLER_SIZE];

其中 NUM_EXCEPTION_VECTORS 和 EARLY_IDT_HANDLER_SIZE 的定义如下:

#define NUM_EXCEPTION_VECTORS 32
#define EARLY_IDT_HANDLER_SIZE 9
ENTRY(early_idt_handler_array)
...
...
...
ENDPROC(early_idt_handler_common)

为中断堆栈设置Stack Canary值

#ifdef CONFIG_CC_STACKPROTECTOR
...
...
...
#else
static inline void boot_init_stack_canary(void)
{
}
#endif
#ifdef CONFIG_X86_64
        BUILD_BUG_ON(offsetof(union irq_stack_union, stack_canary) != 40);
#endif
union irq_stack_union {
        char irq_stack[IRQ_STACK_SIZE];

    struct {
                char gs_base[40];
                unsigned long stack_canary;
        };
};
get_random_bytes(&canary, sizeof(canary));
tsc = __native_read_tsc();
canary += tsc + (tsc << 32UL);

并且通过 this_cpu_write 宏将 canary 值写入了 irq_stack_union 中:

this_cpu_write(irq_stack_union.stack_canary, canary);

禁用/使能本地中断

#ifdef CONFIG_TRACE_IRQFLAGS_SUPPORT
...
#define local_irq_disable() \
         do { raw_local_irq_disable(); trace_hardirqs_off(); } while (0)
...
#else
...
#define local_irq_disable()     do { raw_local_irq_disable(); } while (0)
...
#endif
void trace_hardirqs_off(void)
{
         trace_hardirqs_off_caller(CALLER_ADDR0);
}
EXPORT_SYMBOL(trace_hardirqs_off);
struct lockdep_stats {
...
...
...
int     softirqs_off_events;
int     redundant_softirqs_off;
...
...
...
}

如果你使能了 CONFIG_DEBUG_LOCKDEP 内核配置选项,lockdep_stats_debug_show函数会将所有的调试信息写入 /proc/lockdep 文件中:

static void lockdep_stats_debug_show(struct seq_file *m)
{
#ifdef CONFIG_DEBUG_LOCKDEP
    unsigned long long hi1 = debug_atomic_read(hardirqs_on_events),
        hi2 = debug_atomic_read(hardirqs_off_events),
        hr1 = debug_atomic_read(redundant_hardirqs_on),
        ...
    ...
    ...
    seq_printf(m, " hardirq on events:             %11llu\n", hi1);
    seq_printf(m, " hardirq off events:            %11llu\n", hi2);
    seq_printf(m, " redundant hardirq ons:         %11llu\n", hr1);
#endif
}

你可以如下命令查看其内容:

$ sudo cat /proc/lockdep
 hardirq on events:             12838248974
 hardirq off events:            12838248979
 redundant hardirq ons:               67792
 redundant hardirq offs:         3836339146
 softirq on events:                38002159
 softirq off events:               38002187
 redundant softirq ons:                   0
 redundant softirq offs:                  0
static inline void native_irq_disable(void)
{
        asm volatile("cli": : :"memory");
}
static inline void native_irq_enable(void)
{
        asm volatile("sti": : :"memory");
}
early_boot_irqs_disabled = true;
extern bool early_boot_irqs_disabled;
WARN_ON_ONCE(cpu_online(this_cpu) && irqs_disabled()
                     && !oops_in_progress && !early_boot_irqs_disabled);

内核初始化过程中的早期 trap 初始化

void __init early_trap_init(void)
{
        set_intr_gate_ist(X86_TRAP_DB, &debug, DEBUG_STACK);
        set_system_intr_gate_ist(X86_TRAP_BP, &int3, DEBUG_STACK);
#ifdef CONFIG_X86_32
        set_intr_gate(X86_TRAP_PF, page_fault);
#endif
        load_idt(&idt_descr);
}

这里出现了三个不同的函数调用

  • set_intr_gate_ist

  • set_system_intr_gate_ist

  • set_intr_gate

static inline void set_intr_gate_ist(int n, void *addr, unsigned ist)
{
        BUG_ON((unsigned)n > 0xFF);
        _set_gate(n, GATE_INTERRUPT, addr, 0, ist, __KERNEL_CS);
}
static inline void _set_gate(int gate, unsigned type, void *addr,
                             unsigned dpl, unsigned ist, unsigned seg)
{
        gate_desc s;

        pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg);
        write_idt_entry(idt_table, gate, &s);
        write_trace_idt_entry(gate, &s);
}
  • GATE_INTERRUPT

  • GATE_TRAP

  • GATE_CALL

  • GATE_TASK

并设置了该 IDT 项的present位域:

static inline void pack_gate(gate_desc *gate, unsigned type, unsigned long func,
                             unsigned dpl, unsigned ist, unsigned seg)
{
        gate->offset_low        = PTR_LOW(func);
        gate->segment           = __KERNEL_CS;
        gate->ist               = ist;
        gate->p                 = 1;
        gate->dpl               = dpl;
        gate->zero0             = 0;
        gate->zero1             = 0;
        gate->type              = type;
        gate->offset_middle     = PTR_MIDDLE(func);
        gate->offset_high       = PTR_HIGH(func);
}

然后,我们把这个中断门通过 write_idt_entry 宏填入了 IDT 中。这个宏展开后是 native_write_idt_entry ,其将中断门信息通过索引拷贝到了 idt_table 之中:

#define write_idt_entry(dt, entry, g)           native_write_idt_entry(dt, entry, g)

static inline void native_write_idt_entry(gate_desc *idt, int entry, const gate_desc *gate)
{
        memcpy(&idt[entry], gate, sizeof(*gate));
}

其中 idt_table 是一个 gate_desc 类型的数组:

extern gate_desc idt_table[];

函数 set_intr_gate_ist 的内容到此为止。第二个函数 set_system_intr_gate_ist 的实现仅有一个地方不同:

static inline void set_system_intr_gate_ist(int n, void *addr, unsigned ist)
{
        BUG_ON((unsigned)n > 0xFF);
        _set_gate(n, GATE_INTERRUPT, addr, 0x3, ist, __KERNEL_CS);
}

注意 _set_gate 函数的第四个参数是 0x3,而在 set_intr_gate_ist函数中这个值是 0x0,这个参数代表的是 DPL或称为特权等级。其中,0代表最高特权等级而 3代表最低等级。现在我们了解了 set_system_intr_gate_ist, set_intr_gate_ist, set_intr_gate这三函数的作用并回到 early_trap_init函数中:

set_intr_gate_ist(X86_TRAP_DB, &debug, DEBUG_STACK);
set_system_intr_gate_ist(X86_TRAP_BP, &int3, DEBUG_STACK);

我们设置了 #DB和 int3两个 IDT入口项。这些函数输入相同的参数组:

  • vector number of an interrupt;

  • address of an interrupt handler;

  • interrupt stack table index.

这就是 early_trap_init函数的全部内容,你将在下一章节中看到更多与中断和服务函数相关的内容。

总结

现在已经到了Linux内核中断和中断服务部分的第二部分的结尾。我们在之前的章节中了解了中断与异常处理的相关理论,并在本部分中开始深入阅读中断和异常处理的代码。我们从Linux内核启动最早期的代码中与中断相关的代码开始。下一部分中我们将继续深入这个有趣的主题,并学习更多关于中断处理相关的内容。

链接

在设置完 , 和其他一些东西以后,内核开始进入保护模式,这部分代码在 中实现,你可以在描述如何进入保护模式的 中了解到更多细节。

在最早的章节中我们已经了解到进入保护模式的代码位于 boot_params.hdr.code32_start,你可以在 的末尾看到内核将入口函数指针和启动参数 boot_params传递给了 protected_mode_jump函数:

定义在文件 中的函数protected_mode_jump通过一种的调用 ,通过 ax和 dx两个寄存器来获取参数:

你可能还记得32-bit的入口地址位于汇编文件 中,尽管它的名字包含 _64后缀。我们可以在 arch/x86/boot/compressed目录下看到两个相似的文件:

然而32-bit模式的入口位于第二个文件中,而第一个文件在 x86_64配置下不会参与编译。如 :

代码中的 head_*取决于 $(BITS) 变量的值,而该值由"架构"决定。我们可以在 找到相关信息:

现在我们从 跳入了 startup_32函数,在这个函数中没有与中断处理相关的内容。startup_32函数包含了进入 之前必须的准备工作,并直接进入了 long mode。 long mode的入口位于 startup_64函数中,在这个函数中完成了 的准备工作。内核解压的代码位于 中的 decompress_kernel函数中。内核解压完成以后,程序跳入 中的 startup_64函数。在这个函数中,我们开始构建 identity-mapped pages,并在之后检查 位,配置 Extended Feature Enable Register(见链接),使用 lgdt指令更新早期的Global Descriptor Table,在此之后我们还需要使用如下代码来设置 gs寄存器:

这段代码在之前的 中也出现过。请注意代码最后的 wrmsr指令,这个指令将 edx:eax寄存器指定的地址中的数据写入到由 ecx寄存器指定的 中。由代码可以看到,ecx中的值是 $MSR_GS_BASE,该值在 中定义:

这段代码将 irq_stack_union传递给 INIT_PER_CPU_VAR宏,后者只是给输入参数添加了 init_per_cpu__前缀而已。在此得出了符号 init_per_cpu__irq_stack_union。再看到 ,其中可以看到如下定义:

这段代码告诉我们符号 init_per_cpu__irq_stack_union的地址将会是 irq_stack_union + __per_cpu_load。现在再来看看 init_per_cpu__irq_stack_union和 __per_cpu_load在哪里。irq_stack_union的定义出现在 中,其中的 DECLARE_INIT_PER_CPU宏展开后又调用了 init_per_cpu_var宏:

将所有的宏展开之后我们可以得到与之前相同的名称 init_per_cpu__irq_stack_union,但此时它不再只是一个符号,而成了一个变量。请注意表达式 typeof(per_cpu_var(var)),在此时 var是 irq_stack_union,而 per_cpu_var宏在 中定义:

因此,我们实际访问的是 gs:irq_stack_union,它的类型是 irq_union。到此为止,我们定义了上面所说的第一个变量并且知道了它的地址。再看到第二个符号 __per_cpu_load,该符号定义在 ,这个符号定义了一系列 per-cpu变量:

同时,符号代表了这一系列变量的数据区域的基地址。因此我们知道了 irq_stack_union和 __per_cpu_load的地址,并且知道变量 init_per_cpu__irq_stack_union位于 __per_cpu_load。并且看到 :

此时我们通过 MSR_GS_BASE指定了一个平台相关寄存器,然后将 initial_gs的64-bit地址放到了 edx:eax段寄存器中,然后执行 wrmsr指令,将 init_per_cpu__irq_stack_union的基地址放入了 gs寄存器,而这个地址将是中断栈的栈底地址。在此之后我们将进入 x86_64_start_kernel函数的C语言代码中,此函数定义在 。在这个函数中,我们将完成最后的准备工作,之后就要进入到与平台无关的通用内核代码。如果你读过前文的 章节,你可能记得其中之一的工作就是将中断服务程序入口地址填写到早期 Interrupt Descriptor Table中。

当我写 早期中断和异常处理章节时Linux内核版本是 3.18,而如今Linux内核版本已经生长到了 4.1.0-rc6+,并且 Andy Lutomirski提交了一个与 early_idt_handlers相关的修改 ,该修改即将并入内核代码主线中。NOTE在我写这一段时,这个 已经进入了Linux内核源代码中。现在这段代码变成了:

因此,数组 early_idt_handler_array 存放着中断服务程序入口,其中每个入口占据9个字节。early_idt_handlers 定义在文件中。early_idt_handler_array 也定义在这个文件中:

这里使用 .rept NUM_EXCEPTION_VECTORS 填充了 early_idt_handler_array ,其中也包含了 early_make_pgtable 的中断服务函数入口(关于该中断服务函数的实现请参考章节 )。现在我们完成了所有x86-64平台相关的代码,即将进入通用内核代码中。当然,我们之后还会在 setup_arch 函数中重新回到平台相关代码,但这已经是 x86_64 平台早期代码的最后部分。

正如之前阅读过的关于Linux内核初始化过程的,在之后的下一步进入到了中的函数体最大的函数 start_kernel 中。这个函数将完成内核以 - 1运行第一个init进程 之前的所有初始化工作。其中,与中断和异常处理相关的第一件事是调用 boot_init_stack_canary 函数。这个函数通过设置值来防止中断栈溢出。前面我们已经看过了 boot_init_stack_canary 实现的一些细节,现在我们更进一步地认识它。你可以在中找到这个函数的实现,它的实现取决于 CONFIG_CC_STACKPROTECTOR 这个内核配置选项。如果该选项没有置位,那该函数将是一个空函数:

如果设置了内核配置选项 CONFIG_CC_STACKPROTECTOR ,那么函数boot_init_stack_canary 一开始将检查联合体 irq_stack_union 的状态,这个联合体代表了中断栈,其与 stack_canary 值中间有40个字节的 offset :

如之前所描述, irq_stack_union 联合体的定义如下:

以上定义位于文件。总所周知,中的是一种描述多个数据结构共用一片内存的数据结构。可以看到,第一个数据域 gs_base 大小为40 bytes,代表了 irq_stack 的栈底。因此,当我们使用 BUILD_BUG_ON 对该表达式进行检查时结果应为成功。(关于 BUILD_BUG_ON 宏的详细信息可见)。

紧接着我们使用随机数和计算新的 canary 值:

关于 this_cpu_* 系列宏的更多信息参见。

在 中,与中断和中断处理相关的操作中,设置的 canary 的下一步是调用 local_irq_disable 宏。

这个宏定义在头文件 中,宏如其名,调用这个宏将禁用本地CPU的中断。我们来仔细了解一下这个宏的实现,首先,它依赖于内核配置选项 CONFIG_TRACE_IRQFLAGS_SUPPORT :

如你所见,两者唯一的区别在于当 CONFIG_TRACE_IRQFLAGS_SUPPORT 选项使能时, local_irq_disable 宏将同时调用 trace_hardirqs_off 函数。在Linux死锁检测模块中有一项功能 irq-flags tracing 可以追踪 hardirq 和 softirq 的状态。在这种情况下, lockdep 死锁检测模块可以提供系统中关于硬/软中断的开/关事件的相关信息。函数 trace_hardirqs_off 的定义位于:

可见它只是调用了 trace_hardirqs_off_caller 函数。 trace_hardirqs_off_caller 函数,该函数检查了当前进程的 hardirqs_enabled 域,如果本次 local_irq_disable 调用是冗余的话,便使 redundant_hardirqs_off 域的值增长,否则便使 hardirqs_off_events 域的值增加。这两个域或其它与死锁检测模块 lockdep 统计相关的域定义在文件中的 lockdep_stats 结构体中:

现在我们总算了解了调试函数 trace_hardirqs_off 的一些信息,下文将有独立的章节介绍 lockdep 和 trancing。local_disable_irq 宏的实现中都包含了一个宏 raw_local_irq_disable ,这个定义在 中,其展开后的样子是:

你可能还记得, cli 指令将清除 标志位,这个标志位控制着处理器是否响应中断或异常。与 local_irq_disable 相对的还有宏 local_irq_enable ,这个宏的实现与 local_irq_disable 很相似,也具有相同的调试机制,区别在于使用 sti 指令使能了中断:

如今我们了解了 local_irq_disable 和 local_irq_enable 宏的实现机理。此处是首次调用 local_irq_disable 宏,我们还将在Linux内核源代码中多次看到它的倩影。现在我们位于 中的 start_kernel 函数,并且刚刚禁用了本地中断。为什么叫"本地"中断?为什么要禁用本地中断呢?早期版本的内核中提供了一个叫做 cli 的函数来禁用所有处理器的中断,该函数已经被,替代它的是 local_irq_{enabled,disable} 宏,用于禁用或使能当前处理器的中断。我们在调用 local_irq_disable 宏禁用中断以后,接着设置了变量值:

变量 early_boot_irqs_disabled 定义在文件 中:

并在另外的地方使用。例如在 中的 smp_call_function_many 函数中,通过这个变量来检查当前是否由于中断禁用而处于死锁状态:

在 local_disable_irq 之后执行的函数是 boot_cpu_init 和 page_address_init,但这两个函数与中断和异常处理无关(更多与这两个函数有关的信息请阅读内核初始化过程)。接下来是 setup_arch 函数。你可能还有印象,这个函数定义在 文件中,并完成了很多。在 setup_arch 函数中与中断相关的第一个函数是 early_trap_init 函数,该函数定义于 ,其用许多对程序入口填充了中断描述符表 Interrupt Descriptor Table :

这些函数都定义在 中,他们做的事情也差不多。第一个函数 set_intr_gate_ist 将一个新的中断门插入到IDT中,其实现如下:

该函数首先检查了参数 n 即 是否不大于 0xff 或 255。之前的 中提到过,中断的向量号必须处于 0 到 255 的闭区间。然后调用了 _set_gate 函数将中断门设置到了 IDT 表中:

首先,通过 pack_gate 函数填充了一个表示 IDT 入口项的 gate_desc 类型的结构体,参数包括基地址,限制范围,, 和中断类型。中断类型的取值如下:

如果你有任何建议或疑问,请在我的 页面中留言或抖一抖我。

Please note that English is not my first language, And I am really sorry for any inconvenience. If you find any mistakes please send me PR to .

上一章节
Linux内核启动过程
最开始
arch/x86/boot/pm.c
中断描述符表
保护模式
Interrupt descriptor table
Global Descriptor Table
arch/x86/boot/pmjump.S
章节
arch/x86/boot/pm.c
arch/x86/boot/pmjump.S
8086
约定
arch/x86/boot/compressed/head_64.S
arch/x86/boot/compressed/Makefile
arch/x86/Makefile
arch/x86/boot/compressed/head_64.S
long mode
内核解压
arch/x86/boot/compressed/misc.c
arch/x86/kernel/head_64.S
NX
章节
model specific register
arch/x86/include/uapi/asm/msr-index.h
链接脚本
arch/x86/include/asm/processor.h
arch/x86/include/asm/percpu.h
include/asm-generic/sections.h
System.map
arch/x86/kernel/head64.c
早期中断和异常处理
patch
patch
arch/x86/kernel/head_64.S
早期的中断和异常控制
章节
arch/x86/kernel/head_64.S
init/main.c
pid
canary
arch/x86/include/asm/stackprotector.h
per-cpu
章节
arch/x86/include/asm/processor.h
C语言
联合体
Linux内核初始化过程章节
时戳计数器
Linux kernel documentation
init/main.c
include/linux/irqflags.h
lockdep
kernel/locking/lockdep.c
kernel/locking/lockdep_insides.h
arch/x86/include/asm/irqflags.h
IF
init/main.c
移除
include/linux/kernel.h
kernel/smp.c
章节
arch/x86/kernel/setup.c
架构相关的初始化工作
arch/x86/kernel/traps.c
arch/x86/include/asm/desc.h
中断向量编号
章节
中断栈表
特权等级
twitter
linux-insides
IDT
Protected mode
List of x86 calling conventions
8086
Long mode
NX
Extended Feature Enable Register
Model-specific register
Process identifier
lockdep
irqflags tracing
IF
Stack canary
Union type
this_cpu_* operations
vector number
Interrupt Stack Table
Privilege level
Previous part