系统知识 · 操作系统原理
操作系统是软件世界的底盘:进程与线程、虚拟内存、调度算法、进程间通信(IPC)一次讲清。
知识简介
操作系统 负责管理硬件资源并为应用提供抽象:进程(资源分配单位)与线程(调度执行单位)、虚拟内存(隔离与交换)、调度算法(时间片轮转/优先级)、IPC(管道/消息队列/共享内存)。
核心概念
- 进程 vs 线程:进程有独立地址空间,线程共享进程内存;切换成本线程更低。
- 虚拟内存:每个进程看到连续地址空间,由页表映射到物理内存,缺页时按需换入。
- 调度:RR 时间片轮转公平、SJF 短作业优先、多级反馈队列兼顾响应与吞吐。
- IPC:管道(亲缘进程)、消息队列(无亲缘)、共享内存(最快,需同步)、Socket(跨机器)。
- 死锁:互斥、持有并等待、不可剥夺、循环等待四条件,破坏其一即可预防。
代码示例:Python 多进程与队列
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())代码示例:生产者-消费者(共享内存+锁)
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()学习路径
- 先看书:《深入理解计算机系统》(CSAPP)进程/内存章节。
- 再做实验:Linux 下
fork()、pipe()写小 demo。 - 最后攻内核:xv6(MIT 教学操作系统)逐行读 scheduler 与 page table。
完整可运行工程
运行环境:Python 3.8+,仅标准库(multiprocessing)。把 5 个文件放进同一目录后,python proc_queue.py --selftest 会启动 3 个真实子进程消费 400 项任务并校验累加和;python shm_lock.py --selftest 用 Value + Lock 模拟 1000 件库存并发扣减,断言剩余为 0(无超卖)。两者都会真实创建子进程,不是模拟线程。
proc_queue.py
生产者-消费者:主进程投递任务,N 个子进程并行消费,用哨兵优雅退出。
# 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 保护读-改-写临界区。
# 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 -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)。
# 仅标准库,无需 pip install
README.md
运行步骤、进程与同步原语要点。
# 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__": 保护