UNIX环境高级编程-第8章- 进程控制 - 一

8.3 fork 函数

在 UNIX 系统中,一个现有进程可以调用 fork 函数创建一个新进程。调用 fork 函数的进程称为父进程,由 fork 创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次,两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID首先看下fork函数的原型;

/* 创建进程 */  
  
/* fork 函数 */  
/* 
 * 函数功能:创建一个新的进程; 
 * 返回值: 
 * (1)在父进程中,返回新创建子进程的进程ID; 
 * (2)在子进程中,返回0; 
 * (3)若出错,则返回-1; 
 * 函数原型: 
 */  
#include <unistd.h>  
  
pid_t fork(void);  

        fork 函数调用一次,返回两个值,在父进程中,返回新建子进程的进程ID,因为一个父进程可能有多个子进程,没有获取子进程ID的函数,所以返回子进程的进程ID;在子进程中,返回0,因为子进程的父进程ID可以通过函数 getppid 获取;子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本(UNIX 系统是采用写时复制),意味着父子进程间不共享这些存储空间。UNIX将复制父进程的地址空间内容给子进程,因此,子进程有了独立的地址空间。fork 之后父进程和子进程的执行顺序是不确定的,根据内核所使用的进程调度算法来执行。

下面先看一个比较简单的例子:

#include "apue.h"  
#include <unistd.h>  
  
int main(void)  
{  
    pid_t pid;  
    int count = 0;  
  
    pid = fork();//创建一个子进程;  
  
    if(pid < 0)  
    {  
        err_sys("Created child process error.\n");  
        exit(-1);  
    }  
    else if(0 == pid)//在子进程中,返回0  
    {  
        printf("I am back in the child process, my ID: %d and my parent ID: %d\n",getpid(),getppid());  
        printf("the count is: %d\n",++count);  
    }  
    else //在父进程中,返回新建子进程的ID  
    {  
        printf("I am back in the parent process, my ID: %d and my parent ID: %d\n",pid,getpid());  
        printf("the count is: %d\n",++count);  
    }  
    exit(1);  
}  

输出结果:

I am back in the parent process, my ID: 10684 and my parent ID: 10683  

the count is: 1  

I am back in the child process, my ID: 10684 and my parent ID: 10683  

the count is: 1  

        从输出结果我们可以知道,调用一次fork函数,会返回两次,根据程序的输出,返回之后是先执行父进程,在父进程中,返回值是新建子进程的进程ID,所以此时pid的值即为新建子进程的进程ID,count的值增加1;当父进程结束后,调用子进程,在子进程中返回值是 pid=0,所以想要获取新建子进程的进程ID需要使用 getpid函数相当于获取当前进程的进程ID),注意:count的值依然为1,因为父进程和子进程的数据空间是独立的,所以父子进程的数据不相互共享,count的初始值都还是0

接着看第二个例子:

#include <unistd.h>  
#include <stdlib.h>  
#include "apue.h"  
int main(void)  
{  
    pid_t pid;  
    int count = 0;  
    printf("before fork,enter\n");  //有换行符只输出一次
  
    printf("before fork,no enter:pid=%d",getpid());  //没有换行符,输出两次
    pid = fork();  
    if(pid < 0)  
    {  
        err_sys("Created child process error.\n");  
        exit(-1);  
    }  
    else if(0 == pid)//在子进程中,返回0  
    {  
        printf("\n");  
        printf("I am back in the child process, my ID: %d and my parent ID: %d\n",getpid(),getppid());  
        printf("the count is: %d\n",++count);  
        printf("\n");  
    }  
    else //在父进程中,返回新建子进程的ID  
    {  
        printf("\n");  
        printf("I am back in the parent process, my ID: %d and my parent ID: %d\n",pid,getpid());  
        printf("the count is: %d\n",++count);  
        printf("\n");  
    }  
    exit(1);  
}  

输出结果:

before fork,enter  
before fork,no enter:pid=11384  
I am back in the parent process, my ID: 11385 and my parent ID: 11384  
the count is: 1  
  
before fork,no enter:pid=11384  
I am back in the child process, my ID: 11385 and my parent ID: 11384  
the count is: 1  

从结果可以看到,printf("before fork,enter\n");  有换行符的 printf语句输出一次,就是说只在一个进程中输出;

printf("before fork,no enter:pid=%d",getpid());  没有换行符的 printf 语句输出二次,就是说在每一个进程都输出,这里只有两个进程,所以输出两次,多个进程会输出多次;

出现以上 printf 输出不同的原因很简单,因为 printf 是把数据存储在缓冲区中,在缓冲队列等待输出,若遇到换行符(行缓冲)或者刷新缓冲区时,会直接打印到屏幕。所以第一个语句带有换行符时是直接把数据打印到屏幕,不再缓冲队列等待输出,因此,子进程复制父进程的数据段时,stdout输出缓冲区并不存在数据,则只在父进程输出一次;没有换行符的 printf 函数把数据保存在缓冲区队列中,子进程把该数据复制,因此,父、子进程各自输出一次。

8.4 vfork 函数

vfork 函数也是在现有的进程上创建子进程,而该新进程的目的是exec一个新程序。基本操作和fork 函数类似,与 fork 的区别如下:

(1)fork 的子进程的数据是复制父进程的数据,即数据独立

(2)vfork 的子进程和父进程共享数据

(3)fork 子进程和父进程的执行顺序根据内核进程调度算法决定;

(4)vfork 的执行顺序是,先执行子进程,再执行父进程

例如例子1使用 vfork 创建子进程时,输出结果跟上面的不一样;

#include "apue.h"  
#include <unistd.h>  
  
int main(void)  
{  
    pid_t pid;  
    int count = 0;  
  
    pid = vfork();//创建一个子进程;  
  
    if(pid < 0)  
    {  
        err_sys("Created child process error.\n");  
        exit(-1);  
    }  
    else if(0 == pid)//在子进程中,返回0  
    {  
        printf("I am back in the child process, my ID: %d and my parent ID: %d\n",getpid(),getppid());  
        printf("the count is: %d\n",++count);  
    }  
    else //在父进程中,返回新建子进程的ID  
    {  
        printf("I am back in the parent process, my ID: %d and my parent ID: %d\n",pid,getpid());  
        printf("the count is: %d\n",++count);  
    }  
    exit(1);  
}  

输出结果:

I am back in the child process, my ID: 12171 and my parent ID: 12170  
the count is: 1  
I am back in the parent process, my ID: 12171 and my parent ID: 12170  
the count is: 2  

从结果中可以知道, vfork 函数创建的子进程是和父进程共享数据的,看 count 值的输出就知道,并且是先执行子进程,再执行父进程

8.5 exit函数

在7.3节中介绍了进程退出的八种方式,分别为:

1:从  main  函数返回

2:调用  exit  函数

3:调用  _exit  函数或者是  _Exit  函数

4:从最后一个线程中返回

5:从最后一个线程中调用  pthread_exit

还有三种非正常的结束方式:

6:调用  abort

7:接收到信号

8:应答最后一个线程的取消请求

以下就各个函数做比较详细的解释:

1:执行return从main函数返回等同于调用exit函数。

2:调用exit函数,这个函数有ISO C所定义,包括调用调用所有的被atexit注册过的退出处理程序和关闭所有的标准I/O流。因为ISO C不会处理文件描述符、多进程、作业控制,所以在Unix系统中,这个函数的定义有些不完整。

3:ISO C定义了一个_Exit函数来提供进程结束时,不用执行退出处理程序和信号处理 程序。在Unix系统中,_Exit_exit同义,都不用 刷新标准I/O 。 _exit函数由POSIX.1所定义。  

在大多数 Unix系统中exit函数是标准的C库函数,_exit是系统调用

4:线程的返回值不作为进程的返回值,当最后一个线程从例程返回时,进程结束时的结束状态值为0。

5:和4中的一样,这种情况下同样返回0,与传递到pthread_exit函数中的参数无关。

不管一个进程如何结束,内核都会为这个结束的进程关闭所有的打开的件描述符,释放所利用的内存等。

如果我们想要结束的进程通知父进程自己是如何结束的,对于exit 、 _Exit和_exit ,通过传递退出状态值,作为函数的参数来实现。

对于非正常的结束方式,内核(而不是结束进程),产生一个指示其异常终止原因的终止状态。在任意一种情况下,该进程的父进程可以通过 wait和waitpid函数 来获取结束进程的终止状态值。

二:为什么任何一个进程都有父进程

当调用一个fork函数后,子进程就会有一个父进程。当父进程先于子进程结束时,init进程会成为这个子进程的父进程。

具体的实现如下,通常情况下,在一个进程结束时,内核会遍历所有的活动进程,看是否有结束进程的子进程。如果有子进程,则把该子进程的父进程的ID修改为1(init进程的PID),这样保证了所以的进程都有父进程。

三:僵死进程的产生

在子进程先于父进程结束的情况下,当父进程要检查一个子进程是否结束时,子进程完全消失了,这时父进程不能取得结束子进程的结束状态信息。

在一个进程结束时,内核会保存保存每个结束进程的一定量信息。这些信息最少包含,进程ID,终止状态,进程使用的CPU时间。当父进程调用wait或者是waitpid时,可以得到这些结束信息。在Unix系统术语中,如果一个进程结束了,但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放它仍占用的资源)的进程被称为僵死进程。

如果写一个长时间运行的程序,在这个程序中fork了很多子进程,除非父进程等待取得子进程的终止状态,否则这些子进程就会变成僵死进程。

如果一个被init 进程领养的进程终止时他是否会成为僵死进程,答案是否定的。因为init的任何一个子进程结束时,init会调用wait函数来取得结束状态值。从而避免僵死进程的产生。从上可以看出,在 子进程先于父进程 结束,而父进程没有wait这个子进程才会产生僵死进程。

 

8.6 wait 函数和 waitpid 函数

当一个进程正常或异常终止时,内核会向其父进程发送 SIGCHLD 信号。父进程可以为这个信号提供一个信号处理程序,也可以选择忽略,系统对这种信号默认是忽略。当进程调用 wait 或 waitpid 函数可能会发生以下的情况:

(1)如果其所有子进程都还在运行,则发生阻塞。

(2)如果一个子进程已经终止,正在等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。

(3)如果没有任何子进程,则立即出错返回。

注意:如果进程是由于接收到 SIGCHLD信号而调用 wait 函数,则可期望 wait 立即返回;但是如果是在任意时刻调用 wait,则进程可能会发生阻塞。

如果一个子进程已经终止,并且是一个僵尸进程,则wait立即返回并取得该子进程的状态,否则wait使其调用者阻塞直到一个子进程终止。如果调用者阻塞而且它有多个子进程,则在其一个子进程终止时,wait就立即返回。因为wait返回终止子进程的进程ID,所以它总能了解是哪一个子进程终止了。

/* 等待进程终止 */    
/* 
 * 函数功能:等待一个进程终止; 
 * 返回值:若成功则返回进程ID、0;若出错则返回-1; 
 * 函数原型: 
 */  
#include <sys/wait.h>  
  
pid_t wait(int *statloc);  
  
pid_t waitpid(pid_t pid, int *statloc, int option);  
  
/* 
 * 说明: 
 * 参数statloc是指向存放终止进程的终止状态单元的指针;如果不在意这些信息,则可设为NULL; 
 * pid和option参数是控制waitpid操作; 
 */  
/* 
 * pid参数及作用如下 
 * pid == -1;   等待任一子进程,此时waitpid和wait功能一样; 
 * pid > 0;     等待其进程ID与pid相等的子进程; 
 * pid == 0;    等待其组ID与调用进程组ID相等的任一子进程; 
 * pid < -1;    等待其组ID与pid绝对值相等的任一子进程; 
 */  
/* 
 * 参数option可以是0,也可以是以下参数按位"或"组成: 
 * WCONTINUED   若实现支持作业控制,由pid指定的任一子进程在暂停后已经继续,但其状态尚未报告,则返回其状态; 
 * WNOHANG      若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值0; 
 * WUNTRACED    若某实现支持作业控制,而由pid指定的任一子进程已处于暂停状态,并且其状态自暂停以来还未报告,则返回其状态; 
 */  

这两个函数的区别如下:

(1)在一个子进程终止之前,wait函数使其调用者阻塞,而 waitpid 有一个选项 option,根据选项的不同值可使其调用者不发生阻塞。

(2)waitpid 并不一定要等待在其调用后的第一个子进程终止,根据不同选项,可以控制自己感兴趣要等待的进程。

waitpid 提供了 wait 没有提供的三个功能:

(1)waitpid 可以等待一个特定的子进程,而wait 则返回任一终止子进程的状态;

(2)waitpid 提供一个非阻塞的 wait 版本;

(3)waitpid 支持作业控制;

可以用以下宏查看进程的终止状态:

/* 检查 wait 和 waitpid 所返回的终止状态的宏..... 
 * WIFEXITED(status)    若为正常终止子进程返回的状态,则为真;对于这种情况可执行WEXITSTATUS(status), 取子进程传给exit,_exit或_Exit参数的低8位; 
 * 
 * WIFSIGNALED(status)  若为异常终止子进程返回的状态,则为真;对于这种情况可以执行WTERMSIG(status),取使子进程终止的信号编号;另外,有些实现定义宏WCONREDUMP(status),若已产生终止进程的core文件,则返回真; 
 * 
 * WIFSTOPPED(status)   若为当前暂停子进程的返回状态,则为真;对于这种情况可执行WSTOPSIG(status),取使子进程暂停的信号编号; 
 * 
 * WIFCONTINUED(status) 若在作业控制暂停后已经继续的子进程返回的状态,则为真; 
 * 
 */  

测试程序:

#include <sys/wait.h>  
#include "apue.h"  
  
void pr_exit(int status)  
{  
    if(WIFEXITED(status))  
        printf("normal termination, exit status = %d\n", WEXITSTATUS(status));  
    else if(WIFSIGNALED(status))  
        printf("abnormal termination, signal number = %d%s\n", WTERMSIG(status),  
#ifdef WCOREDUMP  
                WCOREDUMP(status) ? "(core file generated)" : " ");  
#else  
    " ");  
#endif  
  
    else if(WIFSTOPPED(status))  
        printf("child stoped, signal number = %d\n", WSTOPSIG(status));  
}  
  
int main(void)  
{  
    pid_t pid;  
    int status;  
  
    if((pid = fork()) < 0)  
        err_sys("fork error");  
    else if(0 == pid)   /* in child process */  
        exit(7);  
    if(wait(&status) != pid)    /* wait for child process */  
        err_sys("wait error");  
    pr_exit(status);    /* print its status */  
  
    if((pid = fork()) < 0)  
        err_sys("fork error");  
    else if(0 == pid)   /* in child process */  
        abort();        /* generated SIGABRT */  
    if(wait(&status) != pid)    /* wait for child process */  
        err_sys("wait error");  
    pr_exit(status);    /* print its status */  
  
    if((pid = fork()) < 0)  
        err_sys("fork error");  
    else if(0 == pid)   /* in child process */  
        status /= 0;        /* divide by 0 generated SIGFPE */  
    if(wait(&status) != pid)    /* wait for child process */  
        err_sys("wait error");  
    pr_exit(status);    /* print its status */  
  
    exit(0);  
}  

输出结果:

normal termination, exit status = 7  
abnormal termination, signal number = 6(core file generated)  
abnormal termination, signal number = 8(core file generated)  

输出的结果只是对不同状态的打印,即在正常终止子进程、异常终止子进程、当前暂停子进程的状态。从打印信息可以看到,该子进程发生正常终止和异常终止。

程序2:

#include <sys/wait.h>  
#include "apue.h"  
  
void pr_exit(int status)  
{  
    if(WIFEXITED(status))  
        printf("normal termination, exit status = %d\n", WEXITSTATUS(status));  
    else if(WIFSIGNALED(status))  
        printf("abnormal termination, signal number = %d%s\n", WTERMSIG(status),  
#ifdef WCOREDUMP  
                WCOREDUMP(status) ? "(core file generated)" : " ");  
#else  
    " ");  
#endif  
  
    else if(WIFSTOPPED(status))  
        printf("child stoped, signal number = %d\n", WSTOPSIG(status));  
}  
  
int main(void)  
{  
    pid_t pid,fpid;  
    int status;  
  
    if((pid = fork()) < 0)  
        err_sys("fork error");  
    else if(0 == pid)   /* in child process */  
    {  
        printf("child process, sleep 5s.\n");  
        sleep(5);  
        printf("child process, normal exit.\n");  
        exit(0);  
    }  
    else  
    {  
        fpid = wait(&status);/* wait for child process */  
        if(fpid < 0)  
            err_sys("wait error");  
        printf("father process, child process ID: %d\n",fpid);  
        pr_exit(status);    /* print its status */  
    }  
    exit(0);  
}  

        以上程序是在现有进程中fork 创建一个子进程,在 fork 返回的子进程中先睡眠5秒,然后正常退出;在 fork 返回的父进程中,wait 等待子进程终止,然后打印子进程终止状态信息,最后正常退出。输出结果如下:

child process, sleep 5s.  
child process, normal exit.  
father process, child process ID: 6065  
normal termination, exit status = 0  

8.7 waitid 函数

该函数类似于上面的 waitpid 函数功能,也是允许一个进程指定要等待的子进程,但是 waitid 使用单独的参数表示要等待的子进程的类型,而不是将此与进程 ID 或进程组 ID 组合成一个参数。

程序测试: 

#include "apue.h"  
#include <sys/wait.h>  
#include <unistd.h>  
  
int main(void)  
{  
        pid_t pid, fpid;  
        siginfo_t info;  
  
        if((pid = fork()) < 0)  
            err_sys("fork error.");  
        else if(pid == 0)  
        {  
            sleep(5);  
            _exit(0);  
        }  
        while((fpid = waitid(P_PID,pid,&info,WEXITED)) == 0)  
        {  
            printf("terminated process success exit,and process ID: %d.\n",pid);  
            sleep(1);  
        }  
        exit(0);  
}  

该程序是在现有进程使用 fork 创建一个子进程,在fork 返回的子进程中睡眠 5s 后正常退出;在 fork 返回的父进程调用 waitid 函数等待与子进程的进程 ID 与 pid 相等子进程退出状态,waiti 成功则返回0,打印一条信息;输出结果如下:

terminated process success exit,and process ID: 6589.  

8.8 wait3 和 wait4 函数

        函数 wait3 和 wait4 提供的功能比函数 wait,waitpid 和 waitid 所提供的功能要多一个,这与附加参数 rusage 有关,该参数要求内核返回由终止进程及其所有子进程使用的资源汇总。

/* 
 * wait3 和 wait4 函数 
 * 函数功能:等待一个进程终止; 
 * 返回值:若成功则返回进程ID,若出错则返回-1; 
 * 函数原型: 
 */  
#include <sys/types.h>  
#include <sys/wait.h>  
#include <sys/time.h>  
#include <sys/resource.h>  
  
pid_t wait3(int *statloc, int options, struct rusage *rusage);  
pid_t wait4(pid_t pid, int *statloc, int options, struct rusage *rusage);  


 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值