• UNIX环境编程学习笔记(18)——进程管理之进程控制三部曲


    lienhua34
    2014-10-05

    1 进程控制三部曲概述

    UNIX 系统提供了 fork、exec、exit 和 wait 等基本的进程控制原语。通过这些进程控制原语,我们即可完成对进程创建、执行和终止等基本操作。进程的控制可以划分为三部曲,

    • 第一部:fork 创建新进程。

    • 第二部:exec 执行新程序。

    • 第三部:exit 和 wait 处理终止和等待终止。

    2 第一部:fork 创建新进程

    在一个现有的进程中,我们可以通过调用 fork 函数来创建一个新进程,

    #include <unistd.h>
    pid_t fork(void);
    返回值:子进程中返回0,父进程中返回子进程ID,出错则返回-1

    由 fork 创建的新进程被称为子进程。fork 函数调用一次,但返回两次。两次返回的唯一区别是:子进程返回值为 0,而父进程的返回值是新子进程的进程 ID。因为 UNIX 系统没有提供一个函数以获取某个进程的所有子进程 ID,所以父进程可以通过 fork 函数的返回值获取其子进程的进程 ID,并进行后续的处理。而在子进程中,可以通过调用 getppid 函数获取其父进程的进程 ID。

    fork 函数返回之后,子进程和父进程都各自继续执行 fork 调用之后的指令。子进程是父进程的副本。例如,子进程获得了父进程数据空间、堆和栈的副本。但是,父子进程共享正文段。

    例子:

    下面程序调用 fork 创建一个新进程,在父子进程中都分别打印当前进程 ID 和父进程 ID。

    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <errno.h>
    int
    main(void)
    {
        pid_t pid;
        printf("before fork
    ");
        if ((pid = fork()) < 0) {
            printf("fork error: %s
    ", strerror(errno));
            exit(-1);
         } else if (pid == 0) {
            printf("in child process, process ID: %d, parent process ID: %d
    ", getpid(),  getppid());
        } else {
            printf("in parent process, process ID: %d, child process ID: %d
    ", getpid(), pid);
            sleep(1);
        }
        exit(0);
    }

    编译该程序,生成 forkdemo 文件,然后执行该文件,

    lienhua34:demo$ gcc -o forkdemo forkdemo.c
    lienhua34:demo$ ./forkdemo
    before fork
    in parent process, process ID: 3499, child process ID: 3500
    in child process, process ID: 3500, parent process ID: 3499

    第二部:exec 执行新程序

    用 fork 函数创建子进程后,子进程往往要调用一种 exec 函数以执行另一个程序。当进程调用一种 exec 函数时,该进程执行的程序完全替换为新程序,而新程序则从其 main 函数开始执行。调用 exec 并没有创建新进程,所以进程 ID 没有改变,exec 只是用一个新的程序替换了当前进程的正文、数据、堆和栈段。

    UNIX 提供了 6 种不同的 exec 函数可供使用。我们在这里只说明其中的一种,

    #include <unistd.h>
    int execv(const char *pathname, char *const argv[]);
    返回值:若出错则返回-1,若成功则没有返回值

    其中 pathname 是进程要执行的新程序文件的路径,而 argv 参数则是要传递给新程序的参数列表。

    例子:

    我们有一个 sayhello.c 的程序文件,其代码如下,

    #include <stdio.h>
    #include <stdlib.h>
    int
    main(void)
    {
        printf("Hello World! process ID: %d
    ", getpid());
        exit(0);
    }

    编译 sayhello.c 程序,生成执行文件 sayhello,

    lienhua34:demo$ gcc -o sayhello sayhello.c
    lienhua34:demo$ pwd
    /home/lienhua34/program/c/apue/ch08/demo
    lienhua34:demo$ ./sayhello
    Hello World! process ID: 3545

    在 execdemo.c 程序文件中,我们通过 fork 创建新进程,然后在子进程中调用 execv 函数执行 sayhello 文件,其代码如下,

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    char sayhelloPath[] = "/home/lienhua34/program/c/apue/ch08/demo/sayhello";
    int
    main(void)
    {
        pid_t pid;
        if ((pid = fork()) < 0) {
            printf("fork error: %s
    ", strerror(errno));
            exit(-1);
        } else if (pid == 0) {
            printf("in child process, process ID: %d, parent process ID: %d
    ", getpid(), getppid());
            if (execv(sayhelloPath, NULL) < 0) {
                printf("execv error: %s
    ", strerror(errno));
                exit(-1);
            }
        } else {
            printf("in parent process, process ID: %d, child process ID: %d
    ", getpid(), pid);
            sleep(2);
        }
        exit(0);
    }

    编译 execdemo.c 文件,生成并执行 execdemo 文件,

    lienhua34:demo$ gcc -o execdemo execdemo.c
    lienhua34:demo$ ./execdemo
    in parent process, process ID: 3561, child process ID: 3562
    in child process, process ID: 3562, parent process ID: 3561
    Hello World! process ID: 3562

    从上面的运行结果可以看出,子进程(ID:3562)正常执行 sayhello 文件。

    4 第三部:exit 和 wait 处理终止和等待终止

    exit 函数提供了进程终止的功能,在“进程终止”一文中,我们已经学习了 exit 函数的基本知识。我们这里补充一下关于进程的终止状态。在学习 exit 函数时,我们已经知道 exit 函数的参数作为程序的退出状态(exit status)。在进程终止时,内核会将程序的退出状态作为进程的终止状态(termination status)。在进程异常终止的时候,内核会产生一个指示其异常终止原因的终止状态。无论进程是正常终止还是异常终止,该终止进程的父进程都可以通过 wait 或 waitpid 函数获取其终止状态。

    在前面两节中的 forkdemo.c 和 execdemo.c 程序中,fork 调用之后的父进程都调用了 sleep 函数休眠一下。这是因为,在调用 fork 函数之后是父进程先执行还是子进程先执行是不确定的。于是,我们在父进程中调用sleep 休眠一段时间,让子进程先执行结束(注:这样子也不能确保)。

    我们可以在父进程中调用 wait 来等待子进程结束,

    #include <sys/wait.h>
    pid_t wait(int *statloc);
    返回值:若成功则返回进程ID,若出错则返回-1

    statloc 参数是一个整型指针。如果 statloc 不是一个空指针,则终止进程的终止状态就存储在该指针指向的内存单元中。如果不关心终止状态,则可以将参数设置为 NULL。

    进程调用 wait 会导致该调用者阻塞,直到某个子进程终止。如果调用wait 函数时,调用进程没有任何子进程则立即出错返回。

    例子:

    下面程序在上一节的 execdemo.c 基础上,将 fork 之后父进程的 sleep语句替换成 wait 来等待子进程的结束。

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/wait.h>
    char sayhelloPath[] = "/home/lienhua34/program/c/apue/ch08/demo/sayhello";
    int
    main(void)
    {
        pid_t pid;
        int waitres;
        if ((pid = fork()) < 0) {
            printf("fork error: %s
    ", strerror(errno));
            exit(-1);
        } else if (pid == 0) {
            printf("in child process, process ID: %d, parent process ID: %d
    ", getpid(), getppid());
            if (execv(sayhelloPath, NULL) < 0) {
                printf("execv error: %s
    ", strerror(errno));
                exit(-1);
           }
        } else {
            printf("in parent process, process ID: %d, child process ID: %d
    ", getpid(), pid);
            if ((waitres = wait(NULL)) < 0) {
                printf("wait error: %s
    ", strerror(errno));
                exit(-1);
            } else {
                printf("termination child process: %d
    ", waitres);
            }
        }
        exit(0);
    }

    编译该程序,生成并执行 execdemo 文件,

    lienhua34:demo$ gcc -o execdemo execdemo.c
    lienhua34:demo$ ./execdemo
    in parent process, process ID: 3795, child process ID: 3796
    in child process, process ID: 3796, parent process ID: 3795
    Hello World! process ID: 3796
    termination child process: 3796

    (done)

  • 相关阅读:
    第1章 数据结构绪论
    收集的名言警句
    Asp.net MVC知识积累
    我的书单
    ASP.NET Web API
    贱人语录
    正则表达式入门
    Lucene 3.0
    Solr之java操作
    Elasticsearch
  • 原文地址:https://www.cnblogs.com/lienhua34/p/4007141.html
Copyright © 2020-2023  润新知