《Linux内核设计的艺术:图解Linux操作系统架构设计与实现原理》——2.5 异常处理类中断服务程序挂接

简介: 本节书摘来自华章计算机《Linux内核设计的艺术:图解Linux操作系统架构设计与实现原理》一书中的第2章,第2.5节,作者:新设计团队著, 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

2.5 异常处理类中断服务程序挂接

不论是用户进程还是系统内核都要经常使用中断或遇到很多异常情况需要处理,如CPU在参与运算过程中,可能会遇到除零错误、溢出错误、边界检查错误、缺页错误……免不了需要“异常处理”。中断技术也是广泛使用的,系统调用就是利用中断技术实现的。这些中断、异常都需要具体的服务程序来执行。trap_init()函数将中断、异常处理的服务程序与IDT进行挂接,逐步重建中断服务体系,支持内核、进程在主机中的运算。挂接的具体过程及异常处理类中断服务程序在IDT中所占用的位置如图2-6所示。

image

执行代码如下:

//代码路径:init/main.c:
void main(void)
{
    …
    trap_init();
    …
}

//代码路径:kernel/traps.c:
void trap_init(void)
{
    int i;

    set_trap_gate(0,&divide_error);//除零错误
    set_trap_gate(1,&debug);    //单步调试
    set_trap_gate(2,&nmi);    //不可屏蔽中断

    set_system_gate(3,&int3);    /* int3-5 can be called from all */
    set_system_gate(4,&overflow);    //溢出
    set_system_gate(5,&bounds);    //边界检查错误
    set_trap_gate(6,&invalid_op);    //无效指令
    set_trap_gate(7,&device_not_available);    //无效设备
    set_trap_gate(8,&double_fault);    //双故障
    set_trap_gate(9,&coprocessor_segment_overrun);//协处理器段越界
    set_trap_gate(10,&invalid_TSS);    //无效TSS
    set_trap_gate(11,&segment_not_present);    //段不存在
    set_trap_gate(12,&stack_segment);    //栈异常        
    set_trap_gate(13,&general_protection);    //一般性保护异常
    set_trap_gate(14,&page_fault);    //缺页
    set_trap_gate(15,&reserved);    //保留
    set_trap_gate(16,&coprocessor_error);    //协处理器错误
    for (i=17;i<48;i++)    //都先挂接好,中断服务程序函数名初
    //始化为保留
         set_trap_gate(i,&reserved);
    set_trap_gate(45,&irq13);    //协处理器
    outb_p(inb_p(0x21)&0xfb,0x21);    //允许IRQ2中断请求
    outb(inb_p(0xA1)&0xdf,0xA1);    // 允许IRQ2中断请求
    set_trap_gate(39,&parallel_interrupt);    //并口
}

//代码路径:include\asm\system.h:
    …
#define _set_gate(gate_addr,type,dpl,addr)    \
__asm__("movw %%dx,%%ax\n\t" \    //将edx的低字赋值给eax的低字
         "movw %0,%%dx\n\t" \    //%0对应第二个冒号后的第1行的"i"
         "movl %%eax,%1\n\t" \    //%1对应第二个冒号后的第2行的"o"
         "movl %%edx,%2" \    //%2对应第二个冒号后的第3行的"o"
         : \    //这个冒号后面是输出,下面冒号后面
    //是输入
    : "i" ((short) (0x8000 + (dpl<<13) + (type<<8))), \    //立即数
           "o" (*((char *) (gate_addr))), \        //中断描述符前4个字节的地址
           "o" (*(4 + (char *) (gate_addr))), \        //中断描述符后4个字节的地址
           "d" ((char *) (addr)),"a" (0x00080000))    //"d"对应edx,"a"对应eax
    …
#define set_trap_gate(n,addr) \
    _set_gate(&idt[n],15,0,addr)

这些代码的目的就是要拼出第1章1.3.5节讲述过的中断描述符。为了便于阅读,复制在下面,如图2-7所示。

image

上述代码的执行效果如图2-8所示。

image

对比:
set_trap_gate(0,&divide_error)
set_trap_gate(n,addr)
_set_gate(&idt[n],15,0,addr)
_set_gate(gate_addr,type,dpl,addr)
可以看出,n是0;gate_addr是&idt[0],也就是idt的第一项中断描述符的地址;type是15;dpl(描述符特权级)是0;addr是中断服务程序divide_error(void)的入口地址,如图2-9所示。

image

“movw %%dx,%%axnt”是把edx的低字赋值给eax的低字;edx是(char ) (addr),也就是&divide_error;eax的值是0x00080000,这个数据在head.s中就提到过,8应该看成1000,每一位都有意义,这样eax的值就是0x00080000 + ((char )(addr)的低字),其中的0x0008是段选择符,含义与第1章中讲解过的“jmpi 0,8”中的8一致。
"movw %0,%%dxnt”是把(short) (0x8000 + (dpl<<13) + (type<<8))赋值给dx。别忘了,edx是(char *) (addr),也就是&divide_error。
因为这部分数据是按位拼接的,必须计算精确,我们耐心详细计算一下:
0x8000就是二进制的1000 0000 0000 0000;
dpl是00,dpl<<13就是000 0000 0000 0000;
type是15,type<<8就是1111 0000 0000;
加起来就是1000 1111 0000 0000,这就是dx的值。edx的计算结果就是(char *) (addr) 的高字即&divide_error的高字 + 1000 1111 0000 0000。
"movl %%eax,%1nt”是把eax的值赋给((char ) (gate_addr)),就是赋给idt[0]的前4字节。同理,"movl %%edx,%2”是把edx的值赋给(4 + (char ) (gate_addr)),就是赋给idt[0]的前后4字节。8字节合起来就是完整的idt[0]。拼接的效果如图2-10所示。
IDT中的第一项除零错误中断描述符初始化完毕,其余异常处理服务程序的中断描述符初始化过程大同小异。后续介绍的所有中断服务程序与IDT的初始化基本上都是以这种方式进行的。
set_system_gate(n,addr)与set_trap_gate(n,addr)用的_set_gate(gate_addr,type,dpl,addr)是一样的;差别是set_trap_gate的dpl是0,而set_system_gate的dpl是3。dpl为0的意思是只能由内核处理,dpl为3的意思是系统调用可以由3特权级(也就是用户特权级)
调用。
有关特权级更深入的内容,请参看《Intel IA-32 Architectures Software Developer’s Manual Volume 3.pdf》(可以到Intel官方网站下载)。
接下来将IDT的int 0x11~int 0x2F都初始化,将IDT中对应的指向中断服务程序的指针设置为reserved(保留)。
设置协处理器的IDT项。
允许主8259A中断控制器的IRQ2、IRQ3的中断请求。
设置并口(可以接打印机)的IDT项。
32位中断服务体系是为适应“被动响应”中断信号机制而建立的。其特点、技术路线是这样的:一方面,硬件产生信号传达给8259A,8259A对信号进行初步处理并视CPU执行情况传递中断信号给CPU;另一方面,CPU如果没有接收到信号,就不断地处理正在执行的程序,如果接收到信号,就打断正在执行的程序并通过IDT找到具体的中断服务程序,让其执行,执行完后,返回刚才打断的程序点继续执行。如果又接收到中断信号,就再次处理中断……

image

最原始的设计不是这样,那时候CPU每隔一段时间就要对所有硬件进行轮询,以检测它的工作是否完成,如果没有完成就继续轮询,这样就消耗了CPU处理用户程序的时间,降低了系统的综合效率。可见,CPU以“主动轮询”的方式来处理信号是非常不划算的。以“被动响应”模式替代“主动轮询”模式来处理主机与外设的I/O问题,是计算机历史上的一大进步。

相关文章
|
安全 网络协议 Linux
深入理解Linux内核模块:加载机制、参数传递与实战开发
本文深入解析了Linux内核模块的加载机制、参数传递方式及实战开发技巧。内容涵盖模块基础概念、加载与卸载流程、生命周期管理、参数配置方法,并通过“Hello World”模块和字符设备驱动实例,带领读者逐步掌握模块开发技能。同时,介绍了调试手段、常见问题排查、开发规范及高级特性,如内核线程、模块间通信与性能优化策略。适合希望深入理解Linux内核机制、提升系统编程能力的技术人员阅读与实践。
1162 1
|
Ubuntu Linux
Ubuntu 23.04 用上 Linux 6.2 内核,预计下放到 22.04 LTS 版本
Linux 6.2 带来了多项内容更新,修复了 AMD 锐龙处理器设备在启用 fTPM 后的运行卡顿问题,还增强了文件系统。
|
安全 Linux iOS开发
Nessus Professional 10.10 Auto Installer for RHEL 10, AlmaLinux 10, Rocky Linux 10 - Nessus 自动化安装程序
Nessus Professional 10.10 Auto Installer for RHEL 10, AlmaLinux 10, Rocky Linux 10 - Nessus 自动化安装程序
865 6
Nessus Professional 10.10 Auto Installer for RHEL 10, AlmaLinux 10, Rocky Linux 10 - Nessus 自动化安装程序
|
监控 Ubuntu Linux
什么Linux,Linux内核及Linux操作系统
上面只是简单的介绍了一下Linux操作系统的几个核心组件,其实Linux的整体架构要复杂的多。单纯从Linux内核的角度,它要管理CPU、内存、网卡、硬盘和输入输出等设备,因此内核本身分为进程调度,内存管理,虚拟文件系统,网络接口等4个核心子系统。
1211 0
|
Web App开发 缓存 Rust
|
Ubuntu 安全 Linux
Ubuntu 发行版更新 Linux 内核,修复 17 个安全漏洞
本地攻击者可以利用上述漏洞,攻击 Ubuntu 22.10、Ubuntu 22.04、Ubuntu 20.04 LTS 发行版,导致拒绝服务(系统崩溃)或执行任意代码。
|
安全 Linux 数据安全/隐私保护
Vanilla OS:下一代安全 Linux 发行版
【10月更文挑战第30天】
2018 0
Vanilla OS:下一代安全 Linux 发行版
|
Ubuntu Unix Linux
操作系统的最强入门科普(Unix/Linux篇)
下期文章,小枣君会重点聊聊Windows和macOS那条线。敬请关注! 如果大家觉得文章不错,还请帮忙多多转发!谢谢!