目录
1. 进程终止场景
1.1 进程退出码
1.2 进程常见退出方式
2. 进程等待
2.1 进程等待的必要性
2.2 进程等待的方式
wait()方式
waitpid()方式
options参数
status参数
1. 进程终止场景
代码运行完毕,结果正确
代码运行完毕,结果不正确
代码异常终止
进程终止也就是我们通常理解的进程退出了,但是我们又怎样理解上面的三句话呢?什么叫做代码运行完毕结果正确或者不正确?难道是我们每一次都需要自己去调试?有或则是每一个都需要通过IO接口,将程序结果调试出来然后在判断程序是否正确?答案很明显不是的,往下将会给你结果。
1.1 进程退出码
首先,咱们得明白一个概念,判断是一个代码运行完毕结果是否正确,肯定不会是我们之前所想的那样,将程序以printf、cout或者调试的方法用认为的方式判断是否正确,因为对于计算机而言,我们判断是否正确根本没有意义,因为它的运行速度太快了,如果有其它进程需要这个进程的退出信息,难道它会等我们告知一个答案吗?不会的。所以编程语言出现了——进程退出码。
大家第一次看到进程退出码可能会认为这是一个很厉害,很牛逼的东西,其实不是,我们平时练习C语言和C++时都有用到它,只是大伙不知道到底是干嘛的罢了。如下:
int main()
{
return 0;
}
上方代码的0就是进程退出码之一,表示程序正确执行,是否感到了一丝丝无语伙伴们,是不是以为是什么呢?哈哈。不过无语归无语,这个知识点却是很多知识点的桥梁,还是得认真了解的。
0表示程序执行完了结果正确,其余数字都表示结果不正确,为什么?就因为C语言规定了?这玩意你不说谁会懂?我举一个例子:
假如你和你女朋友说话,你女朋友问你“你爱不爱我”,你说“0”,然后你女朋友就觉得莫名其妙,然后再问你“你是不是有病?我问你话呢”,然后你又回了“3”,你女朋友气不过,她认为你在耍她,最后就气跑了。你冤不冤枉,你有好好回答哇,只是她不懂哇。
上面这个故事就表明了,我不管你是啥,我问你东西,你就必须得用我知道的方式来回答,否则我生气,然后把你踢了。所以对于计算机而言,我们就是这个女朋友,他需要用我们知道的方式来回答,所以string.h库中有一个strerror函数,可以将我们的进程退出码的信息以文字方式表示出来。如下:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<sys/types.h>
4 #include<string.h>
5 #include<unistd.h>
6
7 int main()
8 {
9 int i = 0;
10 for(i = 0;i<130;++i)
11 {
12 printf("我的进程退出码是:%d,退出信息是:%s\n",i,strerror(i));
13 }
14
15 return 0;
16 }
上述为进程退出码的部分信息,可以看到0的退出信息是Success,我没骗人吧。我相信大家好好看一下是能看出某些信息自己在写Linux时是看到过的,比如说退出码2,退出码13。我以退出码为例,如下:
看到了没?知识串起来了,以前只是听过Linux是用C语言写的,但是不知道是怎么写的,但是这里有没有感觉?
1.2 进程常见退出方式
1. 从main返回
2. 调用exit
3. _exit
进程退出从main返回的意思就是通过return返回,注意,只有main函数的返回值才被称为进程退出,其它函数的return只是表示这个函数的功能结束了,并带回了一个返回值,接收到这个返回值的地方依旧是这个进程。
而我们的exit和_exit函数则没有这个限制了,它们能够在任意函数结束进程,使用方法如下:
exit("退出码");
_exit("退出码");
这样看起来,好像exit函数和_exit函数没有什么区别呢?
在大多数情况下,这两个函数并没有什么区别,但是还是有一定的区别的,请先看下图:
区别就是_exit是一个系统接口,而exit是stdio.h库提供的一个库函数,并且exit函数内部调用的_exit函数,且exit函数调用没有_exit函数暴力,它会先将前面执行的代码运行完了才结束进程,而_exit会直接退出进程,不管缓冲区的数据等。
int main()
{
printf("hello world");
exit(0); }
int main()
{
printf("hello world");
_exit(0);
}
exit();运行结果:
_exit()运行结果:
2. 进程等待
进程等待一般出现在父子进程之间,父进程等待子进程运行完毕,父进程在执行自己的代码。一般是在网络通信连接时有用。
2.1 进程等待的必要性
1. 子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
2. 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
3. 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
4. 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
上述的概念已经为我们清晰的解释了进程等待的必要性,关于僵尸进程就是子进程退出,但是父进程没有对它的资源做回收,也就是获取进程退出信息,那么子进程的进程状态就会变为Z,并且一直保持这个状态,没有谁能干掉它,它就一直占用系统资源。
2.2 进程等待的方式
wait()方式
wait()是一个在sys/wait.h库里面的函数,函数声明为:
pid_t wait(int*status);
返回值正确为被等待进程pid,不正确为-1,status参数为输出型参数,当我们不关心子进程的退出状态,那么就可以将其设置能为NULL。如下:
代码:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/wait.h>
4 #include<sys/types.h>
5 #include<stdlib.h>
6
7 void test1()
8 {
9 int cnt = 5;
10 while(1)
11 {
12 printf("我是子进程,我还能活%dS\n",cnt);
13 sleep(1);
14 --cnt;
15 if(cnt == 0)
16 exit(110);
17 }
18 }
19
20
21 int main()
22 {
23 pid_t id = fork();
24 if(id == 0)
25 {
26 test1();
27 }
28 pid_t subid = wait(NULL);
29 printf("我是父进程,我等待子进程%d完毕\n",subid);
30
31 return 0;
32 }
结果:
从代码中能够看到父进程的printf语句在子进程被执行完毕之后才执行了,也就表示父进程使用wait函数之后确实处于等待状态,也回收了子进程的信息,因为wait返回了正确的pid。
waitpid()方式
waitpid()是在sys/types.h库和sys/wait.h库中的函数,声明为:
pid_t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
该函数的使用方式和wait函数基本一致,但是它的参数options、和两个函数都有的status输出型参数我需要为大家讲解一下。
options参数
当我们将其输入为0时,此时函数的功能就是进程等待模式,也就是上述图中的模式,但是如果我们将其输入为WNOHANG,那么函数的功能就会变为轮询式等待,什么意思呢,先看代码:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/wait.h>
4 #include<sys/types.h>
5 #include<stdlib.h>
6
7 void test1()
8 {
9 int cnt = 5;
10 while(1)
11 {
12 printf("我是子进程,我还能活%dS\n",cnt);
13 sleep(1);
14 --cnt;
15 if(cnt == 0)
16 exit(110);
17 }
18 }
19
20
21 int main()
22 {
23 pid_t id = fork();
24 if(id == 0)
25 {
26 test1();
27 }
28 int status = 0;
29 pid_t subid = waitpid(id,&status,WNOHANG);
30 printf("我是父进程,我不想等子进程了,先运行了,之后在回收资源\n");
31 return 0;
32 }
上图中可以看出,我们通过WNOHANG参数,父进程就没有等到子进程执行完了再执行代码,而是先运行着自己的代码,子进程自己跑。我将一个故事,大家大概就能懂了。
小明和小芳是一对好朋友,一天小明想要邀请小芳一起出去玩,然后呢,他就打电话给小芳问她现在能不能出去?小芳说:等等我,我在洗澡呢,小明说:好,我等你,电话别挂。然后电话就一直打着,15分钟后,小芳说:我好了。然后挂断了电话,小明和小芳就一起出去了。——————进程等待
还是小明和小芳,同样小明想要邀请小芳出去玩,小芳也还在洗澡,小明打电话问:还在洗吗?小芳说:嗯。然后电话挂了,小明洗了个脸,大概一分钟又问:还在洗?小芳说:嗯。如此循环,直到小芳说出洗完了,才不会继续打电话问了。——————轮询式等待
上述的两个故事就对应了我们进程的等待方式。小明就是父进程、小芳就是子进程。
status参数
什么叫做输出型参数呢?输出型参数就是指,我们只需要传一个地址进入就行,然后其它函数拿到这一个空间,对其进行数值修改,在原函数就能拿到这个值了。
status是一个整型变量,他也是一个位图,意思就是他用一个变量存了进程退出码和异常信息的值,它的高16位没有被使用,次高8位用于存进程退出码,低7位用于存异常信息,还有第8位用于存core dump信息。如下:
printf("我是父进程,子进程的退出码为%d,异常信息为%d\n",(status>>8)&0xFF,status&0x7F);
该方式就是我们的位图,使用方式就是位运算。
值得一提的是,当子进程有一个错误,那么就不会有退出码,也就是说——程序无异常->程序执行是否正确。
n /= 0; 错误语句
程序崩溃,子进程没有成功运行,退出码为0,异常信息为8。
以上就是我对进程终止和进程等待的全部理解了,还请大家多多支持哇,谢谢。