跳转至

01-操作系统核心概念

重要性: ⭐⭐⭐⭐⭐ 实用度: ⭐⭐⭐⭐⭐ 学习时间: 2天 必须掌握: 是


为什么学这一章?

操作系统是硬件和应用程序之间的桥梁。理解操作系统能帮助你: - 理解程序如何与硬件交互 - 掌握系统调用的原理 - 编写高效的系统级程序 - 理解现代软件的运行环境

学完这一章,你将能够: - ✅ 解释操作系统的核心功能和架构 - ✅ 理解用户态和内核态的区别 - ✅ 掌握系统调用的实现机制 - ✅ 理解中断和异常处理流程


📖 核心概念

1. 操作系统的作用

Text Only
┌─────────────────────────────────────────────────────────────────────┐
│                    操作系统作为资源管理器                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  应用程序层                                                          │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐                   │
│  │ 浏览器   │ │ 编辑器   │ │ 游戏     │ │ 数据库   │                   │
│  └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘                   │
│       └─────────────┴─────────────┴─────────────┘                   │
│                        ↓                                            │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │                      操作系统 (OS)                              │ │
│  │  ┌─────────────────────────────────────────────────────┐     │ │
│  │  │  进程管理  │  内存管理  │  文件系统  │  设备管理     │     │ │
│  │  └─────────────────────────────────────────────────────┘     │ │
│  │  ┌─────────────────────────────────────────────────────┐     │ │
│  │  │  系统调用接口 (System Call Interface)               │     │ │
│  │  └─────────────────────────────────────────────────────┘     │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                        ↓                                            │
│  硬件层                                                              │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐                   │
│  │  CPU    │ │  内存    │ │  磁盘    │ │  网卡    │                   │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘                   │
│                                                                     │
│  操作系统的核心职责:                                                  │
│  1. 进程管理 - 创建、调度、终止进程                                    │
│  2. 内存管理 - 分配、回收、保护内存                                    │
│  3. 文件系统 - 组织、存储、访问文件                                    │
│  4. 设备管理 - 管理I/O设备                                            │
│  5. 安全保护 - 权限控制、资源隔离                                      │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

2. 操作系统架构

内核架构类型

Text Only
┌─────────────────────────────────────────────────────────────────────┐
│                    操作系统内核架构对比                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  1. 宏内核 (Monolithic Kernel) - Linux                              │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │                                                                │ │
│  │  ┌─────────┬─────────┬─────────┬─────────┬─────────┐          │ │
│  │  │ 进程管理 │ 内存管理 │ 文件系统 │ 设备驱动 │ 网络协议 │          │ │
│  │  └────┬────┴────┬────┴────┬────┴────┬────┴────┬────┘          │ │
│  │       └─────────┴─────────┴─────────┴─────────┘                │ │
│  │                      内核空间                                   │ │
│  │  ┌─────────────────────────────────────────────────────┐       │ │
│  │  │              硬件抽象层 (HAL)                        │       │ │
│  │  └─────────────────────────────────────────────────────┘       │ │
│  │                                                                │ │
│  │  特点:所有功能在内核空间运行,性能高,但一个模块崩溃影响整个系统   │ │
│  │                                                                │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
│  2. 微内核 (Microkernel) - Minix, QNX                              │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │                                                                │ │
│  │  ┌─────────────────┐                                          │ │
│  │  │   微内核核心     │  进程通信 │ 内存管理 │ 中断处理 │          │ │
│  │  └────────┬────────┘                                          │ │
│  │           │ 消息传递                                           │ │
│  │  ┌────────┴────────┬────────┬────────┬────────┐               │ │
│  │  │   文件服务器    │ 设备驱动 │ 网络服务 │ ...   │  用户空间    │ │
│  │  └─────────────────┴────────┴────────┴────────┘               │ │
│  │                                                                │ │
│  │  特点:核心功能最小化,其他服务在用户空间,可靠性高,但性能开销大    │ │
│  │                                                                │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
│  3. 混合内核 (Hybrid Kernel) - Windows, macOS                       │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │                                                                │ │
│  │  结合宏内核和微内核的优点                                        │ │
│  │  • 核心服务在内核空间                                           │ │
│  │  • 部分服务可在用户空间                                          │ │
│  │  • 平衡性能和可靠性                                              │ │
│  │                                                                │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

3. 用户态与内核态

为什么需要区分?

Text Only
┌─────────────────────────────────────────────────────────────────────┐
│                    用户态 vs 内核态                                    │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  特权级别 (x86-64架构)                                               │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │  Ring 0  │  内核态 (Kernel Mode)  │  最高特权,可执行所有指令   │ │
│  │  Ring 1  │  (未使用)              │                           │ │
│  │  Ring 2  │  (未使用)              │                           │ │
│  │  Ring 3  │  用户态 (User Mode)    │  最低特权,受限指令        │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
│  用户态限制:                                                         │
│  • 不能直接访问硬件                                                  │
│  • 不能修改页表                                                      │
│  • 不能执行特权指令 (如CLI, HLT)                                     │
│  • 不能访问其他进程的内存                                             │
│                                                                     │
│  内核态权限:                                                         │
│  • 可以执行所有指令                                                  │
│  • 可以访问所有内存                                                  │
│  • 可以直接操作硬件                                                  │
│                                                                     │
│  类比:                                                               │
│  • 用户态 = 普通员工,只能使用公司资源                                │
│  • 内核态 = 管理员,可以管理公司所有资源                              │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

状态切换

Text Only
┌─────────────────────────────────────────────────────────────────────┐
│                    用户态与内核态切换                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  用户态 ────────────────────────────────────────────────────────→  │
│    │                                                              │
│    │ 1. 系统调用 (System Call)                                     │
│    │    • 程序主动请求内核服务                                      │
│    │    • 如:read(), write(), open()                              │
│    ↓                                                              │
│  内核态 ←───────────────────────────────────────────────────────→  │
│    │                                                              │
│    │ 2. 中断 (Interrupt)                                           │
│    │    • 硬件发出的异步信号                                        │
│    │    • 如:键盘输入,磁盘I/O完成                                 │
│    ↓                                                              │
│  用户态 ←───────────────────────────────────────────────────────→  │
│    │                                                              │
│    │ 3. 异常 (Exception)                                           │
│    │    • 程序执行错误                                              │
│    │    • 如:除零错误,页错误 (Page Fault)                         │
│    ↓                                                              │
│  内核态 ←───────────────────────────────────────────────────────→  │
│                                                                     │
│  切换开销:                                                           │
│  • 保存/恢复寄存器状态                                                │
│  • 切换页表                                                          │
│  • 刷新TLB (Translation Lookaside Buffer)                          │
│  • 典型开销:100-1000个时钟周期                                       │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

4. 系统调用机制

系统调用流程

Text Only
┌─────────────────────────────────────────────────────────────────────┐
│                    系统调用执行流程                                    │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  用户程序                                                            │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │  // C库函数封装                                                  │ │
│  │  ssize_t read(int fd, void* buf, size_t count) {               │ │
│  │      // 1. 准备参数                                              │ │
│  │      // 2. 触发系统调用                                          │ │
│  │      mov $0, %rax        # 系统调用号 (sys_read)               │ │
│  │      mov fd, %rdi        # 第1参数:文件描述符                  │ │
│  │      mov buf, %rsi       # 第2参数:缓冲区地址                  │ │
│  │      mov count, %rdx     # 第3参数:读取字节数                  │ │
│  │      syscall             # 触发系统调用指令                     │ │
│  │      // 3. 返回结果                                              │ │
│  │      return %rax;        # 返回读取的字节数                     │ │
│  │  }                                                             │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                              ↓                                      │
│  CPU执行syscall指令                                                 │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │  1. 保存用户态上下文 (寄存器, PC等)                              │ │
│  │  2. 切换到内核栈                                                 │ │
│  │  3. 提升特权级别到Ring 0                                         │ │
│  │  4. 根据系统调用号跳转到处理函数                                  │ │
│  │  5. 执行内核代码 (sys_read)                                     │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                              ↓                                      │
│  内核处理                                                            │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │  sys_read() {                                                  │ │
│  │      // 1. 验证参数合法性                                        │ │
│  │      // 2. 查找文件描述符对应的文件                              │ │
│  │      // 3. 调用文件系统的read函数                                │ │
│  │      // 4. 从磁盘/缓存读取数据                                   │ │
│  │      // 5. 拷贝数据到用户缓冲区                                  │ │
│  │      // 6. 返回结果                                              │ │
│  │  }                                                             │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                              ↓                                      │
│  返回用户态                                                          │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │  1. 恢复用户态上下文                                             │ │
│  │  2. 降低特权级别到Ring 3                                         │ │
│  │  3. 恢复用户栈                                                   │ │
│  │  4. 继续执行用户程序                                             │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

常见系统调用

系统调用 功能 C库封装
sys_read 从文件读取 read()
sys_write 写入文件 write()
sys_open 打开文件 open()
sys_close 关闭文件 close()
sys_mmap 内存映射 mmap()
sys_fork 创建进程 fork()
sys_execve 执行程序 execve()
sys_exit 退出进程 exit()
sys_socket 创建套接字 socket()
sys_connect 连接网络 connect()

5. 中断和异常处理

中断类型

Text Only
┌─────────────────────────────────────────────────────────────────────┐
│                    中断和异常分类                                      │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  中断 (Interrupt) - 异步事件                                          │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │                                                                │ │
│  │  硬件中断 (Hardware Interrupt)                                  │ │
│  │  ├── I/O中断:键盘输入,鼠标移动,磁盘完成                      │ │
│  │  ├── 定时器中断:时钟中断,用于进程调度                         │ │
│  │  └── 外部中断:网络数据到达,USB设备插入                        │ │
│  │                                                                │ │
│  │  软件中断 (Software Interrupt)                                  │ │
│  │  └── 系统调用 (int 0x80 / syscall指令)                         │ │
│  │                                                                │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
│  异常 (Exception) - 同步事件                                          │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │                                                                │ │
│  │  故障 (Fault) - 可恢复                                          │ │
│  │  ├── 页错误 (Page Fault):访问未映射的内存                      │ │
│  │  │   处理:分配物理页,建立映射,重新执行指令                   │ │
│  │  └── 段错误 (Segmentation Fault):访问非法地址                  │ │
│  │                                                                │ │
│  │  陷阱 (Trap) - 有意为之                                         │ │
│  │  ├── 断点 (Breakpoint):调试器设置的单步执行                    │ │
│  │  └── 系统调用:程序主动请求服务                                 │ │
│  │                                                                │ │
│  │  终止 (Abort) - 严重错误                                        │ │
│  │  ├── 双重故障:处理异常时又发生异常                             │ │
│  │  └── 机器检查:硬件错误                                         │ │
│  │                                                                │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

中断处理流程

Text Only
┌─────────────────────────────────────────────────────────────────────┐
│                    中断处理流程                                        │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  1. 中断发生                                                         │
│     CPU收到中断信号                                                  │
│                                                                     │
│  2. 保存上下文                                                       │
│     ┌───────────────────────────────────────────────────────────┐  │
│     │  • 保存程序计数器 (PC/RIP)                                 │  │
│     │  • 保存标志寄存器 (RFLAGS)                                 │  │
│     │  • 保存寄存器状态                                          │  │
│     │  • 切换到中断栈                                            │  │
│     └───────────────────────────────────────────────────────────┘  │
│                                                                     │
│  3. 确定中断源                                                       │
│     ┌───────────────────────────────────────────────────────────┐  │
│     │  • 查询中断控制器 (APIC)                                   │  │
│     │  • 获取中断向量号 (0-255)                                  │  │
│     │  • 跳转到对应的中断处理程序                                 │  │
│     └───────────────────────────────────────────────────────────┘  │
│                                                                     │
│  4. 执行中断处理程序                                                  │
│     ┌───────────────────────────────────────────────────────────┐  │
│     │  键盘中断处理:                                              │  │
│     │  • 从键盘控制器读取扫描码                                   │  │
│     │  • 转换为按键码                                             │  │
│     │  • 放入键盘缓冲区                                           │  │
│     │  • 唤醒等待输入的进程                                       │  │
│     │                                                             │  │
│     │  时钟中断处理:                                              │  │
│     │  • 更新系统时间                                             │  │
│     │  • 检查是否需要进程调度                                     │  │
│     │  • 更新进程时间片                                           │  │
│     └───────────────────────────────────────────────────────────┘  │
│                                                                     │
│  5. 恢复上下文                                                       │
│     ┌───────────────────────────────────────────────────────────┐  │
│     │  • 恢复寄存器状态                                          │  │
│     │  • 恢复标志寄存器                                          │  │
│     │  • 恢复程序计数器                                          │  │
│     │  • 返回被中断的程序                                        │  │
│     └───────────────────────────────────────────────────────────┘  │
│                                                                     │
│  注意:中断处理程序执行时要快,避免丢失其他中断                         │
│        复杂的工作应该延迟到下半部 (Bottom Half) 处理                  │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

🧪 动手实验

实验1:使用strace跟踪系统调用

目的:观察程序的系统调用

步骤

  1. 创建一个简单程序

    C++
    // syscall_test.cpp
    #include <iostream>
    #include <fcntl.h>
    #include <unistd.h>
    
    int main() {
        // 系统调用:open
        int fd = open("test.txt", O_CREAT | O_WRONLY, 0644);
    
        // 系统调用:write
        const char* msg = "Hello, System Call!\n";
        write(fd, msg, 20);
    
        // 系统调用:close
        close(fd);
    
        std::cout << "Done!" << std::endl;
        return 0;
    }
    

  2. 编译并跟踪

    Bash
    g++ -o syscall_test syscall_test.cpp
    strace -o trace.log ./syscall_test
    cat trace.log
    

  3. 分析输出

  4. 找到open, write, close系统调用
  5. 观察参数和返回值

实验2:编写内联汇编进行系统调用

目的:理解系统调用的底层实现

步骤

  1. 创建程序

    C++
    // inline_syscall.cpp
    #include <iostream>  // 引入头文件
    #include <sys/syscall.h>
    #include <unistd.h>
    
    // 使用内联汇编进行write系统调用
    ssize_t my_write(int fd, const void* buf, size_t count) {  // 指针:存储变量的内存地址
        ssize_t result;
    
        // 使用专用寄存器约束直接指定参数位置
        // "a"=rax, "D"=rdi, "S"=rsi, "d"=rdx
        // 注意:不要用通用"r"约束 + 显式mov,
        // 否则编译器可能将输入分配到会被覆盖的寄存器中
        __asm__ volatile (
            "syscall"
            : "=a" (result)                        // 输出:rax = 返回值
            : "a" ((long)SYS_write),               // rax = 系统调用号
              "D" ((long)fd),                      // rdi = 第1参数:fd
              "S" (buf),                           // rsi = 第2参数:buf
              "d" (count)                          // rdx = 第3参数:count
            : "rcx", "r11", "memory"              // syscall会破坏rcx和r11
        );
    
        return result;
    }
    
    int main() {
        const char msg[] = "Hello from inline assembly!\n";
        my_write(STDOUT_FILENO, msg, sizeof(msg) - 1);
        return 0;
    }
    

  2. 编译运行

    Bash
    g++ -o inline_syscall inline_syscall.cpp
    ./inline_syscall
    

实验3:观察中断处理

目的:了解系统中断统计

步骤

  1. 查看中断统计

    Bash
    # Linux系统
    cat /proc/interrupts
    
    # 观察特定CPU的中断
    watch -n 1 'cat /proc/interrupts | head -20'  # |管道:将前一命令的输出作为后一命令的输入
    

  2. 分析输出

  3. LOC:本地定时器中断
  4. IPI:处理器间中断
  5. 设备特定的中断号

💡 核心要点总结

操作系统核心功能

  1. 进程管理 - 创建、调度、终止进程
  2. 内存管理 - 分配、回收、保护内存
  3. 文件系统 - 组织、存储、访问文件
  4. 设备管理 - 管理I/O设备
  5. 安全保护 - 权限控制、资源隔离

用户态 vs 内核态

特性 用户态 内核态
特权级别 Ring 3 Ring 0
内存访问 受限 全部
硬件访问 不允许 允许
指令执行 非特权指令 所有指令

系统调用流程

Text Only
用户程序 → C库 → syscall指令 → 内核处理 → 返回结果

中断 vs 异常

特性 中断 异常
同步性 异步 同步
来源 硬件/软件 CPU执行
示例 键盘、定时器 页错误、除零

❓ 常见问题

Q1:为什么系统调用比函数调用慢?

A:系统调用需要: - 保存/恢复上下文 - 切换特权级别 - 切换页表 - 刷新TLB 典型开销:100-1000个时钟周期

Q2:如何减少系统调用开销?

A:策略: - 批量处理 (如readv/writev) - 使用缓存减少I/O - 使用内存映射 (mmap) - 使用异步I/O

Q3:用户程序能直接执行特权指令吗?

A:不能。尝试执行会导致一般保护故障 (General Protection Fault),操作系统会终止该程序。

Q4:中断和异常处理程序运行在什么特权级别?

A:都在内核态 (Ring 0) 运行,因为需要访问硬件和敏感资源。


📚 扩展阅读

  1. 《操作系统导论》 - Remzi & Andrea Arpaci-Dusseau
  2. 《深入理解Linux内核》 - Daniel P. Bovet
  3. Linux手册:man 2 syscall, man 2 intro

🎯 下一步

继续学习操作系统交互的后续内容,深入理解进程调度、内存管理、文件系统等核心机制。