进程终止

通常情况下,要终止一个进程,要么在程序中调用exit()要么等待程序退出main函数。

还有一种异常情况,就是收到一个signal。例如:一个进程对于接收SIGBUS(总线异常), SIGSEGV(段错误), 和SIGFPE(浮点溢出)信号的默认处理都是终止该进程。

在终端按Ctrl+c 会产生一个SIGINT发送给正在运行的进程;在程序中调用abort会给自己发送一个SIGABRT信号。这些都会使接受信号的进程终止(对信号默认处理)。SIGTERM需要kill命令行发送,收到的进程默认终止。终止进程最强有力的信号是SIGKILL,只要进程收到该信号不会出现阻塞,会立刻终止。

kill命令行可以发送任何signal,只要携带不同参数便会发送指定的signal.如:kill -KILL pid
也可以在程序中调用kill函数去终止子进程,如:kill(child_pid,SIGTERM);返回值为0表示正确,非0错误。

1.等待进程终止

linux 系统是多任务的,当在程序中创建一个子进程,无法预知父进程和子进程哪个先被系统调度,有可能是同时运行。如果要得知子进程终止时返回的状态,就必须用一种方式 让子进程在父进程之前终止。
针对这个问题,linux提供了一组家族函数:wait. 这个函数可以等待子进程执行完毕,并且获取子进程的终止状态,释放子进程资源。根据wait家族函数,可以获取已经终止进程的信息,也可以用来等待某个进程终止。
wait家族中最简单的方法就是wait,调用之后程序阻塞,直到某个子进程终止。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int spawn(char* program,char** arg_list)
{
        pid_t child_pid;
        child_pid = fork();
        if(child_pid!=0)
        {
                printf("This is the parent process.\n");
                return child_pid;

        }
        else
        {
                printf("This is the child process.\n");
                execvp(program,arg_list);
                fprintf(stderr,"an error occurred in execvp.\n");
                abort();
        }
}
int main()
{
        int child_status;
        char* arg_list[]={
                        "ls",
                        "-l",
                        "/",
                        NULL};
        spawn("ls",arg_list);
        wait(&child_status);
        if(WIFEXITED(child_status))
        {
                printf("the child process exited normally,with exit code %d\n",WEXITSTATUS(child_status));
        }
        else
                printf("the child process exited abnormally\n");


       return 0;
}

调用wait会阻塞,直到子进程终止或者出错才会返回。WIFEXITED可以根据子进程返回的状态child_status判断子进程是正常终止还是异常终止(如:收到signal默认处理)。WEXITSTATUS根据child_status得到子进程结束时返回code。
wait家族函数介绍:
waitpid : 指定一个子进程,专门等待这个子进程而不是等待所有的子进程。
wait3 : 可以返回终止子进程之后CPU的统计数据
wait4 : 返回指定的子进程状态改变,并且会返回关于子进程使用资源的信息

2.僵尸进程
如果在程序中调用wait,当子进程终止后会将状态信息返回给wait,并且清除资源。如果在程序中没有调用wait,子进程结束之后所有的状态都会丢失,分配的资源也没有完全释放,这样的进程称为僵尸进程(Zombie Process)。
当一个进程结束后,对于资源清除工作是它父进程的责任。当子进程在wait之前结束会成为僵尸进程,执行到wait时会获取到结束的状态并清除子进程资源;如果调用wait时子进程还没终止,wait会阻塞知道子进程结束。

3.异步清除子进程

在程序中存在以下两个问题:
1)wait在程序中调用早了会出现阻塞,之后的程序不能尽快运行,如:

int main()
{
		int count=0;
	    pid_t child_pid;
        child_pid = fork();
        if(child_pid!=0)
        {
            printf("This is the parent process.\n");
        }
        else
        {
                printf("This is the child process.\n");
                execvp(program,arg_list);
        }
		wait(&child_status);
		count++;
		return 0;

}
如果需要在子进程结束前执行count++;是不可能的,只有等到子进程结束wait才返回。

2)wait在程序中调用晚了会出现部分短暂的僵尸进程,短暂的占用资源,如:

int main()
{
		int count=0;
	    pid_t child_pid;
        child_pid = fork();
        if(child_pid!=0)
        {
            printf("This is the parent process.\n");
        }
        else
        {
                printf("This is the child process.\n");
                execvp(program,arg_list);
        }
		data_compute();
		wait(&child_status);
		return 0;
}

data_compute();是一个数据处理函数,需要占用很多的资源。如果子进程在data_compute();调用前已经结束了,但成为僵尸进程资源没有被释放,会导致data_compute();对资源紧缺。

解决以上问题有两种方法:一种是调用wiat3,wait4用标志位WNOHANG实现非阻塞,可以在合适的地点调用。


另一种是用signal,当子进程终止时会发给父进程一个SIGCHLD信号,父进程可以在SIGCHLD信号处理函数中调用wait清理子进程。
以下为一个使用SIGCHLD进行wait子进程的程序:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <signal.h>

sig_atomic_t child_exit_status;
void clean_up_child_process(int signal_number)
{
        int status;
        wait(&status);
        child_exit_status = status;
        printf("The singnal_number is :%d\n",signal_number);
        printf("The child exit stauts:%d\n",WEXITSTATUS(status));
}
int spawn(char* program,char** arg_list)
{
        pid_t child_pid;
        child_pid = fork();
        if(child_pid!=0)
        {
                printf("This is the parent process.\n");
                return child_pid;

        }
        else
        {
                printf("This is the child process.\n");
                execvp(program,arg_list);
                fprintf(stderr,"an error occurred in execvp.\n");
                abort();
        }
}
int main()
{       struct sigaction sigchld_action;
        memset(&sigchld_action,0,sizeof(sigchld_action));
        sigchld_action.sa_handler = &clean_up_child_process;
        sigaction(SIGCHLD,&sigchld_action,NULL);
        printf("SIGCHLD:%d\n",SIGCHLD);
        char* arg_list[]={
                        "ls",
                        "-l",
                        "/",
                        NULL};
        spawn("ls",arg_list);
        sleep(10);
       return 0;
}

子进程的退出状态child_exit_status在信号处理函数中赋值,为了在访问时不被打断,这里用原子类型sig_atomic_t

你可能感兴趣的:(信号,僵尸进程,终止进程)