Linux Kernel 1
操作系统基本术语与概念
用户态 vs 内核态(User vs Kernel)
- 内核:是操作系统中运行权限最高的部分,负责管理硬件资源、调度进程、提供系统调用等核心功能。
- 用户:通常指的是运行在低权限模式下的应用程序,比如浏览器、编辑器等。
📌
- 内核态程序可以直接访问硬件,用户态程序不行。
- 为了系统安全和稳定性,普通程序运行在用户空间中,不允许随意干涉内核。
用户模式和内核模式(User Mode vs Kernel Mode)
“用户模式(User Mode)”和“内核模式(Kernel Mode)”是指CPU的执行权限级别:
- 运行在 Kernel Mode(内核模式) 下的代码具有完全的硬件控制权限。
- 运行在 User Mode(用户模式) 下的代码受到限制,不能直接访问硬件或执行特权操作。
例如,只有在内核模式下,才可以操作如下内容:
- 禁用或启用本地 CPU 中断
- 操作内存映射寄存器
- 访问 IO 端口
如果在用户模式下尝试执行这些操作,CPU 会触发异常,并由内核接管处理。
📌 补充说明:
有些 CPU 架构还引入了比内核模式权限更高的模式,比如:
- Hypervisor Mode(虚拟机监控模式):用于虚拟化技术,由 Hypervisor 控制虚拟机运行。
也就是说,权限等级如下:
User Mode < Kernel Mode < Hypervisor Mode \text{User Mode} < \text{Kernel Mode} < \text{Hypervisor Mode} User Mode<Kernel Mode<Hypervisor Mode
用户空间 vs 内核空间(User Space vs Kernel Space)
这两个术语通常用来描述内存地址空间的划分:
- 用户空间(User Space):
- 是用户进程可访问的内存区域。
- 程序代码、堆、栈等数据都在这个区域中。
- 内核空间(Kernel Space):
- 是内核专用的内存区域,用户进程不能直接访问。
- 用于存储内核代码、内核数据结构、设备驱动等。
📌 内存保护机制解释:
为了防止用户程序非法访问敏感数据,现代操作系统采用虚拟内存机制(Virtual Memory),并通过页表和权限位将用户空间和内核空间隔离。
📌
- 用户模式下的代码不能访问内核空间;
- 内核模式下的代码可以访问用户空间。
概念 | 描述 | 权限级别 |
---|---|---|
用户模式 | 应用程序运行环境 | 低 |
内核模式 | 操作系统核心代码运行环境 | 高 |
用户空间 | 用户进程使用的内存区 | 受限访问 |
内核空间 | 内核使用的内存区 | 高权限访问 |
操作系统架构:内核、系统调用与驱动模块
在典型的操作系统架构中(见下图),操作系统内核(kernel) 负责在多应用程序之间安全且公平地访问和共享硬件资源。
一、系统调用(System Calls)
内核通过一组称为 系统调用(System Calls) 的 API 向应用程序提供服务。
- 这些系统调用是用户程序与内核之间的接口边界。
- 当应用程序发出系统调用时,CPU 执行模式将从用户态(User Mode)切换到内核态(Kernel Mode)。
- 这使得系统调用与普通的库函数调用(如
printf()
)在底层实现机制上有本质区别。
📌 重点解释:
- 系统调用是受控入口点:应用程序不能直接运行内核代码,只能通过系统调用间接请求服务。
- 常见系统调用包括:
- read() \text{read()} read():从文件读取
- write() \text{write()} write():向文件写入
- fork() \text{fork()} fork():创建进程
- exec() \text{exec()} exec():加载程序
二、系统调用的稳定性
为了确保应用程序兼容性,操作系统的系统调用接口很少被修改。
- 这意味着老版本的程序即使在新版本内核上运行,也能正常使用原来的系统调用。
- Linux 尤其强调这一点 —— 与此相对的是,内核内部 API(供驱动或模块调用的接口)可以随版本升级而改变。
📌 举例说明:
- 用户程序调用 open("/dev/input0") \text{open("/dev/input0")} open("/dev/input0") 不会因内核升级而失效;
- 但设备驱动中使用的内部函数,如
kmem_cache_alloc()
,其签名或行为可能在内核版本之间有所不同。
三、内核结构:核心代码与设备驱动
操作系统内核代码可以逻辑上分为两个部分:
1. 核心内核代码(Core Kernel Code)
- 提供通用操作系统功能,如:
- 文件系统管理
- 网络协议栈
- 进程调度与管理
- 内存管理
2. 设备驱动代码(Device Drivers)
- 专门用于与具体硬件设备进行交互(如显卡、键盘、网卡等)。
- 通常是通过调用核心内核提供的 API 来实现对硬件的访问
单体内核(Monolithic Kernel)
单体内核 是一种内核架构,其特点是:
- 内核的各个子系统之间没有严格的访问保护机制;
- 各个子系统中的公共函数可以被其他子系统直接调用。
一、什么是“单体内核”?
单体内核是一种将所有功能模块(如文件系统、进程调度、内存管理、设备驱动等)全部编译进同一个内核镜像中的设计方式。
📌 对比:
- 单体内核(Monolithic Kernel):所有核心功能在一个内核映像中运行,彼此直接通信。
- 微内核(Microkernel):尽量把驱动、文件系统等功能移出内核,使用消息机制与核心通信。
二、单体内核的内部结构特点
尽管单体内核允许模块之间自由调用函数,大多数单体内核实现还是在逻辑上划分了子系统,例如:
- 内核核心(core kernel)与设备驱动(device drivers)之间通过明确的 API 接口进行交互。
- 这些 API 虽然不是固定不变的(not necessarily fixed in stone),但仍然相对严格,必须遵守。
📌 关键解释:
- 所谓的“逻辑分离”并不意味着内存隔离,而是指模块设计上彼此有边界和职责划分;
- 这有助于模块化开发与调试,但其本质上仍然是在同一地址空间内运行。
三、访问控制与风险
由于没有物理隔离:
- 内核模块之间的错误可能相互影响(例如一个驱动程序的 bug 可能导致整个系统崩溃);
- 安全性和稳定性依赖于良好的代码规范和模块接口控制。
📌 安全机制补充:
- 虽然没有强制内存隔离,但通过遵守内核 API 规范,可以减少子系统之间的耦合。
- 内核代码的质量和测试覆盖率至关重要。
四、是否固定结构?
不同的操作系统内核对“单体内核”结构的实现存在差异:
- Linux 被认为是典型的单体内核,但它支持模块化加载(Loadable Kernel Modules),某种程度上也具备了部分微内核的特征。
- 因此,“单体”并不意味着没有灵活性,而是一种运行架构上的集中式设计。
特性 | 描述 |
---|---|
架构类型 | 单体(Monolithic) |
子系统间隔离 | 无硬性隔离,函数可直接调用 |
接口控制 | 逻辑上有严格 API,确保模块边界 |
内核大小 | 较大,功能集中 |
风险 | 出错范围大,一个模块崩溃可能导致系统崩溃 |
示例系统 | Linux、BSD 系列 |
微内核(Microkernel)
微内核架构 是一种将大部分操作系统功能从内核态剥离到用户态服务中运行的设计方式。这意味着:
- 内核中仅保留极少的核心代码;
- 其余功能(如文件系统、驱动程序等)作为用户空间中的独立服务Services运行;
- 正因如此,这种架构被称为 “微”内核(micro-kernel)。
一、微内核包含哪些核心功能?
在微内核架构中,运行在内核模式(Kernel Mode) 下的代码非常少,通常只包含以下功能:
- 进程调度器(Scheduler):决定哪个进程获得 CPU 执行。
- 进程间通信机制(IPC, Inter-Process Communication):提供服务之间消息传递的手段。
- 基础内存管理(Basic Memory Management):设置进程之间的内存隔离与访问权限。
📌 关键词解释:
- IPC \text{IPC} IPC 是微内核的核心支撑机制,它允许各个服务在用户空间通过发送消息进行通信;
- 微内核不再直接执行文件读写、网络连接等,而是通过 IPC 请求相应服务进程来完成。
二、优点:模块化与隔离性
微内核架构的一个显著优势是:
各个服务(如文件系统、设备驱动、网络协议等)彼此隔离,互不影响。
- 如果某个服务发生崩溃,比如文件服务(File Server)失败,理论上我们可以只重启这个服务;
- 整个系统不需要重启,提高了系统的可靠性(Reliability)和可恢复性(Recoverability)。
📌 补充说明:
例如,如果文件服务器崩溃,理论上可以执行:
restart(file_server) \text{restart(file\_server)} restart(file_server)
以恢复文件访问服务。
三、实际中的问题:依赖性与恢复复杂性
虽然理论上服务可以独立重启,但在实际系统中仍然存在一些困难:
- 服务之间存在依赖关系:一个服务崩溃可能影响多个依赖它的应用;
- 例如,如果文件服务器崩溃,所有已打开文件的进程都可能因为文件描述符失效而报错;
- 因此,服务的热重启机制需要复杂的状态恢复策略才能做到真正无感恢复。
四、代价:性能损耗
微内核架构通过模块化和服务分离带来了更高的安全性和稳定性,但也牺牲了一部分性能。
- 在单体内核Monolithic Kernel中,两个子系统之间调用函数是直接的;
- 而在微内核架构中,这种调用需要通过:
- 发起 IPC 消息;
- 上下文切换(Context Switch);
- 服务端处理消息;
- 返回响应。
这意味着一个原本 O(1) \text{O(1)} O(1) 级别的函数调用可能变成多个系统调用加调度切换,带来明显性能损耗。
📌 公式对比:
- 单体内核调用代价: T m o n o = O ( 1 ) \text{T}_{mono} = O(1) Tmono=O(1)
- 微内核调用代价: T m i c r o = O ( N i p c + N s w i t c h ) \text{T}_{micro} = O(N_{ipc} + N_{switch}) Tmicro=O(Nipc+Nswitch)
特性 | 微内核(Microkernel) |
---|---|
核心职责 | 调度器、IPC、基础内存管理 |
模块化 | 高,所有服务拆分为独立进程 |
安全性 | 高,服务间隔离,便于错误恢复 |
性能 | 相对较低,频繁的消息传递增加开销 |
示例系统 | Minix、QNX、L4、seL4 |
微内核 vs 单体内核(Micro-kernels vs Monolithic Kernels)
一、微内核支持者的观点
微内核的支持者认为,微内核之所以更优越,是因为它强制实现了模块化(Modular Design)。
- 每个服务(如驱动、文件系统等)都是独立的用户态进程;
- 服务之间通过 $\text{IPC} $(进程间通信)进行交互,具有较高的隔离性和安全性。
二、现代单体内核也支持模块化
尽管传统意义上的单体内核是将所有功能编译在一个内核镜像中,但现代单体内核通过多种方式实现了模块化:
-
编译时选择组件启用与禁用
- 在内核编译阶段,通过配置开启/关闭某些子系统或驱动模块。
-
支持可加载模块(Loadable Kernel Modules, LKM)
- 在系统运行时动态加载或卸载驱动程序或扩展功能。
-
逻辑上的子系统划分
- 将内核组织为若干相互独立的逻辑子系统(如内存管理、调度器、网络栈等)。
-
使用严格但高效的接口机制
- 比如宏定义(macro)、内联函数(inline functions)、函数指针(function pointers)等,用来在不损失性能的情况下实现模块之间的接口抽象。
📌 术语解释:
- LKM \text{LKM} LKM 是 Linux 等操作系统的关键机制之一,支持热插拔驱动;
- 内联函数能减少函数调用开销,兼顾性能与接口清晰。
三、“混合内核”是否存在?
有一类操作系统自称为混合内核(Hybrid Kernel),介于单体和微内核之间,例如:
- Windows NT 系列(包括 Windows 10)
- macOS(基于 XNU 内核)
但问题在于:
- 这些操作系统中的大部分系统服务(文件系统、驱动程序等)仍然运行在内核态(Kernel Mode);
- 并没有真正做到微内核的服务隔离和运行在用户态。
因此,许多操作系统和内核专家认为“混合内核”这个说法没有技术价值,只是市场营销术语。
四、Linus Torvalds 的观点
Linux 内核之父 Linus Torvalds 对“混合内核”一词评价如下:
“关于‘混合内核’这个说法——这只是营销罢了。
微内核宣传做得好,那我们怎么为我们的‘好用的’内核争取一些好宣传呢?
哦,我知道了,我们起个酷炫的名字,让别人以为我们也有那些好处。”
五、总结对比表
特性 | 微内核(Microkernel) | 单体内核(Monolithic Kernel) |
---|---|---|
模块化实现 | 强制服务分离(用户态) | 可选编译模块 + 动态加载模块(内核态) |
安全性 | 服务间隔离强 | 依赖接口规范、无物理隔离 |
性能 | IPC 多次上下文切换,性能损耗较高 | 函数调用直接进行,性能更优 |
恢复能力 | 单一服务崩溃可独立重启 | 服务崩溃通常影响整个系统 |
典型系统 | Minix、seL4、QNX | Linux、FreeBSD、旧版 Unix |
“混合内核”观点 | 多数被认为是营销术语 | 实质上依旧是单体内核结构 |