Ran Wei/计算机科学系列/10
English
计算机科学基础 — Ran Wei

模块 10: 操作系统

理解进程、线程、调度、虚拟内存与文件,有意复现丢失更新并用锁修复。

约 5 小时4 个时段2 个实验8 道练习6 道自测题

完成后你能够

  • 区分进程与线程。
  • 比较调度目标。
  • 解释地址转换。
  • 使用有作用域文件操作。
  • 识别并保护临界区。

开始之前

建议先修模块: 03, 09.

了解程序状态与内存。实验运行短子进程、创建临时文件并等待有限线程。

目录

学习计划

5 小时

四个 75 分钟时段,包含练习。拓展任务或不熟悉的先修知识可能需要更多时间。进度本地保存,两种语言共享。

时段 275 分钟
构建与探究
时段 375 分钟
应用与拓展
时段 475 分钟
推理与复习
1

进程与线程

进程是带资源与地址空间上下文的运行程序。进程内线程共享内存,但有独立执行状态与栈。不同进程通常隔离普通内存,通信需管道、套接字或共享设施。进程可就绪、运行或因 I/O 阻塞。程序文件静态,同一文件可由多个进程执行。线程并发不代表 CPU 同时执行,解释器限制不同于系统保证。

检查理解

同进程两线程共享全部栈帧吗?

完整解答

不,栈独立,但可访问共享对象。

2

调度与上下文切换

调度器选择就绪工作。先来先服务简单,但长任务阻挡短任务。时间片轮转改善交互响应,片过小增加切换开销。周转为完成减到达,响应为首次服务减到达。保存恢复寄存器让切换后继续。公平、吞吐与延迟是不同目标,评估策略需负载与目标。

检查理解

响应时间等于周转时间吗?

完整解答

不,任务可很早开始却很晚完成。

3

虚拟内存与分页

虚拟地址通过页映射转换到物理存储,页号选映射、偏移选页内字节。4096 字节页中地址 8197 为页二、偏移五。权限与独立映射隔离进程,TLB 缓存转换。缺页把控制交给系统,可分配、加载或拒绝,并非总读磁盘。虚拟内存是地址空间抽象,而非仅用磁盘增加 RAM。

检查理解

1024 字节页中,2051 如何拆分?

完整解答

页二、偏移三。

4

文件、句柄与持久化

文件提供有名字的持久数据,句柄跟踪打开实例与位置。用上下文管理器在异常后也可靠关闭。目录组织名字,权限管理操作。缓冲意味着写调用成功不一定已物理持久。抗崩溃更新需明确持久与原子策略。临时目录实验展示生命周期,而非持久数据库事务。

检查理解

写入缓冲保证断电后仍在吗?

完整解答

不保证,持久性需额外保证。

5

竞态、锁与死锁

读改写可交错:双方读零、都写一,一次递增丢失。需同一锁保护整个临界区,而非仅最终写。常规 Python 的 GIL 不是应用事务保证。线程持有资源并循环等待其他资源可死锁,统一锁顺序有助避免。屏障实验强制特定顺序,无需依赖睡眠或随机机会。

检查理解

为何只锁赋值不足?

完整解答

陈旧读取可能已在锁外发生。

6

常见误解

  • 并发可以不同时执行。
  • 缺页未必是错误或磁盘操作。
7

实验准备

下载脚本,在终端中使用 Python 3.11 或更新版本运行:python m10_process.py. Windows 也可使用 py -3;部分系统使用 python3。实验仅用标准库。先预测结果,再运行并完成变体。不要使用 -O,以保留断言。下方输出由构建器实际运行捕获,两种语言使用相同代码与输出。

8

实验 1 — 进程与作用域文件

检查子进程 ID 不同,输出不嵌入机器相关 ID。

下载 m10_process.py

"""Observe an isolated child process and scoped file cleanup."""
import json, os, subprocess, sys, tempfile
from pathlib import Path
child = subprocess.run([sys.executable, "-c", "import os,json; print(json.dumps({'pid':os.getpid(),'answer':6*7}))"],
                       check=True, capture_output=True, text=True, timeout=10)
data = json.loads(child.stdout)
assert data["pid"] != os.getpid() and data["answer"] == 42
print("child is a different process:", data["pid"] != os.getpid())
print("child result:", data["answer"])
with tempfile.TemporaryDirectory() as folder:
    path = Path(folder) / "catalogue.txt"
    path.write_text("Dune\nFoundation\n", encoding="utf-8")
    print("file records:", path.read_text(encoding="utf-8").splitlines())
print("temporary directory removed:", not Path(folder).exists())
实际运行输出
child is a different process: True
child result: 42
file records: ['Dune', 'Foundation']
temporary directory removed: True
  1. 让子进程非零退出。
  2. 在调用者捕获 CalledProcessError。
  3. 解释临时路径为何消失。
完整解答

check=True 把非零退出转为 CalledProcessError,可查 returncode。临时目录上下文结束会清理自身内容,异常退出也如此。

9

实验 2 — 强制并修复竞态

屏障确保双方写前都已读旧值,安全版本锁住完整更新。

下载 m10_race.py

"""Force a lost update deterministically, then protect a critical section."""
from threading import Barrier, Lock, Thread
counter, barrier = [0], Barrier(2)
def unsafe():
    old = counter[0]
    barrier.wait(timeout=5)  # both have read zero before either writes
    counter[0] = old + 1
threads = [Thread(target=unsafe) for _ in range(2)]
for t in threads: t.start()
for t in threads: t.join(timeout=10); assert not t.is_alive()
print("forced lost update:", counter[0])
assert counter[0] == 1

counter[0] = 0
lock = Lock()
def safe():
    for _ in range(1000):
        with lock:
            counter[0] = counter[0] + 1
threads = [Thread(target=safe) for _ in range(2)]
for t in threads: t.start()
for t in threads: t.join(timeout=10); assert not t.is_alive()
print("protected updates:", counter[0])
assert counter[0] == 2000
实际运行输出
forced lost update: 1
protected updates: 2000
  1. 画强制交错。
  2. 把读取移到锁外,解释错误。
  3. 解释屏障放进互斥锁为何阻碍推进。
完整解答

A 读零、B 读零,双方写一。锁外读取可陈旧。若持锁者等双人屏障,另一人无法取锁到达,超时暴露死锁模式。

10

练习与完整解答

先尝试,再展开解答。★ 应用概念;★★ 结合概念;★★★ 进行设计或证明。

练习 1 — 程序与进程★

一个文件可有两个运行进程吗?

完整解答

可以,每次启动有独立状态与资源。

练习 2 — 页拆分★

4096 字节页拆分 4100。

完整解答

页一,偏移四。

练习 3 — 调度★★

任务八与一同时到达,比较先 A 与先 B 平均周转。

完整解答

先 A 完成八九,平均 8.5;先 B 一九,平均五。工作相同,顺序影响均值。

练习 4 — 丢失更新★★

双方读五并写旧值加一,结果?

完整解答

六而非七。

练习 5 — 共享状态★★

共享字典与线程局部循环索引哪个需协调?

完整解答

共享可变字典不变式需协调,独立局部索引本身不造成共享竞态。

练习 6 — 死锁顺序★★★

两线程需 X、Y 锁,给一致获取规则。

完整解答

都先 X 后 Y,反序释放,移除两锁相反获取的循环等待。

练习 7 — 临界区边界★★★

两调用者借最后一本,什么需原子?

完整解答

库存检查、扣减与借阅创建需统一事务,仅锁扣减不能避免双方先通过检查。

练习 8 — 生命周期★★

解析异常为何仍需关文件?

完整解答

可靠释放资源,with 在正常与异常退出时都清理。

11

自测

选择答案查看反馈,重置后可重做。无需 JavaScript 也可阅读答案表。

1

同进程线程共享什么?

2

轮转选择什么?

3

缺页总是读盘吗?

4

锁应覆盖什么?

5

GIL 保证应用事务吗?

6

一致锁顺序有助避免什么?

答案表
  1. A — 栈独立。
  2. B — 轮换就绪工作。
  3. C — 还可分配或拒绝访问。
  4. A — 锁外陈旧读取仍危险。
  5. B — 仍需明确协调。
  6. C — 针对死锁条件。
12

引导阅读

  • OSTEP 进程 — 阅读进程状态,区分就绪与运行。
  • OSTEP 锁 — 跟踪丢失更新与保护版本。
13

复习与下一步

解释屏障如何产生丢失更新,再说明锁保护的不变式。模块 11 将通过协议连接进程。

14

关键术语

术语含义
上下文切换保存恢复执行状态以切换运行工作。
临界区需协调访问共享状态的操作。
虚拟地址通过进程映射上下文解释的地址。