8.1 操作系统架构
操作系统是计算机系统中最核心的系统软件,它管理着计算机的所有硬件和软件资源,为上层应用提供抽象的接口和运行环境。理解操作系统的架构,能帮助我们更好地理解程序的运行机制。
操作系统的地位和作用
操作系统位于硬件和应用程序之间,承上启下:
┌─────────────────────────┐
│ 应用程序 │ 浏览器、办公软件、服务等
├─────────────────────────┤
│ 操作系统 │ 内核、系统库、驱动程序
├─────────────────────────┤
│ 硬件层 │ CPU、内存、磁盘、外设等
└─────────────────────────┘
操作系统的核心作用:
- 资源管理:管理CPU、内存、磁盘、外设等所有硬件资源,公平合理地分配给各个进程使用
- 抽象接口:为上层应用提供统一的抽象接口,屏蔽底层硬件的细节,让应用程序不需要关心硬件的具体实现
- 隔离保护:隔离不同的进程,防止进程之间互相干扰,保证系统安全稳定
- 提升效率:通过调度、缓存、并发等技术提升系统的整体效率和资源利用率
内核态与用户态
为了保护操作系统的安全,现代CPU都提供了不同的特权级别,操作系统将运行环境分为内核态和用户态:
内核态(Kernel Mode)
- 拥有最高特权,可以直接访问所有硬件资源,执行任意指令
- 操作系统的核心代码运行在内核态,包括进程调度、内存管理、驱动程序等
- 内核态的错误是致命的,可能导致整个系统崩溃
用户态(User Mode)
- 特权级别较低,不能直接访问硬件资源,也不能直接访问内核地址空间
- 所有的应用程序都运行在用户态
- 用户态的程序要访问硬件资源,必须通过系统调用接口进入内核态,由操作系统代为访问
- 用户态的错误通常只会影响当前进程,不会导致整个系统崩溃
状态切换
当用户态程序需要执行特权操作(比如读写文件、分配内存、发送网络请求)时,需要通过系统调用从用户态切换到内核态,操作系统完成操作后再切换回用户态。
- 状态切换有一定的开销,需要保存和恢复上下文
- 频繁的系统调用会降低程序性能
操作系统内核架构
内核是操作系统的核心部分,负责管理系统资源和提供核心功能,常见的内核架构有三种:宏内核、微内核、混合内核。
1. 宏内核(Monolithic Kernel)
宏内核是最传统的内核架构,所有的操作系统功能(进程管理、内存管理、文件系统、驱动程序、网络协议栈等)都运行在内核态,是一个单独的大程序。
- 优点:
- 性能高,所有模块都在内核态运行,模块之间直接调用,没有通信开销
- 实现简单,成熟稳定
- 缺点:
- 内核庞大复杂,维护难度大
- 一个模块的bug可能导致整个内核崩溃
- 扩展性差,添加新功能需要重新编译内核
- 代表系统:Linux、Unix、早期的Windows
Linux是最典型的宏内核实现,但Linux也吸收了微内核的优点,支持动态加载内核模块,需要的功能可以动态加载,不需要的时候卸载,兼顾了性能和扩展性。
2. 微内核(Micro Kernel)
微内核架构的设计思想是尽可能把内核的功能放到用户态运行,内核只保留最核心的功能:
- 内核只负责最基本的功能:进程调度、进程间通信、基本的内存管理
- 文件系统、驱动程序、网络协议栈等都作为独立的用户态服务运行
- 服务之间通过消息传递进行通信
- 优点:
- 内核小巧,稳定可靠,安全性高
- 扩展性好,添加功能只需要修改对应的用户态服务,不需要修改内核
- 一个服务崩溃不会影响整个系统,稳定性高
- 缺点:
- 性能较低,服务之间通过消息传递通信,需要频繁的用户态和内核态切换,开销大
- 实现复杂
- 代表系统:Minix、QNX、L4、HarmonyOS微内核
微内核适合对稳定性、安全性要求高的场景,比如嵌入式系统、航空航天、自动驾驶等领域。
3. 混合内核(Hybrid Kernel)
混合内核结合了宏内核和微内核的优点,内核核心部分运行在内核态,同时把一些非核心的功能放到内核态或者用户态运行,兼顾性能和扩展性。
- 核心功能在内核态运行,保证性能
- 驱动程序、文件系统等模块可以动态加载,运行在内核态
- 吸收了微内核的消息传递机制,同时保留了宏内核的高性能
- 代表系统:Windows NT内核、macOS XNU内核、Android内核
Windows是典型的混合内核,大部分功能运行在内核态,同时支持动态加载驱动,兼顾性能和扩展性。
操作系统的核心模块
无论是哪种架构的操作系统,通常都包含以下核心模块:
1. 进程管理模块
- 负责进程和线程的创建、调度、销毁
- 实现CPU调度算法,分配CPU时间
- 进程间通信机制
- 进程同步与互斥支持
2. 内存管理模块
- 管理物理内存和虚拟地址空间
- 实现分页、分段、虚拟内存机制
- 负责内存的分配、回收、置换
- 内存保护,隔离不同进程的地址空间
3. 文件系统模块
- 管理磁盘上的文件和目录
- 实现文件系统的逻辑,处理文件的读写、权限管理
- 磁盘空间管理
- 缓存管理,提升文件访问性能
4. 设备驱动模块
- 管理和控制各种硬件设备(磁盘、网卡、显卡、键盘等)
- 提供统一的设备访问接口,屏蔽硬件差异
- 处理硬件中断
5. 网络协议栈
- 实现各种网络协议(TCP/IP、UDP、HTTP等)
- 处理网络数据的发送和接收
- 提供网络编程接口
6. 系统调用接口
- 是用户态程序和内核之间的接口
- 提供标准化的系统调用函数,比如open、read、write、fork等
- 处理用户态到内核态的切换
系统调用与标准库
应用程序通常不会直接调用系统调用,而是通过标准库封装的接口:
- 系统调用:操作系统提供的最底层接口,和平台相关,每个操作系统的系统调用不同
- 标准库:对系统调用进行封装,提供跨平台的统一接口,比如C标准库的fopen、fread等函数,内部封装了不同操作系统的系统调用
- 标准库会根据不同的操作系统调用对应的系统调用,实现跨平台兼容性
比如C语言的fopen()函数:
- 在Linux上内部调用
open()系统调用 - 在Windows上内部调用
CreateFile()系统调用 - 应用程序只需要调用标准库函数,不需要关心底层操作系统的差异
思考问题
- 为什么操作系统要区分内核态和用户态?如果所有程序都运行在内核态会有什么问题?
- 宏内核和微内核各有什么优缺点?分别适合什么场景?
- 为什么应用程序通常不直接调用系统调用,而是通过标准库?
- 上下文切换和系统调用切换有什么区别?哪个开销更大?