系统知识 · 操作系统原理

操作系统是软件世界的底盘:进程与线程、虚拟内存、调度算法、进程间通信(IPC)一次讲清。

系统知识 技术栈:C / Python / 概念图解 来源:GitHub 参考

知识简介

操作系统 负责管理硬件资源并为应用提供抽象:进程(资源分配单位)与线程(调度执行单位)、虚拟内存(隔离与交换)、调度算法(时间片轮转/优先级)、IPC(管道/消息队列/共享内存)。

核心概念

  • 进程 vs 线程:进程有独立地址空间,线程共享进程内存;切换成本线程更低。
  • 虚拟内存:每个进程看到连续地址空间,由页表映射到物理内存,缺页时按需换入。
  • 调度:RR 时间片轮转公平、SJF 短作业优先、多级反馈队列兼顾响应与吞吐。
  • IPC:管道(亲缘进程)、消息队列(无亲缘)、共享内存(最快,需同步)、Socket(跨机器)。
  • 死锁:互斥、持有并等待、不可剥夺、循环等待四条件,破坏其一即可预防。

代码示例:Python 多进程与队列

py
import multiprocessing as mp

def worker(q, name):
    q.put(f"{name} done: {sum(range(1000))}")

if __name__ == "__main__":
    q = mp.Queue()
    ps = [mp.Process(target=worker, args=(q, f"p{i}")) for i in range(4)]
    for p in ps: p.start()
    for p in ps: p.join()
    while not q.empty():
        print(q.get())

代码示例:生产者-消费者(共享内存+锁)

py
import threading, time

buf, lock, cap = [], threading.Lock(), 3

def producer():
    for i in range(6):
        with lock:
            while len(buf) >= cap:
                time.sleep(0.01)
            buf.append(i)
            print("produce", i)
        time.sleep(0.05)

def consumer():
    for _ in range(6):
        with lock:
            while not buf:
                time.sleep(0.01)
            print("consume", buf.pop(0))
        time.sleep(0.08)

t1, t2 = threading.Thread(target=producer), threading.Thread(target=consumer)
t1.start(); t2.start(); t1.join(); t2.join()

学习路径

  1. 先看书:《深入理解计算机系统》(CSAPP)进程/内存章节。
  2. 再做实验:Linux 下 fork()、pipe() 写小 demo。
  3. 最后攻内核:xv6(MIT 教学操作系统)逐行读 scheduler 与 page table。

完整可运行工程

可直接运行零第三方依赖Python 3.8+

运行环境:Python 3.8+,仅标准库(multiprocessing)。把 5 个文件放进同一目录后,python proc_queue.py --selftest 会启动 3 个真实子进程消费 400 项任务并校验累加和;python shm_lock.py --selftest 用 Value + Lock 模拟 1000 件库存并发扣减,断言剩余为 0(无超卖)。两者都会真实创建子进程,不是模拟线程。

knowledge-os-lab/ ├── proc_queue.py 多进程队列:生产者-消费者 + 哨兵退出 + 校验和(含 --selftest) ├── shm_lock.py 共享内存 + 锁:并发扣减库存,断言无超卖(含 --selftest) ├── test_os.py 6 个 unittest 用例(逻辑层 + 真实进程带超时保护) ├── requirements.txt 依赖清单(仅标准库) └── README.md 运行步骤 / 进程与同步原语要点

proc_queue.py

生产者-消费者:主进程投递任务,N 个子进程并行消费,用哨兵优雅退出。

python
# proc_queue.py:多进程队列的生产者-消费者(任务不丢不重)
# 用法:python proc_queue.py --selftest / python proc_queue.py --n 400 --workers 3
import argparse
import multiprocessing as mp
import time


def consumer(q, out, name):
    got = 0
    while True:
        item = q.get()
        if item is None:                 # 哨兵:收到即退出
            break
        got += item
    out.put((name, got))


def run(n=200, workers=2):
    q, out = mp.Queue(), mp.Queue()
    procs = [mp.Process(target=consumer, args=(q, out, "c%d" % i)) for i in range(workers)]
    for p in procs:
        p.start()
    for i in range(n):
        q.put(i)
    for _ in range(workers):             # 每个消费者一个哨兵
        q.put(None)
    for p in procs:
        p.join(15)
    if any(p.is_alive() for p in procs):
        for p in procs:
            if p.is_alive():
                p.terminate()
        raise RuntimeError("子进程未在 15s 内退出(当前环境可能限制 multiprocessing)")
    return dict(out.get(timeout=10) for _ in procs)


def selftest():
    t0 = time.time()
    n, workers = 400, 3
    parts = run(n, workers)
    assert sum(parts.values()) == n * (n - 1) // 2, parts     # 每项恰好被消费一次
    print("selftest OK:%d 项任务分发给 %d 个进程,累加和 %d,耗时 %.2fs"
          % (n, workers, sum(parts.values()), time.time() - t0))


def main(argv=None):
    ap = argparse.ArgumentParser(description="多进程队列生产者-消费者")
    ap.add_argument("--n", type=int, default=400)
    ap.add_argument("--workers", type=int, default=3)
    ap.add_argument("--selftest", action="store_true")
    a = ap.parse_args(argv)
    if a.selftest:
        selftest()
        return 0
    t0 = time.time()
    parts = run(a.n, a.workers)
    print("任务数 %d,消费者 %d,分布:" % (a.n, a.workers), parts)
    print("累加和 %d(期望 %d),耗时 %.2fs" % (sum(parts.values()), a.n * (a.n - 1) // 2, time.time() - t0))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

shm_lock.py

共享内存并发扣减:Value 存库存、Lock 保护读-改-写临界区。

python
# shm_lock.py:共享内存 + 锁(无超卖,可并发扣减)
# 用法:python shm_lock.py --selftest / python shm_lock.py --stock 1000 --workers 4
import argparse
import multiprocessing as mp


def worker(stock, lock, rounds, out, name):
    got = 0
    for _ in range(rounds):
        with lock:                       # 临界区:读-改-写必须原子
            if stock.value > 0:
                stock.value -= 1
                got += 1
    out.put((name, got))


def run(stock_n=1000, workers=4):
    stock = mp.Value("i", stock_n)
    lock = mp.Lock()
    out = mp.Queue()
    rounds = stock_n // workers + 1
    procs = [mp.Process(target=worker, args=(stock, lock, rounds, out, "w%d" % i)) for i in range(workers)]
    for p in procs:
        p.start()
    for p in procs:
        p.join(15)
    if any(p.is_alive() for p in procs):
        for p in procs:
            if p.is_alive():
                p.terminate()
        raise RuntimeError("子进程未在 15s 内退出(当前环境可能限制 multiprocessing)")
    parts = dict(out.get(timeout=10) for _ in procs)
    return stock.value, parts


def selftest():
    stock_n, workers = 1000, 4
    left, parts = run(stock_n, workers)
    assert left == 0, "库存应为 0,实际 %d(说明存在超卖或未消费完)" % left
    assert sum(parts.values()) == stock_n, parts
    print("selftest OK:%d 库存被 %d 个进程扣完且无超卖(剩余 %d,各进程 %s)"
          % (stock_n, workers, left, parts))


def main(argv=None):
    ap = argparse.ArgumentParser(description="共享内存 + 锁 的并发扣减")
    ap.add_argument("--stock", type=int, default=1000)
    ap.add_argument("--workers", type=int, default=4)
    ap.add_argument("--selftest", action="store_true")
    a = ap.parse_args(argv)
    if a.selftest:
        selftest()
        return 0
    left, parts = run(a.stock, a.workers)
    print("初始库存 %d,剩余 %d,各进程扣减量 %s" % (a.stock, left, parts))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

test_os.py

unittest 回归:哨兵分配不丢不重、锁保护扣减不超卖、真实子进程(带超时保护)。

python
# 回归测试:python -m unittest -v test_os
# 说明:多进程 + Windows(spawn) + unittest 三者组合下创建子进程易出现长时间等待,
#       因此本文件用「同步逻辑测试」校验语义(把 consumer / worker 直接当普通函数调用),
#       真实子进程验证交给 `python proc_queue.py --selftest` 与 `python shm_lock.py --selftest`。
import multiprocessing as mp
import unittest

import proc_queue
import shm_lock


def _drain(q):
    return q.get(timeout=5)


class TestProcQueueLogic(unittest.TestCase):
    def test_one_consumer_sums_all_tasks(self):
        n = 15
        q, out = mp.Queue(), mp.Queue()
        for i in range(n):
            q.put(i)
        q.put(None)
        proc_queue.consumer(q, out, "c0")
        self.assertEqual(_drain(out)[1], sum(range(n)))
        self.assertTrue(q.empty())

    def test_two_consumers_share_queue_without_loss(self):
        n = 20
        q, out = mp.Queue(), mp.Queue()
        for i in range(n):
            q.put(i)
        q.put(None)
        q.put(None)
        proc_queue.consumer(q, out, "c0")
        proc_queue.consumer(q, out, "c1")
        parts = dict(_drain(out) for _ in range(2))
        self.assertEqual(sorted(parts), ["c0", "c1"])
        self.assertEqual(sum(parts.values()), sum(range(n)))
        self.assertTrue(q.empty())


class TestShmLockLogic(unittest.TestCase):
    def test_deducts_until_rounds_used_up(self):
        stock, lock, out = mp.Value("i", 10), mp.Lock(), mp.Queue()
        shm_lock.worker(stock, lock, 7, out, "w0")     # 10 件库存,只发起 7 次扣减
        self.assertEqual(stock.value, 3)
        self.assertEqual(_drain(out)[1], 7)

    def test_never_goes_negative(self):
        stock, lock, out = mp.Value("i", 3), mp.Lock(), mp.Queue()
        shm_lock.worker(stock, lock, 10, out, "w0")
        self.assertEqual(stock.value, 0)
        self.assertEqual(_drain(out)[1], 3)


class TestRealChildren(unittest.TestCase):
    def test_proc_queue_real_children(self):
        try:
            parts = proc_queue.run(30, 2)
        except RuntimeError as exc:
            self.skipTest("当前环境无法稳定创建子进程:%s" % exc)
        self.assertEqual(sum(parts.values()), sum(range(30)))

    def test_shm_lock_real_children(self):
        try:
            left, parts = shm_lock.run(60, 2)
        except RuntimeError as exc:
            self.skipTest("当前环境无法稳定创建子进程:%s" % exc)
        self.assertEqual(left, 0)
        self.assertEqual(sum(parts.values()), 60)


if __name__ == "__main__":
    unittest.main(verbosity=2)

requirements.txt

依赖清单:仅 Python 标准库(multiprocessing)。

text
# 仅标准库,无需 pip install

README.md

运行步骤、进程与同步原语要点。

markdown
# knowledge-os-lab

用两个最小工程说明多进程的两类协作方式:消息队列与共享内存。

## 快速开始

```bash
python proc_queue.py --selftest
python shm_lock.py --selftest
python -m unittest -v test_os       # 逻辑层回归,不拉起子进程,秒级完成
```

## 要点

- 队列传递的是序列化副本,适合任务分发;共享内存传递的是同一块地址,适合高频计数,但必须配锁。
- 消费者退出要靠哨兵(`None`),哨兵数量与消费者数量相等,否则会有关进程永久阻塞。
- `Value` / `Array` 自带的值锁不覆盖多步操作,读-改-写必须显式 `with lock:`。
- Windows 使用 spawn 启动子进程,`if __name__ == "__main__":` 保护必不可少。

运行标注:①可直接运行——两个 --selftest 与 python -m unittest -v test_os 均零配置通过;②需按需修改——任务数 / 库存数 / 进程数均通过 --n、--stock、--workers 传参;③平台说明——Windows 走 spawn,首次创建子进程会重新导入模块,故工程内所有入口都套了 if __name__ == "__main__":。

依赖清单:零第三方依赖 —— 仅 Python 标准库(multiprocessing / threading)

可直接运行:可直接运行 python proc_queue.py --selftest 与 python shm_lock.py --selftest 零配置自测;python -m unittest -v test_os 逻辑层回归(不拉起子进程)

需先改的配置项:无配置项需要修改。Windows 用 spawn 启动子进程,务必保留 if __name__ == "__main__": 保护

复制结果为 # ===== 文件名 ===== 分隔的多文件文本,按文件名分别保存即可;也可逐块用右上角“复制代码”单独取用。

来源参考