fork() :创建子进程
我们都知道,一个可执行程序加载到内存中,就叫做进程,进程执行对应的代码。可以看得出,我们是在执行一个进程,如果想要在该进程中再创建一个进程呢,该如何进行分流。
这时了解一下 fork() 函数:
fork() 函数使得该进程派生出子进程,两个进程之间是父子关系,和 .bash 是爷孙关系,而 fork() 函数有两个返回值,没错,有两个返回值!!至于为什么,等一下细说。
1、那么在 main 函数中,在调用 fork() 前的代码会被谁执行?
当然是父进程啦,那时候子进程都还没出生呢,想要创建子进程,首先自己得是进程,而 fork() 之后的代码才是父子共享。
2、在调用 fork() 后,操作系统构建相对应的 task_struct、mm_struct、页表等等,就等于创建进程,生成的子进程和父进程共享一个代码,那数据呢?
没改当然是一样的咯,改了再说。
3、fork() 之后就有了 2 个进程,谁先被调度?
这个不一定,因为每个进程都有相对于的时间片,可能刚刚创建完子进程就换台了,还有很多因素,进程会根据 OS 的调度算法(优先级、状态等)调度。
进程状态:
进程有六种:
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
R--运行状态:
运行状态代表这进程处于运行状态,但是这样说不正确。在查看所有的进程运行状态时,我们可以看到有许多 R 状态的进程,那岂不是说有多个进程正在同时运行,难道是多核多 CPU 吗?非也!其实就算是单个 CPU 也能有多个 R 状态的进程,因为 R 状态不代表正在运行,而是代表可被调度!
S--浅度睡眠:
通常用来进程等待某种时间发生,随时可以被唤醒,也可以被杀掉。
D--深度睡眠:
磁盘睡眠一般表示深度睡眠,表示该进程不会被杀掉,即使是操作系统也无法杀掉,只能自己自动唤醒,是无敌的!
举个例子,一个进程要去磁盘里查找某个文件,但是速度比较慢,文件比较多,在等待返回信号的时候会在内存中休眠等待,但是在内存中就会消耗资源,如果操作系统把它杀掉了,返回的信息该由谁来接收,所以操作系统不能杀掉该进程,只能等返回信息回馈到该进程时,进程自动销毁,一般很少遇见。
T--暂停状态:
当 T 状态被 kill-9 时,不会第一时间被杀掉,而是等它再次被唤醒时才会发现自己被杀了。
kill -SIGSTOP(19) -- 暂停进程
kill -SIGCONT(18) -- 继续进程
kill -l -- 查看系统可发送的信号集
Z--僵尸进程:
僵尸进程会造成内存泄漏!!
当父进程创建子进程是,父进程需要接收子进程跑完后的信息,因为要知道子进程的运行结果,当父进程接收后才结束,而构成僵尸进程的原因是父进程没有接收子进程的返回信号,所以子进程就一直挂着。
为什么要接收子进程的返回信号呢?因为父进程派子进程去完成一件事情,需要知道完成得如何,是成功了还是失败了。而一个父亲能有多个孩子。
僵尸进程的危害:如果父进程一直不读取,子进程就一直处于 Z 状态, PCB 就会一直维护,浪费了内存资源,造成了内存泄漏!且杀不掉。
X--死亡状态:
进程结束,是基础返回值被读取而释放掉的状态,往往是一刻,基本看不到。
监控内存状态小脚本:
while :; do ps axj | head -1 && ps axj | grep xxx ; echo "---------" ; sleep 1 ; done
孤儿进程:
通常是因为父进程退出了,子进程还在运行,就如同其名字一样。
如果说,后来子进程运行完毕了,没有父亲来接它,岂不是变成了僵尸进程,那谁来回收?这时候,操作系统就会充当孤儿院院长的角色,把孤儿进程领养,而领养它的则是 1 号进程。
./myproc &
把进程放到后台,需要用 kill 命令才能删除。