进程控制
# 进程控制
# 1、进程退出
#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);
status参数:是进程退出时得一个状态信息。父进程回收子进程资源得时候可以获取到。
1
2
3
4
5
2
3
4
5
//exit.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
printf("hello\n");
printf("world");
// exit(0);
_exit(0);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 🟨:因为
exit()
会刷新了 I/O 缓冲,所以会全部打印出来 - 🟥:这里用的是
_exit()
函数,遇到\n
(换行符) 才刷新缓冲区,所以使用_exit()
就只打印了 hello
# 2、孤儿进程
- 父进程运行结束,但子进程还在运行(未运行结束),这样的子进程就称为孤儿进程 (Orphan Process)。
- 每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init ,而 init 进程会循环地
wait()
它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init 进程就会代表党和政府出面处理它的一切善后工作。 - 因此孤儿进程并不会有什么危害。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid = fork();
// 判断,判断是父进程还是子进程
if (pid > 0)
{
printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());
}
else if (pid == 0)
{
//注意我们这里的sleep 如果写到下面输出语句的下面,那输出的父进程id就不一定是1了,因为他在输出时,凯南父进程还没死亡,所以我们要写到输出语句上面
sleep(1);
// 当前是子进程
printf("i am child process, pid : %d, ppid : %d\n", getpid(), getppid());
}
// for循环
for (int i = 0; i < 3; i++)
{
printf("i : %d, pid : %d\n", i, getpid());
}
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
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
- 🪀:可以看到产生了孤儿进程;
- 孤儿进程会被内核分配给进程号为1的进程(init进程),由进程号为1的进程去回收进程号为1的资源。
- 🟨:之所以出现黄色这里显示完终端后,又跟着显示子进程输出的内容是因为:
- 运行可执行程序一般会默认切换到后台去运行,当有输出时它又会把内容输出到前台(终端),当父进程死亡以后,我们父进程的父进程(终端)就知道了,父进程已经运行完毕了,所以就要切换回前台,就会显示出🟨的内容,但是此时,子进程还没结束,所以就继续执行子进程,子进程也把他的输出打印到了当前终端
# 2.1 思考
- 为什么子进程是打印到当前的终端?
- 原因:
- 子进程和父进程内核区某些数据也是共享的,比如文件描述符,文件描述符表,而文件描述符表的前3个对应的标志输入(0),标准输出(1),标准输入(2)指向的都是当前终端,就是父进程的父进程,所以,子进程的输出也是在当前终端里
# 3、僵尸进程
- 每个进程结束之后, 都会释放自己地址空间中的用户区数据,内核区的 PCB 没有办法自己释放掉,需要父进程去释放。
- 进程终止时,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸 (Zombie)进程。
- 僵尸进程不能被 kill -9 杀死,这样就会导致一个问题,如果父进程不调用 wait() 或 waitpid() 的话,那么保留的那段信息就不会释放,其进程号就会一直被占用, 但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程,此即为僵尸进程的危害,应当避免。
//zombie.c
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid = fork();
// 判断,判断是父进程还是子进程
if (pid > 0)
{
while (1)
{
printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());
sleep(1);
}
}
else if (pid == 0)
{
// 当前是子进程
printf("i am child process, pid : %d, ppid : %d\n", getpid(), getppid());
}
// for循环
for (int i = 0; i < 3; i++)
{
printf("i : %d, pid : %d\n", i, getpid());
}
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
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
执行
./zombie
后子进程变成僵尸进程使用
kill 9 僵尸进程id
没办法直接杀死僵尸进程不调用wait() he waitpid() 函数回收僵尸进程方法:
- 杀死僵尸进程的父进程,让僵尸进程托管给进程号为1的进程,由进程号为1的进程回收僵尸进程的资源;
- 手动
ctrl+c
终止进程 kill -9 僵尸进程的父进程id
杀死父进程
- 手动
- 杀死僵尸进程的父进程,让僵尸进程托管给进程号为1的进程,由进程号为1的进程回收僵尸进程的资源;
# 4、wait 函数
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
功能:等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收子进程的资源。
注意: 一次只能回收一个子进程。
参数:int *wstatus
进程退出时的状态信息,传入的是一个int类型的地址,传出参数
返回值:
-成功: 返回被回收的子进程的id
-失败: -1(所有子进程都结束,没有子进程需要回收或调用函数失败)
//调用wait函数的进程会被挂起(阻塞),直到它的一个子进程退出或者收到一个不能被忽略的信号时才被唤醒(相当于继续往下执行)
//如果没有子进程,函数立刻返回,返回-1;如果子进程都已经结束了,也会立即返回,返回-1
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
//wait.c
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
// 有一个父进程,创建5个子进程(兄弟)
pid_t pid;
// 创建5个子进程
for (int i = 0; i < 5; i++)
{
pid = fork();
if (pid == 0) // 这样子进程就不会去调用 fork() 去产生孙子进程
{
break;
}
}
if (pid > 0)
{
// 父进程
while (1)
{
printf("parent, pid = %d\n", getpid());
// wait 参数写 NULL表示我们不需要获得子进程退出的状态
int ret = wait(NULL);
// if(ret == -1){
// break;
// }
printf("child die, pid = %d\n", ret);
sleep(1);
}
}
else if (pid == 0)
{
// 子进程
while (1)
{
printf("child, pid = %d\n", getpid());
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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
41
42
43
44
45
46
47
48
49
50
左上角,是在
终端1
运行可执行文件wait
- 可以看到程序在执行完一次父进程到 wait() 函数时,就被阻塞了
左下图是,在
终端2
里查看进程,还有杀死进程- 杀死 13269 子进程,父进程的wait()就会回收该进程的资源,如果没有wait函数去回收资源,就杀不死该进程 。。。其他进程也相同🐱🐉
- 子进程都被杀死后,父进程的
wait()
函数返回值为-1
🪀;
如果要在没有子进程后父进程也结束,上面父进程代码里要在加这个判断代码:
...
int ret = wait(NULL);
if(ret == -1){
break;
}
...
1
2
3
4
5
6
2
3
4
5
6
# 4.1 退出信息相关宏函数
WIFEXITED(status) 非0,进程正常退出
WEXITSTATUS(status) 如果上宏为真,获取进程退出的状态(exit的参数)
WIFSIGNALED(status) 非0,进程异常终止
WTERMSIG(status) 如果上宏为真,获取使进程终止的信号编号
WIFSTOPPED(status) 非0,进程处于暂停状态
WSTOPSIG(status) 如果上宏为真,获取使进程暂停的信号的编号
WIFCONTINUED(status) 非0,进程暂停后已经继续运行
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
// 有一个父进程,创建5个子进程(兄弟)
pid_t pid;
// 创建5个子进程
for (int i = 0; i < 5; i++)
{
pid = fork();
if (pid == 0) // 这样子进程就不会去调用 fork() 去产生孙子进程
{
break;
}
}
if (pid > 0)
{
// 父进程
while (1)
{
printf("parent, pid = %d\n", getpid());
// wait 参数写 NULL表示我们不需要获得子进程退出的状态
int st;
int ret = wait(&st);
if (ret == -1)
{
break;
}
if (WIFEXITED(st))
{
// 是不是正常退出
printf("退出的状态码:%d\n", WEXITSTATUS(st));
}
if (WIFSIGNALED(st))
{
// 是不是异常终止
printf("被哪个信号干掉了:%d\n", WTERMSIG(st));
}
printf("child die, pid = %d\n", ret);
sleep(1);
}
}
else if (pid == 0)
{
// 子进程
//while (1)
//{
//}
printf("child, pid = %d\n", getpid());
sleep(1);
exit(0);
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
- 如果把上面代码的 exit(0) 改成 exit(1)就是下图左边的显示,如果为0,就是右边的显示
//将上面代码的子程序代码修改如下
else if (pid == 0){
// 子进程
while(1) {
printf("child, pid = %d\n",getpid());
sleep(1);
}
exit(0);
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 5、waitpid 函数
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
功能:回收指定进程号的子进程,可以设置是否阻塞。
参数:
-pid:
pid > 0 : 某个子进程的id
pid = 0 :回收当前进程组的所有子进程
pid = -1 : 回收任意的子进程,相当于 wait() (最常用)
pid < -1 : 回收某个进程组的组id的绝对值,回收指定进程组中的子进程
-int *wstatus
进程退出时的状态信息,传入的是一个int类型的地址,传出参数
- options:设置阻塞或者非阻塞
0 : 阻塞
WNOHANG :非阻塞
- 返回值:
> 0 : 返回子进程的id
= 0 : options = WNOHANG时才可能返回等于0,表示还有子进程活着
= -1 :错误,或者没有子进程了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 5.1 阻塞
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
// 有一个父进程,创建5个子进程(兄弟)
pid_t pid;
// 创建5个子进程
for (int i = 0; i < 5; i++)
{
pid = fork();
if (pid == 0) // 这样子进程就不会去调用 fork() 去产生孙子进程
{
break;
}
}
if (pid > 0)
{
// 父进程
while (1)
{
printf("parent, pid = %d\n", getpid());
// wait 参数写 NULL表示我们不需要获得子进程退出的状态
int st;
//waitpid(-1, &st, 0) 阻塞回收所有的子进程,相当于 wait()
int ret = waitpid(-1, &st, 0);
if (ret == -1)
{ break; }
if (WIFEXITED(st))
{
// 是不是正常退出
printf("退出的状态码:%d\n", WEXITSTATUS(st));
}
if (WIFSIGNALED(st))
{
// 是不是异常终止
printf("被哪个信号干掉了:%d\n", WTERMSIG(st));
}
printf("child die, pid = %d\n", ret);
sleep(1);
}
}
else if (pid == 0)
{
// 子进程
while (1)
{
printf("child, pid = %d\n", getpid());
sleep(1);
}
exit(0);
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
- 执行出来的结果和上面那个退出信息相关宏函数是一样的,其实代码除了waitpid那句不一样其他也一样,所以直接用了上面的运行截图
# 5.2 非阻塞
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
// 有一个父进程,创建5个子进程(兄弟)
pid_t pid;
// 创建5个子进程
for (int i = 0; i < 5; i++)
{
pid = fork();
if (pid == 0) // 这样子进程就不会去调用 fork() 去产生孙子进程
{
break;
}
}
if (pid > 0)
{
// 父进程
while (1)
{
printf("parent, pid = %d\n", getpid());
// 看之前的代码会发现sleep(1)写在父进程函数的最下面,这里写到前面是因为:
// 我们现在是非阻塞的,如果写到下面,那么我们父进程因为一直在循环,会一直输出,所以我们把sleep写到上面来放慢输出速度
sleep(1);
// wait 参数写 NULL表示我们不需要获得子进程退出的状态
int st;
int ret = waitpid(-1, &st, WNOHANG);
if (ret == -1)
{
break;
}
// 非阻塞,代码会一直往下执行,所以我们用下面这个判断
else if (ret == 0)
{
// 说明还有子进程存在
continue;
}
// 如果没有上面那个(ret == 0)的判断,那么下面这个ret >0 判断还是很有必要的
// 因为如果不判断就直接执行里面的函数,那么每次循环都会执行下面的代码
// 如果有上面的(ret==0)就跳出本次循环的判断,那这里不判断也一样
// 建议还是都写判断吧,可读性比较好,可以一眼看出不同返回值的情况
else if (ret > 0)
{
if (WIFEXITED(st))
{
// 是不是正常退出
printf("退出的状态码:%d\n", WEXITSTATUS(st));
}
if (WIFSIGNALED(st))
{
// 是不是异常终止
printf("被哪个信号干掉了:%d\n", WTERMSIG(st));
}
printf("child die, pid = %d\n", ret);
}
}
}
else if (pid == 0)
{
// 子进程
while (1)
{
printf("child, pid = %d\n", getpid());
sleep(1);
}
exit(0);
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
- 🧡:可以看出父进程没有被阻塞
编辑 (opens new window)
上次更新: 2023/09/13, 12:29:52