本文主要介绍进程控制中的进程切换(进程的上下文切换)、进程创建 fork()
、进程加载与执行 exec()
、进程等待与终止 wait(), exit()
。
进程的上下文切换(context switch)会停止当前运行的进程(从运行状态变成其它状态),并调度其它进程运行(从就绪状态变成运行状态)。
需要存储什么上下文?
进程的上下文切换示意图:
操作系统为活跃进程准备了进程控制块(PCB),并将其放在一个合适的队列里:
Unix 进程创建的两个 系统调用:fork()
和 exec()
:
fork()
把一个进程复制成二个进程:
parent(old PID)
和 child(new PID)
,两个进程只有 PID
号不一样。exec()
用新程序来重写当前进程,但不改变当前进程的 PID
号:
exec('program', argc, argv0, argv1, ...);
用 fork()
和 exec()
创建进程的示例程序:
1 | // 父进程代码 |
fork()
创建一个继承的子进程:
fork()
的返回值:
fork()
返回 0,父进程的 fork()
返回子进程的 PID
号;fork()
的返回值可方便后续使用,子进程可使用 getpid()
获取 PID
号。fork()
的地址空间复制示意图:
上图中,父进程在执行到 fork()
代码的时候,会复制一个子进程,然后父子进程相继向下执行,但是父进程和子进程的局部变量 childPID
的值不同,会进入不同的 if
分支继续执行。
fork()
的实现开销昂贵,在 99% 的情况下,我们在调用 fork()
之后调用exec()
,也就是说:
fork()
操作的内存复制是没有意义的;Unix 的 vfork()
,有时也称为轻量级 fork()
:
exec()
;使用系统调用 exec()
来加载程序,并取代当前运行的程序。
main(int argc, char *argv[])
开始执行;argc
和字符串参数数组 *argv[]
;如果调用成功,则是相同的进程,但是运行了一个不同的程序。
1 | // in the parent process |
执行完 exec()
后,pid
改变了,open files
的路径也改变了;PCB 中的代码段完全被新的程序 calc
所替换,且执行地址也发生了变化。
wait()
系统调用的作用是父进程用来等待子进程的结束:
exit()
向父进程返回一个值,父进程通过 wait()
接收并处理返回值。子进程无法释放掉自己的 PCB,父进程在子进程执行结束后,接收返回值,帮助子进程释放内存中的 PCB 等资源。
wait()
系统调用的功能:
exit()
时,操作系统会解锁父进程,并将 exit()
的返回值作为父进程中 wait()
的返回值。wait()
立即返回。wait()
立即返回其中一个僵尸进程的返回值。僵尸进程(Zombie Process)是指一个已经完成执行(子进程已经终止),但是父进程尚未调用
wait()
或waitpid()
来获取子进程的终止状态的进程。
进程结束执行时调用 exit()
,完成进程资源回收。
exit()
系统调用的功能:
进程终止是最终的垃圾收集(资源回收)。
执行 exec()
时,进程可能处于不同的状态。
参考资料:
1:https://github.com/OXygenMoon/OperatingSystemInDepth
2:https://blog.csdn.net/weixin_53407527/article/details/125027431