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跟踪系统调用¶
目的:观察程序的系统调用
步骤:
-
创建一个简单程序
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; } -
编译并跟踪
-
分析输出
- 找到open, write, close系统调用
- 观察参数和返回值
实验2:编写内联汇编进行系统调用¶
目的:理解系统调用的底层实现
步骤:
-
创建程序
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; } -
编译运行
实验3:观察中断处理¶
目的:了解系统中断统计
步骤:
-
查看中断统计
-
分析输出
- LOC:本地定时器中断
- IPI:处理器间中断
- 设备特定的中断号
💡 核心要点总结¶
操作系统核心功能¶
- 进程管理 - 创建、调度、终止进程
- 内存管理 - 分配、回收、保护内存
- 文件系统 - 组织、存储、访问文件
- 设备管理 - 管理I/O设备
- 安全保护 - 权限控制、资源隔离
用户态 vs 内核态¶
| 特性 | 用户态 | 内核态 |
|---|---|---|
| 特权级别 | Ring 3 | Ring 0 |
| 内存访问 | 受限 | 全部 |
| 硬件访问 | 不允许 | 允许 |
| 指令执行 | 非特权指令 | 所有指令 |
系统调用流程¶
中断 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) 运行,因为需要访问硬件和敏感资源。
📚 扩展阅读¶
- 《操作系统导论》 - Remzi & Andrea Arpaci-Dusseau
- 《深入理解Linux内核》 - Daniel P. Bovet
- Linux手册:man 2 syscall, man 2 intro
🎯 下一步¶
继续学习操作系统交互的后续内容,深入理解进程调度、内存管理、文件系统等核心机制。