进程 (状态机) 管理 API

进程 (状态机) 管理 API

2025 南京大学《操作系统原理》
进程 (状态机) 管理 API

进程管理系统调用

操作系统 = 状态机的管理者

  • 进程管理 = 状态机管理

一个直观的想法

  • 创建状态机:spawn(path, argv)
  • 销毁状态机: _exit()
    • 这是一个合理的设计 (例子:Windows)

UNIX 的答案

  • 复制状态机: fork()
  • 复位状态机: execve()
2025 南京大学《操作系统原理》
进程 (状态机) 管理 API

1. 创建状态机

pid_t fork(void);

现在我们已经有 “一个状态机” 了

  • 只需要 “创建状态机” 的 API 即可
  • UNIX 的答案: fork
    • 做一份状态机完整的复制 (内存、寄存器现场)

center

2025 南京大学《操作系统原理》
进程 (状态机) 管理 API

fork() 的行为

立即复制状态机

  • 包括所有状态的完整拷贝
    • 寄存器 & 每一个字节的内存
    • Caveat: 进程在操作系统里也有状态: ppid, 文件, 信号, ...
      • 小心这些状态的复制行为
    • 复制失败返回 -1
      • errno 会返回错误原因 (man fork)

如何区分两个状态机?

  • 新创建进程返回 0
  • 执行 fork 的进程返回子进程的进程号——“父子关系”
2025 南京大学《操作系统原理》
进程 (状态机) 管理 API

进程树

进程的创建关系形成了进程树

  • ABCA \to B \to C,如果 BB 终止了……C 的 ppid 是什么?
    • 看似简单:我们 “往上提” 一层就行
    • 实际复杂:
      • 子进程结束会通知父进程
        • 通过 SIGCHLD 信号
        • 父进程可以捕获这个信号 (参考 testkit 的实现)
      • “往上提” 就发错人了

如何验证这个行为?

  • 让 AI 帮我们写程序!
2025 南京大学《操作系统原理》
进程 (状态机) 管理 API

Fork Bomb

刚才的示例程序

  • 1 变 2, 2 变 4, 第 kk 层有 2k12^{k-1} 个进程
  • 很快资源就消耗光了
    • 曾经会使系统彻底卡死,但现在 Linux 有 OOM 保护

center

2025 南京大学《操作系统原理》
进程 (状态机) 管理 API

代码解析: Fork Bomb

:(){:|:&};:   # 刚才的一行版本
:() {         # 格式化一下
  : | : &
}; :
f() {      # bash: 允许冒号作为 identifier
  f | f &
}
f
  • “核裂变”: 进程分裂 v.s. 原子核分裂引发指数级增长
2025 南京大学《操作系统原理》
进程 (状态机) 管理 API

理解 fork: 习题 (1)

阅读程序,写出运行结果

pid_t x = fork();
pid_t y = fork();
printf("%d %d\n", x, y);

一些重要问题

  • 到底创建了几个状态机?
  • pid 分别是多少?
    • “状态机视角” 帮助我们严格理解

我的偷懒 (出期末考试题) 方法

  • 拍脑袋 → 估算难度 → 用 model checker 跑结果
2025 南京大学《操作系统原理》
进程 (状态机) 管理 API

理解 fork: 习题 (2)

阅读程序,写出运行结果

for (int i = 0; i < 2; i++) {
    fork();
    printf("Hello\n");
}

状态机视角帮助我们严格理解程序行为

  • ./a.out
  • ./a.out | cat
    • 计算机系统里没有魔法
    • (无情执行指令的) 机器永远是对的
2025 南京大学《操作系统原理》
进程 (状态机) 管理 API

2. 复位状态机

int execve(const char *filename,
           char * const argv[], char * const envp[]);

UNIX 选择只给一个复位状态机的 API

  • 将当前进程重置成一个可执行文件描述状态机的初始状态
  • 操作系统维护的状态不变:进程号、目录、打开的文件……
    • (程序员总犯错,因此打开文件有了 O_CLOEXEC)

execve 是唯一能够 “执行程序” 的系统调用

  • 因此也是一切进程 strace 的第一个系统调用
2025 南京大学《操作系统原理》
进程 (状态机) 管理 API

execve() 设置了进程的初始状态

argc & argv: 命令行参数

  • 困扰多年的疑问得到解答:main 的参数是 execve 给的!

envp: 环境变量

  • 使用 env 命令查看
    • PATH, PWD, HOME, DISPLAY, PS1, ...
  • export: 告诉 shell 在创建子进程时设置环境变量
    • 小技巧:export TK_VERBOSE=1

程序被正确加载到内存

  • 代码、数据、PC 位于程序入口
2025 南京大学《操作系统原理》
进程 (状态机) 管理 API

例子:PATH 环境变量

可执行文件搜索路径

  • 还记得 gcc 的 strace 结果吗?
[pid 28369] execve("/usr/local/sbin/as", ["as", "--64", ...
[pid 28369] execve("/usr/local/bin/as", ["as", "--64", ...
[pid 28369] execve("/usr/sbin/as", ["as", "--64", ...
[pid 28369] execve("/usr/bin/as", ["as", "--64", ...
  • 这个搜索顺序恰好是 PATH 里指定的顺序
$ PATH="" /usr/bin/gcc a.c
gcc: error trying to exec 'as': execvp: No such file or directory
$ PATH="/usr/bin/" gcc a.c

计算机系统里没有魔法。机器永远是对的。

2025 南京大学《操作系统原理》
进程 (状态机) 管理 API

3. 销毁状态机

void _exit(int status);

这个没有争议

  • 立即摧毁状态机,允许有一个返回值
    • 返回值可以被父进程获取
2025 南京大学《操作系统原理》
进程 (状态机) 管理 API

UNIX 进程的生存周期

UNIX 中实现 “创建新状态机” 的方式

  • Spawn = fork + execve
  • 我们会在之后介绍这些系统调用的灵活应用
int pid = fork();
if (pid == -1) { // 错误
    perror("fork"); goto fail;
} else if (pid == 0) { // 子进程
    execve(...);
    perror("execve"); exit(EXIT_FAILURE);
} else { // 父进程
    ...
    int status;
    waitpid(pid, &status, 0); // testkit.c 中有
}
2025 南京大学《操作系统原理》