int main()
{
return 10;
}
但是由于退出码都有特定的含义,所以不建议随便修改。
exit()与_exit() exit()和exit()也可以控制进程退出,其中括号中的内容为退出码。两者的区别在于:exit()退出后缓冲区会被刷新,而_exit退出后缓冲区不会进行刷新。
printf("hello exit");
sleep(5);
exit(1);
printf("wrong\n");
printf("hello exit");
sleep(5);
_exit(1);
printf("wrong\n");
其中使用exit退出可以打印出hello exit,因为刷新了缓冲区;而使用_exit退出没有打印出该字符串,因为没有刷新缓冲区。 这里将进程的退出码设为了1,可以在终端自行验证。 注意,虽然没有刷新缓冲区,操作系统也一定将缓冲区中的内容释放掉了,否则会造成内存泄漏。
2.进程等待 (1)进程等待的原因 进行进程等待的通常是父进程,父进程需要等待子进程执行结束之后再进行进程退出。
1.通过获取子进程退出的信息,来获得子进程退出的结果。 2.可以保证时序问题,子进程先退出,父进程后退出。 3.为避免子进程进入僵尸状态,父进程需要等子进程结束后回收子进程的资源。
(2)wait函数 我们在父进程中使用wait函数来进行进程的等待。下面我们将介绍两个函数来实现进程的等待。
返回值与参数 等待成功,wait将返回子进程的id,如果等待失败,wait会返回-1。 wait的参数status和waitpid的status表示一个意思,在下面会西索。
举例 父进程可以通过进程等待来达到回收僵尸进程的目的:
pid_t id=fork();
if(id==0)
{
int cnt=5;
while(cnt)
{
printf("child:%d is running,cnt is %d\n",getpid(),cnt);
cnt--;
sleep(1);
}
exit(0);
}
else
{
sleep(10);
printf("father wait begin\n");
pid_t ret=wait(NULL);
if(ret>0)
{
printf("father wait %d\n",ret);
}
else
{
printf("wait fail");
}
printf("wait success\n");
sleep(10);
}
这段代码的目的是:先让子进程运行5s,父进程休眠10s,这就导致了子进程在前5s是运行态,在后5s是僵尸状态(父进程在休眠,无法进行资源的回收)。当父进程进行进程等待的时候,回收了子进程的资源,此时子进程死亡。父进程在10s再退出。 当我们不进行进程等待的时候,父进程执行结束,子进程会变成孤儿进程被操作系统领养。 如果进行进程等待,子进程就不会被领养,资源被父进程回收后再死亡。 通过简单的命令行脚本我们可以进行观察。 wait其实有两种作用,1是等待子进程结束后回收,2是通过status获得子进程的信息。
(3)waitpid函数 返回值与参数 wait的返回值也是一个pid_t类型:
1.正常情况下,返回子进程的pid。 2.如果设置了选项WRONGHANG,而调用waitpid发现没有子进程可以被回收,则返回0。 3.如果嗲破蛹出错则返回-1。 waitpid有三个参数:
pid 一个进程可以有多个子进程,wait函数等待的是所有子进程执行结束,而waitpid可以通过参数pid来指定要结束的进程。 当pid为-1时,代表等待所有的进程结束,与wait相同。
pid_t ret=waitpid(id,NULL,0);
只需要将上面进程等待的代码改成这样,由于父进程中的fork返回值是子进程的id,赋值给了变量id,所以这里表示的是等待子进程执行结束。
status status的值 status是一个指针类型,是一个输出型参数。输出型参数的意义在于在waitpid中修改了status的值是通过指针修改的,对函数外界是有影响的。我们其实就是通过status来获得进程退出的结果的。 因此我们需要自己定义一个int类型的数据,并向status传入它的地址,然后通过该数据来获得进程退出的结果。 我们可以将子进程的退出码改为20,然后打印一下status的值来进行观察:
pid_t id=fork();
if(id==0)
{
int cnt=5;
while(cnt)
{
printf("child:%d is running,cnt is %d\n",getpid(),cnt);
cnt--;
sleep(1);
}
exit(10);
}
else
{
// sleep(10);
int status=0;
printf("father wait begin\n");
pid_t ret=waitpid(id,&status,0);
if(ret>0)
{
printf("father wait %d\n",ret);
printf("status is %d\n",status);
}
else
{
printf("wait fail");
}
printf("wait success\n");
// sleep(10);
}
此时我们发现status的值是2560,那么这个数字代表什么呢? 首先我们要明确,status是反映子进程退出时的状态的,而进程退出有三种情况:正常退出,结果正确;正常退出,结果错误;程序崩溃退出。所以status其实是反映这三种情况的。
status的构成 status指向的内容有32个比特位,但是只有低的16位有意义,高的16位没有意义。 程序崩溃即代码异常终止,本质上是收到了一个终止的信号,waitpid会首先判断是否是接收到终止信号导致进程结束,如果是则直接对status指向的内容进行修改。 其中终止信号占7位,coredump占一位。 如果不是程序异常终止,则status进行如下赋值: 因此我们就可以知道2560的由来了,退出码是10,status就是0000 1010 0000 0000的值刚好是2560。
通过status获得退出码 知道了status的构成,我们就可以通过移位操作,通过status来获得程序的退出码和退出信号。
(status>>8)&0xFF//获得退出码
status&(0x7F)//获得退出信号
了解了这一原理,我们还会得出这样的结论:bash就是通过wait方法获得的各个退出码,因此我们可以才能通过echo $?来获得各个进程的退出码。 我们发现如果要获得退出码还需要进行移位操作,非常的不方便,因此大佬们又引入了两个宏,来帮助我们获取退出码:
WIFEXITED(status):若为正常退出,则返回非0。 WEXITSTATUS(status):如果WIFEXITED为非0,则提取子进程的退出码。
if(WIFEXITED(status))
{
printf("father wait %d\n",ret);
printf("statusexit is %d\n",WEXITSTATUS(status));
}
此时可以打印到退出码10:
options waitpid的第三个选项options有两种取值:一种是0,一种是WNOHANG。 其中0表示的是阻塞等待,而WNOHANG表示的是非阻塞等待。 阻塞等待指的是父进程在等待子进程结束的期间内,不进行任何操作。而非阻塞等待指的是父进程在等待子进程结束的期间内不断调用waitpid的过程。(注意返回值条件,当设置WNOHANG且子进程没有结束时,返回值为0,否则返回子进程的pid)。 当取值为0,即为阻塞等待时,本质上其实就是将父进程由运行队列转移到等待队列。等子进程执行结束之后再将父进程由等待队列转移到运行队列。之前的例子都是阻塞赋值的例子。 我们也可以验证一下非阻塞赋值:
while(1)
{
pid_t ret=waitpid(id,&status,WNOHANG);
if(ret==0)
{
printf("do my things\n");
}
else if(ret>0)
{
printf("wait success\n");
break;
}
else
{
printf("wait failed\n");
}
sleep(1);
}
我们可以通过循环来使用非阻塞等待,帮助父进程完成其他内容。
3.总结 1.进程结束有三种原因。使进程结束有两种主要的方法。 2.进程等待有两个主要的函数,并且等待分为阻塞等待和非阻塞等待。 3…三连叭球球了。