C++八股——中断和系统调用

文章目录

    • 1. 中断(Interrupt)
      • 1.1 什么是中断
      • 1.2 中断的分类
      • 1.3 中断处理的具体流程
      • 1.4 中断处理中的关键机制
      • 1.5 实例:键盘输入的中断处理流程
    • 2. 系统调用(System Call)
      • 2.1 什么是系统调用
      • 2.2 系统调用的核心作用
      • 2.3 系统调用的具体流程
      • 2.4 关键机制与优化
      • 2.5 示例:Linux中`write()`系统调用流程
      • 2.6 系统调用的性能开销

1. 中断(Interrupt)

1.1 什么是中断

中断(Interrupt)是计算机系统中一种关键的事件响应机制,用于暂停当前正在执行的程序,转而处理更紧急或高优先级的任务。中断可以由硬件设备、软件指令或CPU内部异常触发,其核心目的是实现多任务处理、实时响应和错误处理

1.2 中断的分类

中断可分为两大类:中断异常,而中断可分为硬件中断软件中断

  1. 硬件中断(Hardware Interrupt)

    • 触发源:由外部硬件设备(如键盘、磁盘、网卡等)通过中断控制器(如8259A、APIC)向CPU发送信号。

    • 特点

      • 异步:随时可能发生,与当前程序执行无关。
      • 可屏蔽:可通过中断屏蔽字(如cli指令)临时关闭某些中断。
    • 类型

      • 可屏蔽中断(Maskable Interrupt):如I/O设备中断,可被CPU忽略。

      • 不可屏蔽中断(NMI, Non-Maskable Interrupt):如硬件故障(内存校验错误),必须立即处理。

  2. 软件中断(Software Interrupt)

    • 触发源:由程序主动执行特定指令(如int 0x80syscall)触发。
    • 特点:同步执行,由程序主动发起。
    • 典型应用:系统调用(如Linux的int 0x80)、调试断点(int 3)。
  3. 异常(Exception)

    • 触发源:CPU执行指令时检测到错误或特殊条件(如除零、缺页、非法指令)。
    • 类型
      • 故障(Fault):可修复的错误(如缺页异常),修复后可继续执行。
      • 陷阱(Trap):用于调试(如int 3),执行后继续后续指令。
      • 中止(Abort):严重错误(如硬件故障),导致程序终止。

Intel x86系列微机共支持256中向量中断,Linux对256个向量的分配如下:

  1. 从0~31的向量对应异常和非屏蔽中断。
  2. 从32~47的向量分配给屏蔽中断。
  3. 从48~255的向量用来标识软中断。Linux只用了其中一个(128向量即0x80向量)用来实现系统调用。

注:Linux为什么只使用一个中断号来对应所有的系统调用,而不是一个中断号对应一个系统调用?
因为中断号是有限的,而系统调用又太多了。

1.3 中断处理的具体流程

  1. 中断触发
  • 硬件中断:设备通过中断控制器(如APIC)向CPU发送中断信号。
  • 软件中断:程序执行intsyscall指令。
  • 异常:CPU检测到错误(如除零操作)。
  1. CPU响应中断

    • 检查中断是否允许

      • 若当前中断被屏蔽(如IF标志位为0),则忽略可屏蔽中断。
      • 不可屏蔽中断(NMI)和异常必须立即处理。
    • 保存当前执行上下文

      • 程序计数器(PC)、状态寄存器(EFLAGS)和其他寄存器压入内核栈。
      • 切换到内核态(特权模式,如x86的Ring 0)。
  2. 确定中断源

    • 获取中断号

      • 硬件中断:从中断控制器读取中断号(如IRQ编号)。
      • 软件中断和异常:从中断指令或CPU内部获取中断号。
    • 查询中断向量表(IDT)

      根据中断号在中断描述符表(Interrupt Descriptor Table, IDT)中找到对应的中断服务程序(ISR, Interrupt Service Routine)入口地址

  3. 执行中断服务程序(ISR)

    • 保存剩余寄存器状态(防止破坏用户程序现场)。

    • 处理中断请求

      • 硬件中断:读取设备状态,处理数据(如从键盘缓冲区读取按键)。
      • 异常:尝试修复错误(如缺页时加载页面),若无法修复则终止程序。
      • 软件中断:执行系统调用逻辑(如文件读写)。
    • 发送中断结束信号(EOI)(仅硬件中断需要):

      通知中断控制器(如8259A)当前中断处理完成。

    • 可能触发任务调度(如时钟中断触发进程切换)。

  4. 恢复现场并返回

    • 恢复寄存器状态:从内核栈中弹出保存的寄存器值。
    • 切换回用户态(若中断前在用户态):通过iret指令恢复程序计数器(PC)和状态寄存器(EFLAGS)。
    • 继续执行原程序:从被中断的指令或下一条指令继续执行。

1.4 中断处理中的关键机制

  1. 中断优先级

    • 多个中断同时发生时,CPU按优先级处理(如NMI > 异常 > 可屏蔽中断)。
    • 中断控制器(如APIC)可配置优先级。
  2. 中断嵌套

    高优先级中断可抢占低优先级中断的处理(需在ISR中重新启用中断)。

  3. 中断共享

    多个设备共享同一中断号(如PCI设备),需在ISR中轮询设备状态。

  4. 上半部与下半部(Linux为例)

    • 上半部(Top Half):快速处理关键任务(如读取硬件数据),立即执行。
    • 下半部(Bottom Half):延迟处理耗时操作(如数据处理),通过软中断(SoftIRQ)、任务队列(Tasklet)或工作队列(Workqueue)实现。

1.5 实例:键盘输入的中断处理流程

  1. 用户按下键盘按键,键盘控制器生成中断信号(IRQ1)。
  2. CPU响应中断,保存当前程序上下文,切换到内核态。
  3. 查询IDT,找到键盘中断对应的ISR(如keyboard_interrupt)。
  4. ISR执行
    • 从键盘缓冲区读取按键编码。
    • 将按键数据存入输入队列。
    • 发送EOI信号通知中断控制器。
  5. 恢复用户程序上下文,继续执行被中断的任务。

2. 系统调用(System Call)

2.1 什么是系统调用

系统调用(System Call)是操作系统内核提供给用户程序访问底层硬件资源和核心服务的接口。用户程序运行在用户态(User Mode),无法直接执行特权操作(如操作硬件、管理进程等),因此需要通过系统调用切换到内核态(Kernel Mode),由内核代为完成这些操作

2.2 系统调用的核心作用

  • 权限隔离:保护硬件和系统资源,防止用户程序直接操作。
  • 抽象接口:为用户程序提供统一的资源访问方式(如文件读写、网络通信等)。
  • 安全控制:内核验证用户请求的合法性,避免非法操作。

2.3 系统调用的具体流程

  1. 用户程序触发系统调用

    • 通过库函数调用:用户程序通常不直接调用系统调用,而是通过标准库(如C库的glibc)封装的函数(如open()read()fork())。
    • 参数准备:将系统调用号(标识具体操作)和参数按约定存入寄存器或堆栈。例如:
      • x86架构:eax存放系统调用号,ebxecxedx等存放参数。
      • x86-64架构:rax存放系统调用号,rdirsirdx等存放参数。
  2. 触发模式切换(用户态 → 内核态)

    • 软中断或专用指令

      • 传统方式:通过软中断(如int 0x80)触发中断处理程序。
      • 现代方式:使用更高效的专用指令(如x86的syscall/sysenter,ARM的svc)。
    • CPU切换上下文

      • CPU切换到内核态,提升权限级别(如从Ring 3切换到Ring 0)。
      • 保存用户态寄存器状态(如程序计数器、栈指针等)。
  3. 内核处理系统调用

    • 查找系统调用表:根据系统调用号(如eax的值)在系统调用表(sys_call_table)中找到对应的内核函数。
    • 参数验证:内核检查用户传递的参数是否合法(如内存地址是否属于用户空间)。
    • 执行内核函数:调用内核中的服务例程(如sys_read()处理文件读取)。
    • 阻塞与调度:若操作需等待(如磁盘I/O),可能挂起当前进程,触发调度器切换进程。
  4. 返回结果并恢复用户态

    • 结果返回:将执行结果(如读取的字节数)和错误码(如errno)存入寄存器(如x86的eax)。
    • 恢复上下文:恢复之前保存的用户态寄存器状态。
    • 模式切换回用户态:通过指令(如iretsysret)切换回用户态,继续执行用户程序。
  5. 用户程序处理结果

    • 检查返回值:若为负数,通常表示错误(通过errno获取具体原因)。
    • 执行后续逻辑:如读取成功则处理数据,失败则进行错误处理。

2.4 关键机制与优化

  • 快速系统调用指令syscall/sysret比传统软中断更快,减少了上下文切换开销。
  • 虚拟动态共享库(VDSO):允许部分系统调用(如gettimeofday())直接在用户态执行,避免切换。
  • 参数传递优化:通过寄存器传递参数(而非堆栈)提升效率。

2.5 示例:Linux中write()系统调用流程

  1. 用户程序调用write(fd, buf, count)
  2. glibc将系统调用号__NR_write(x86-64中为1)存入rax,参数依次存入rdirsirdx
  3. 执行syscall指令触发内核模式切换。
  4. 内核调用sys_write()处理写操作,返回实际写入的字节数。
  5. 结果通过rax返回,用户程序检查是否成功。

2.6 系统调用的性能开销

  • 主要开销来源:上下文切换、参数检查、内核态与用户态的数据拷贝(如read()需将数据从内核缓冲区复制到用户空间)。
  • 优化手段:零拷贝技术(如sendfile())、批处理系统调用(如io_uring)。

参考

  • DeepSeek
  • 系统调用的概念及原理-CSDN博客

你可能感兴趣的:(c++,开发语言)