进程创建,进程等待,进程终止

inux下的进程控制:

1、进程创建,
2、进程等待,
3、进程终止

进程创建

进程创建被定义为通过父进程创建子进程的过程。

fork函数
函数原型:pid_t fork(void);

特点:
1.fork函数调用一次,返回两次两次返回值得区别分别是子进程当中的返回值为0,父进程当中的返回值为新建子进程的ID(将ID返回给父进程的原因是没有函数可以使父进程得到子进程的ID,这样会便于管理);
2.子进程被创建出来后,子进程是父进程的副本(子进程获得父进程数据空间,堆,栈的副本<一定注意是副本>);
3.父进程和子进程只共享正文段(.text);
4.由于fork之后经常跟随者exec(程序加载函数),所以现在很多操作系统的实现并不执行一个父进程的副本,而是使用了写时拷贝(这些区域由父子进程所共享,而且内核将这些区域的访问权限设定为只读权限,如果父进程和子进程中的任意一个区域试图被修改,则内核会为修改区域产生一个副本)。
5.fork之后父进程和子进程的执行顺序是随机的,取决于内核所使用的调度算法。
6.父进程和子进程的区别:
- fork的返回值不同
- 进程ID,父进程ID不同
- 子进程不继承父进程设置的文件锁
- 子进程未处理的闹钟被清楚
- 子进程未处理的信号集,被设置为空集

函数vfork

vfork函数也是创建进程,但是与fork函数不相同的是:
1.vfork创建一个新进程,而新进程的目的是exec一个新程序;
2.vfork并不将父进程的地址空间完全复制到子进程中;
3.子进程在调用exec函数或者exit函数之前,子进程在父进程的空间中运行;
4.vfork函数保证子进程先运行,在它调用exec或exit函数之后父进程才会被恢复运行。

进程终止

进程有五种正常终止方式和三种异常终止方式,他们分别是:
五种正常终止方式
1.在main函数内执行return 语句。它等效于调用exit;
2.调用exit函数,exit函数由C库定义,其操作包含调用各种终止处理程序,关闭所有标准I/O流等。
3.调用_exit函数,exit函数和_exit函数的不用地方就是它为进程提供了一种无需终止运行终止处理程序或信号处理程序而终止的办法。
4.进程的最后一个线程在其启动例程中执行return语句.(这个现在大家先了解即可,关于线程的知识后面会详细分析)
5.进程的最后一个线程调用pthread_exit函数。

三种异常终止方式
1.调用absort。
2.当进程接收到某些信号时。
3.最后一个线程对“取消请求作出响应”。

不管进程以上面的何种方式进行终止,都会执行内核中的同一段代码,这段代码为相应的进程关闭所有打开的描述符,释放进程所拥有的地址空间。 另外,对任意一种终止情形,父进程都希望得到所终止的子进程的相关退出信息,对于三个终止函数,都采用了输出型参数status得到子进程的退出状态和信息;在异常情况中,内核产生一个指示其异常终止原因的终止状态,另外,父进程可以用wait函数和waitpid函数取得子进程的终止状态。上面提到的都是子进程先被终止,然后父进程回收子进程的相关状态,这时被终止的子进程被称为僵死进程(注意父进程此时还没有被终止);但是如果父进程在子进程被终止前先被终止,此时我们称子进程为孤儿进程,这时子进程的父进程会变成ID号为1的init进程,这时孤儿进程的领养机制。

进程等待

关于进程等待我们首先应该想到这两个函数,wait和waitpid函数都是用来提供给父进程检测子进程的状态的,但两种函数也是有区别的,我们先来看看wait函数:

wait函数

1.函数原型:pid_t wait(int* status);
其中,status参数是前面提到的父进程用来监测子进程退出状态的输出型参数,我们将会在下面进项讲解;另外wait函数的返回值为pid_t ,可想而知,它返回的是一个进程标识符;

扫描二维码关注公众号,回复: 2165456 查看本文章

2.调用wait函数时会发生什么?
- 如果有子进程再运行,那么当前父进程就处于阻塞状态(可以理解为什么事情都没有做,一直在检测子进程是否在运行);
- 如果子进程都已经终止,那么wait可立即获得子进程的终止状态(退出码,退出信息),子进程的终止状态是体现在status参数上的,另外wait还会返回所终止的子进程的标识符;
- 如果当前进程没有任何子进程,那么wait会立即出错返回(此时返回值为-1);
- 如果有一个子进程终止,那么wait便返回;

下面我们来了解一下waitpid函数:

1.函数原型:pid _t waitpid(pid_t pid,int *status,int options);
参数解释
pid为监测子进程的标识符;
status:子进程的终止状态信息,如果不是空指针,则终止进程的终止信息就存放在它所指向的单元内,不关心终止状态可以将status制成NULL;
options:指定参数,默认情况下waitpid与wait做的事情都是一样的,为阻塞式监测子进程终止状态;当options=WNOHANG时,此时为非阻塞方式,就是监测时如果子进程没有终止,调用者可以做其他事情,另外还有两个参数分别为WCONTINUED和WUNTRACED,这两个参数是跟作业控制有关的,我们在那部分是在介绍。
return val:返回值与wait函数相同

2.与wait函数的不同点:

在一个子进程终止前,wait使调用者阻塞,而waitpid有一个选项可以使调用者不发生阻塞;
waitpid并不等待在其调用之后的第一个终止子进程,它有若干选项,并可以控制子进程
waitpid可以等待一个特定的进程,通过pid参数进行设定;
3.status的构成:我们现在不关心status参数的高16位,在低十六位中,次低8位反应了子进程终止时的退出码,低8位反应了子进程发生异常终止时的返回状态。

下面我们就来写段代码分析一下waitpid和status:

用信号杀死子进程(子进程不正常返回的情况)
这里写图片描述

子进程正常返回(有结束码)

4.检查wait和waitpid所返回的终止状态的宏

在上面我们是通过移位操作来获取到子进程返回的终止状态信息status,其实,Linux还为用户提供了四种宏定义,用来检查子进程所返回的终止状态。
WIFEXITED(status):表示若为正常终止子进程返回的状态,则为真;即该宏定义拿到status的低8位,如果低8位为0则表示子进程正常返回,如果低八位不为0则表示子进程异常返回;
WEXITSTATUS(status):这个宏相当于读取子进程的退出码,如果WIFEXITED(status)不为真,则这个宏的返回值将毫无意义;
WIFSIGNALED(status):这个宏表示若为异常终止子进程的返回状态,则返回真并接收到一个不捕捉的信号,他可以和WTERMSIG(status)结合使用,后者为获取子进程的终止的信号编号。
WIFSTOPPED(status):这个宏是用来判断若当前子进程的返回状态为暂停状态,则为真。对于这种情况,可以结合WSTOPSIG(status),获取使子进程暂停的信号编号,这个宏的用法和上面的很相似;
WIFCONTINUED(status):这个宏常用在作业控制暂停后已经继续的子进程返回了状态,则为真,现在我们先不讨论这个宏的使用;

我写段代码来使用一下前两个宏定义,大家可以试着使用下其他宏:

    int id=fork();
    int count=10;
    if(id==0)
    {
        //child proc
        while(count--)
        {
            sleep(1);
            printf("CHild proc.id:: %d\n",getpid());
        }
        exit(11);
    }
    else{
        //father proc
        int status=0;
        int ret=waitpid(id,&status,0);    //option = 0
        if(ret>0){
            if(WIFEXITED(status))
            {
                printf("child proc return normal,exit code:%d\n",\
                        WEXITSTATUS(status));
                printf("Child proc dead success,ret=%d,status=%d\n",\
                    (status>>8)&0xff,(status&0xff));
            }
            else{
                //retuen val is false
                printf("child proc return unnormal\n");
                if(WIFSIGNALED(status))
                {
                  printf("child proc is killed by signal,signal code is %d\n",WTERMSIG(status));
                }
                else{
                    printf("child proc is not killed bu signal\n");
                }
            }
        }
        else{
            printf("Child proc dead false,ret=%d\n",ret);
        }
        printf("Status::%d\n",status);
    }

5.关于waitpid的pid参数问题

pid==-1:表示等待任意子进程,相当于wait函数;
pid>0:表示等待进程ID与pid相等的进程;
pid==0:表示等在进程组ID等于调用进程组ID的任一子进程;
pid<-1:表示等待组ID等于pid绝对值的任一子进程;

6.关于waitpid函数的option参数问题

option参数可以使我们能进一步控制waitpid的操作,下面我们就来看一看:
option==0:表示执行waitpid函数的默认版本,将会导致调用者阻塞;
option==WNOHANG:表示由pid指定的子进程并不是立即能够返回的,可以使waitpid函数为非阻塞状态,此时如果没有子进程ID返回,那么这个函数的返回值为0;

下面我们来看看waitpid函数怎样实现非阻塞检测子进程功能:
代码如下:

 int id=fork();
    int count=10;
    if(id==0){
        //child proc
        while(count--){
            printf("I am child proc,ID:%d\n",getpid());
            sleep(1);
        }
        exit(9);        //exit code 
    }
    else{
        // father proc
        int status=0;
        int ret=0;
        do{
            printf("Do other thing and check child proc status\n");
            sleep(1);
        }while((ret=waitpid(id,&status,WNOHANG))==0);
        if(ret>0){
            printf("child proc dead\n");
            if(WIFEXITED(status)){
                printf("child proc dead normal.exit code:%d\n"\
                        ,WEXITSTATUS(status));
            }
            else{
                if(WIFSIGNALED(status)){
                    printf("child proc is killed by signal,signal code is \
                            %d\n",WTERMSIG(status));
                }
                else{
                    printf("child proc is killed by other things\n");
                }
            }
        }
    }

总结

这一篇我们提到了进程创建,进程等待,进程终止等进程控制方面的内容,关于进程控制,大家可以去看看《UNIX环境高级编程中的第八章》.另外,在进程控制中,我们如果没有特殊需要,尽量不要使用vfork函数,因为vfork函数和fork函数与父进程共用一块地址空间,如果子进程没有调用exit或者加载程序的话,另外在提取子进程的终止状态时,我们应该尽量使用宏来代替移位操作。

猜你喜欢

转载自blog.csdn.net/zl_8577/article/details/79471493