日期:2025.1.29(凌晨)
学习内容:
- 僵尸进程
- 筛选输出
- 多进程与信号
个人总结:
僵尸进程:
首先我们需要先了解一件事情,我们在使用fork函数的时候可以使得当前的进程再创建出来一个子进程,这个子进程在大多数的时候会复制一份新的我们的内存地址,数据内存等。那么这个子进程所占有的资源如何释放呢?对于我们当前的进程占有的资源如何释放,我们显然知道,当return的时候,那么进程就应该结束了。但是子进程什么时候结束我们并不知道,所以这时候我们就需要一个可以帮助我们判断子进程是否结束的函数wait()函数。
- wait函数:参数是一个指针,这个指针代表的意义是存储子进程的退出状态。这个退出状态具体怎么判断我们还需要宏,WIFEXITED,WEXITSTATUS,WTERMSIG。(宏的参数都是int)第一个是用来判断子进程退出状态是否正常,第二个是用来记录退出状态正常时的退出状态码,第三个是用来记录退出状态异常时的退出状态码(信号的编号)。
- 退出状态正常:使用return或者exit函数退出,返回的值就是退出状态码
- 退出状态异常:使用信号来中断进程,返回的值其实也就是信号的编号
这里还有一个小的前提是,当我们知道一个进程什么时候结束,那么就可以交由操作系统来回收在其所消耗的资源。
所以我们使用wait函数便可以回收。
一般会有两种情况,一种是父进程先结束,子进程再结束,第二种是子进程先结束,父进程再结束。
前者:当父进程提前结束时,子进程会被pid=1的进程托管,成为其新的父进程。进程名为init。而init自带wait函数,可以回收利用。
后者:由于子进程先结束,假设父进程此刻没有wait函数,那么我们不知道子进程结束的时机,数据内存无法释放,进程编号也无法回收被一直占用,这种情况,便是僵尸进程。
兜兜转转绕了一大圈,但是解决办法我们也已经说完啦,接下来直接上代码。
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;int main()
{pid_t pid = fork();if (pid > 0) {// wait(nullptr);int *status=new int;wait(status);if (WIFEXITED(*status)) {cout << "GOOD" << " " << WEXITSTATUS(*status) << endl;return 0;}else {cout << "BAD" << " "<<WTERMSIG(*status) << endl;return -1;}}else if (pid == 0) {cout << "子进程结束" << endl;return 0;}else {cout << "fork error" << endl;return -1;}return 0;
}
当我们不在乎子进程的退出状态,可以直接传递一个空指针。
wait(nullptr);
cout << "FATHER END" << endl;
当然除了用wait函数,还有一个解法:使用signal(SIGCHLD,SIG_IGN);表示忽略子进程的信号,便会直接释放数据结构。和传递一个空指针没什么太大关系。
补充:
如果父进程还在运行,有一些子进程变成了僵尸进程,父进程结束之后,子进程依旧会被托管,然后释放。
筛选输出
(一个输出的命令) | grep xxx
这个指令依次解释:
|:代表将前面的命令变成后面命令的输入
grep:筛选作用,后面跟上一个名字xx,代表找寻含有xx的内容
所以找一个符合要求的进程,就可以使用
ps -ef | grep xxxx
-
ps
是一个用于显示当前系统上运行的进程状态的命令。 -
-e
选项表示显示所有进程。 -
-f
选项表示使用完整的格式显示进程信息。
多进程与信号
kill命令经常使用,但在c中可以使用kill函数向其他进程发送信号。
int kill(pid_t pid,int sig)
向pid进程发送sig信号:
- 当pid>0时,便是指定那个pid的进程发送sig
- 当pid=0时,便是向与目前进程属于同一个组的所有进程发送sig(包括当前进程本身)
- 当pid=-1,则是向所有的进程发送信号
先上代码:
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;void faExit(int sig)
{signal(SIGINT, SIG_IGN);signal(SIGTERM, SIG_IGN);cout << "父进程退出,id是" << sig << endl;kill(0, SIGTERM);exit(0);
}void chldExit(int sig)
{cout << "子进程" << getpid() << "退出,id是" << sig << endl;exit(0);
}
int main()
{for (int i = 1; i <= 64; i++) {signal(i, SIG_IGN);}signal(SIGINT, faExit);signal(SIGTERM, faExit);while (1) {pid_t pid = fork();if (pid > 0) {sleep(5);}else {signal(SIGTERM, chldExit);signal(SIGINT, SIG_IGN);while (1) {cout << "子进程" << getpid() << "在running..." << endl;sleep(2);}}}return 0;
}
代码中,父进程每一次都会创建一个新的父进程(也就是自己)和子进程,而子进程会进入最里面的while1循环,开始输出running。
在代码中,我们现将所有的sig都忽视了,防止别的命令对其产生影响。
然后我们使用kill函数遵循一个原则:杀死子进程可以直接杀,杀死父进程需要把子进程杀光,再杀自己。
我们假设用SIGTERM信号。
那么杀死父进程的操作,我们需要kill(0, SIGTERM); exit(0);
,但是如果这样做了,我们同时也把自己杀了,没有保证先后序。所以我们要防止SIGTERM信号杀死自己,要在执行这一段之前先用SIG_IGN来忽视掉,就好了。
那么接下来处理杀死子进程的操作,我们只需要直接exit
就好了。
此刻小tip:我们要在进入子进程的循环输出之前,先把信号的处理函数修改了,不然处理函数还会是父进程的处理函数。