进程 第二天 (fork函数&子进程与父进程&守护进程)

详细标注:进程 第二天 (fork函数&子进程与父进程&守护进程)

一、fork()函数

在Linux系统内,创建子进程的方法是使用系统调用fork()函数。fork()函数是Linux系统内一个非常重要的函数,它与我们之前学过的函数有一个显著的区别:fork()函数调用一次却会得到两个返回值。

fork()函数的用法:

    函数fork()

    所需头文件:#include<sys/types.h>

                        #include<unistd.h>

    函数原型:pid_t fork()

    函数参数:无

    函数返回值:

          0    子进程

        >0    父进程,返回值为创建出的子进程的PID

         -1    出错

    fork()函数用于从一个已经存在的进程内创建一个新的进程,新的进程称为“子进程”,相应地称创建子进程的进程为“父进程”。使用fork()函数得到的子进程是父进程的复制品,子进程完全复制了父进程的资源,包括进程上下文、代码区、数据区、堆区、栈区、内存信息、打开文件的文件描述符、信号处理函数、进程优先级、进程组号、当前工作目录、根目录、资源限制和控制终端等信息,而子进程与父进程的区别有进程号、资源使用情况和计时器等

    由于复制父进程的资源需要大量的操作,十分浪费时间与系统资源,因此Linux内核采取了写时拷贝技术(copy on write)来提高效率

由于子进程几乎对父进程完全复制,因此父子进程会同时运行同一个程序。因此我们需要某种方式来区分父子进程。区分父子进程常见的方法为查看fork()函数的返回值或区分父子进程的PID。

示例:使用fork()函数创建子进程,父子进程分别输出不同的信息

#include<stdio.h>
 
#include<sys/types.h>
 
#include<unistd.h>
 
int main()
 
{
 
    pid_t pid;
 
    pid = fork();//获得fork()的返回值,根据返回值判断父进程/子进程
 
    if(pid==-1)//若返回值为-1,表示创建子进程失败
 
    {
 
        perror("cannot fork");
 
        return -1;
 
    }
 
    else if(pid==0)//若返回值为0,表示该部分代码为子进程
 
    {
 
        printf("This is child process\n");
 
        printf("pid is %d, My PID is %d\n",pid,getpid());
 
    }
 
    else//若返回值>0,则表示该部分为父进程代码,返回值是子进程的PID
 
    {
 
        printf("This is parent process\n");
 
        printf("pid is %d, My PID is %d\n",pid,getpid()); //getpid()获得的是自己的进程号
 
    }
 
    return 0;
 
}

    第一次使用fork()函数的同学可能会有一个疑问:fork()函数怎么会得到两个返回值,而且两个返回值都使用变量pid存储,这样不会冲突么?

在使用fork()函数创建子进程的时候,我们的头脑内始终要有一个概念:在调用fork()函数前是一个进程在执行这段代码,而调用fork()函数后就变成了两个进程在执行这段代码。两个进程所执行的代码完全相同,都会执行接下来的if-else判断语句块。

当子进程从父进程内复制后,父进程与子进程内都有一个"pid"变量:在父进程中fork()函数会将子进程的PID返回给父进程,即父进程的pid变量内存储的是一个大于0的整数;而在子进程中,fork()函数会返回0,即子进程的pid变量内存储的是0;如果创建进程出现错误,则会返回-1,不会创建子进程。

    fork()函数一般不会返回错误,若fork()函数返回错误,则可能是当前系统内进程已经达到上限,或者内存不足。

注意:父子进程的运行先后顺序是完全随机的(取决于系统的调度),也就是说在使用fork()函数的默认情况下,无法控制父进程在子进程前进行还是子进程在父进程前进行。

/***********vfork()函数****************/

fork()函数还有一个兄弟函数:vfork()。

    函数vfork()

    所需头文件:#include<sys/types.h>

                        #include<unistd.h>

    函数原型:

        pid_t vfork()

    返回值:同fork()函数

    vfork()函数功能与fork()函数功能类似不过更加彻底:内核不再给子进程创建虚拟空间,直接让子进程共享父进程的虚拟空间当父子进程中有更改相应段的行为发生时,再为子进程相应的段创建虚拟空间并分配物理空间。在vfork()函数创建子进程后父进程会阻塞,保证子进程先行运行

vfork()函数创建的子进程会与父进程(在调用exec函数族函数或exit()函数前)共用地址空间,此时子进程如果使用变量则会直接修改父进程的变量值。因此,vfork()函数创建的子进程可能会对父进程产生干扰。另外,如果子进程未调用exec函数族函数或exit()函数,则父子进程会出现死锁现象

举个例子,vfork()函数创建了一个“儿子”暂时“霸占”“老爹”的房产,此时需要委屈老爹一下,让老爹歇息(阻塞)。当儿子买房了(执行exec函数族函数)或者儿子死了(执行exit()退出),就相当于分家了,此时老爹得到自己的房产。

fork()函数与vfork()函数的主要区别如下:

    1.vfork()函数保证子进程先行运行,在子进程调度exec函数族函数或者exit()函数后父进程才会被调度运行。如果子进程需要依赖父进程的进一步动作,则会产生死锁

    2.fork()函数需要拷贝父进程的进程环境,而vfork()函数则不需要完全拷贝父进程的进程环境,在子进程调用exec函数族函数或者exit()函数之前,子进程与父进程共享进程环境(此时子进程相当于线程),父进程阻塞等待。

/***********vfork()函数end*************/

练习1:修改示例代码,首先让父进程输出一段信息,3秒后让子进程输出一段信息,再3秒后让父进程输出另一段信息(使用sleep()函数控制)

答案:

#include<stdio.h>
 
#include<sys/types.h>
 
#include<unistd.h>
 
int main()
 
{
 
    pid_t pid;
 
    pid = fork();
 
    if(pid==-1)
 
    {
 
        perror("cannot fork");
 
        return -1;
 
    }
 
    else if(pid==0)
 
    {
 
        sleep(3);
 
        printf("This is child process\n");
 
        printf("pid is %d\n, My PID is %d\n",pid,getpid());
 
    }
 
    else
 
    {
 
        printf("This is parent process\n");
 
        printf("pid is %d\n, My PID is %d\n",pid,getpid());
 
        sleep(6);
 
        printf("This is parent process\n");
 
    }
 
    return 0;
 
}
 

练习2(选做):在练习2的基础上,添加“文件锁”,使得父子进程分别对文件进行写操作

答案:

#include<stdio.h>
 
#include<sys/types.h>
 
#include<unistd.h>
 
#include<sys/stat.h>
 
#include<fcntl.h>
 
#define MAX 26
 
int lock_set(int fd, int type)
 
{
 
    struct flock lock;
 
    lock.l_whence = SEEK_SET;
 
    lock.l_start = 0;
 
    lock.l_len = 0;
 
    lock.l_type = type;
 
    lock.l_pid = -1;
 
    switch(type)
 
    {
 
        case F_RDLCK:
 
        case F_WRLCK:
 
        if((fcntl(fd,F_SETLKW,&lock))<0)
 
        {
 
            printf("lock failed:type=%d\n",lock.l_type);
 
            return -1;
 
        }
 
        break;
 
        case F_UNLCK:
 
        if((fcntl(fd,F_SETLKW,&lock))<0)
 
        {
 
            printf("unlock failed\n");
 
            return -1;
 
        }
 
        break;
 
        default:
 
        printf("input error\n");
 
    }
 
    return 0;
 
}
 
int main(int argc, const char *argv[])
 
{
 
    int fd;
 
    pid_t pid;
 
    int i;
 
    char buffer[2];//write()函数缓冲区
 
    if((fd=(open(argv[1],O_RDWR|O_CREAT|O_TRUNC,0666)))<0)
 
    {
 
        perror("cannot open file");
 
        return -1;
 
    }
 
    pid = fork();
 
    if(pid==-1)
 
    {
 
        perror("cannot fork");
 
        return -1;
 
    }
 
    else if(pid==0)//子进程
 
    {
 
        lock_set(fd,F_WRLCK);
 
        printf("child process begin\n");
 
        for(i=0;i<26;i++)
 
        {
 
            sprintf(buffer,"%c ",'A'+i);
 
            write(fd,buffer,2);
 
            printf("child process write %c\n",'A'+i);
 
            sleep(1);//延时1秒
 
        }
 
        printf("child process over\n");
 
        lock_set(fd,F_UNLCK);
 
    }
 
    else//父进程
 
    {
 
        lock_set(fd,F_WRLCK);
 
        printf("parent process begin\n");
 
        for(i=0;i<26;i++)
 
        {
 
            sprintf(buffer,"%c ",'a'+i);
 
            write(fd,buffer,2);
 
            printf("parent process write %c\n",'a'+i);
 
            sleep(1);//延时1秒
 
        }
 
        printf("parent process over\n");
 
        lock_set(fd,F_UNLCK);
 
        wait(NULL);
 
    }
 
    close(fd);
 
    return 0;
 
}

二、exec函数族

    如果我们使用fork()函数创建一个子进程,则该子进程几乎复制了父进程的全部内容,也就是说,子进程与父进程在执行同一个可执行程序。那么我们能否让子进程不执行父进程正在执行的程序呢?

    exec函数族提供了让进程运行另一个程序的方法。exec函数族内的函数可以根据指定的文件名或目录名找到可执行程序,并加载新的可执行程序,替换掉旧的代码区、数据区、堆区、栈区与其他系统资源。这里的可执行程序既可以是二进制文件,也可以是脚本文件。在执行exec函数族函数后,除了该进程的进程号PID,其他内容都被替换了。

    通常情况下,我们首先使用fork()函数创建一个子进程,然后调用exec函数族内函数将子进程内程序替换成其他的可执行程序,这样看起来就像父进程诞生了一个新的且完全不同于父进程的子进程。

    exec函数族有6个函数,这些函数的函数名、函数功能、函数参数列表有相似之处,我们在使用的过程中一定要仔细区分这些函数的区别避免混淆。有关exec函数族的更多使用方法内容请查阅man手册。

    exec函数族函数

    所需头文件:#include<unistd.h>

    函数原型:

        int execl(const char *path, const char *arg,…)

        int execlp(const char *file, const char *arg,…)

        int execle(const char *path, const char *arg,…, char *const envp[])

       

        int execv(const char *path, char *const argv[])

        int execvp(const char *file, char *const argv[])

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

        execl(完整的路径名,列表……);

        execlp(文件名,列表……);

        execle(完整的路径,列表……,环境变量的向量表)

 

        execv(完整的路径名,向量表);

        execvp(文件名,向量表);

        execve(完整的路径,向量表,环境变量的向量表)    

    函数参数:

        path:文件路径,使用该参数需要提供完整的文件路径

        file:文件名,使用该参数无需提供完整的文件路径,终端会自动根据$PATH的值查找文件路径

        arg:以逐个列举方式传递参数

        argv:以指针数组方式传递参数

        envp:环境变量数组

    返回值:-1(通常情况下无返回值,当函数调用出错才有返回值-1)

    这6个函数的函数功能类似,但是在使用语法规则上有细微区别。我们可以看出,其实exec函数族的函数都是exec+后缀来命名的,具体的区别如下:

区别1:参数传递方式(函数名含有l还是v

exec函数族的函数传参方式有两种:逐个列举或指针数组

    若函数名内含有字母'l'(表示单词list),则表示该函数是以逐个列举的方式传参,每个成员使用逗号分隔,其类型为const char *arg,成员参数列表使用NULL结尾

    若函数名内含有字母'v'(表示单词vector),则表示该函数是以指针数组的方式传参,其类型为char *const argv[],命令参数列表使用NULL结尾

区别2:查找可执行文件方式(函数名是否有p

我们可以看到这几个函数的形参有些为path,而有些为file。其中:

    若函数名内没有字母'p',则形参为path,表示我们在调用该函数时需要提供可执行程序的完整路径信息

    若函数名内含有字母'p',则形参为file,表示我们在调用该函数时只需给出文件名,系统会自动按照环境变量$PATH的内容来寻找可执行程序

区别3:是否指定环境变量(函数名是否有e

exec可以使用默认的环境变量,也可以给函数传入具体的环境变量。其中:

    若函数名内没有字母'e',则使用系统当前环境变量

    若函数名内含有字母'e'(表示单词environment),则可以通过形参envp[]传入当前进程使用的环境变量

exec函数族简单命名规则如下:

    后缀    能力

    l        接收以逗号为分隔的参数列表,列表以NULL作为结束标志

    v        接收一个以NULL结尾的字符串数组的指针

    p        提供文件的完整的路径信息 或 通过$PATH查找文件

    e        使用系统当前环境变量 或 通过envp[]传递新的环境变量

这6个exec函数族的函数,execve()函数属于系统调用函数,其余5个函数属于库函数。

示例1:使用execl()函数,在子进程内运行ps -ef命令

#include<stdio.h>
 
#include<unistd.h>
 
#include<stdlib.h>
 
int main()
 
{
 
    pid_t pid;
 
    pid = fork();
 
    if(pid==-1)
 
    {
 
        perror("cannot fork");
 
        return -1;
 
    }
 
    else if(pid==0)//子进程
 
    {
 
        printf("This is Child process\n");
 
        if(execl("/bin/ps","ps","-ef",NULL)<0)//子进程执行ps -ef,注意参数的写法,且需要使用NULL结尾
 
        {
 
            perror("cannot exec ps");
 
        }
 
    }
 
    else//父进程
 
    {
 
        printf("This is Parent process\n");
 
        sleep(1);//父进程延时1s,让子进程先运行
 
    }
 
    return 0;
 
}

    运行该程序会发现,子进程会运行ps -ef命令,这与我们在终端直接输入ps -ef得到的结果是相同的。

    注意我们在调用exec函数族的函数时,一定要加上错误判断语句。当exec函数族函数执行失败时,返回值为-1,并且报告给内核错误码,我们可以通过perror将这个错误码的对应错误信息输出。常见的exec函数族函数执行失败的原因有:

    1.找不到文件或路径

    2.参数列表arg、数组argv和环境变量数组列表envp未使用NULL指定结尾

    3.该文件没有可执行权限

示例2:使用execlp()函数完成示例1的代码,注意execlp()与execl()函数的参数的区别

#include<stdio.h>
 
#include<unistd.h>
 
#include<stdlib.h>
 
int main()
 
{
 
    pid_t pid;
 
    pid = fork();
 
    if(pid==-1)
 
    {
 
        perror("cannot fork");
 
        return -1;
 
    }
 
    else if(pid==0)//子进程
 
    {
 
        printf("This is Child process\n");
 
        if(execlp("ps","ps","-ef",NULL)<0)//第一个参数只需要写ps即可,系统会根据环境变量自行寻找ps程序的位置
 
        {
 
            perror("cannot exec ps");
 
        }
 
    }
 
    else//父进程
 
    {
 
        printf("This is Parent process\n");
 
        sleep(1);//父进程延时1s,让子进程先运行
 
    }
 
    return 0;
 
}

示例3:使用execvp()函数完成示例2的代码,注意execvp()与execlp()函数的参数的区别

#include<stdio.h>
 
#include<unistd.h>
 
#include<stdlib.h>
 
int main()
 
{
 
    pid_t pid;
 
    char *arg[]={"ps","-ef",NULL};//设定参数向量表,注意使用NULL结尾
 
    pid = fork();
 
    if(pid==-1)
 
    {
 
        perror("cannot fork");
 
        return -1;
 
    }
 
    else if(pid==0)//子进程
 
    {
 
        printf("This is Child process\n");
 
        if(execvp("ps",arg)<0)//注意该函数的参数与execlp()函数的区别
 
        {
 
            perror("cannot exec ps");
 
        }
 
    }
 
    else//父进程
 
    {
 
        printf("This is Parent process\n");
 
        sleep(1);//父进程延时1s,让子进程先运行
 
    }
 
    return 0;
 
}

示例4:使用execle()函数将一个新的环境变量添加到子进程中,并使用env命令查看

#include<stdio.h>
 
#include<unistd.h>
 
#include<stdlib.h>
 
int main()
 
{
 
    pid_t pid;
 
    char *envp[]={"PATH=/tmp","USER=liyuge",NULL};//设定新的环境变量,注意使用NULL结尾
 
    pid = fork();
 
    if(pid==-1)
 
    {
 
        perror("cannot fork");
 
        return -1;
 
    }
 
    else if(pid==0)//子进程
 
    {
 
        printf("This is Child process\n");
 
        if(execle("/usr/bin/env","env",NULL,envp)<0)
 
        {
 
            perror("cannot exec env");
 
        }
 
    }
 
    else//父进程
 
    {
 
        printf("This is Parent process\n");
 
        sleep(1);//父进程延时1s,让子进程先运行
 
    }
 
    return 0;
 
}

    运行该程序,我们可以看到输出了两个新的环境变量信息:PATH和USER,这两个新的环境变量与旧的环境变量(父进程)是不同的,有兴趣的同学可以将父进程的环境变量也输出作比较。

示例5:使用execve()函数完成示例2的代码,注意execve()与execle()函数的参数的区别

#include<stdio.h>
 
#include<unistd.h>
 
#include<stdlib.h>
 
int main()
 
{
 
    pid_t pid;
 
    char *arg[]={"env",NULL};//设定参数向量表,注意使用NULL结尾
 
    char *envp[]={"PATH=/tmp","USER=liyuge",NULL};//设定新的环境变量,注意使用NULL结尾
 
    pid = fork();
 
    if(pid==-1)
 
    {
 
        perror("cannot fork");
 
        return -1;
 
    }
 
    else if(pid==0)//子进程
 
    {
 
        printf("This is Child process\n");
 
        if(execve("/usr/bin/env",arg,envp)<0)
 
        {
 
            perror("cannot exec env");
 
        }
 
    }
 
    else//父进程
 
    {
 
        printf("This is Parent process\n");
 
        sleep(1);//父进程延时1s,让子进程先运行
 
    }
 
    return 0;
 
}

练习:使用exec函数族函数,在子进程内执行自己编译的可执行程序a.out文件

答案:使用execl()函数,其余函数的用法请同学们自己思考

#include<stdio.h>
 
#include<unistd.h>
 
#include<stdlib.h>
 
int main()
 
{
 
    pid_t pid;
 
    pid = fork();
 
    if(pid==-1)
 
    {
 
        perror("cannot fork");
 
        return -1;
 
    }
 
    else if(pid==0)//子进程
 
    {
 
        printf("This is Child process\n");
 
        if(execl("/home/linux/a.out","./a.out",NULL)<0)//使用execl()函数
 
        {
 
            perror("cannot exec a.out");
 
        }
 
    }
 
    else//父进程
 
    {
 
        printf("This is Parent process\n");
 
        sleep(1);//父进程延时1s,让子进程先运行
 
    }
 
    return 0;
 
}

三、exit()函数与_exit()函数

    当我们需要结束一个进程的时候,我们可以使用exit()函数或_exit()函数来终止该进程。当程序运行到exit()函数或_exit()函数时,进程会无条件停止剩下的所有操作,并进行清理工作,最终将进程停止。

    函数exit()

    所需头文件:#include<stdlib.h>

    函数原型:

        void exit(int status)

    函数参数:

        status    表示让进程结束时的状态(会由主进程的wait();负责接收这个返回值【也可以不接收】-->类似函数的返回值),默认使用0表示正常结束

    返回值:无

 

    函数_exit()

    所需头文件:#include<unistd.h>

    函数原型:

        void _exit(int status)

    函数参数:

        status    同exit()函数

    返回值:无

exit()函数与_exit()函数用法类似,但是这两个函数还是有很大的区别的:

    _exit()函数直接使进程停止运行,当调用_exit()函数时,内核会清除该进程的内存空间,并清除其在内核中的各种数据。

    exit()函数则在_exit()函数的基础上进行了升级,在退出进程之间增加了若干工序。exit()函数在终止进程之前会检测进程打开了哪些文件,并将缓冲区内容写回文件。

    因此,exit()函数与_exit()函数最主要的区别就在于是否会将缓冲区数据保留并写回。_exit()函数不会保留缓冲区数据,直接将缓冲区数据丢弃,直接终止进程运行;而exit()函数会将缓冲区内数据写回,待缓冲区清空后再终止进程运行。

下面的两段示例代码演示了exit()函数与_exit()函数的区别

示例1:使用exit()函数终止进程

#include<stdio.h>
 
#include<stdlib.h>
 
int main()
 
{
 
    printf("Using exit()\n");
 
    printf("This is the content in buffer");
 
    exit(0);
 
}
 
示例2:使用_exit()函数终止进程
 
#include<stdio.h>
 
#include<unistd.h>
 
int main()
 
{
 
    printf("Using exit()\n");
 
    printf("This is the content in buffer");
 
    _exit(0);
 
}

在两个示例程序中,示例1会输出"This is the content in buffer",而示例2不会输出

 

四、wait()函数与waitpid()函数

使用wait()函数与waitpid()函数让父进程回收子进程的系统资源,两个函数的功能大致类似,waitpid()函数的功能要比wait()函数的功能更多。

    函数wait()

    所需头文件:#include<sys/types.h>

                        #include<sys/wait.h>

    函数原型:

        pid_t wait(int *status)

    函数参数:

        status    保存子进程结束时的状态(由exit();返回的值)。使用地址传递,父进程获得该变量。若无需获得状态,则参数设置为NULL

    返回值:

        成功:已回收的子进程的PID

        失败:-1

        

    函数waitpid()

    所需头文件:#include<sys/types.h>

                        #include<sys/wait.h>

    函数原型:

        pid_t waitpid(pid_t pid, int *status, int options)

    函数参数:

        pid        pid是一个整数,具体的数值含义为:

            pid>0    回收PID等于参数pid的子进程

            pid==-1    回收任何一个子进程。此时同wait()

            pid==0    回收其组ID等于调用进程的组ID的任一子进程

            pid<-1    回收其组ID等于pid的绝对值的任一子进程

        status    同wait()

        options    

            0:同wait(),此时父进程会阻塞等待子进程退出

            WNOHANG:若指定的进程未结束,则立即返回0(不会等待子进程结束

    返回值:

        >0        已经结束运行的子进程号

          0        使用WNOHANG选项且子进程未退出

         -1        错误

    当进程结束时,该进程会向它的父进程报告。wait()函数用于使父进程阻塞,直到父进程接收到一个它的子进程已经结束的信号为止。如果该进程没有子进程或所有子进程都已结束,则wait()函数会立即返回-1。

    waitpid()函数的功能与wait()函数一样,不过waitpid()函数有若干选项,所以功能也比wait()函数更加强大。实际上,wait()函数只是waitpid()函数的一个特例而已,Linux内核总是调用waitpid()函数完成相应的功能。

wait(NULL)等价于waitpid(-1,NULL,0)

示例1:使用wait()函数,让父进程在子进程结束后再运行

#include<stdio.h>
 
#include<unistd.h>
 
#include<sys/types.h>
 
#include<sys/wait.h>
 
int main()
 
{
 
    pid_t pid;
 
    pid = fork();
 
    if(pid==-1)
 
    {
 
        perror("cannot fork");
 
        return -1;
 
    }
 
    else if(pid==0)//子进程
 
    {
 
        printf("This is Child process\n");
 
        printf("Child process ID is %d\n",getpid());
 
        printf("Child process will exit\n");
 
    }
 
    else//父进程
 
    {
 
        pid = wait(NULL);//等待子进程结束
 
        printf("This is Parent process\n");
 
        printf("Child process %d is over\n",pid);
 
    }
 
    return 0;
 
}

示例2:使用waitpid()函数,让父进程回收子进程。参数使用WNOHANG使父进程不会阻塞,若子进程暂时未退出,则父进程在1s后再次尝试回收子进程

#include<stdio.h>
 
#include<stdlib.h>
 
#include<unistd.h>
 
#include<sys/types.h>
 
#include<sys/wait.h>
 
int main()
 
{
 
    pid_t pid;
 
    pid = fork();
 
    if(pid<0)
 
    {
 
        perror("cannot fork");
 
        return -1;
 
    }
 
    else if(pid==0)//子进程
 
    {
 
        printf("This is Child process\n");
 
        sleep(5);//模拟子进程运行5s
 
        exit(0);//子进程正常退出
 
    }
 
    else//父进程
 
    {
 
        int ret;
 
        do//循环直至子进程退出为止
 
        {
 
            ret = waitpid(pid,NULL,WNOHANG);//回收子进程,使用WNOHANG选项参数
 
            if(ret==0)
 
            {
 
                printf("The Child process is running, can't be exited\n");
 
                sleep(1);//1秒后再次尝试
 
            }
 
        }while(ret==0);
 
        if(pid==ret)//如果检测到子进程退出
 
        {
 
            printf("Child process exited\n");
 
        }
 
        else
 
        {
 
            printf("Some error occured\n");
 
        }
 
    }
 
    return 0;
 
}

练习:若将示例2的程序内的waitpid()函数去掉WNOHANG选项参数,会出现什么效果?编程验证自己的猜想。

答案:父进程会一直阻塞等待子进程结束为止。在终端上只会输出1个"Child process exited\n",而不会输出"The Child process is running, can't be exited\n"

 

综合练习:使用fork()函数、exec函数族函数、exit()函数、waitpid()函数完成以下功能:

    该程序有3个进程,其中1个为父进程,另外2个为子进程。其中一个子进程运行"ls -l"命令,另外一个子进程延时5秒后退出。父进程首先使用阻塞的方式等待第一个子进程结束,再采用非阻塞的方式等待第二个子进程结束。待两个子进程都退出后,父进程退出。

该练习题给出两种答案,一种正确,一种错误,请观察两段代码的区别,并指出错误的代码产生错误的原因

答案1(错误答案):

#include<stdio.h>
 
#include<stdlib.h>
 
#include<sys/types.h>
 
#include<sys/wait.h>
 
#include<unistd.h>
 
int main()
 
{
 
    pid_t child1,child2,child;
 
    child1 = fork();//?
 
    child2 = fork();//?
 
    if(child1<0)
 
    {
 
        printf("Child1 fork error\n");
 
        exit(1);
 
    }
 
    else if(child1==0)
 
    {
 
        printf("In Child1 execute 'ls -l'\n");
 
        if(execlp("ls","ls","-l",NULL)<0)
 
        {
 
            perror("child1 execlp error");
 
        }
 
    }
 
    if(child2<0)
 
    {
 
        printf("Child2 fork error\n");
 
        exit(1);
 
    }
 
    else if(child2==0)
 
    {
 
        printf("In Child2 sleep 5s\n");
 
        sleep(5);
 
        exit(0);
 
    }
 
    else
 
    {
 
        printf("In parent process:\n");
 
        child = waitpid(child1,NULL,0);
 
        if(child==child1)
 
        {
 
            printf("Get child1 exit\n");
 
        }
 
        else
 
        {
 
            printf("Error occured\n");
 
        }
 
        do
 
        {
 
            child = waitpid(child2,NULL,WNOHANG);
 
            if(child==0)
 
            {
 
                printf("The child2 process hasn't exited\n");
 
                sleep(1);
 
            }
 
        }while(child==0);
 
        if(child==child2)
 
        {
 
            printf("Get child2 exit\n");
 
        }
 
        else
 
        {
 
            printf("Error occured\n");
 
        }
 
    }
 
    exit(0);
 
}

答案2(正确答案):

#include<stdio.h>
 
#include<stdlib.h>
 
#include<sys/types.h>
 
#include<sys/wait.h>
 
#include<unistd.h>
 
int main()
 
{
 
    pid_t child1,child2,child;
 
    child1 = fork();
 
    if(child1<0)
 
    {
 
        printf("Child1 fork error\n");
 
        exit(1);
 
    }
 
    else if(child1==0)
 
    {
 
        printf("In Child1 execute 'ls -l'\n");
 
        if(execlp("ls","ls","-l",NULL)<0)
 
        {
 
            perror("child1 execlp error");
 
        }
 
    }
 
    else
 
    {
 
        child2 = fork();
 
        if(child2<0)
 
        {
 
            printf("Child2 fork error\n");
 
            exit(1);
 
        }
 
        else if(child2==0)
 
        {
 
            printf("In Child2 sleep 5s\n");
 
            sleep(5);
 
            exit(0);
 
        }
 
        else
 
        {
 
            printf("In parent process:\n");
 
            child = waitpid(child1,NULL,0);
 
            if(child==child1)
 
            {
 
                printf("Get child1 exit\n");
 
            }
 
            else
 
            {
 
                printf("Error occured\n");
 
            }
 
            do
 
            {
 
                child = waitpid(child2,NULL,WNOHANG);
 
                if(child==0)
 
                {
 
                    printf("The child2 process hasn't exited\n");
 
                    sleep(1);
 
                }
 
            }while(child==0);
 
            if(child==child2)
 
            {
 
                printf("Get child2 exit\n");
 
            }
 
            else
 
            {
 
                printf("Error occured\n");
 
            }
 
        }
 
    }
 
    exit(0);
 
}
 
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值