进程控制函数
孤儿进程和僵尸进程
很多时候 我们会让子进程先退出,然后再退出父进程
如果父进程先死亡, 那么 子进程就是孤儿进程【很容易理解】
- 僵尸进程
- 僵尸进程:子进程退出了,但是父进程没有用wait或waitpid去获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这种进程称为僵死进程。
僵尸进程危害场景:
例如有个进程,它定期的产 生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程 退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。 严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能瞑目而去了。
父进程有义务回收 自己创建的子进程
- 解决方法:
- 调用 wait方法回收子进程
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的 PCB 还保留着,内核在其中保留了一些信息。如果是正常终止,则保存退出状态;如果是异常终,则保存着导致该进程终止的信号是哪一个。这个进程的父进程可以调用 wait 或 waitpid 获取这些信息 ,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在 Shell 中用特殊变量 $? 查看,因为 Shell 是它的父进程,当它终止时 Shell 调用 wait 或者 waitpid 得到它的退出状态同时彻底清除掉这个进程。
MMU 原理
fork学习视频
说到子进程只是一个额外的流程,那他跟父进程的联系和区别是什么呢?
我很想建议你看看linux内核的注解(有兴趣可以看看,那里才有本质上的了解),总之,fork后,子进程会复制父进程的task_struct结构,并为子进程的堆栈分配物理页。理论上来说,子进程应该完整地复制父进程的堆,栈以及数据空间,但是2者共享正文段。
关于写时复制:由于一般 fork后面都接着exec,所以,现在的 fork都在用写时复制的技术,顾名思意,就是,数据段,堆,栈,一开始并不复制,由父,子进程共享,并将这些内存设置为只读。直到父,子进程一方尝试写这些区域,则内核才为需要修改的那片内存拷贝副本。这样做可以提高 fork的效率。
三.多线程
线程是可执行代码的可分派单元。这个名称来源于“执行的线索”的概念。在基于线程的多任务的环境中,所有进程有至少一个线程,但是它们可以具有多个任务。这意味着单个程序可以并发执行两个或者多个任务。
简而言之,线程就是把一个进程分为很多片,每一片都可以是一个独立的流程。这已经明显不同于多进程了,进程是一个拷贝的流程,而线程只是把一条河流截成很多条小溪。它没有拷贝这些额外的开销,但是仅仅是现存的一条河流,就被多线程技术几乎无开销地转成很多条小流程,它的伟大就在于它少之又少的系统开销。(当然伟大的后面又引发了重入性等种种问题,这个后面慢慢比较)。
还是先看linux提供的多线程的系统调用:
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg); |
Returns: 0 if OK, error number on failure |
第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址。
最后一个参数是运行函数的参数。
fork函数
- 创建一个子进程:
- pid_t fork(void) , 失败返回 -1, 成功返回 子进程的ID(非负)
- man fork 可以查看 文档
- fork 函数返回值有2个:
- 返回子进程的 pid
- 返回 0 表示成功
1
2
3
4
5
6
7
8
9
|
lyr@DESKTOP-FSVN6C0:~$ g++ main.cpp
lyr@DESKTOP-FSVN6C0:~$ ./a.out
:<<EOF
fork 子进程 224
fork成功: 父进程获得 子进程id:= 224
fork 子进程 0
这个是子进程 := 0
EOF
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cerrno>
using namespace std;
int main(void) {
pid_t pid = fork();
printf("fork 子进程 %d\n", pid);
if (pid == -1) {
perror("fork进程失败");
exit(1);
}
if (pid == 0 ){
printf("这个是子进程 := %d\n", pid);
}
else {
printf("fork成功: 父进程获得 子进程id:= %d\n", pid);
}
return 0;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cerrno>
using namespace std;
int main(void) {
pid_t pid = fork();
// printf("fork 子进程 %d\n", pid);
if (pid == -1) {
perror("fork进程失败");
exit(1);
}
if (pid == 0 ){
printf("父进程id: = %d 这个是子进程 := %d\n", getpid() , pid);
}
else {
printf("fork成功:父进程ID:=%d 父进程获得 子进程id:= %d\n",getpid(), pid);
}
return 0;
}
/*
lyr@DESKTOP-FSVN6C0:~$ g++ main.cpp && ./a.out
fork成功:父进程ID:=264 父进程获得 子进程id:= 265
父进程id: = 265 这个是子进程 := 0
getpid() 表示获得当前进程的 id ,proccess_id
*/
|
循环 fork 出现的问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cerrno>
using namespace std;
int main(void) {
// printf("fork 子进程 %d\n", pid);
for(int i=0;i<5;++i ) {
pid_t pid = fork();
if( pid == -1) {
perror("fork error");
exit(1);
}
else if( pid == 0 ) {
printf("这个是子进程 := %d\n",pid);
}else {
printf("这个是父亲进程 := %d, 子进程:=%d\n", getpid(), pid);
sleep(1);
}
}
return 0;
}
|
解决的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cerrno>
using namespace std;
int main(void) {
// printf("fork 子进程 %d\n", pid);
for(int i=0;i<5;++i ) {
pid_t pid = fork();
if( pid == -1) {
perror("fork error");
exit(1);
}
else if( pid == 0 ) {
//这样就不会继续 fork下去了
break;
//printf("这个是子进程 := %d\n",pid);
}else {
printf("这个是父亲进程 := %d, 子进程:=%d\n", getpid(), pid);
sleep(1);
}
}
return 0;
}
|
exec 原理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cerrno>
using namespace std;
int main(void) {
// printf("fork 子进程 %d\n", pid);
pid_t pid;
pid = fork();
if (pid == -1 ) {
exit(1);
}
if (pid == 0 ) {
//第2个参数随便写,不会被用
execlp("ls","ls" ,"-l" , "-d" ,NULL);
} else {
printf("parent\n" );
sleep(2);
}
return 0;
}
|
进程的复制
进程的创建,其实就是对 linux 0号进程或者当前进程的复制,堆栈的复制
参考文章
其他笔记
[[post/03.基础学科/01.操作系统/7.面试问题整理 |面试问题]]
fork原理
linux 内核视频教程
父子进程共享文件描述符
子进程继承的父进程属性有哪些