一、结束进程
void exit(int value);
void _exit(int value);
exit()和_exit()函数功能一样,主要的区别在于:exit()是标准库函数,会刷新缓冲区,_exit()是系统调用,不会刷新缓冲区。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("hi, lh, you are so good"); // 打印,没有换行符"\n"
exit(0); // 结束进程,标准库函数,刷新缓冲区,printf()的内容能打印出来
// _exit(0); // 结束进程,系统调用函数,printf()的内容不会显示到屏幕
while(1); // 不让程序结束
return 0;
}
二、等待进程结束
在每个进程退出的时候,内核会释放该进程所有的资源,包括打开的文件,占用的内存等,但是仍然会为其保留一定的信息,这些信息主要是指进程控制块信息(包括进程号、退出状态、运行时间等)。如果父进程不回收子进程的资源,就会导致僵尸进程的产生。
当一个进程正常或者异常终止时,内核就会向父进程发送SIGCHLD信号,相当于告诉父进程他哪个子进程挂了。而父进程可以调用wait()或waitpid()函数等待子进程结束,获取子进程结束时的状态,同时回收他们的资源。
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
//status进程退出时的状态信息,如果不关心终止状态可指定为空指针
//pid有四种情况:
//1.pid==-1 等待任意子进程
//2.pid>0 等待进程ID与pid相等的子进程
//3.pid==0 等待组ID等于调用进程组ID的任意子进程
//4.pid<-1 等待组ID等于pid绝对值的任意子进程
//options控制waitpid的操作:
//1,2是支持作业控制
//1.WCONTINUED
//2.WUNTRACED
//wait/waitpid的返回值
//正常都是返回结束的子进程的pid,但是waitpid稍微复杂一点:
//(1) 如果设置了WNOHANG,而在调用的时候发现没有子进程退出,返回0
//(2) 如果调用出错,返回-1,这是errno会被设置成相应的错误值
//status中包含了多个字段,直接使用没有意义,下面的两个宏取出其中的每个字段
WIFEXITED(status); //如果进程是正常终止的,取出的字段值非零
WEXITSTATUS(status); //返回子进程的退出状态,也就是子进程exit(value)的value的值
三、避免僵尸进程
进程已经结束,但进程占用的资源未被回收,这样的进程称为僵尸进程。
当我们fork()一次后,存在父进程和子进程。这时有两种方法来避免僵尸进程:
1) 父进程调用wait()/waitpid()等函数来接收子进程退出状态
2)父进程先结束,子进程则自动托管到init进程(pid = 1)
第一种情况使用wait()/waitpid()回收
子进程先于父进程结束,当父进程回收之前,子进程将处于僵尸状态
子进程结束前,父进程阻塞在wait调用
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
int main(int agc, char *argv[])
{
pid_t pid;
if ((pid = fork()) < 0)
{
printf("fork error.\n");
return -1;
}
else if (pid == 0) //子进程
{
printf("in child.\n");
sllep(2);
}
else
{
int stat;
pid_t pid = wait(&stat);
printf("child exit stat:%d.\n", stat);
}
return 0;
}
在TCP Server中,通常父进程都是一直存在,因此需要用wait()/waitpid()来进行回收,但是由于wait函数会阻塞父进程,所以一般是通常注册信号处理函数,通过非阻塞的方式来处理。因为前面说过,子进程退出时会给父进程发送SIGCHLD信号。
void sig_child(int signo)
{
int stat;
pid_t pid;
while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
{
printf("child exit:%d.\n", pid);
}
}
typedef void (*signal_handler_t)(int); //定义函数指针
int main()
{
//...
signal_handler_t sig_handler1 = sig_child;
signal(SIGCHILD, sig_handler1); //注册信号处理函数
//...
}
解释一下,为什么要用while循环,且为什么要用waitpid的非阻塞模式:
1、用while循环
如果在信号处理函数中,有子进程结束,通过while循环一次性回收
2、非阻塞模式
保证在回收最后一个终止的子进程后,没有子进程退出时,waitpid会出错返回,主进程从信号处理函数中跳出而不是阻塞在信号处理函数中。
第二种情况init进程负责回收
父进程先结束,这样子进程将托管给init进程,由init进程回收子进程。
如果一个进程fork一个子进程,但不要它等待子进程终止,也不希望子进程处于僵死状态直到父进程终止,实现这一要求需要用两个fork
int main(void)
{
pid_t pid;
if ((pid = fork()) < 0) //error
{
fprintf(stderr,"Fork error!\r\n");
exit(-1);
}
else if (pid == 0) //第一个子进程
{
if ((pid = fork()) < 0) //在第一个子进程中fork
{
fprintf(stderr,"Fork error!/n");
exit(-1);
}
else if (pid > 0) //第一个子进程,也就是第二个子进程的父进程
exit(0); /* parent from second fork == first child */
//直接退出
/*
* We're the second child; our parent becomes init as soon
* as our real parent calls exit() in the statement above.
* Here's where we'd continue executing, knowing that when
* we're done, init will reap our status.
*/
sleep(2);
printf("Second child, parent pid = %d\r\n", getppid());
exit(0);
}
if (waitpid(pid, NULL, 0) != pid) /* wait for first child */
{
fprintf(stderr,"Waitpid error!\r\n");
exit(-1);
}
/*
* We're the parent (the original process); we continue executing,
* knowing that we're not the parent of the second child.
*/
exit(0);
}