第十一章 进程和信号(一)

        我们可以通过调用fork来创建一个新进程。这个系统调用会复制当前进程,在进程表中创建一个实体,而且与当前进程具有相同的属性。新进程与原始进程几乎是相同的,执行相同的代码,但是具有其自己的数据空间,环境与文件描述符。

#include <sys/types.h>                                                                                             

 #include <unistd.h>

pid_t fork(void);

                                                  

       正如我们在图中所看到的,父进程中的fork调用会返回新的子进程的PID。新进程会继续执行,就如原始进程一样,所不同的是子进程中的fork调用返回0。这可以使得父进程与子进程彼此区分。

       如果fork失败则会返回-1。这通常是由于父进程可以拥有的子进程的数量(CHILD_MAX)所引起的,在这种情况下,errno会被设置为EAGAIN。如果在进程表中没有足够的空间,或是没有足够的虚拟内存,errno变量会被设置为ENOMEM。

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

int main()
{
    pid_t pid;
    char *message;
    int n;

    printf("fork program starting\n");
    pid = fork();

    switch(pid)
    {
        case -1:
            perror("fork failed\n");
            exit(1);
        case 0:
            message = "This is the child";
            n = 5;
            break;
        default:
            message = "This is the parent";
            n = 3;
            break;
    }

    for(; n > 0; n--)
    {
        puts(message);
        sleep(1);
    }
    exit(0);
} 
结果:

fork program starting
This is the parent
This is the child
This is the parent
This is the child
This is the parent
This is the child
$ This is the child
This is the child

        程序在调用 fork()时被分为两个独立的进程。程序通过 fork()调用返回的非零值确定父进程,并根据该值来设置消息的输出次数,两次消息的输出之间间隔一秒。

等待一个进程

      当用fork启动一个子进程时,子进程就有了他自己的生命周期并且独立运行。有时,我们希望知道一个子进程何时结束。例如,在前一个例子中,父进程在子进程之前结束,从而我们得到的输出结果有点乱。我们可以通过调用 wait()来让父进程子进程的结束。

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *stat_loc);
       wait系统调用将暂停父进程直到他的子进程结束为止。这个函数调用返回子进程的PID,它通常是已经结束运行的子进程的PID。状态信息允许父进程了解子进程的退出状态,即子进程的 main 函数返回的值或子进程中 exit 函数的退出码。如果 stat_loc 不是空指针,状态信息将被写入它所指向的位置。

      我们可以用 sys/wait.h 中定义的宏来解释状态信息,如下表所示:

说明
WIFEXITED(stat_val) 如果子进程正常结束,它就取一个非零值
WEXITSTATUS(stat_val)如果WIFEXITED为非零,它返回子进程的退出码
WIFSIGNALED(stat_val)如果子进程因为一个未捕获的信号而终止,它就取一个非零值
WTERMSIG(stat_val) 如果WIFSIGNALED非零,它就返回一个信号代码
WIFSTOPPED(stat_val)如果子进程意外终止,它就取一个非零值
WSTOPSIG(stat_val)如果WIFSTOPPED非零它就返回一个信号代码

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

int main()
{
    pid_t pid;
    char *message;
    int n;
    int exit_code;

    printf("fork program starting\n");
    pid = fork();

    switch(pid)
    {
        case -1:
            perror("fork failed\n");
            exit(1);
        case 0:
            message = "This is the child";
            n = 5;
	    exit_code = 37;
            break;
        default:
            message = "This is the parent";
            n = 3;
	    exit_code = 0;
            break;
    }

    for(; n > 0; n--)
    {
        puts(message);
        sleep(1);
    }
    
    if(pid != 0)
    {
        int stat_val;
	pid_t child_pid;
	
	child_pid = wait(&stat_val);
	
	printf("Child has finished: PID = %d\n", child_pid);
	if(WIFEXITED(stat_val))
	    printf("Child exited with code %d\n", WEXITSTATUS(stat_val));
	else
	    printf("Child terminated abnormally\n");
    }
//    exit(exit_code);
    return exit_code;
} 

结果:
fork program starting
This is the child
This is the parent
This is the parent
This is the child
This is the parent
This is the child
This is the child
This is the child
Child has finished: PID = 1582
Child exited with code 37
$
       父进程(从 fork 调用中获得一个非零的返回值)用 wait 系统调用将自己的执行挂起,知道子进程的状态信息出现为止。这发生在子进程调用 exit的时候。我们将子进程的退出码设置为37。父进程然后继续执行,通过 wait 调用的返回值来判断子进程是否正常终止。如果是,就从状态信息中提取出子进程的退出吗。

僵尸进程  
       用 fork 来创建进程确实非常有用,但你清楚子进程的运行状况。子进程终止时,它与父进程之间的关联还会保持,直到父进程正常终止或父进程调用 wait 才告结束。因此,进程表中代表子进程的表项不会立刻释放。虽然子进程已经不再运行,但它仍然存在与系统中,因为它的退出码还需要保存起来,以备父进程今后的 wait 调用使用。这时它将成为一个死(defunct)进程或僵尸(zombie)进程。
      如果修改 fork 示例程序中的消息输出次数,我们就能看到僵尸进程。如果子进程输出消息的次数少于父进程,它就会率先结束并成为僵尸进程直到父进程也结束。
     交换父进程、子进程输出消息的次数,代码如下:
switch(pid)
{
case -1:
    perror(“fork failed”);
    exit(1);
case 0:
    message = “This is the child”;
    n = 3;
    break;
default:
    message = “This is the parent”;
    n = 5;
    break;
}

        如果我们使用./fork2 & 命令来运行上面这个程序,然后在子进程结束之后父进程结束之前调用 ps 程序,我们将会看到如下阴影显示的一行(一些系统可能使用<zombie>而不是<defunct>)。
$ ps –al
   F  S   UID  PID PPID C PRI NI ADDR SZ WCHAN  TTY       TIME CMD
004 S     0 1273 1259 0 75   0 -   589 wait4  pts/2 00:00:00 su
000 S     0 1274 1273 0 75   0 -   731 schedu pts/2 00:00:00 bash
000 S   500 1463 1262 0 75   0 -   788 schedu pts/1 00:00:00 oclock
000 S   500 1465 1262 0 75   0 - 2569  schedu pts/1 00:00:01 emacs
000 S   500 1603 1262 0 75   0 -   313 schedu pts/1 00:00:00 fork2
003 Z   500 1604 1603 0  75  0 -     0 do_exi pts/1 00:00:00 fork2 <defunct>
000 R   500 1605 1262 0  81  0 -   781 -      pts/1 00:00:00 ps
       如果此时父进程异常终止,子进程将自动把 PID 为 1 的进程 (即 init )作为自己的父进程。子进程现在是一个不再运行的僵尸进程,但因为其父进程异常终止,所以它由 init 进程接管。僵尸进程将一直保留在进程表中知道被 init 进程发现并释放。进程表越大,这一过程就越慢。应该尽量避免产生僵尸进程,因为在 init 清理它们之前,它们将一直消耗系统的资源。
       还有另外一个系统调用可用来等待子进程的结束,它是 waitpid 函数。你可以用它来等待某个特定进程的结束。
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *stat_loc, int options);
      pid 参数指定需要等待的子进程的 PID 。如果它的值为 -1,waitpid 将返回任何一子进程的信息。与 wait 一样,如果 stat_loc 不是空指针,waitpid 将把状态信息写到它所指向的位置。option 参数可用来改变 waitpid 的行为,其中最有用的一个选项是 WNOHANG,它的作用是防止 waitpid 调用将调用者的执行挂起。你可以用这个选项来查找是否有子进程一斤不过结束,如果没有,程序将继续执行。其它的选项和 wait 调用的选项相同。
      因此,如果想让父进程周期性地检查某个特定的子进程是否已终止,就可以使用如下调用方式:
waitpid(child_pid, (int *) 0, WNOHANG);
     如果子进程没有结束或意外终止,它就返回0,否返回 child_pid。如果 waitpid 失败,它将返回 -1 并设置 errno。失败的情况包括:没有子进程(errno设置为ECHILD)、调用被某个信号中断(EINTR),或选项参数无效(EINVAL)。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值