进程控制
进程生命周期存在多个状态,多个状态间可以相互转换。在unix系统中提供了一系列与进程状态相关的系统调用,比如fork用于创建新进程,exit用于退出进程,wait用于父进程等待子进程结束。
进程切换
进程切换的概念
- 暂停当前运行进程 从运行状态变成其他状态
- 调度另一个进程从就绪状态变成运行状态
进程切换的要求
- 切换前 保存进程上下文
- 切换后 恢复进程上下文
- 切换快速,因为在程序执行过程中进程切换的频率相当高
进程切换图示
进程控制块
进程控制块记录内核的进程状态,内核为每个进程维护对应的进程控制块,将相同状态的进程的PCB放在同一队列。
进程创建
不同操作系统均提供了进程创建的系统调用接口。
- Windows进程创建API CreateProcess()
- Unix进程创建系统调用 fork/exec
- fork() 把一个进程复制成两个进程 父子进程的PID不同
- exec() 用新程序来重写当前进程 PID 不变
fork示例
1 | int pid = fork(); // 创建子进程,复制两个进程 |
- fork() 创建一个继承的子进程
- 复制父进程的所有变量和内存
- 复制父进程的所有 CPU 寄存器(一个寄存器例外,用于区别父进程和子进程)
- fork() 的返回值
- 子进程的 fork() 返回值 为 0
- 父进程的 fork() 返回值为 子进程标识符
- 子进程可使用 getpid() 获取 PID
- 系统调用exec()加载新程序取代当前运行进程,除了pid相同,其余均替换。
fork地址空间复制
fork使用示例
1 | int main() { |
执行后生成进程如下所示
进程个数:1->2->4->8,从上我们可以看出,进程pid号并不是严格按照创建进程的顺序执行,根据调度算法,就绪队列执行顺序不同。
fork 的开销
- 对子进程分配内存
- 复制父进程的内存和CPU寄存器到子进程
在大多数情况下,调用 fork() 以后会调用 exec() 将 fork() 复制出来的子进程的内存给覆盖掉 fork()。因此在fork()操作中内存操作是没有任何作用的,子进程可能关闭打开文件和连接。
vfork()
- 创建进程时,不再创建一个同样的内存映像。
- 子进程应该立即调用 exec()
- 现在使用 Copy on Write (COW)即写时复制技术
(北京工业大学)子进程可以继承它的父进程所拥有的所有资源()
- 对
- 错
子进程继承了父进程的代码段和数据段资源,堆栈段则是自己的
程序加载与执行
exec()加载新程序并覆盖原来的内存地址空间取代当前运行程序,代码段、堆栈、和堆完全重写,pid号仍和之前相同。
系统调用exec( )允许进程“加载”一个完全不同的程序,并从main开始执行。
进程等待与退出
进程等待
wait() 系统调用用于父进程等待子进程的结束
- 子进程结束时通过exit() 向父进程返回一个值
- 父进程通过wait() 接受并处理返回值
父进程wait()先于子进程exit(),
- 父进程进入等待状态,等待子进程的返回结果
- 当某子进程调用 exit() 时 唤醒父进程 将 exit() 返回值作为 父进程 wait() 的返回值
父进程wait()后于子进程exit(),有僵尸子进程等待,wait()立即返回其中一个值。
当无子进程存活时,wait()立即返回。
进程退出
进程结束执行时,调用 exit() 完成进程资源回收。
exit() 系统调用的功能
- 将调用参数作为进程的 结果(返回值)
- 关闭所有打开的文件等占用资源
- 释放内存
- 释放大部分进程相关的内核数据结构
- 检查父进程是否还存活
- 存活 保留结果的值 直到父进程需要它 进入 僵尸(zombie/defunct)状态
- 非存活 释放所有的数据结构和结果
- 清理所有等待的僵尸进程
什么是僵尸进程和孤儿进程
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。
进程控制与进程状态关系
其他进程控制系统调用
- 优先级控制
- nice() 指定进程的初始优先级
- Unix系统中 进程优先级会随着执行时间而衰减
- 进程调试支持
- ptrace() 允许一个进程控制另一个进程的执行
- 设置断点和查看寄存器等
- 定时
- sleep() 可以让进程在定时器的等待队列中等待指定的时间