操作系统之 进程控制

进程切换 (上下文切换)

  1. 暂停当前运行进程 从运行状态变成其他状态
  2. 调度另一个进程从就绪状态变成运行状态

进程切换的要求

  • 切换前 保存进程上下文(寄存器 CPU状态 内存地址空间(大部分不用保存 因为两个进程的内存地址空间不会被覆盖))
  • 切换后 恢复进程上下文
  • 快速切换
1
2
3
进程 P0 因为时间片用完 进入时钟中断服务例程
保存 进程状态到 PCB 0 后 从 PCB 1 恢复进程状态继续执行 进程 P1
暂停 进程 P0

context_switch

进程状态记录 PCB 过程

  • 内核为每个进程 维护 对应的进程控制块(PCB)
  • 内核将相同状态的 进程的PCB 放置在同一队列

PCB_queue

进程创建

  • Windows进程创建Api CreateProcess()
  • Unix进程创建系统调用 fork/exec
    • fork() 把一个进程复制成两个进程 父子进程的PID不同
    • exec() 用新程序来重写当前进程 PID 不变

进程复制 fork

1
2
3
4
int pid = fork();		// 创建子进程
if (pid == 0) { // 子进程在这里继续
exec(“program”, argc, argv0, argv1, …);
}
  • fork() 创建一个继承的子进程
    • 复制父进程的所有变量和内存
    • 复制父进程的所有 CPU 寄存器(一个寄存器例外 是用来识别父进程和子进程的)
  • fork() 的返回值
    • 子进程的 fork() 返回值 为 0
    • 父进程的 fork() 返回值为 子进程标识符
    • 子进程可使用 getpid() 获取 PID
fork 地址空间复制

fork() 执行过程对于 子进程来说 是在调用时间 对父进程地址空间的一次复制(父进程 fork() 返回值为 子进程标识符 子进程 fork() 返回值为 0)

fork

fork 循环调用示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main() {
pid_t pid;
int i;
for (i=0; i<LOOP; i++) {
pid = fork();
if (pid < 0) {
fprintf(stderr, “Fork Failed”);
exit(-1);
}
else if (pid == 0) {
fprintf(stdout, “i=%d, pid=%d, parent pid=%d\n”,I,
getpid() ,getppid());
}
}
wait(NULL);
exit(0);
}

每次循环 都会 fork 一个新的子进程 同时原来被 fork 出来的子进程 也会去 fork 一个新的子进程 这里一共循环了 3次 共有 8个 进程

fork_loop

fork 的开销
  • 对子进程分配内存
  • 复制父进程的内存和CPU寄存器到子进程

在大多数情况下 调用了 fork() 以后 会调用 exec() 将 fork() 复制出来的子进程的内存给覆盖掉 fork() 复制父进程的内存的开销是可以节约的
因此 Windows 下 通过一个系统调用 来完成进程的创建和加载

早期 Unix的 vfork() 也做类似的事情 创建进程时 不再创建一个同样的内存映像 称之为轻量级 fork() 子进程应该立即调用 exec() 现在使用 Copy on Write (COW 写时拷贝) 技术

进程加载与执行 exec

系统调用 exec() 加载新程序取代当前运行程序

1
2
fork() 创建一个继承的子进程 并复制 父进程的内存地址空间和CPU寄存器
exec() 加载新程序 并 覆盖原来的内存地址空间 取代当前运行程序 和之前是相同的进程 但运行了不同的程序

代码段 堆栈 堆 完全重写

fork_exec_pcb

进程等待与退出

wait() 系统调用用于父进程等待子进程的结束

  • 子进程结束时通过 exit() 向父进程返回一个值
  • 父进程通过 wait() 接受并处理返回值

当 父进程 先 wait() 子进程 后 exit() 时

  • 父进程进入等待状态 等待子进程的返回结果
  • 当某子进程调用 exit() 时 唤醒父进程 将 exit() 返回值作为 父进程 wait() 的返回值

当 子进程 先 exit() 父进程 后 wait() 时

  • 说明有僵尸子进程等待 wait() 立即返回其中一个值

当 无子进程存活 而 父进程 wait() 时

  • wait() 立即返回

进程退出 exit

进程结束执行时 调用 exit() 完成进程资源回收

  • exit() 系统调用的功能
    • 将调用参数作为进程的 结果(返回值)
    • 关闭所有打开的文件等占用资源
    • 释放内存
    • 释放大部分进程相关的内核数据结构
    • 检查父进程是否还存活
      • 存活 保留结果的值 直到父进程需要它 进入 僵尸(zombie/defunct)状态
      • 非存活 释放所有的数据结构和结果
    • 清理所有等待的僵尸进程

进程终止是最终的垃圾收集(资源回收)

进程控制与进程状态关系

process_control_with_process_state

其他进程控制系统调用

  • 优先级控制
    • nice() 指定进程的初始优先级
    • Unix系统中 进程优先级会随着执行时间而衰减
  • 进程调试支持
    • ptrace() 允许一个进程控制另一个进程的执行
    • 设置断点和查看寄存器等
  • 定时
    • sleep() 可以让进程在定时器的等待队列中等待指定的时间