操作系统 --基础篇
操作系统的内核(Kernel)和中央处理器(CPU,Central Processing Unit)区别:
- 操作系统的内核(Kernel)属于操作系统层面,而 CPU 属于硬件。
- CPU 主要提供运算,处理各种指令的能力。内核(Kernel)主要负责系统管理比如内存管理,它屏蔽了对硬件的操作。
下图清晰说明了应用程序、内核、CPU 这三者的关系。

用户态和内核态
- 用户态:
- 用户态运行的进程可以直接读取用户程序的数据,拥有较低的权限,当需要执行某些特殊权限的时候,例如读写磁盘,网络通信,就需要向操作系统申请资源权限。
- 内核态:
- 内核态的权限很大,几乎可以完成对系统的所有操作
- 操作系统得到指令后,会从用户态转移到内核态,执行对磁盘的操作后将结果返回进程,后转回用户态
- 虽然拥有更高的权限,但是由于进入内核态时候的上下游调用,开销花费较大,所以要尽量减少进入内核态的次数
用户态: 应用程序,库函数
内核态: 进程管理,网络管理,数据管理,内核管理, 设备管理
为什么要有用户态,只要一个内核态不行吗?
答:
- 内核态:CPU 拥有最高权限,可以访问所有硬件资源(内存、磁盘、网络、IO设备)。操作系统内核在这个模式下运行。
- 用户态:权限受限,只能运行用户程序的指令,不能直接操作硬件,必须通过系统调用进入内核态。
(1) 安全性
- 如果没有用户态,所有程序都在内核态运行 → 意味着任何一个普通应用都能直接操作磁盘、内存、网络、甚至关机/重启。
- 一旦一个程序写错了代码或带有恶意行为,它就能直接破坏系统。
- 用户态限制了应用程序的权限,避免了用户程序影响系统稳定性。
👉 类比:
- 内核态 = 大楼的管理员,有钥匙可以进所有房间、控制电源。
- 用户态 = 普通住户,只能进自己房间,不能随便动配电箱。
(2) 稳定性
- 应用程序可能会崩溃,但操作系统必须保证稳定。
- 如果所有程序都在内核态运行,一个用户程序崩溃可能导致整个系统蓝屏。
- 用户态 + 内核态的隔离让 “程序挂了 ≠ 系统挂了”。
(3) 多用户/多任务管理
- 现代操作系统是多用户、多任务的。
- 内核态负责:
- 进程/线程调度
- 内存保护
- 文件系统管理
- 网络与外设访问
- 用户程序必须通过 系统调用(syscall) 请求内核服务 → 这样操作系统才能管理资源分配,保证不同用户之间互不干扰
3. 如果只有内核态会怎样?
- 缺点:
- 安全隐患巨大(任何应用都有最高权限)。
- 容错性差(一个程序出 bug,全系统崩溃)。
- 难以支持多用户、多任务的隔离。
- 优点:
- 切换开销小(因为没有用户态和内核态的切换)。
- 性能可能更高。
👉 实际上,早期的单任务操作系统(DOS、CP/M) 就基本只有“内核态”,应用程序能直接访问硬件。
但这在多用户、多任务和联网时代是无法接受的。
用户态和内核态是怎么切换的?
系统调用(System Call)
- 最常见方式。
- 用户程序无法直接访问硬件,需要通过内核提供的“服务接口”。
- 用户程序执行一条特殊指令(如
int 0x80、syscall、sysenter),触发软中断/陷入内核,CPU 切换到内核态。- 内核完成操作后,再返回用户态。
👉 举例:1
write(1, "hello", 5); // 用户态调用
底层会触发系统调用号 → 内核进入内核态 → 写入文件描述符 → 返回结果。
异常(Exception / Trap)
- 用户态执行过程中出现错误/特殊情况,由 CPU 硬件触发异常,自动进入内核态的异常处理程序。
- 常见异常:
- 除零错误(Divide by zero)
- 缺页异常(Page Fault,用于虚拟内存机制)
- 非法指令(Illegal Instruction)
- 操作系统在内核态捕获并处理这些异常,比如缺页异常时分配物理页,然后再恢复用户态执行
👉 举例:
访问一个未在内存中的虚拟地址 → 触发缺页异常 → 内核处理 → 返回用户态。
外部中断(Hardware Interrupt)
- 外部设备向 CPU 发中断信号时,CPU 暂停当前用户态执行,切换到内核态中断服务程序。
- 常见外设中断:
- 键盘输入
- 网络数据到达
- 硬盘读写完成
- 内核态中断处理完毕后,再恢复到用户态继续执行。
👉 举例:
你在打字时 → 键盘发出中断 → CPU 切换到内核态,执行键盘中断处理程序 → 把输入放入缓冲区 → 返回用户态。
总结举例:
- 系统调用是程序主动请求,就像是 ”我打电话找警察“
- 异常是程序被动请求,就像是“ 我犯罪了警察打电话找我 ”
- 中断是外部硬件设备打断CPU,就像是“ 有人按门铃,事件是警察来了(外部事件)”
系统调用的过程
- 用户态调用
比如用户程序调用:write(1, "hello\n", 6);
这是一个 库函数调用(glibc 的 write()),但本质上它需要内核帮忙往文件描述符 1(标准输出)写数据。
- C 库封装 → 触发陷入指令
glibc中的write()会准备好系统调用号和参数(比如 fd=1,buf=”hello”,len=6)。- 然后执行一条特殊的 CPU 指令,使 CPU 从 用户态切换到内核态:
- 早期 Linux 用
int 0x80(触发软件中断)。 - 新的 CPU 用更高效的
syscall或sysenter指令。
- 早期 Linux 用
👉 这一步叫 陷入(trap)。
- 内核态入口(系统调用处理器)
- CPU 自动切换到 内核态,并跳到 系统调用入口(内核预先注册好的中断向量表/IDT)。
内核从寄存器里取出系统调用号(比如
SYS_write),找到对应的内核函数。- 内核处理
内核执行对应的系统调用实现函数(比如
sys_write)。- 在这个过程中,内核可以直接操作硬件驱动(比如把字符串写到终端设备)。
这里的操作都是内核态代码,有最高权限。
- 返回用户态
内核处理完后,把结果(比如写了多少字节)放到寄存器里作为返回值。
然后通过
iret或sysret指令返回到用户态,继续执行用户程序。流程总结(简化版)
用户态调用 → C库封装 → 触发陷入指令 → CPU切换到内核态 → 内核查系统调用号 → 执行内核服务 → 返回结果 → 回到用户态
图示类比
1 | [用户态] |