• Unix环境高级编程—进程控制(二)


    一、函数waitwaitpid

    今天我们继续通过昨天那个死爹死儿子的故事来讲(便于记忆),现在看看waitwaitpid函数。

    #include<sys/wait.h>

    pid_t wait(int *statloc); 

    pid_t waitpid(pid_t pid , int *statloc , int options);  若成功,返回进程ID,若出错,返回0-1

    wait系列函数的作用就是通知父亲儿子的死讯的,父进程一般接收到SIGCHLD信号而调用wait,(等待终止),可能有三种情况发生:

    (1).子进程都在Running,则阻塞

    (2).如果一个子进程终止,则父进程获取其终止状态(statloc指针指向的空间),然后立即返回

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

    wait可以返回的四个终止状态的互斥的宏,分别为:

    WIFEXITED(status)               正常终止

    WIFSIGNALED(status)             异常终止

    WIFSTOPPED(status)              暂停子进程

    WIFCONTINUED(status)           暂停后已经继续的子进程

    例子:

    #include "apue.h"
    #include <sys/wait.h>
    
    void pr_exit(int status)
    {
            if ( WIFEXITED(status)  )
                    printf("normal termination,exit status = %d
    ",
                            WEXITSTATUS(status));
            else if (WIFSIGNALED(status))
                    printf("abnormal termination,signal number = %d
    ",
                            WTERMSIG(status));
            else if (WIFSTOPPED(status))
                    printf("child stopped, signal number = %d
    ",
                            WSTOPSIG(status));
    }
    int
    main(void)
    {
            pid_t   pid;
            int     status;
    
            if ((pid = fork()) < 0)
                    err_sys("fork error");
            else if (pid == 0)                            /* child */
                    exit(7);
    
            if (wait(&status) != pid)               /* wait for child */
                    err_sys("wait error");
            pr_exit(status);                       /* and print its status */
    
            if ((pid = fork()) < 0)
                    err_sys("fork error");
            else if (pid == 0)                              /* child */
                    abort();                           /* generates SIGABRT */
    
            if (wait(&status) != pid)               /* wait for child */
                    err_sys("wait error");
            pr_exit(status);                            /* and print its status */
    
            if ((pid = fork()) < 0)
                    err_sys("fork error");
            else if (pid == 0)                           /* child */
                    status /= 0;                        /* divide by 0 generates SIGFPE */
    
            if (wait(&status) != pid)               /* wait for child */
                    err_sys("wait error");
            pr_exit(status);                         /* and print its status */
    
            exit(0);
    }

    waitpid函数,顾名思义,其作用就在于可以等待特定的pid的子进程终止。此外,waitpidoptions参数提供了WNOHANG(相当于不阻塞的wait)、WCONTINUEDWUNTRACED(支持作业控制)三个常量选项。

    下面是一个非常经典的fork子、孙进程的示例:(两次fork,有效的避免僵死进程)

    #include "apue.h"
    #include <sys/wait.h>
    int
    main(void)
    {
            pid_t   pid;
    
            if ((pid = fork()) < 0) {
                    err_sys("fork error");
            } else if (pid == 0) {          /* first child */
                    if ((pid = fork()) < 0)
                            err_sys("fork error");
                    else if (pid > 0)
                            exit(0);        /* parent from second fork == first child */
    
                    /*
                     * We're the second child; our parent becomes init as soon
                     * as our real parent calls exit() in the statement above.
                     * Here's where we'd continue executing, knowing that when
                     * we're done, init will reap our status.
                     */
                    sleep(60);
                    printf("second child, parent pid = %ld
    ", (long)getppid());
                    exit(0);
            }
    
            if (waitpid(pid, NULL, 0) != pid)       /* wait for first child */
                    err_sys("waitpid error");
    
            /*
             * We're the parent (the original process); we continue executing,
             * knowing that we're not the parent of the second child.
             */
            sleep(30);
            exit(0);
    }


      为了便于测试效果明显,我将父进程sleep了30秒,然后退出,将孙进程sleep了60秒,然后退出。在这段代码中,最先退出的是子进程,为了防止孙进程僵死,我们让它sleep的时间更长,而父进程执行完毕退出后,孙进程被init进程收养,孙进程执行自己的任务,执行完毕后正常退出,使得该过程有效的避免了僵死进程。

       此外,waitid类似于waitpid,但更为灵活,wait3wait4的附加参数可以返回所有子进程使用的资源概况,不在此详细说明。


    二、函数exec

    当我们fork一个进程后,新的进程往往要调用exec执行一个启动例程,如第七章所述图:


    因此,调用exec并不创建新的进程,只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段、栈段。

    7个不同的exec函数:

    #include <unistd.h>

    int execl(const char *pathname, const char *arg0, ... /* (char *)0 */ );

    int execv(const char *pathname, char *const argv[]);

    int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */ );

    int execve(const char *pathname, char *const argv[], char *const envp[]);

    int execlp(const char *filename, const char *arg0, ... /* (char *)0 */ );

    int execvp(const char *filename, char *const argv[]);

    int fexecve(int fd, char *const argv[], char *const envp[]);

                                                     返回值:成功不返回,出错返回-1

    七个exec函数的区别:

    (1).前四个取路径名作为参数,后两个取文件名作为参数,最后一个以fd作为参数

    (2).参数表的传递不同(l表示列表listv表示矢量vector)包含l和包含v

    (3).向新程序传递环境表不同。包含e和不含e

    七个exec直接的关系如下图,只有execve是内核的系统调用,另外6个只是库函数,它们最终都要调用该execve系统调用。


    exec函数使用例程:

    #include "apue.h"
    #include <sys/wait.h>
    
    char    *env_init[] = { "USER=unknown", "PATH=/tmp", NULL };
    
    int
    main(void)
    {
            pid_t   pid;
    
            if ((pid = fork()) < 0) {
                    err_sys("fork error");
            } else if (pid == 0) {  /* specify pathname, specify environment */
                    if (execle("/home/webber/test/echoall", "echoall", "myarg1",
                                    "MY ARG2", (char *)0, env_init) < 0)
                            err_sys("execle error");
            }
    
            if (waitpid(pid, NULL, 0) < 0)
                    err_sys("wait error");
    
            if ((pid = fork()) < 0) {
                    err_sys("fork error");
            } else if (pid == 0) {  /* specify filename, inherit environment */
                    if (execlp("echoall", "echoall", "only 1 arg", (char *)0) < 0)
                            err_sys("execlp error");
            }
    
            exit(0);
    }

    echoall.c文件代码如下:

    #include "apue.h"
    int
    main(int argc, char *argv[])
    {
            int                     i;
            char            **ptr;
            extern char     **environ;
    
            for (i = 0; i < argc; i++)              /* echo all command-line args */
                    printf("argv[%d]: %s
    ", i, argv[i]);
    
            for (ptr = environ; *ptr != 0; ptr++)   /* and all env strings */
                    printf("%s
    ", *ptr);
            sleep(100);
            exit(0);
    }


    这里同样是为了测试,我们sleep了100秒,模拟子进程的工作时间。

    注意,需要预先把echoall.c文件编程,gcc echoall.c -o echoall ,否则,当我们exec找到该文件后无法执行。这样,我们就完成了启动子进程的一整个流程



  • 相关阅读:
    Java 到底是值传递还是引用传递
    Java.lang.Comparable接口和Java.util.Comparator接口的区别
    线程安全和线程不安全的区别
    剑指offer第五天
    快速排序法
    剑指offer第四天
    length()方法,length属性和size()的方法的区别
    linux 下创建管理员权限账户
    Centos6.5 rpm方式指定目录安装JDK
    阿里云云服务器硬盘分区及挂载
  • 原文地址:https://www.cnblogs.com/webber1992/p/5850757.html
Copyright © 2020-2023  润新知