Linux 多进程开发

0、程序和进程

程序 是包含一系列信息的文件,这些信息描述了如何在运行时创建一个进程。
进程 是正在运行的程序的实例。是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。

1、区别
程序是静态的,进程是动态的,程序是存储在某种介质上的二进制代码,进程对应了程序的执行过程,系统不需要为一个不执行的程序创建进程,一旦进程被创建,就处于不断变化的动态过程中,对应了一个不断变化的上下文环境。

2、联系
进程是程序的一次执行,而进程总是对应至少一个特定的程序。一个程序可以对应多个进程,同一个程序可以在不同的数据集合上运行,因而构成若干个不同的进程。几个进程能并发地执行相同的程序代码,而同一个进程能顺序地执行几个程序。

关于进程和程序的区别,《现代操作系统》中用了一个比喻形象说明:一位有一手好厨艺的计算机科学家正在为他的女儿烘制生日蛋糕。他有做生日蛋糕的食谱,厨房里有所需要的原料,在这个比喻中,做蛋糕的食谱就是程序(即用适当形式描述的算法),计算机科学家就是处理机(CPU),而做蛋糕的各种原料就是输入数据。进程就是厨师阅读食谱,取来各种原料以及烘制蛋糕等一系列动作的总和。

1、并行和并发

在这里插入图片描述

2、进程控制块(PCB)—— 进程信息管理

为了管理进程,内核必须对每个进程所做的事情进行清楚的描述。内核为每个进程分配一个
PCB(Processing Control Block)进程控制块,维护进程相关的信息,Linux 内核的进程控制块是
task_struct 结构体。
在 /usr/src/linux-headers-xxx/include/linux/sched.h 文件中可以查看 struct task_struct 结构体定义。
其内部成员有很多,我们只需要掌握以下部分即可:

  • 进程id:系统中每个进程有唯一的 id,用 pid_t 类型表示,其实就是一个非负整数
  • 进程的状态:有就绪、运行、挂起、停止等状态
  • 进程切换时需要保存和恢复的一些CPU寄存器
  • 描述虚拟地址空间的信息
  • 描述控制终端的信息
  • 当前工作目录(Current Working Directory)
  • umask 掩码
  • 文件描述符表,包含很多指向 file 结构体的指针
  • 和信号相关的信息
  • 用户 id 和组 id
  • 会话(Session)和进程组
  • 进程可以使用的资源上限(Resource Limit)

3、进程的状态

进程状态转换

进程状态反映进程执行过程的变化。这些状态随着进程的执行和外界条件的变化而转换。在三态模型中,进程状态分为三个基本状态,即就绪态,运行态,阻塞态。在五态模型中,进程分为新建态、就绪态,运行态,阻塞态,终止态。

  • 运行态:进程占有处理器正在运行;
  • 就绪态:进程具备运行条件,等待系统分配处理器以便运行。当进程已分配到除CPU以外的所有必要资源后,只要再获得CPU,便可立即执行。在一个系统中处于就绪状态的进程可能有多个,通常 将它们排成一个队列,称为就绪队列;
  • 阻塞态:又称为等待(wait)态或睡眠(sleep)态,指进程不具备运行条件,正在等待某个事件的完成;
    在这里插入图片描述

进程处于阻塞态,CPU的资源会释放掉,调度程序调度给别的程序执行。
程序运行时与用户交互(控制台等待输入),调用的就是阻塞函数。
阻塞态无法直接变为运行态,首先转换为就绪态,然后被调用运行(抢CPU资源)才能转为运行态,因为阻塞时会把CPU资源释放掉。

  • 新建态:进程刚被创建时的状态,尚未进入就绪队列;
  • 终止态:进程完成任务到达正常结束点,或出现无法克服的错误而异常终止,或被操作系统及有终
    止权的进程所终止时所处的状态。进入终止态的进程以后不再执行,但依然保留在操作系统中等待
    善后。一旦其他进程完成了对终止态进程的信息抽取之后,操作系统将删除该进程。
    在这里插入图片描述

就绪态可以直接到终止态。
程序终止后,虚拟地址空间中用户区的数据被释放,内核区的数据还保留在系统中,等待善后。

进程相关的命令

ps aux / ajx
a:显示终端上的所有进程,包括其他用户的进程
u:显示进程的详细信息
x:显示没有控制终端的进程
j:列出与作业控制相关的信息

1、ps aux

ps aux      显示当前进程的一个快照(非动态)

在这里插入图片描述

  • TTY是进程所属的终端,比如ps aux这个进程属于 pts/3 终端。
  • STAT是状态,比如ps aux就是R状态,即正在运行。
  • COMMAND是命令,比如ps aux。

在这里插入图片描述
2、ps ajx

ps ajx     显示当前进程的一个快照(非动态)

在这里插入图片描述
PID是进程ID(学生)
PPID是父进程ID
PGID是进程组ID,一个组里有多个进程(教室)
SID是会话ID,一个会话有多个组(学校)

3、top

top            动态的显示进程信息

可以在使用 top 命令时加上 -d 来指定显示信息更新的时间间隔,在 top 命令执行后,可以按以下按键对显示的结果进行排序:

  • M 根据内存使用量排序
  • P 根据 CPU 占有率排序
  • T 根据进程运行时间长短排序
  • U 根据用户名来筛选进程
  • K 输入指定的 PID 杀死进程

在这里插入图片描述
4、kill
杀死进程(kill名并不是去杀死一个进程,而是给进程发送某个信号)

kill [-signal] pid
kill –l 列出所有信号
kill –SIGKILL 进程ID
kill -9 进程ID
killall name 根据进程名杀死进程

在这里插入图片描述

进程号相关函数

1、每个进程都由进程号来标识,其类型为 pid_t(整型),进程号的范围:0~32767。进程号总是唯一的,但可以重用。当一个进程终止后,其进程号就可以再次使用。

2、任何进程(除 init 进程)都是由另一个进程创建,该进程称为被创建进程的父进程,对应的进程号称为父进程号(PPID)
在这里插入图片描述

  • 任何进程都有父进程,比如 a.out 的父进程就是 终端

3、进程组是一个或多个进程的集合。他们之间相互关联,进程组可以接收同一终端的各种信号,关联的进程有一个进程组号(PGID)。默认情况下,当前的进程号会当做当前的进程组号。

进程号和进程组的获取函数:

pid_t getpid(void); //当前进程ID
pid_t getppid(void); //当前进程父进程ID
pid_t getpgid(pid_t pid); //当前进程进程组的ID

4、进程创建 —— fork函数

系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成 进程树结构模型
fork() 通过复制调用进程来创建一个新进程。 新进程被称为子进程。 调用进程称为父进程。
fork函数,意译为分叉。

man 2 fork

在这里插入图片描述

#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
函数的作用:用于创建子进程。返回值:fork()的返回值会返回两次。一次是在父进程中,一次是在子进程中。如何区分父进程和子进程:通过fork的返回值。

返回值:

  • 成功:返回两次,在子进程中返回 0,在父进程中返回子进程 ID
  • 失败:在父进程中返回-1,表示创建子进程失败,并且设置errno

失败的两个主要原因:

  • 当前系统的进程数已经达到了系统规定的上限,这时 errno 的值被设置为 EAGAIN
  • 系统内存不足,这时 errno 的值被设置为 ENOMEM

测试一下:

// 创建子进程pid_t pid = fork(); //此时会分叉// 判断是父进程还是子进程if (pid > 0){printf("pid : %d\n", pid);// 如果大于0,返回的是创建的子进程的进程号,当前是父进程printf("im parent process, pid: %d, ppid: %d\n", getpid(), getppid());} else if (pid == 0){// 如果等于0,当前是子进程printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());}for (int i = 0; i < 5;i++){printf("i : %d , pid : %d\n", i , getpid());sleep(1);}

输出:

petri@XX:~/lesson02/01$ ./fork
//因为fork分叉,执行了两组代码://父进程部分
pid : 575
im parent process, pid: 574, ppid: 27267
i : 0 , pid : 574//子进程部分
i am child process, pid : 575, ppid : 574
i : 0 , pid : 575
i : 1 , pid : 574
i : 1 , pid : 575
i : 2 , pid : 575
i : 2 , pid : 574
i : 3 , pid : 574
i : 3 , pid : 575
i : 4 , pid : 575
i : 4 , pid : 574

从子进程的输出情况看出,父进程和子进程是交替执行的。

ps aux 查找父进程的 ppid: 27267,发现是一个 bash 进程(如下图):
在这里插入图片描述

父子进程用户区数据读写

父子进程虚拟地址空间:
刚才在fork函数中也看到,创建子进程会复制一个新的地址空间:
在这里插入图片描述
Description:
The child process and the parent process run in separate memory spaces. At the time of fork() both memory spaces have the same content. Memory writes, file mappings (mmap(2)), and unmappings (munmap(2)) performed by one of the processes do not affect the other.
子进程和父进程在不同的内存空间中运行。 在执行 fork() 时,两个内存空间的内容相同。 其中一个进程执行的内存写入、文件映射(mmap(2))和解除映射(munmap(2))操作不会影响另一个进程

在上面代码中初始化一个num=10,分别在子进程和父进程中操作num,两者的操作互不影响,效果如下:
在这里插入图片描述
注意:
实际上,更准确来说,Linux 的 fork() 使用是通过写时拷贝 (copy- on-write) 实现。
写时拷贝是一种可以推迟甚至避免拷贝数据的技术

在这里插入图片描述

面试题: fork()函数会复制整个进程的地址空间吗?

答:内核并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间,只有在需要写入的时候才会复制地址空间

在这里插入图片描述
fork函数返回值是两个位于父子进程的栈空间的局部变量。

总结:读时共享,写时拷贝!

GDB多进程调试


在这里插入图片描述

(gdb) show follow-fork-mode
Debugger response to a program call of fork or vfork is "parent".(gdb) show detach-on-fork
Whether gdb will detach the child of a fork is on.

如果设置 detach-on-fork 为 off,则子进程会挂起。

查看调试的进程:
‘ * ’ 表示当前调试的进程。

在这里插入图片描述

切换当前的进程:

在这里插入图片描述
在这里插入图片描述

使进程脱离GDB调试:

在这里插入图片描述

会把detach inferiors的进程继续执行完。

5、exec函数族

exec 函数族: 一系列功能相同或相似的函数,类似C++函数重载。

exec 函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就
是在调用进程内部执行一个可执行文件。

exec 函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被
新的内容取代,只留下进程 ID 等一些表面上的信息仍保持原样,颇有些神似“三十六计”中的“金蝉脱
壳”。看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回 -1,从原程序的调
用点接着往下执行。
比如,内核区调用了exec函数,当前用户区的内容会被a.out的用户去内容替换掉:
在这里插入图片描述
常用exec函数:

int execl(const char *path, const char *arg, .../* (char  *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
//linux系统函数man 2 execve,其余的函数都是在这个函数上的封装 man 2 execl
int execve(const char *filename, char *const argv[], char *const envp[]); 

execl

int execl(const char *path, const char *arg, …);

#include <unistd.h>int execl(const char *path, const char *arg, ...);- 参数:- path:需要 指定的执行的文件的路径 或者 名称a.out /home/nowcoder/a.out 推荐使用绝对路径./a.out hello world- arg:是执行可执行文件所需要的参数列表第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称从第二个参数开始往后,就是程序执行所需要的的 参数列表。参数最后需要以NULL结束(哨兵)- 返回值:只有当调用失败,才会有返回值,返回-1,并且设置errno如果调用成功,没有返回值 (内容替换成功)。
#include <unistd.h>
#include <stdio.h>int main() {// 创建一个子进程,在子进程中执行exec函数族中的函数pid_t pid = fork();if(pid > 0) {// 父进程printf("i am parent process, pid : %d\n",getpid());sleep(1);}else if(pid == 0) {// 子进程execl("hello","hello",NULL);// execl("/bin/ps", "ps", "aux", NULL);//执行shell命令,ps aux 查看当前进程信息//execl("ps", "ps", "aux", NULL);//execl: No such file or directory//execl("hello", "ps", "aux", NULL); //hello, worldperror("execl");printf("i am child process, pid : %d\n", getpid());}for(int i = 0; i < 3; i++) {printf("i = %d, pid = %d\n", i, getpid());}return 0;
}

输出:

i am parent process, pid : 8873
hello, world
i = 0, pid = 8873
i = 1, pid = 8873
i = 2, pid = 8873

可以看到执行子进程时,execl用hello把它替换了,子进程后续内容不再执行。

execlp

int execlp(const char *file, const char *arg, … );

#include <unistd.h>int execlp(const char *file, const char *arg, ... );- 会到 环境变量 中查找指定的可执行文件,如果找到了就执行,找不到就执行不成功。- 参数:- file:需要执行的可执行文件的文件名a.outps- arg:是执行可执行文件所需要的参数列表第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称从第二个参数开始往后,就是程序执行所需要的的参数列表。参数最后需要以NULL结束(哨兵)- 返回值:只有当调用失败,才会有返回值,返回-1,并且设置errno如果调用成功,没有返回值。int execv(const char *path, char *const argv[]);argv是需要的参数的一个字符串数组char * argv[] = {"ps", "aux", NULL};execv("/bin/ps", argv);
#include <unistd.h>
#include <stdio.h>int main() {// 创建一个子进程,在子进程中执行exec函数族中的函数pid_t pid = fork();if(pid > 0) {// 父进程printf("i am parent process, pid : %d\n",getpid());sleep(1);}else if(pid == 0) {// 子进程execlp("ps", "ps", "aux", NULL);printf("i am child process, pid : %d\n", getpid());}for(int i = 0; i < 3; i++) {printf("i = %d, pid = %d\n", i, getpid());}return 0;
}

输出:

i am parent process, pid : 10233
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   9868   656 ?        Ssl  08:57   0:00 /init
root       108  0.0  0.0   9968   324 tty2     Ss   08:57   0:00 /init
petri      109  0.0  0.0  10656   680 tty2     S    08:57   0:00 sh -c "$VSCODE_WSL_EXT_LOCATION/scripts/wslServer.sh" e170252f762678dec6ca2cc69aba1570769a5d39 stable code-server .vscode-server --host=127.0.0.1 --port=0 --connection-token=3276537263
petri      110  0.0  0.0  10656   728 tty2     S    08:57   0:00 sh /mnt/c/Users/17335/.vscode/extensions/ms-vscode-remote.remote-wsl-0.88.0/scripts/wslServer.sh e170252f762678dec6ca2cc69aba1570769a5d39 stable code-server .vscode-server --host=127.0
petri      115  0.0  0.0  10656   720 tty2     S    08:57   0:00 sh /home/petri/.vscode-server/bin/e170252f762678dec6ca2cc69aba1570769a5d39/bin/code-server --host=127.0.0.1 --port=0 --connection-token=3276537263-925675798-3382388098-1089837822 --use
petri      119  0.1  0.3 995220 111568 tty2    Sl   08:57   0:07 /home/petri/.vscode-server/bin/e170252f762678dec6ca2cc69aba1570769a5d39/node /home/petri/.vscode-server/bin/e170252f762678dec6ca2cc69aba1570769a5d39/out/server-main.js --host=127.0.0.1
root       202  0.0  0.0   9880   324 tty3     Ss   08:57   0:00 /init
petri      203  0.0  0.1 614816 39208 tty3     Sl   08:57   0:00 /home/petri/.vscode-server/bin/e170252f762678dec6ca2cc69aba1570769a5d39/node -e const net = require('net'); process.stdin.pause(); const client = net.createConnection({ host: '127.0.0.
root       210  0.0  0.0   9880   324 tty4     Ss   08:57   0:00 /init
petri      211  0.0  0.1 609696 34860 tty4     Sl   08:57   0:00 /home/petri/.vscode-server/bin/e170252f762678dec6ca2cc69aba1570769a5d39/node -e const net = require('net'); process.stdin.pause(); const client = net.createConnection({ host: '127.0.0.
petri      218  0.5  0.4 1035192 146836 tty2   Sl   08:57   0:29 /home/petri/.vscode-server/bin/e170252f762678dec6ca2cc69aba1570769a5d39/node --dns-result-order=ipv4first /home/petri/.vscode-server/bin/e170252f762678dec6ca2cc69aba1570769a5d39/out/bo
petri      229  0.0  0.1 857652 37300 tty2     Sl   08:57   0:01 /home/petri/.vscode-server/bin/e170252f762678dec6ca2cc69aba1570769a5d39/node /home/petri/.vscode-server/bin/e170252f762678dec6ca2cc69aba1570769a5d39/out/bootstrap-fork --type=fileWatch
petri      244  0.0  0.1 658344 44016 tty2     Rl   08:57   0:00 /home/petri/.vscode-server/bin/e170252f762678dec6ca2cc69aba1570769a5d39/node /home/petri/.vscode-server/bin/e170252f762678dec6ca2cc69aba1570769a5d39/out/bootstrap-fork --type=ptyHost -
petri      260  0.0  0.1 133412 55900 tty2     Sl   08:57   0:02 /home/petri/.vscode-server/extensions/ms-vscode.cpptools-1.19.9-linux-x64/bin/cpptools
petri      285  0.0  0.0  17140  2724 pts/0    Ss   08:57   0:00 /bin/bash --init-file /home/petri/.vscode-server/bin/e170252f762678dec6ca2cc69aba1570769a5d39/out/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
petri    10155  0.3  0.0 4280760 8244 tty2     Sl   10:31   0:00 /home/petri/.vscode-server/extensions/ms-vscode.cpptools-1.19.9-linux-x64/bin/cpptools-srv 260 {AB84DDF0-E8B0-49F3-B906-82F973B6A78D}
petri    10224  0.0  0.0  10656   680 tty2     S    10:31   0:00 /bin/sh -c "/home/petri/.vscode-server/bin/e170252f762678dec6ca2cc69aba1570769a5d39/out/vs/base/node/cpuUsage.sh" 285
petri    10225  0.0  0.0  16664  1748 tty2     S    10:31   0:00 /bin/bash /home/petri/.vscode-server/bin/e170252f762678dec6ca2cc69aba1570769a5d39/out/vs/base/node/cpuUsage.sh 285
petri    10228  0.0  0.0  15276   816 tty2     S    10:31   0:00 sleep 1
petri    10233  0.0  0.0  10536   576 pts/0    S    10:31   0:00 ./execlp
petri    10234  0.0  0.0  18660  1896 pts/0    R    10:31   0:00 ps aux
i = 0, pid = 10233
i = 1, pid = 10233
i = 2, pid = 10233

总结一下exec后缀的作用:

int execl(const char *path, const char *arg, .../* (char  *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
//linux系统函数man 2 execve,其余的函数都是在这个函数上的封装 man 2 execl
int execve(const char *filename, char *const argv[], char *const envp[]); 

在这里插入图片描述

6、进程控制

进程退出

#include <stdlib.h>
void exit(int status);#include <unistd.h>
void _exit(int status);status参数:是进程退出时的一个状态信息。父进程回收子进程资源的时候可以获取到。

exit和_exit的区别:
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main() {printf("hello\n"); // 带\n printf可以自动刷新I/O缓冲区,把hello打印出来,它是标准C库的函数printf("world");// exit(0);_exit(0);return 0;
}

_exit(0)只输出 hello,因为\n会把hello放到缓冲区里并刷新到控制台上,但是 world 因为_exit(0)不自带刷新缓冲区的功能,所以就不显示了。如果两个printf都不带\n的话,就什么也不会显示了。
所以平时用 exit 比较多一些。

孤儿进程

父进程运行结束,但子进程还在运行(未运行结束),这样的子进程就称为孤儿进程(Orphan Process)

每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init (pid=1),而 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(20);// 当前是子进程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;
}
输出:
i am parent process, pid : 15056, ppid : 285
petri@XX:~/lesson02/03$ i am child process, pid : 15057, ppid : 1

注意看,子进程打印到下一个终端上了,而且 ppid (父亲进程)是 1,即 init 的 pid。

僵尸进程

每个进程结束之后, 都会释放自己地址空间中的用户区数据,内核区PCB(进程控制块) 没有办法自己释放掉,需要父进程去释放。

进程终止时,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。

操作系统的进程号是有限的。

僵尸进程不能被 kill -9 杀死,这样就会导致一个问题,如果父进程不调用 wait()waitpid() 的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程,此即为僵尸进程的危害,应当避免。

#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;
}

输出:

i am parent process, pid : 21018, ppid : 285
i am child process, pid : 21019, ppid : 21018
i : 0 , pid : 21019
i : 1 , pid : 21019
i : 2 , pid : 21019
i am parent process, pid : 21018, ppid : 285
i am parent process, pid : 21018, ppid : 285
i am parent process, pid : 21018, ppid : 285
i am parent process, pid : 21018, ppid : 285
i am parent process, pid : 21018, ppid : 285

因为父进程一直在循环,并不会释放子进程的PCB,导致子进程变为僵尸进程。

ps auxpetri    21018  0.0  0.0  10536   576 pts/0    S    12:17   0:00 ./zombie
petri    21019  0.0  0.0      0     0 pts/0    Z    12:17   0:00 [zombie] <defunct>
petri    21044  0.0  0.0  17140  2640 pts/1    Ss   12:17   0:00 /bin/bash --init-file /home/petri/.vscode-server/bin/e170252f762678dec6ca2cc69aba1570769a5d39/out/vs/workbench/contrib/terminal/browser/media/shellIntegration-bas
petri    21199  0.0  0.0  18660  1900 pts/1    R    12:17   0:00 ps aux

pid = 21019 的为僵尸进程,此时 kill -9 21019 并不会杀死该僵尸进程,除非kill -9 父进程 21018,子进程变为孤儿进程,然后 被 pid = 1 的 init 进程 做善后处理。
因此,需要 wait() waitpid() 这两个函数。

进程回收

在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等。
但是仍然为其保留一定的信息,这些信息主要主要指进程控制块PCB的信息(包括进程号、退出状态、运行时间等)

父进程可以通过调用 waitwaitpid 得到它的退出状态同时彻底清除掉这个进程。

wait() 和 waitpid() 函数的功能一样,区别在于,wait() 函数会阻塞,waitpid() 可以设置不阻塞,
waitpid() 还可以指定等待哪个子进程结束。

**注意:**一次 wait 或 waitpid 调用只能清理一个子进程,清理多个子进程应使用循环

wait()

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);功能:等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收子进程的资源。参数:int *wstatus进程退出时的状态信息,传入的是一个int类型的地址,传出参数。这个int类型的地址可以输入宏函数查看进程状态。返回值:- 成功:返回被回收的子进程的id- 失败:-1 (所有的子进程都结束,调用函数失败)调用wait函数的进程会被挂起(阻塞),直到它的一个子进程退出或者收到一个不能被忽略的信号时才被唤醒(相当于继续往下执行)
如果没有子进程了,函数立刻返回,返回-1;如果子进程都已经结束了,也会立即返回,返回-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) {//不可能只产生5个子进程。为了让子进程不再产生进程,加入一个pid == 0来判断,让子进程终止for循环break;}}if(pid > 0) {// 父进程while(1) {printf("parent, pid = %d\n", getpid());int ret = wait(NULL);// 没有子进程后 wait 函数返回 -1,结束循环int ret = wait(&st);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);       }exit(0);}return 0; // exit(0)
}

在这里插入图片描述

父进程只打印了一次,因为wait函数使其阻塞,此时 kill -9 27501 这个子进程:

在这里插入图片描述
父进程被唤醒(相当于继续往下执行),继续执行循环,然后又碰到wait函数,进入阻塞状态。

在这里插入图片描述
修改下父进程代码:

    if(pid > 0) {// 父进程while(1) {printf("parent, pid = %d\n", getpid());// int ret = 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);}

新建一个bash,输入:

kill -9 5647
child, pid = 5648
child, pid = 5647
被哪个信号干掉了:9
child die, pid = 5647
child, pid = 5649
child, pid = 5646

waitpid()

#include <sys/types.h>
#include <sys/wait.h>pid_t waitpid(pid_t pid, int *wstatus, int options);功能:回收指定进程号的子进程,可以设置是否阻塞。参数:- pid:pid > 0 : 某个子进程的pidpid = 0 : 回收当前 进程组 的所有子进程    (ps ajx 可以查看各个进程所属的组)pid = -1 : 回收所有的子进程,相当于 wait()  (最常用)pid < -1 : 某个进程组的组id的绝对值(负值?),回收指定进程组中的子进程 (比如 -765- options:设置阻塞或者非阻塞0 : 阻塞WNOHANG : 非阻塞 (宏值,如果没有子进程结束(还有子进程),立刻返回)- 返回值:> 0 : 返回子进程的id= 0 : options=WNOHANG, 表示还有子进程= -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) {break;}}if(pid > 0) {// 父进程while(1) {printf("parent, pid = %d\n", getpid());sleep(1);int st;// int ret = waitpid(-1, &st, 0);int ret = waitpid(-1, &st, WNOHANG);if(ret == -1) {break;} else if(ret == 0) {// 说明还有子进程存在continue;} 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; 
}
child, pid = 16913
child, pid = 16914
child, pid = 16915
child, pid = 16916
parent, pid = 16912
child, pid = 16917
child, pid = 16913
child, pid = 16914
child, pid = 16915
child, pid = 16916
parent, pid = 16912
child, pid = 16917
child, pid = 16913

设置的非阻塞 WNOHANG,此时如果不进行任何操作,ret == 0,所以parent会一直循环输出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/11920.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1689 ssm社区老人危机干预系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java ssm社区老人危机干预系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主…

常见加解密算法03 - RC4逆向认识

各位聪明绝顶&#xff0c;才高八斗的读者们你们好&#xff01;今天我们主要讨论编译之后的RC4算法识别。 题外话&#xff0c;之前看到一个蛋疼的小知识&#xff0c;说“势”这个字最好不好查词典释义。我是很好奇的&#xff0c;果然后来无法直视势不可挡这个成语。 言归正传&am…

【MySQL】常见的数据类型

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;MySQL 目录 &#x1f449;&#x1f3fb;常见的数据类型bit类型enumset集合查询函数find_ in_ set &#x1f449;&#x1f3fb;浮点类型float类型decimal &am…

Ansys Zemax|HUD 设计实例

说明 本文介绍了HUD设计实例。 实例说明 规格如下&#xff1a; 显示器尺寸&#xff1a;24*8mm 眼盒尺寸&#xff1a;100*40mm 放大倍率&#xff1a;5 &#xff08;虚像尺寸 120*40mm&#xff09; 虚像距离&#xff1a;1.8m 最终光学系统的整体布局如下图所示。 从HUD发出的…

官宣!招商工作全面启动“2024南京智博会”众多企业踊跃报名

2024南京智博会&#xff0c;作为一场盛大的科技盛宴&#xff0c;经过多年的发展与沉淀&#xff0c;已经成功跻身国内顶尖的高新技术产品及解决方案的展示平台之列&#xff0c;成为了引领行业趋势的风向标。本届智博会不仅汇聚了众多知名科技企业&#xff0c;更展现了国内外最前…

中北大学软件学院javaweb实验三JSP+JDBC综合实训(一)__数据库记录的增加、查询

目录 1.实验名称2.实验目的3.实验内容4.实验原理或流程图5.实验过程或源代码&#xff08;一&#xff09;编程实现用户的登录与注册功能【步骤1】建立数据库db_news2024和用户表(笔者使用的数据库软件是navicat)【步骤2】实现用户注册登录功能(与上一实验报告不同的是&#xff0…

哪个品牌led灯好?五大好用护眼台灯推荐

哪个品牌led灯好&#xff1f;目前LED护眼台灯当中做得比较好的有明基、松下、书客等品牌。在如今LED灯市场的海洋中&#xff0c;选择一款可靠的护眼台灯变得愈发重要。然而&#xff0c;众多品牌和产品的涌现也让消费者面临着选择困难。为了帮助大家找到最合适的LED台灯&#xf…

Chromium 调试指南2024 Windows11篇-使用日志来辅助调试(八)

1. 日志&#xff1a;你的第一个调试工具 日志是开发者最简单也是最常用的调试工具之一&#xff0c;它能够提供程序运行时的详细记录。通过合理的日志记录策略&#xff0c;开发者可以快速定位问题发生的上下文&#xff0c;理解程序的运行流程和状态。 2. 如何在Chromium中使用…

百度云防护自定义访问策略URI使用说明

百度云防护的创建防御模板里的自定义访问策略功能是一个不错自定义拦截功能。 其中URI拦截策略是一种非常不错的拦截手段&#xff0c;今天我们来说明下如何使用URI。 首先什么是URI&#xff1f;关于什么是URI百度上写了很多&#xff0c;不过对于小白来说&#xff0c;是非常难…

中国平安发布“绿美广东·平安古树守护行动” 为广东古树名木提供超2600万风险保障

为响应国家关于生态文明建设的号召&#xff0c;发展绿色金融&#xff0c;助力构建“绿美广东”生态建设新格局&#xff0c;5月11日&#xff0c;中国平安在广东省韶关市南华寺成功举办“绿美广东平安古树守护行动”活动&#xff0c;并发布“我为古树上保险”计划&#xff0c;将为…

Qt实现水平方向流式布局FlowLayout简单又实用!

Qt中常见的布局管理器有&#xff1a; QHBoxLayout&#xff1a;水平布局&#xff08;常用&#xff09; QVBoxLayout&#xff1a;垂直布局&#xff08;常用&#xff09; QGridLayout&#xff1a;表格布局&#xff08;常用&#xff09; QFormLayout&#xff1a;表单布局&#…

4.分支与循环

逻辑控制分为三部分&#xff1a; 1.顺序结构---》顺序执行代码 2.分支结构---》if语句和switch语句 3.循环执行---》for语句 while语句 和do while语句 顺序结构比较简单&#xff0c;按照代码书写的顺序一行一行执行 分支结构&#xff08;if、switch语句&#xff09; 也就是…

StarCloud开源行动:激发算力调度的创新潜力

01 关于StarCloud OpenCSG StarCloud 是一个集开源系统(Kubernetes ,K8S)与高性能计算(High Performance Computing,HPC)一体的混合算力调度平台。它专注于大模型训练和推理&#xff0c;并提供一站式服务&#xff0c;包括从训练到部署&#xff0c;以及多模型比较等。除了在人…

【OpenVINO™】在 C# 中使用OpenVINO™ 部署PP-YOLOE实现物体检测

前言 OpenVINO™ C# API 是一个 OpenVINO™ 的 .Net wrapper&#xff0c;应用最新的 OpenVINO™ 库开发&#xff0c;通过 OpenVINO™ C API 实现 .Net 对 OpenVINO™ Runtime 调用&#xff0c;使用习惯与 OpenVINO™ C API 一致。OpenVINO™ C# API 由于是基于 OpenVINO™ 开发…

DevOps 温故知新

【引】伴随着微服务架构以及云技术的广泛使用&#xff0c;DevOps相应地引起了人们的关注&#xff0c;尤其在互联网企业展开了大量的探索和实践。去年赋闲在家的时候&#xff0c; 有幸精读了三本书&#xff0c;分别是《持续架构实践——敏捷和DevOps时代下的软件架构》&#xff…

Linux安装MySQL(CentOS 7)

安装步骤 下载的MySQL版本为mysql-8.0.26 进入网站MySQL&#xff0c;点击下载 找到mysql社区版 点击Archive&#xff0c;查看所有相关不同版本 点击MySQL Community Server 注意下载MySQL对应的Linux版本&#xff0c;CentOS7 对应 Linux7&#xff0c;如果下成Linux 8 则后面…

解决SpringBoot整合MyBatis和MyBatis-Plus,请求后不打印sql日志

问题发现 在整合springBootmyBatis时&#xff0c;发现请求不打印sql日志&#xff0c;示例代码如下&#xff1a; RestController public class MyController {AutowiredProductMapper productMapper;GetMapping("/test")public void test() {System.out.println(&qu…

全国大学生数学建模竞赛【集训营E题】丨 近5年赛题实现,模拟参赛体验

全国大学生数学建模竞赛E题集训营即将开营 基于Python的近5年E题数学建模基础巩固 近5年E题赛题实现 模拟参赛体验与作品评审

数据库笔记-【视图】

视图 视图通俗是企业想展示给用户看的&#xff0c;数据库存储的数据有很多&#xff0c;但是也有很多是不能对外公开的&#xff0c;做项目的过程就通过视图这个媒介达到这种效果 视图也可以保证数据库表结构字段的隐私安全等 create or replace view stu_v_1 as select id st…

✨✨使用vue3打造一个el-form表单及高德地图的关联组件实例✨

✨1. 实现功能 &#x1f31f;表单内显示省市县以及详细地址 点击省市县输入框时&#xff0c;打开对应地图弹窗&#xff0c;进行位置选择选择位置回显入对应输入框表单内的省市县以及地址输入框同外嵌表单走相同的校验方式触发校验后点击reset实现清除校验与清空数据 &#x1f…