进程管理📇
1. 进程相关概念🚩
1.1 程序和进程
- 程序,是指编译好的二进制文件,在磁盘上,占用磁盘空间,是一个静态的概念
- 进程,一个启动的程序,进程占用的是系统资源,如:物理内存,CUP,终端等,是一个动态的概念
【🎫】程序
→
\rightarrow
→ 剧本,进程
→
\rightarrow
→ 戏
同一个剧本可以在多个舞台同时上演。同样,同一个程序也可以加载为不同的进程(彼此之间互不影响)
每启动一个程序就会有一个进程PID,即使是相同的程序多次启动也会有不同的PID
1.2 并发和并行
- 并发,在一个时间段内,是在同一个cpu上,同时运行多个程序
- 并行,在一个时间片内,有多个程序在执行(多核cpu)
【📢】cup将一个大的时间段分成多个小的时间片,让进程轮流使用cpu的时间片
1.3 PCB进程控制块
每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是 task_struct
结构体
- 进程id,系统中每个进程有唯一的id,在C语言中用
pid_t
类型表示,其实就是一个非负整数 - 进程的状态,有就绪、运行、挂起、终止等状态
- 进程切换时需要保存和恢复的一些cpu寄存器
- 描述虚拟地址空间的信息
- 描述控制终端的信息
- 当前工作目录
getcwd
获取当前工作目录 umask
掩码- 文件描述符表,包含很多指向 file 结构体的指针
- 和信号相关的信息
- 用户 id 和组 id
- 会话和进程组
- 进程可以使用的资源上限
ulimit -a
查看资源上限
1.4 进程状态
-
运行,进程获得cpu时间片,有执行资格,在cpu时间片用完后进入就绪状态
-
就绪,进程有执行资格,但没有或得cpu时间片,在获得cpu时间片后进入运行状态
-
挂起,进程没有执行资格,不能获取cpu时间片(就绪、运行均可直接转挂起)
从挂起状态不能直接回到运行态,必须先回到就绪状态,再进入运行状态 -
终止,进程结束(就绪、运行、挂起均可直接转终止)
2. 创建进程🌵
2.1 fork函数
-
函数作用:创建子进程
-
原型:
pid_t fork(void);
-
返回值:
- 父进程返回的是子进程的PID,这个值大于 0;
- 子进程返回的是 0 ;
注意⚠️ :并不是一个进程返回两个值,而是由父子进程各自返回一个值
-
父子进程的执行逻辑:
父进程执行pid>0
的逻辑,子进程执行pid==0
的逻辑 -
父子进程执行先后:
谁先抢到cpu时间片谁先执行
【📢】父进程调用fork函数创建一个子进程,子进程的用户区和父进程的用户区完全一样,但是内核区不完全一样,如父进程的PID和子进程的PID不一样
【📢 】父子进程全局变量(无法实现通信)
写时复制,副本修改;读时共享。
1 //fork函数测试
2 #include<stdio.h>
3 #include<stdlib.h>
4 #include<string.h>
5 #include<sys/types.h>
6 #include<sys/stat.h>
7 #include<unistd.h>
8 #include<fcntl.h>
9
10 int main(int argc, char *argv[])
11 {
12 printf("before fork, pid:[%d]\n",getpid());
13 //创建子进程
14 //pid_t fork(void);
15 pid_t pid = fork();
16 if (pid<0) //创建失败
17 {
18 perror("fork error");
19 return -1;
20 }
21 else if (pid>0) //父进程
22 {
23 printf("father: pid=[%d]\n",getpid());
24 //sleep (1);
25 }
26 else if (pid==0) //子进程
27 {
28 printf("child: pid=[%d]\n",getpid());
29 //sleep (1);
30 }
31
32 printf("after fork, pid:[%d]\n",getpid());
33 return 0;
34 }
>>>>执行结果1,子进程先退出
before fork, pid:[117039]
father: pid=[117039], fpid=[8096]
child: pid=[117040], fpid=[117039]
after fork, pid:[117039]
after fork, pid:[117040]
>>>>执行结果2,父进程先退出
before fork, pid:[128429]
father: pid=[128429], fpid=[8096]
after fork, pid:[128429]
[xfk@centos FORKFUNC]$ child: pid=[128430], fpid=[1] //1进程成为父进程
after fork, pid:[128430]
//父进程退出返回shell,但没有真正关闭终端,因为子进程还没有退出(父子进程共享同一个终端)
循环创建子进程
【🎫】每次创建的子进程数 2^i
一共创建子进程个数 2^n-1
总进程数 2^n
注意⚠️:i
循环变量(0开始),n
循环次数
【♻️】i=0
创建1个子进程
i=1
创建2个子进程
i=2
创建4个子进程
… …
1 //循环创建子进程
2 #include<stdio.h>
3 #include<stdlib.h>
4 #include<string.h>
5 #include<sys/types.h>
6 #include<sys/stat.h>
7 #include<unistd.h>
8 #include<fcntl.h>
9
10 int main(int argc, char *argv[])
11 {
12 int i=0;
13 for(i=0;i<3;i++)
14 {
15 //创建子进程
16 //pid_t fork(void);
17 pid_t pid = fork();
18 if (pid<0) //创建失败
19 {
20 perror("fork error");
21 return -1;
22 }
23 else if (pid>0) //父进程
24 {
25 printf("father: pid=[%d], fpid=[%d]\n", getpid(), getppid());
26 }
27 else if (pid==0) //子进程
28 {
29 printf("child: pid=[%d], fpid=[%d]\n", getpid(), getppid());
30 }
31 }
32 sleep(10);
33 return 0;
34 }
>>>>执行结果
father: pid=[46993], fpid=[8096]
father: pid=[46993], fpid=[8096]
father: pid=[46993], fpid=[8096]
child: pid=[46995], fpid=[46993]
father: pid=[46995], fpid=[46993]
child: pid=[46996], fpid=[46993]
child: pid=[46994], fpid=[46993]
father: pid=[46994], fpid=[46993]
father: pid=[46994], fpid=[46993]
child: pid=[46997], fpid=[46995]
child: pid=[46998], fpid=[46994]
father: pid=[46998], fpid=[46994]
child: pid=[46999], fpid=[46994]
child: pid=[47000], fpid=[46998]
//一共创建7个子进程
【🔒 】break;
阻止子进程在循环中继续创建子进程
1 //循环创建3个兄弟子进程
2 #include<stdio.h>
3 #include<stdlib.h>
4 #include<string.h>
5 #include<sys/types.h>
6 #include<sys/stat.h>
7 #include<unistd.h>
8 #include<fcntl.h>
9
10 int main(int argc, char *argv[])
11 {
12 int i=0;
13 for(i=0;i<3;i++)
14 {
15 //创建子进程
16 //pid_t fork(void);
17 pid_t pid = fork();
18 if (pid<0) //创建失败
19 {
20 perror("fork error");
21 return -1;
22 }
23 else if (pid>0) //父进程
24 {
25 printf("father: pid=[%d], fpid=[%d]\n", getpid(), getppid());
26 }
27 else if (pid==0) //子进程
28 {
29 printf("child: pid=[%d], fpid=[%d]\n", getpid(), getppid());
30 break; //阻止递归创建子进程
31 }
32 }
33 if (i==0) //第1个子进程
34 {
35 printf("[%d]--[%d]:child\n",i,getpid());
36 }
37 if (i==1) //第2个子进程
38 {
39 printf("[%d]--[%d]:child\n",i,getpid());
40 }
41 if (i==2) //第3个子进程
42 {
43 printf("[%d]--[%d]:child\n",i,getpid());
44 }
45 if (i==3) //父进程
46 {
47 printf("[%d]--[%d]:child\n",i,getpid());
48 }
49 sleep(10);
50 return 0;
51 }
>>>>执行结果
father: pid=[123588], fpid=[8096]
father: pid=[123588], fpid=[8096]
father: pid=[123588], fpid=[8096]
[3]--[123588]:child
child: pid=[123589], fpid=[123588]
[0]--[123589]:child
child: pid=[123590], fpid=[123588]
[1]--[123590]:child
child: pid=[123591], fpid=[123588]
[2]--[123591]:child
2.2 ps命令/kill命令
ps
:查看进程
ps aux | grep "xxx"
ps ajx | grep "xxx"
选项 | 含义 |
---|---|
-a | 当前系统所有用户的进程 |
-u | 进程所有者及其他一些信息 |
-x | 没有控制终端的进程(不能与用户进行交互的进程) |
-j | 与作业控制相关的信息 |
kill
kill -l
查看有哪些信号
kill -9 pid
杀死某个进程
3. exec函数族🎃
3.1 函数作用和函数介绍
有时需要在一个进程里面执行其他的命令或者是用户自定义的应用程序,此时就用到了exec函数族当中的函数
使用方法:一般都是在父进程里面调用 fork 创建子进程,然后在子进程里面调用 exec
execl函数
- 函数原型:
int execl(const char *path, const char *arg, ...);
- 函数参数:
path
要执行程序的绝对/相对路径arg
变参 要执行程序需要的参数arg
占位参数,通常写应用 程序的名字arg
后面参数,命令参数- 参数写完后
NULL
- 返回值:
- 成功:不返回,不会再执行 exec 函数后面的代码
- 失败:执行 exec 函数后面的代码,可用 perror 打印错误
【📢】execl
函数一般执行自己写的程序
execlp函数
- 函数原型:
int execlp(const char *file, const char *arg, ...);
- 函数参数:
file
执行命令的名字,根据PATH环境变量来搜索该命令arg
占位参数arg
后面参数,命令参数- 参数写完后
NULL
- 返回值:
- 成功:不返回,不会再执行 exec 函数后面的代码
- 失败:执行 exec 函数后面的代码,可用 perror 打印错误
【📢】execlp
函数一般执行系统自带的程序或命令
3.2 exce函数原理
【🎫】exce 函数是用一个新程序替换了当前进程的代码段、数据段、堆和栈;
原有的进程空间没有发生变化,并没有创建新的进程,进程PID没有发生变化
1 //exec函数测试 execl execlp
2 #include<stdio.h>
3 #include<stdlib.h>
4 #include<string.h>
5 #include<sys/types.h>
6 #include<sys/stat.h>
7 #include<unistd.h>
8 #include<fcntl.h>
9
10 int main(int argc, char *argv[])
11 {
12 //创建子进程
13 //pid_t fork(void);
14 pid_t pid = fork();
15 if (pid<0) //创建失败
16 {
17 perror("fork error");
18 return -1;
19 }
20 else if (pid>0) //父进程
21 {
22 printf("father: pid=[%d], fpid=[%d]\n", getpid(), getppid());
23 }
24 else if (pid==0) //子进程
25 {
26 printf("child: pid=[%d], fpid=[%d]\n", getpid(), getppid());
27 //execl("/bin/ls","ls","-l",NULL);
28 execlp("ls","ls","-l",NULL);
29 perror("execl error");
30 }
31
32 return 0;
33 }
>>>>执行结果
father: pid=[116620], fpid=[8096]
[xfk@centos EXEC]$ child: pid=[116621], fpid=[1]
总用量 16
-rwxrwxr-x. 1 xfk xfk 8704 1月 17 21:33 execfunc
-rw-rw-r--. 1 xfk xfk 636 1月 17 21:33 execfunc.c
4. 进程回收♻️
当一个进程退出之后,进程能够回收自己的用户区的资源,但是不能回收内核空间的PCB资源,必须由它的父进程调用 wait 或者 waitpid 函数完成对于子进程的回收,避免造成系统资源的浪费
4.1 孤儿进程🧢
- 孤儿进程的概念
若子进程的父进程已经死掉,而子进程还活着,这个进程就成了孤儿进程 - 为了保证每个进程都有一个父进程,孤儿进程会被 init 进程领养,init 进程成为了孤儿进程的养父进程,当孤儿进程退出之后,由 init 进程完成对孤儿进程的回收
4.2 僵尸进程🧟
- 僵尸进程的概念
若子进程死了,父进程还活着,但是父进程没有调用 wait 或者 waitpid 函数完成对子进程的回收,则子进程就成了僵尸进程
【🎫】不能使用kill -9
杀死僵尸进程,原因是僵尸进程是一个死掉的进程,无法接收信号
通过杀死僵尸进程的父进程可以解决僵尸进程,原因是杀死僵尸进程的父进程,最后由 init 进程回收僵尸进程
4.3 进程回收函数
#include <sys/types.h>
#include <sys/wait.h>
【📢】调用一次 wait
或 waitpid
函数只能回收一个子进程
wait函数
- 函数作用:
- 阻塞并等待子进程退出
- 回收子进程残留资源
- 获取子进程结束状态(退出原因)
- 函数原型:
pid_t wait(int *status);
- 函数参数:子进程的退出状态(传出参数)
WIFEXITED(status)
为非0>>>进程正常结束
WEXITSTATUS(status)
获取进程退出状态WIFSIGNALED(status)
为非0>>>进程异常结束
WTERMSIG(status)
获取进程终止的信号编号
- 返回值:
- 成功
>0
:清理掉的子进程ID - 失败:-1(没有子进程)
- 成功
1 //wait函数测试
2 #include<stdio.h>
3 #include<stdlib.h>
4 #include<string.h>
5 #include<sys/types.h>
6 #include<sys/stat.h>
7 #include<unistd.h>
8 #include<fcntl.h>
9 #include<sys/wait.h>
10
11 int main(int argc, char *argv[])
12 {
13 //创建子进程
14 //pid_t fork(void);
15 pid_t pid = fork();
16 if (pid<0) //创建失败
17 {
18 perror("fork error");
19 return -1;
20 }
21 else if (pid>0) //父进程
22 {
23 printf("father: pid=[%d], fpid=[%d]\n", getpid(), getppid());
24 //pid_t wpid = wait(NULL);
25 int status;
26 pid_t wpid = wait(&status);
27 if (WIFEXITED(status))
28 {
29 printf("child normal exit, status=[%d]\n", WEXITSTATUS(status));
30 }
31 else if (WIFSIGNALED(status))
32 {
33 printf("child kill by signal, signal=[%d]\n", WTERMSIG(status));
34 }
35 printf("wpid==[%d]\n", wpid);
36 }
37 else if (pid==0) //子进程
38 {
39 printf("child: pid=[%d], fpid=[%d]\n", getpid(), getppid());
40 sleep(5);
41 return 9;
42 }
41 return 9;
42 }
43
44 return 0;
45 }
>>>>执行结果
father: pid=[58063], fpid=[8096]
child: pid=[58064], fpid=[58063]
child normal exit, status=[9]
wpid==[58064]
waitpid函数
- 函数作用:同 wait 函数
- 函数原型:
pid_t waitpid(pid_t pid, int *status, int options);
- 函数参数:
pid
pid=-1
等待任一子进程(wait等效)pid>0
等待其进程ID与pid相等的子进程pid=0
等待进程组ID与目前进程相同的任何子进程,也就是说任何和调用waitpid函数的进程在同一个进程组的进程pid<-1
等待其组ID等于pid的绝对值的任一子进程(适用于子进程在其他组的情况)
status
子进程退出状态(wait等效)option
设置为WNOHANG
,函数非阻塞,设置为0,函数阻塞
- 返回值
- 成功
>0
:清理掉的子进程ID - 失败:-1(没有子进程)
=0
参数3为WNOHANG
,且子进程正在运行
- 成功
1 //waitpid函数测试
2 #include<stdio.h>
3 #include<stdlib.h>
4 #include<string.h>
5 #include<sys/types.h>
6 #include<sys/stat.h>
7 #include<unistd.h>
8 #include<fcntl.h>
9 #include<sys/wait.h>
10
11 int main(int argc, char *argv[])
12 {
13 //创建子进程
14 //pid_t fork(void);
15 pid_t pid = fork();
16 if (pid<0) //创建失败
17 {
18 perror("fork error");
19 return -1;
20 }
21 else if (pid>0) //父进程
22 {
23 printf("father: pid=[%d], fpid=[%d]\n", getpid(), getppid());
24 int status;
25 //pid_t wpid = waitpid(pid, &status, 0); //与wait等效
26 while(1)
27 {
28 pid_t wpid = waitpid(-1, &status, WNOHANG); //等待任一子进程,非阻塞
29 if (wpid>0)
30 {
31 if (WIFEXITED(status))
32 {
33 printf("child normal exit, status=[%d]\n", WEXITSTATUS(status));
34 }
35 else if (WIFSIGNALED(status))
36 {
37 printf("child kill by signal, signal=[%d]\n", WTERMSIG(status));
38 }
39 }
40 else if (wpid==0) //子进程还活着
41 {
42 //printf("child is living ,wpid==[%d]\n",wpid);
43 }
44 else if (wpid==-1) //没有子进程了
45 {
46 printf("no child is living ,wpid==[%d]\n",wpid);
47 break;
48 }
49 }
50 }
51 else if (pid==0) //子进程
52 {
53 printf("child: pid=[%d], fpid=[%d]\n", getpid(), getppid());
54 sleep(5);
55 return 9;
56 }
57 return 0;
58 }
>>>>执行结果
father: pid=[52254], fpid=[8096]
child: pid=[52255], fpid=[52254]
child normal exit, status=[9]
no child is living ,wpid==[-1]
✍️ 小方块xfk
📅 写于 2023年1月