Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第六十二 操作系统

操作系统(Operating System, OS)是管理计算机硬件与软件资源的系统软件,是用户与计算机硬件之间的桥梁。从手机里的 Android/iOS 到服务器上的 Linux,从嵌入式设备的 RTOS 到超级计算机的专用系统,操作系统无处不在。


一、操作系统概述

1.1 操作系统的四大功能

功能说明示例
进程管理进程/线程的创建、调度、同步与通信Linux 的 fork()、sched
内存管理内存分配、虚拟内存、地址转换分页、交换(swap)
文件管理文件的组织、存储、访问与权限ext4、NTFS、FAT32
设备管理设备驱动、中断处理、I/O 调度显卡驱动、磁盘驱动

1.2 操作系统的历史

1940s  无操作系统:程序员直接操作硬件
1950s  批处理系统:作业成批提交
1960s  分时系统:多个用户共享计算机(CTSS、Multics)
1970s  Unix 诞生:Ken Thompson、Dennis Ritchie 在贝尔实验室
1980s  MS-DOS、macOS 相继出现
1990s  Linux 发布(1991,Linus Torvalds)、Windows NT
2000s  移动操作系统:iOS(2007)、Android(2008)
2020s  云计算与容器:Linux 成为云与 AI 的基础底座

1.3 主流操作系统

操作系统类型特点
Linux开源、类 Unix服务器、云计算、AI 训练的事实标准
Windows闭源桌面与办公市场占有率最高
macOS类 Unix(BSD 内核)苹果生态、开发者友好
Android基于 Linux 内核全球装机量最大的移动系统
iOS类 Unix(XNU 内核)苹果移动生态
RTOS(FreeRTOS 等)实时系统嵌入式、汽车、工业控制

二、进程与线程

2.1 进程

进程(Process)是程序的一次执行实例,拥有独立的地址空间。

进程的五大状态:

创建 → 就绪 → 运行 → 终止
          ↑      ↓
          └─ 阻塞 ←┘
状态说明
新建(New)进程正在被创建
就绪(Ready)等待 CPU,随时可运行
运行(Running)正在占用 CPU 执行
阻塞(Blocked)等待某个事件(如 I/O 完成)
终止(Terminated)执行完毕或被强制结束

2.2 进程与线程的区别

维度进程线程
资源拥有独立地址空间和资源共享所属进程的地址空间
开销创建/切换开销大创建/切换开销小
通信需通过 IPC 机制直接读写共享内存
独立性一个崩溃不影响其他进程一个线程崩溃可能导致进程崩溃
应用隔离要求高的场景高并发计算场景

2.3 Rust 中的进程与线程

#![allow(unused)]
fn main() {
use std::process::Command;

// 创建子进程
fn spawn_process() {
    let output = Command::new("ls")
        .arg("-l")
        .output()
        .expect("failed to execute");
    println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
}

// 创建线程
fn spawn_thread() {
    let handle = std::thread::spawn(|| {
        for i in 0..10 {
            println!("子线程: {i}");
        }
    });
    handle.join().unwrap(); // 等待线程结束
}
}

三、进程调度

3.1 调度算法

算法说明优点缺点
FCFS(先来先服务)按到达顺序执行公平、实现简单短任务等待长任务(护航效应)
SJF(短作业优先)优先执行最短任务平均等待时间最短长任务可能饿死
RR(时间片轮转)每个任务分得固定时间片响应时间均匀时间片大小难权衡
优先级调度高优先级先执行灵活低优先级可能饿死
多级反馈队列多队列 + 动态优先级兼顾响应与吞吐实现复杂

3.2 时间片轮转示例

时间片 = 4
任务队列: A(8) B(4) C(2)

A A A A | B B B B | C C | A A A A
0       4         8     10      14

四、进程间通信(IPC)

4.1 IPC 方式对比

方式说明特点
管道(Pipe)父子进程间单向数据流简单,Linux 命令 | 的底层
命名管道(FIFO)任意进程间通信需创建文件系统入口
消息队列消息的链表,按类型读取有界缓冲、消息粒度
共享内存多个进程映射同一块内存最快,需自行同步
信号量计数器,用于同步与互斥常与共享内存配合
信号(Signal)异步事件通知如 SIGINT(Ctrl+C)
Socket网络通信,也可本地通信跨机器通用
进程 A ──写──> [共享内存] <──读── 进程 B
                 ↑
            信号量(互斥保护)

4.2 Rust 中的线程同步

#![allow(unused)]
fn main() {
use std::sync::{Arc, Mutex};
use std::thread;

fn mutex_example() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        }));
    }

    for h in handles {
        h.join().unwrap();
    }
    println!("count = {}", *counter.lock().unwrap()); // count = 10
}
}

五、同步与互斥

5.1 临界区与竞态条件

多个线程同时读写共享数据时,结果依赖执行顺序,称为竞态条件(Race Condition)。解决方法是让共享数据的访问进入临界区(Critical Section),同一时刻只允许一个线程进入。

5.2 互斥的实现

机制说明
互斥锁(Mutex)一次只有一个线程持有锁
读写锁(RwLock)读读共享、读写互斥
信号量(Semaphore)允许多个(N 个)线程进入
条件变量(Condvar)等待某个条件满足后被唤醒

5.3 死锁

死锁(Deadlock)产生的四个必要条件:

条件说明
互斥资源一次只能被一个进程占用
占有并等待持有资源的同时等待其他资源
不可剥夺资源只能由持有者主动释放
循环等待多个进程形成等待环路
#![allow(unused)]
fn main() {
// 经典死锁场景:两个线程以相反顺序加锁
// 线程 A: lock(x) -> lock(y)
// 线程 B: lock(y) -> lock(x)
// 破局方法:所有线程按相同顺序加锁
}

六、内存管理

6.1 虚拟内存

操作系统为每个进程提供独立的虚拟地址空间,通过页表映射到物理内存。虚拟内存让进程“以为“自己独占全部内存。

虚拟地址 → 页号 + 偏移量
              │
          页表查询
              │
物理地址 ← 帧号 + 偏移量

6.2 页面置换算法

当内存不足时,需要把部分页面换出到磁盘:

算法说明特点
FIFO换出最早进入的页实现简单,可能有 Belady 异常
LRU换出最久未使用的页性能好,实现开销大
时钟(Clock)近似 LRU 的环形扫描折中方案,Linux 采用
OPT换出未来最久不用的页理论最优,无法实现(用于对比)

6.3 Rust 内存安全

Rust 的所有权(Ownership)和借用(Borrow)机制在编译期保证内存安全,从根源上杜绝了 C/C++ 常见的悬垂指针、缓冲区溢出、释放后使用等问题——这正是 Rust 被称为“内存安全语言“的原因。

#![allow(unused)]
fn main() {
fn memory_safety() {
    let s = String::from("hello");
    let t = &s;           // 借用,不可变
    // let u = &mut s;    // 编译错误:不能同时可变借用
    println!("{t}");
    // s 在离开作用域时自动释放,不会泄漏也不会重复释放
}
}

七、文件系统

7.1 文件系统结构

组件说明
目录文件的层级组织(树形结构)
inode保存文件元数据(权限、大小、数据块位置)
超级块描述整个文件系统的元信息
挂载点文件系统接入目录树的入口(如 /、/home)

7.2 常见文件系统

文件系统平台特点
ext4Linux默认文件系统,支持日志、大文件
XFSLinux高性能,适合大文件
BtrfsLinux支持快照、压缩、校验和
NTFSWindows支持权限、加密、日志
APFSmacOS苹果默认,支持快照与加密
FAT32/exFAT跨平台U 盘和存储卡通用

7.3 Rust 文件操作

#![allow(unused)]
fn main() {
use std::fs;
use std::io::Write;

fn file_ops() {
    // 写文件
    fs::write("hello.txt", "Hello, OS!\n").unwrap();

    // 追加写入
    let mut f = fs::OpenOptions::new()
        .append(true)
        .open("hello.txt")
        .unwrap();
    f.write_all(b"Append line.\n").unwrap();

    // 读文件
    let content = fs::read_to_string("hello.txt").unwrap();
    println!("{content}");

    // 遍历目录
    for entry in fs::read_dir(".").unwrap() {
        println!("{:?}", entry.unwrap().file_name());
    }
}
}

八、设备管理与 I/O

8.1 I/O 控制方式

方式说明特点
程序查询(Polling)CPU 不断轮询设备状态简单,浪费 CPU
中断(Interrupt)设备完成后通知 CPU高效,现代系统主流
DMA设备直接读写内存,完成后中断传输大数据时不占用 CPU

8.2 中断处理流程

设备发出中断信号
    ↓
CPU 保存现场(寄存器、PC)
    ↓
跳转到中断处理程序(ISR)
    ↓
处理设备请求
    ↓
恢复现场,继续执行原程序

九、Linux 基础

9.1 常用命令

命令用途
ls / cd / pwd查看目录、切换目录、显示当前路径
ps / top查看进程 / 动态查看进程与资源
kill发送信号终止进程
chmod / chown修改权限 / 修改所有者
mount / df挂载文件系统 / 查看磁盘使用
grep / find文本搜索 / 文件查找
systemctl管理 systemd 服务

9.2 文件权限

-rwxr-xr--  1 user group  4096 Aug 16 10:00 main.rs
|  |  |  |                     |
|  |  |  └─ 其他用户: r--
|  |  └─ 组用户: r-x
|  └─ 所有者: rwx
└─ 文件类型: - 普通文件 / d 目录 / l 符号链接

9.3 系统调用

用户程序不能直接操作硬件,必须通过**系统调用(System Call)**请求内核服务。

用户态(User Mode)              内核态(Kernel Mode)
┌─────────────────┐   syscall   ┌─────────────────┐
│  应用程序        │ ──────────> │  内核服务        │
│  read() write() │ <────────── │  文件/网络/内存  │
└─────────────────┘  返回结果    └─────────────────┘
#![allow(unused)]
fn main() {
use std::fs::File;
use std::io::Read;

// std::fs::File::open 底层调用 open(2) 系统调用
fn syscall_example() {
    let mut f = File::open("/etc/hostname").unwrap();
    let mut buf = String::new();
    f.read_to_string(&mut buf).unwrap();
    println!("hostname: {buf}");
}
}

十、Rust 操作系统生态

Crate用途
std::thread线程创建与管理
std::syncMutex、RwLock、Condvar、Arc
std::process子进程管理
std::fs文件系统操作
nixUnix 系统调用封装(安全)
libcC 标准库 FFI 绑定
tokio异步运行时(基于 epoll/io_uring)
crossbeam并发数据结构与工具

用 Rust 写操作系统

Rust 的**无标准库(#![no_std])**模式和所有权模型非常适合编写操作系统内核。著名的教学项目包括:

项目说明
Writing an OS in Rust最经典的 Rust 操作系统教程(Phil Oppermann)
Redox用 Rust 编写的完整类 Unix 操作系统
Tock面向嵌入式设备的 Rust 安全操作系统
#![allow(unused)]
fn main() {
// 最小的 Rust 内核入口(示意)
#![no_std]
#![no_main]

use core::panic::PanicInfo;

#[no_mangle]
pub extern "C" fn _start() -> ! {
    loop {}
}

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}
}

十一、总结与练习

本章小结

知识点要点
操作系统功能进程、内存、文件、设备四大管理
进程与线程独立地址空间 vs 共享地址空间
进程调度FCFS、SJF、RR、优先级、多级反馈队列
进程间通信管道、消息队列、共享内存、信号量、信号、Socket
同步与互斥Mutex、RwLock、信号量、条件变量、死锁四条件
内存管理虚拟内存、页表、页面置换(LRU、Clock)
文件系统ext4、NTFS、inode、权限、挂载
I/O 方式轮询、中断、DMA
系统调用用户态/内核态切换
Rust 生态std::thread、std::sync、nix、tokio、no_std 内核

练习建议

  1. 多线程累加:用 Rust 的 Arc<Mutex<u64>> 实现 10 个线程并发累加,观察结果并解释为什么需要互斥锁。
  2. 生产者-消费者:用 std::sync::mpsc 通道实现生产者-消费者模型,验证有界缓冲的阻塞行为。
  3. 死锁复现:设计两个线程以相反顺序加两把锁的程序,观察死锁现象,再用“统一加锁顺序“修复。
  4. 文件系统探索:用 Rust 遍历一个目录树,统计各类文件的数量和总大小,模拟 du 命令。
  5. 调度模拟:实现 FCFS 和 RR 两种调度算法的模拟器,比较不同任务序列下的平均等待时间。
  6. 系统调用观察:用 strace 跟踪 ls 命令的系统调用,找出它访问了哪些文件、调用了哪些 syscall。