01|初识操作系统
操作系统是计算机世界的”隐形管家”。你看不见它的工作,但没有它,一切都将停摆。本章将带你揭开这层面纱,理解操作系统最核心的概念。
📋 开篇自测:你已经知道多少?
- 你能用一句话说清楚操作系统的核心职责是什么吗?
- 内核态和用户态的区别是什么?为什么需要这种区分?
- 你能列举出三种主流操作系统,并说出它们各自的典型应用场景吗?
一、操作系统到底是什么
想象你走进一家五星级酒店。你只需要告诉前台”我要一间海景房”,然后就有人帮你办好入住、打扫房间、调好空调、送上饮品。你不需要知道电力从哪里来、空调怎么运转、水管怎么铺设。操作系统扮演的正是这个”酒店管理系统”的角色——它把复杂的硬件资源管理起来,对上层应用提供简洁统一的服务。
1.1 从硬件到软件的鸿沟
一台裸机摆在你面前:CPU 只认识机器指令,内存只是一大片字节数组,硬盘上是连续的磁道和扇区,网卡收发的是电信号。如果没有操作系统,每个程序员都必须直接和这些硬件打交道——你得自己写代码去控制磁头寻道、自己管理内存地址分配、自己处理 CPU 在多个任务之间的切换。
这显然是不可接受的。操作系统的诞生,就是为了填补硬件和应用之间的鸿沟。
1.2 操作系统的四大核心职责
操作系统本质上是一个资源管理器加服务提供者。它的核心职责可以归纳为四个方面:
+----------------------------------------------------------+
| 应用程序层 |
| (浏览器、编辑器、数据库、游戏、你写的代码...) |
+----------------------------------------------------------+
|
系统调用接口
|
+----------------------------------------------------------+
| 操作系统内核 |
| |
| +--------------+ +------------+ +-----------+ |
| | 进程管理 | | 内存管理 | | 文件系统 | |
| +--------------+ +------------+ +-----------+ |
| +--------------+ +------------+ |
| | 设备管理 | | 网络管理 | |
| +--------------+ +------------+ |
+----------------------------------------------------------+
|
硬件抽象层
|
+----------------------------------------------------------+
| 硬件层 |
| (CPU、内存、硬盘、网卡、显卡、键盘、鼠标...) |
+----------------------------------------------------------+
进程管理:决定哪个程序什么时候可以使用 CPU,如何创建和销毁进程,如何在多个进程之间公平分配计算资源。
内存管理:把有限的物理内存分配给多个进程使用,通过虚拟内存技术让每个进程都以为自己独享了整个内存空间。
文件系统:把硬盘上杂乱无章的磁道和扇区组织成我们熟悉的”文件夹 + 文件”结构,提供读写、权限控制等功能。
设备管理:统一管理各种硬件设备(键盘、鼠标、打印机、网卡等),为上层应用提供标准化的访问接口。
1.3 一个类比:操作系统是大楼的物业
把操作系统比作一栋写字楼的物业管理公司,会非常贴切:
| 物业管理 | 操作系统 |
|---|---|
| 分配办公室给各家公司 | 分配内存给各个进程 |
| 排班电梯、管理会议室 | 调度 CPU、管理共享资源 |
| 管理停车场进出 | 管理磁盘 I/O 读写 |
| 门禁系统防止闲杂人等 | 权限管理防止非法访问 |
| 保安巡逻处理突发事件 | 中断处理响应硬件事件 |
物业公司不直接产出效益,但没有它,整栋楼将陷入混乱。操作系统也是如此——它自己不完成用户的”业务目标”,但它让所有业务程序得以顺畅运行。
🤔 想一想 如果没有操作系统,两个程序同时想往屏幕上输出文字,会发生什么?
二、内核态与用户态:权力的边界
操作系统中最重要的概念之一,就是内核态(Kernel Mode)和用户态(User Mode)的区分。这不是一个可有可无的设计,而是整个系统安全和稳定的基石。
2.1 为什么需要两种运行模式
假设你开了一家公司,你会给每个员工配一把公司保险柜的钥匙吗?显然不会。普通员工只需要操作自己工位上的东西,只有财务主管才能打开保险柜。
CPU 的设计者也是这么想的。现代 CPU 至少提供两个特权级别:
- 内核态(Ring 0):拥有最高权限,可以执行所有 CPU 指令,可以直接访问任何内存地址、操作任何硬件设备
- 用户态(Ring 3):权限受限,不能直接执行特权指令,不能直接访问硬件,只能访问分配给自己的内存区域
特权级别
Ring 0 (内核态) +---------------------------------+
最高权限 | 操作系统内核代码 |
| - 硬件驱动程序 |
| - 中断处理 |
| - 内存管理 |
| - 进程调度 |
+---------------------------------+
Ring 3 (用户态) +---------------------------------+
受限权限 | 用户应用程序 |
| - 浏览器、编辑器 |
| - 数据库服务 |
| - 你编写的程序 |
+---------------------------------+
x86 架构实际上定义了 Ring 0 到 Ring 3 共四个特权级,但大多数操作系统只使用了 Ring 0 和 Ring 3 这两个。
2.2 内核态与用户态的切换
用户程序运行在用户态,当它需要操作系统提供服务时(比如读文件、发送网络数据),就必须通过系统调用切换到内核态。这个切换过程大致如下:
用户程序正在执行
|
| 需要读取文件
|
v
发起系统调用 (如 read())
|
| ------- 用户态/内核态 边界 -------
|
v
CPU 切换到内核态
保存用户程序上下文
|
v
内核执行对应的服务代码
(真正执行磁盘读取操作)
|
v
执行完毕,恢复用户程序上下文
CPU 切换回用户态
|
| ------- 用户态/内核态 边界 -------
|
v
用户程序继续执行
(获得读取结果)
这个切换是有代价的。每次从用户态切到内核态,CPU 需要保存当前程序的寄存器状态、切换页表、跳转到内核代码执行,完成后再反向恢复。这个过程通常需要几微秒的时间。虽然看起来很短,但如果一个程序每秒进行数十万次系统调用,累积起来的开销就相当可观了。
2.3 三种进入内核态的方式
用户态进入内核态,通常有三条路径:
系统调用(System Call):用户程序主动请求内核服务。这是最常见的方式,比如 open()、read()、write()、fork() 等。
中断(Interrupt):外部硬件设备发出信号通知 CPU。比如键盘按下一个键、网卡收到一个数据包、定时器到期。CPU 会暂停当前任务,跳转到内核中对应的中断处理函数。
异常(Exception):程序执行过程中发生了错误或特殊情况。比如除以零、访问了非法的内存地址(段错误)、执行了特权指令。内核会接管处理这些异常。
🤔 想一想 一个 printf(“hello”) 看起来很简单,它背后会触发用户态到内核态的切换吗?为什么?
三、系统调用:应用程序与内核之间的”窗口”
3.1 什么是系统调用
系统调用是操作系统提供给用户程序的一组”官方接口”。你可以把它理解成政府部门的办事大厅——你想办什么事,到对应的窗口去填表、提交材料就行。你不需要知道政府内部是怎么运作的,只需要知道大厅提供哪些服务、怎么使用。
在 Linux 系统中,系统调用编号已超过 460 个(以 x86-64 架构、Linux 6.x 内核为例,其中包含少量保留编号,且随内核版本持续增长)。它们覆盖了操作系统的所有核心功能:
| 类别 | 常用系统调用 | 作用 |
|---|---|---|
| 进程控制 | fork, exec, exit, wait | 创建/替换/终止/等待进程 |
| 文件操作 | open, read, write, close | 打开/读/写/关闭文件 |
| 内存管理 | mmap, brk, munmap | 映射内存、调整堆空间 |
| 设备操作 | ioctl, read, write | 控制设备、读写设备 |
| 网络通信 | socket, bind, listen, accept | 创建套接字、网络连接 |
| 信息维护 | getpid, time, uname | 获取进程 ID、时间、系统信息 |
3.2 系统调用的执行过程
以 Linux x86-64 架构为例,一个系统调用的完整执行过程如下:
用户空间 内核空间
应用程序代码
|
| 1. 将系统调用号放入 rax 寄存器
| 将参数放入 rdi, rsi, rdx 等寄存器
|
| 2. 执行 syscall 指令
|
+-------- CPU 特权级切换 --------> 3. CPU 跳转到系统调用入口
(entry_SYSCALL_64)
|
| 4. 根据 rax 中的调用号
| 查找 sys_call_table
|
| 5. 调用对应的内核函数
| (如 sys_read, sys_write)
|
| 6. 将返回值放入 rax
|
<-------- CPU 特权级切换 --------+ 7. 执行 sysret 指令
|
| 8. 用户程序获得返回值
| 继续执行
v
你平时写代码时调用的 read()、write() 这些函数,其实并不是直接的系统调用。它们是 C 标准库(glibc)提供的包装函数——glibc 帮你处理了寄存器设置、syscall 指令调用、错误码转换等琐碎工作,让你可以像调用普通函数一样使用系统调用。
3.3 系统调用 vs 库函数
初学者容易混淆系统调用和库函数。来看一个对比:
printf("hello, world\n"); <-- 库函数 (glibc)
|
| 内部会调用
v
write(1, "hello, world\n", 13); <-- 系统调用包装
|
| 通过 syscall 指令
v
sys_write() <-- 内核中真正的系统调用实现
printf() 是 glibc 的库函数,它内部做了格式化、缓冲区管理等工作,最终当缓冲区满或者遇到换行符时,才会调用 write() 系统调用。所以一次 printf() 不一定立刻触发一次系统调用——glibc 会帮你做批量处理来减少切换开销。
四、操作系统的内核架构
4.1 宏内核 vs 微内核
操作系统的内核设计,历史上有两大流派:
宏内核(Monolithic Kernel):把进程管理、内存管理、文件系统、设备驱动、网络协议栈等所有功能都放在内核空间里运行。Linux、FreeBSD 采用的就是这种设计。
+------------------------------------------+
| 用户空间 |
| 应用A 应用B 应用C 应用D |
+------------------------------------------+
| 内核空间 (全部在 Ring 0) |
| +--------+ +--------+ +----------+ |
| |进程调度 | |内存管理 | |文件系统 | |
| +--------+ +--------+ +----------+ |
| +--------+ +--------+ |
| |设备驱动 | |网络协议栈| |
| +--------+ +--------+ |
+------------------------------------------+
| 硬件 |
+------------------------------------------+
优势:各模块之间直接函数调用,性能极高。 劣势:任何一个模块出了 bug(比如一个有缺陷的驱动),都可能导致整个内核崩溃。
微内核(Microkernel):内核只保留最基本的功能(进程间通信、基本调度、地址空间管理),其他功能(文件系统、设备驱动、网络协议栈)都作为独立的用户态进程运行。QNX、Minix、seL4 是典型代表。
+------------------------------------------+
| 用户空间 |
| 应用A 应用B 文件系统 设备驱动 |
| 服务器 服务器 |
+------------------------------------------+
| 内核空间 (极小) |
| +--------+ +--------+ +----------+ |
| |IPC通信 | |基本调度 | |地址空间管理| |
| +--------+ +--------+ +----------+ |
+------------------------------------------+
| 硬件 |
+------------------------------------------+
优势:隔离性好,一个服务崩溃不会拖垮整个系统,安全性和可靠性高。 劣势:各服务之间通信需要通过 IPC(进程间通信),性能开销较大。
混合内核:取两者之长。macOS/iOS 的 XNU 内核、Windows NT 内核都属于混合内核——核心功能在内核态运行获取性能,部分服务以可加载模块或用户态进程的形式提供灵活性。
4.2 Linux 的模块化宏内核
Linux 采用宏内核架构,但它引入了**可加载内核模块(LKM, Loadable Kernel Module)**机制来弥补宏内核的灵活性不足。你可以在运行时动态地加载或卸载内核模块(比如设备驱动),而不需要重新编译整个内核。
$ lsmod # 查看当前加载的内核模块
$ insmod mydriver.ko # 加载一个内核模块
$ rmmod mydriver # 卸载一个内核模块
这种设计让 Linux 既保持了宏内核的高性能,又获得了接近微内核的灵活性。
五、主流操作系统对比
5.1 三大操作系统家族
现代操作系统主要分为三大家族:
操作系统
|
+--------------+--------------+
| | |
Unix-Like Windows NT 其他
| | |
+------+------+ | +----+----+
| | | | | |
Linux macOS BSD Win10/11 RTOS 嵌入式OS
| | | |
+--+--+ | +--+--+ FreeRTOS
| | | | | VxWorks
Ubuntu | iOS Free Open
CentOS | BSD BSD
Debian |
Android |
macOS/iOS
5.2 横向对比
| 特性 | Linux | Windows | macOS |
|---|---|---|---|
| 内核类型 | 宏内核(模块化) | 混合内核(NT) | 混合内核(XNU) |
| 源码 | 开源(GPL) | 闭源 | 部分开源(Darwin) |
| 主要应用场景 | 服务器、云、嵌入式 | 桌面、办公、游戏 | 创意工作、开发 |
| 文件系统 | ext4, XFS, Btrfs | NTFS, ReFS | APFS, HFS+ |
| 包管理 | apt, yum, pacman | MSI, MSIX | Homebrew, pkg |
| 多用户支持 | 天然多用户 | 多用户(不如Linux) | 多用户 |
| 实时性 | 可选RT补丁 | 不支持硬实时 | 不支持硬实时 |
5.3 为什么服务器世界选择了 Linux
根据行业统计数据,全球超过 90% 的公有云工作负载运行在 Linux 上。这并非偶然,而是由多个因素共同决定的:
- 开源免费:无需支付许可费,可以自由修改和分发
- 稳定高效:Linux 内核经过数十年的打磨和千万开发者的审查
- 生态丰富:几乎所有服务端软件(Nginx、MySQL、Redis、Kafka…)都原生支持 Linux
- 可定制性强:从手表(Android Wear)到超级计算机(全球 TOP500 超算 100% 运行 Linux),Linux 都能胜任
- 命令行优先:适合自动化运维和远程管理
🤔 想一想 Android 是基于 Linux 内核的,那它算 Linux 操作系统吗?它和我们通常说的 Linux 发行版有什么关键区别?
六、操作系统的启动过程
6.1 从按下电源键到看见桌面
很多人从来没有想过,按下电源键之后到底发生了什么。这个过程其实精密得像一场接力赛:
1. 通电
|
v
2. CPU 从固定地址开始执行
- 早期 8086: 0xFFFF0 (20位地址空间)
- 现代 x86 (80386+): 0xFFFFFFF0 (4GB以下16字节)
|
v
3. BIOS/UEFI 固件启动
- 硬件自检 (POST)
- 检测CPU、内存、硬盘、显卡等
|
v
4. 加载引导程序 (GRUB/systemd-boot)
- 从硬盘的引导扇区 (MBR/GPT) 读取
- 显示操作系统选择菜单
|
v
5. 加载内核镜像到内存
- 解压内核
- 初始化基本硬件
|
v
6. 内核初始化
- 建立页表,启动虚拟内存
- 初始化中断处理
- 初始化各子系统(进程、内存、文件、网络)
|
v
7. 启动第一个用户态进程 (init / systemd)
- PID = 1
- 所有其他进程的祖先
|
v
8. init/systemd 按配置启动系统服务
- 网络服务、SSH、日志、定时任务等
|
v
9. 启动登录管理器或Shell
- 图形界面 (GDM/LightDM) 或命令行
|
v
用户可以开始使用系统
6.2 BIOS vs UEFI
传统的 BIOS(Basic Input/Output System)和现代的 UEFI(Unified Extensible Firmware Interface)都是固件层面的程序,它们的职责是在操作系统启动之前完成硬件初始化。
| 特性 | BIOS | UEFI |
|---|---|---|
| 诞生年代 | 1975 年(概念起源于 CP/M)/ 1981 年(IBM PC BIOS) | 2005 年(UEFI Forum 成立)/ 2006 年(UEFI 2.0 规范发布) |
| 分区表 | MBR(最大 2TB) | GPT(最大 18EB) |
| 运行模式 | 16 位实模式 | 32/64 位保护模式 |
| 启动速度 | 较慢 | 较快 |
| 安全启动 | 不支持 | 支持 Secure Boot |
| 界面 | 文字界面 | 可图形化 |
如今新出厂的计算机几乎全部使用 UEFI。它支持更大的磁盘、更快的启动速度,并且可以通过 Secure Boot 验证引导程序的签名,防止恶意软件篡改启动过程。
七、动手实践:观察你的操作系统
理论讲了很多,现在来动手看看你正在使用的操作系统。
7.1 在 Linux 上探索
# 查看内核版本
uname -r
# 输出示例: 6.5.0-14-generic
# 查看操作系统发行版信息
cat /etc/os-release
# 查看 CPU 信息
lscpu
# 查看内存信息
free -h
# 查看当前运行的进程数
ps aux | wc -l
# 查看系统已运行时间
uptime
# 查看系统调用次数统计(执行 ls 命令)
strace -c ls /tmp 2>&1 | tail -20
7.2 观察系统调用
strace 是一个强大的工具,它能让你”窥视”一个程序到底调用了哪些系统调用:
# 追踪 ls 命令的系统调用
strace ls /tmp 2>&1 | head -30
# 你会看到类似这样的输出:
# execve("/usr/bin/ls", ["ls", "/tmp"], ...) = 0
# brk(NULL) = 0x55a8c7b43000
# openat(AT_FDCWD, "/etc/ld.so.cache", ...) = 3
# read(3, "\177ELF..."..., 832) = 832
# mmap(NULL, 8192, ...) = 0x7f3a2c5b0000
# ...
即使一个简单的 ls 命令,背后也涉及几十个系统调用:execve 启动程序、openat 打开目录、getdents64 读取目录项、write 输出结果。这就是操作系统在幕后默默完成的工作。
🤔 想一想 运行 strace -c echo “hello” 统计一下,一个最简单的 echo 命令需要多少次系统调用?这些调用分别在做什么?
八、本章总结
本章我们建立了对操作系统的整体认知框架:
+---------------------------------------------------+
| 核心概念速览 |
+---------------------------------------------------+
| |
| 操作系统 = 资源管理器 + 服务提供者 |
| |
| 四大职责: 进程管理 | 内存管理 | 文件系统 | 设备管理 |
| |
| 内核态 vs 用户态: 保护机制,通过系统调用穿越边界 |
| |
| 内核架构: 宏内核(Linux) / 微内核(QNX) / 混合(XNU) |
| |
| 启动过程: 固件 -> 引导器 -> 内核 -> init -> 服务 |
| |
+---------------------------------------------------+
操作系统是后续所有章节的基础。理解了它的角色和基本架构,你就能更好地理解进程、线程、内存、文件系统等具体话题。
📝 结尾自测:检验你的收获
- 操作系统的四大核心职责分别是什么?请各用一句话描述。
- 用户态程序进入内核态有哪三种方式?各自的触发场景是什么?
- 宏内核和微内核的核心区别是什么?Linux 采用哪种架构?它如何弥补灵活性不足?
- 系统调用和库函数有什么区别?printf() 和 write() 分别属于哪一种?
- 画出从按下电源键到用户登录的完整启动流程(至少包含 6 个阶段)。
购买课程解锁全部内容
系统底层入门:10 章掌握操作系统核心
¥29.90