27 - 操作系统概述与结构

操作系统是计算机系统中最底层的软件,它既是硬件的资源管理器,也是用户与硬件之间的抽象层。理解操作系统的结构,是深入掌握 Linux 乃至任何计算机系统的基石。本章以 Linux 为核心实例,将操作系统原理的抽象概念具象化。


27.1 什么是操作系统

操作系统 = 资源管理器 + 抽象层

graph TD
 A["用户 / 应用程序"] --> B["操作系统<br/>(资源管理 + 抽象)"]
 B --> C["CPU"]
 B --> D["内存"]
 B --> E["磁盘"]
 B --> F["网卡"]
 B --> G["GPU"]
 B --> H["键盘/鼠标"]
 style B fill:#e1f5fe,stroke:#333,stroke-width:3px
 style A fill:#fff9c4,stroke:#333
 style C fill:#f3e5f5,stroke:#333
 style D fill:#f3e5f5,stroke:#333
 style E fill:#f3e5f5,stroke:#333
 style F fill:#f3e5f5,stroke:#333
 style G fill:#f3e5f5,stroke:#333
 style H fill:#f3e5f5,stroke:#333

操作系统承担两个核心角色:

角色说明在 Linux 中的体现
资源管理器管理 CPU 时间、内存空间、I/O 设备、文件存储,在竞争进程间公平分配CFS 调度器、buddy 内存分配器、VFS 文件系统
抽象层将复杂的硬件操作封装为简洁的接口,使用户程序不直接操作物理硬件系统调用、/dev 设备文件、/proc 虚拟文件系统

核心洞察:操作系统本质上解决的是”共享”与”隔离”的矛盾——既要让多个程序高效地共享硬件资源,又要保证它们彼此不干扰。Linux 通过内核态/用户态分离、虚拟内存、权限系统等多层机制共同实现这一目标。


27.2 操作系统的历史演进

从手工到批处理

阶段年代特征核心问题
手工操作1940s程序员直接操作开关和插线板人机速度严重不匹配
批处理系统1950s将多个作业编组,由常驻监控程序顺序执行内存划分保护、中断处理
多道批处理1960s内存中同时驻留多道作业,CPU 在 I/O 等待时切换资源争抢、作业调度
分时系统1960s-70s多个用户通过终端同时交互,CPU 时间片轮转CTSS、Multics、Unix

Unix 的诞生与 Linux 的继承

1969: Ken Thompson 在 PDP-7 上编写 Unix(汇编)
1972: Dennis Ritchie 发明 C 语言,Unix 用 C 重写
1977: BSD(Berkeley Software Distribution)出现
1983: Richard Stallman 启动 GNU 项目
1987: Andrew Tanenbaum 发布 Minix(教学用)
1991: Linus Torvalds 发布 Linux 0.01

Unix 哲学的核心原则在 Linux 中完整保留:一切皆文件小而专一的工具管道组合。这些设计理念至今仍是 Linux 生态的基石。

现代操作系统的四大趋势

timeline
 title 现代操作系统演进方向
 虚拟化 : KVM / Docker
 : Hypervisor
 移动化 : Android (Linux kernel)
 : iOS (XNU/Mach)
 云计算 : 多租户隔离
 : 弹性伸缩
 异构计算 : GPU/TPU/NPU
 : 大页/SIMD

27.3 内核空间与用户空间

两级权限模型

现代 CPU 通过保护环(Protection Ring)实现权限分级:

graph TD
 subgraph "x86 保护环 (Ring)"
 R0["Ring 0<br/>Linux 内核<br/>最高权限"]
 R1["Ring 1<br/>(未使用)"]
 R2["Ring 2<br/>(未使用)"]
 R3["Ring 3<br/>用户进程<br/>最低权限"]
 end
 R0 -->|"系统调用"| R3
 R3 -->|"系统调用<br/>int 0x80 / syscall"| R0
 style R0 fill:#ff8a80,stroke:#333
 style R3 fill:#b9f6ca,stroke:#333

在 Linux 中,大量信息暴露于 /proc 文件系统,以可读形式展示内核空间与用户空间的界限:

# 内核空间占用的物理内存(不可换出)
cat /proc/meminfo | grep -E "KernelStack|Slab|PageTables"
 
# 当前进程在用户态和内核态的 CPU 耗时
cat /proc/$$/stat | awk '{print "utime:",$14,"stime:",$15}'
# utime = 用户态 tick 数,stime = 内核态 tick 数
 
# 查看系统调用统计
cat /proc/syscalls 2>/dev/null || echo "需要 CONFIG_SYSCALL_STATS"

上下文切换 是用户空间与内核空间之间最核心的性能开销:

# 查看上下文切换次数
grep ctxt /proc/$$/status
# voluntary_ctxt_switches: 主动切换(如等待 I/O)
# nonvoluntary_ctxt_switches: 被动切换(时间片耗尽)
 
# 系统级别的上下文切换统计
vmstat 1 5
# cs 列:每秒上下文切换次数

27.4 内核架构:单体 vs 微内核 vs 混合

三种经典架构对比

graph TD
 subgraph "单体内核 (Monolithic)"
 MK_APP["应用程序"] --> MK_SYSCALL["系统调用"]
 MK_SYSCALL --> MK_KS["内核空间<br/>VFS │ 调度器 │ 内存管理 │ 网络栈 │ 驱动<br/>全部在同一地址空间"]
 MK_KS --> MK_HW["硬件"]
 end
graph TD
 subgraph "微内核 (Microkernel)"
 UK_APP["应用程序"] --> UK_SRV["文件系统服务<br/>(用户态)"]
 UK_APP --> UK_SRV2["网络服务<br/>(用户态)"]
 UK_APP --> UK_SRV3["设备驱动<br/>(用户态)"]
 UK_SRV & UK_SRV2 & UK_SRV3 -.->|"IPC"| UK_MK["微内核<br/>进程通信 │ 基础调度 │ 中断"]
 UK_MK --> UK_HW["硬件"]
 end
特性单体内核 (Linux)微内核 (seL4 / QNX)混合内核 (Windows / macOS)
核心思想所有服务在内核态运行最小化内核,大部分服务在用户态微内核结构,但关键服务在内核态
性能函数调用,快速IPC 通信,相对慢折中
稳定性一个驱动崩溃可能拖垮内核服务崩溃不影响内核折中
可扩展性编译时选择模块服务可以热替换模块化
代表系统Linux, Unix, BSDMinix, QNX, seL4, GNU HurdWindows NT, macOS (XNU)

Linux 内核模块:单体内核的灵活性

Linux 虽然是单体内核,但通过 可加载内核模块(LKM) 实现了灵活扩展:

# 查看已加载的内核模块
lsmod | head -20
# 输出:模块名、大小、被哪些模块依赖
 
# 查看模块信息
modinfo ext4
# 输出:路径、描述、许可协议、依赖、参数
 
# 动态加载模块(例如 loop 设备)
sudo modprobe loop
 
# 查看模块参数
cat /sys/module/ext4/parameters/ 2>/dev/null && ls /sys/module/ext4/parameters/
 
# 动态卸载模块
sudo modprobe -r loop
// 一个最小的内核模块示例 (hello.c)
#include <linux/module.h>
#include <linux/kernel.h>
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Student");
MODULE_DESCRIPTION("A minimal kernel module");
 
static int __init hello_init(void) {
 printk(KERN_INFO "Hello from kernel space!\n");
 return 0;
}
 
static void __exit hello_exit(void) {
 printk(KERN_INFO "Goodbye from kernel space!\n");
}
 
module_init(hello_init);
module_exit(hello_exit);

Linux 的单体内核架构选择体现了 实用主义:虽然微内核在理论上更优雅,但单体内核通过函数调用避免 IPC 开销,获得了更高的性能。内核模块机制又在保持性能的同时提供了模块化能力。详见 38-Linux内核基础与模块


27.5 系统调用:用户空间冲向内核的桥梁

系统调用原理

当用户程序需要执行特权操作(读写文件、创建进程、网络通信等),必须通过系统调用请求内核代为执行:

sequenceDiagram
 participant U as 用户程序
 participant L as glibc 封装
 participant K as Linux 内核

 U->>L: write(fd, buf, count)
 L->>L: 设置寄存器<br/>(rax=1 系统调用号,<br/> rdi=fd, rsi=buf, rdx=count)
 L->>K: syscall 指令 (x86-64)
 Note over K: 进入内核态<br/>查 sys_call_table
 K->>K: sys_write()
 K->>K: 执行实际写操作
 K-->>L: 返回结果
 L-->>U: 返回写入字节数

常见系统调用

系统调用函数类别说明
readread(fd, buf, count)文件 I/O从文件描述符读取数据
writewrite(fd, buf, count)文件 I/O向文件描述符写入数据
openopen(pathname, flags, mode)文件 I/O打开或创建文件
closeclose(fd)文件 I/O关闭文件描述符
forkfork()进程管理创建子进程
execveexecve(path, argv, envp)进程管理执行新程序
exitexit(status)进程管理终止进程
waitwaitpid(pid, status, options)进程管理等待子进程状态变化
mmapmmap(addr, length, prot, flags, fd, offset)内存管理映射文件或匿名内存
brkbrk(addr)内存管理修改数据段大小
cloneclone(fn, stack, flags, arg)进程/线程创建新进程或线程

在 Linux 中追踪系统调用

# strace: 追踪进程的系统调用
strace -c ls /tmp # 统计汇总
strace -e trace=open,close ls # 只看文件打开/关闭
strace -p $(pgrep nginx) -f # 附加到正在运行的进程
 
# 示例输出片段(strace ls)
# openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
# mmap(NULL, 8192, PROT_READ|PROT_WRITE, ...) = 0x7f1234567000
# write(1, "file1 file2\n", 13) = 13
 
# perf: 统计系统调用频率
sudo perf stat -e 'syscalls:sys_enter_*' -- ls 2>&1
 
# /proc 接口查看系统调用表
cat /proc/kallsyms | grep sys_call_table

27.6 中断与异常:当按下键盘时发生了什么

中断处理流程

sequenceDiagram
 participant H as 键盘硬件
 participant PIC as 中断控制器<br/>(APIC)
 participant CPU as CPU
 participant K as Linux 内核

 H->>PIC: 键盘按下(电信号)
 PIC->>CPU: IRQ 1(中断请求)
 CPU->>CPU: 保存当前上下文<br/>(寄存器压栈)
 CPU->>K: 查 IDT(中断描述符表)
 K->>K: do_IRQ → handle_irq → 键盘中断处理
 K->>K: 从 I/O 端口读取键码
 K->>K: 将扫描码转为键码
 K->>K: 放入 tty 输入缓冲区
 K->>K: 唤醒等待输入的进程
 K-->>CPU: iret 返回
 Note over CPU: 恢复上下文<br/>继续执行

中断与异常的区别

类型来源同步/异步示例
中断(Interrupt)外部硬件异步键盘输入、网卡收包、磁盘 I/O 完成
异常(Exception)CPU 执行指令同步除零错误、缺页(page fault)、非法指令
软中断(Software Interrupt)int/syscall 指令同步系统调用、调试断点

Linux 中的中断观测

# 查看所有中断统计
cat /proc/interrupts
# 输出列:IRQ号 CPU0计数 CPU1计数 ... 中断控制器 设备名
# IRQ 1: 键盘中断
# IRQ 12: 鼠标中断
 
# 查看中断的实时变化
watch -n 1 cat /proc/interrupts
 
# 查看软中断统计
cat /proc/softirqs
# 输出:HI(高优先级tasklet) TIMER NET_TX NET_RX BLOCK ...

27.7 Linux 内核架构概览

五大子系统

graph TD
 SYSCALL["系统调用接口"]
 SYSCALL --> PM["进程调度器<br/>(Process Scheduler)"]
 SYSCALL --> MM["内存管理<br/>(Memory Manager)"]
 SYSCALL --> VFS["虚拟文件系统<br/>(Virtual FileSystem)"]
 SYSCALL --> NET["网络栈<br/>(Network Stack)"]
 SYSCALL --> DD["设备驱动<br/>(Device Drivers)"]
 PM --> HW_CPU["CPU"]
 MM --> HW_MEM["内存"]
 VFS --> HW_DISK["磁盘"]
 NET --> HW_NIC["网卡"]
 DD --> HW_DEV["各种设备"]
 style SYSCALL fill:#e1f5fe,stroke:#333,stroke-width:2px
 style PM fill:#fff9c4,stroke:#333
 style MM fill:#fff9c4,stroke:#333
 style VFS fill:#fff9c4,stroke:#333
 style NET fill:#fff9c4,stroke:#333
 style DD fill:#fff9c4,stroke:#333

各子系统详解

进程调度器(Process Scheduler)

负责 CPU 时间分配。Linux 使用 CFS(完全公平调度器)

# 查看当前调度器的信息
cat /proc/sched_debug | head -80
 
# 查看特定进程的调度详情
cat /proc/$$/sched
# se.sum_exec_runtime: 总运行时间
# se.vruntime: 虚拟运行时间(CFS 的核心排序键)
 
# 查看进程的调度策略
chrt -p $$
# pid 12345's current scheduling policy: SCHED_OTHER
# pid 12345's current scheduling priority: 0

详见 33-处理机调度

内存管理(Memory Manager)

负责分配物理内存、管理虚拟地址空间、处理缺页:

# 查看内存区域分布
cat /proc/meminfo
# MemTotal, MemFree, Cached, SwapTotal, ...
 
# 查看进程地址空间布局
cat /proc/$$/maps | head -20
# 输出示例:
# 555555554000-555555555000 r--p ... /usr/bin/bash (代码段)
# 555555555000-5555555a0000 r-xp ... /usr/bin/bash (代码段可执行)
# 5555555a0000-5555555c4000 r--p ... /usr/bin/bash (只读数据)
# 7ffff7c00000-7ffff7c28000 r--p ... /usr/lib/libc.so
 
# 查看大页信息
grep Huge /proc/meminfo

详见 34-存储管理35-虚拟存储与页面置换

虚拟文件系统(VFS)

为不同类型的文件系统提供统一接口:

# 查看当前支持的文件系统
cat /proc/filesystems
# nodev sysfs
# nodev proc
# ext4
# btrfs
# xfs
 
# 查看挂载点与文件系统类型
findmnt -t ext4,xfs,btrfs
 
# VFS 的核心数据结构可通过代码查看
# include/linux/fs.h: struct super_block, struct inode, struct file

详见 36-文件系统设计42-文件系统深入

网络栈(Network Stack)

实现 TCP/IP 协议族:

# 查看网络协议统计
cat /proc/net/snmp # SNMP MIB
cat /proc/net/netstat # 网络统计扩展
cat /proc/net/sockstat # 各类套接字使用量
 
# 查看路由表
cat /proc/net/route
ip route show
 
# 查看网络设备
cat /proc/net/dev

设备驱动(Device Drivers)

内核中最大的代码体,约占总代码量的 60%:

# 查看已加载驱动的设备
cat /proc/devices
# Character devices 和 Block devices 两大类
 
# 查看 PCI 设备
lspci -k | head -20
# -k 显示每块设备使用的驱动
 
# 通过 sysfs 查看设备树
ls /sys/devices/

详见 37-I-O设备管理43-硬件驱动与设备管理


27.8 POSIX 标准

什么是 POSIX

POSIX(Portable Operating System Interface)由 IEEE 制定(IEEE Std 1003),旨在定义操作系统应有的标准接口,确保程序可以在不同 Unix-like 系统间移植。

标准内容
POSIX.1 (1003.1)基础系统接口:进程、信号、文件 I/O、管道
POSIX.1b (1003.1b)实时扩展:信号量、消息队列、共享内存、定时器
POSIX.1c (1003.1c)线程扩展:pthread
POSIX.2 (1003.2)Shell 和工具

Linux 的 POSIX 兼容性

# 检查 glibc 是否提供 POSIX 兼容的接口
man 7 posixoptions
 
# 获取系统支持的 POSIX 选项
getconf -a | grep POSIX
# POSIX_V7_ILP32_OFF32 支持
# POSIX_V7_ILP32_OFFBIG 支持
# ...
 
# 查看系统是否遵循 LSB(Linux Standard Base)
lsb_release -a 2>/dev/null || echo "Arch Linux 默认不安装 lsb-release"

POSIX 是 Linux 生态便携性的基石。C 程序员依赖的 unistd.hfcntl.hpthread.h 等头文件均源自 POSIX 规范。跨 Unix 平台开发的 C 程序,只要遵循 POSIX,理论上在 Linux、macOS、FreeBSD 上均可编译运行。


27.9 内核版本与源码

理解内核版本号

6.12.8-arch1-1
│ │ │ │
│ │ │ └── 发行版补丁版本
│ │ └────── 稳定版修正号
│ └───────────── 次版本号(奇数=开发版,偶数=稳定版,Linux 2.6 之后取消此约定)
└──────────────── 主版本号
# 查看完整版本信息
uname -r
cat /proc/version
 
# 内核编译配置
zcat /proc/config.gz 2>/dev/null | head -50 || \
zcat /proc/config.gz 2>/dev/null | head -50 || \
echo "CONFIG 文件在 /boot/config-$(uname -r)"
 
# 查看编译时使用的编译器
cat /proc/version
# Linux version 6.9.7-arch1-1 (linux@archlinux) (gcc (GCC) 14.1.1) #1 SMP PREEMPT_DYNAMIC ...

浏览内核源码(在线与本地)

# Arch Linux 的方法:用 asp 获取 PKGBUILD
asp export linux
 
# 或者直接下载 tarball
# 内核源码位于 kernel.org
# 在线浏览:https://elixir.bootlin.com/linux/latest/source
 
# 查看模块源码结构
# fs/ — 文件系统
# mm/ — 内存管理
# kernel/ — 核心调度
# net/ — 网络栈
# drivers/ — 设备驱动(最大子目录)
# arch/ — 架构相关代码
# include/ — 头文件

详见 Linux 内核基础与模块


27.10 核心概念速查

graph LR
 A["硬件<br/>CPU/内存/磁盘/网卡"] --> B["内核<br/>(特权模式)"]
 B --> C["系统调用接口"]
 C --> D["glibc 库封装"]
 D --> E["用户程序"]
 B -.-> F["内核模块<br/>(动态加载)"]
 C -.-> G["/proc<br/>/sys<br/>/dev<br/>(观测接口)"]
 style B fill:#e1f5fe,stroke:#333,stroke-width:2px
 style E fill:#b9f6ca,stroke:#333
 style F fill:#fff9c4,stroke:#333
 style G fill:#f3e5f5,stroke:#333
概念一句话解释观测命令
内核空间CPU Ring 0,可直接访问硬件cat /proc/meminfo | grep Kernel
用户空间CPU Ring 3,受限权限cat /proc/$$/stat
系统调用用户程序请求内核服务的唯一入口strace ls
中断硬件通知 CPU 的异步信号cat /proc/interrupts
上下文切换在两个进程或线程间切换 CPUvmstat 1
内核模块可动态加载的内核代码lsmod
POSIX操作系统可移植接口标准getconf -a
VFS文件系统抽象层cat /proc/filesystems

相关链接