目录

第1关:进程等待

任务描述

相关知识

进程等待的原因

wait函数

waitpid函数

编程要求

测试说明

答案: 

第2关: 进程退出控制

任务描述

相关知识

进程退出的种类

进程退出方式的比较

父子进程退出的顺序

编程要求

测试说明

答案: 

第3关:system系统调用

任务描述

相关知识

system函数

编程要求

测试说明

答案:


第1关:进程等待

任务描述

试想这样的场景:

你和你的树懒一起到外面吃午餐,你吃的比较快,但是要等待树懒吃完后才能一起走。

整个过程如下:

你和树懒开始就餐;

你需要等待树懒吃完后再一起走。

以进程等待代替整个过程, 对于上述过程可以理解为创建子进程->等待子进程退出->主进程退出。

本关任务:

创建子进程;

等待子进程退出。

相关知识

在 Linux 系统中,进程的生命周期内主要包含:进程就绪、进程执行、进程等待和进程退出

就绪转执行 处于就绪状态的进程,当进程调度程序为之分配了处理机(CPU)后,该进程便由就绪状态转变成执行状态。

执行转就绪 处于执行状态的进程在其执行过程中,因分配给它的一个时间片已用完或更高优先级的进程抢占而不得不让出处理机,于是进程从执行状态转变成就绪状态。

执行转等待 正在执行的进程因等待某种事件发生而无法继续执行时,便从执行状态变成等待状态。

等待转就绪 处于阻塞状态的进程,若其等待的事件已经发生,于是进程由等待状态转变为就绪状态。

执行转退出 进程执行完毕,撤销而退出。

其中,进程等待是进程控制的关键,也是本关的重点。

进程等待的原因

**进程控制块(PCB)**是进程在整个运行过程中非常重要的一个数据结构,里面包含以下几种信息:

进程描述信息;

进程控制信息;

资源信息;

现场保护信息。

进程终止或者退出的时候,进程会关闭所有文件描述符,释放用户空间分配的内存,但是PCB却会暂时保留。假如进程正常终止, PCB 里面就记录进程的退出状态;如果是异常终止,那么 PCB 里面存放着导致该进程终止的信号。

子进程的退出状态必须由父进程回收,也就是说,父进程必须得等待子进程退出后再自己退出,否则子进程会变成僵尸进程(没有任何可执行代码,不会被调度,只有一个进程描述符用来记录退出的状态,除此之外不再占用其他任何资源)。

wait函数
#include<sys/types.h> 
#include<sys/wait.h>
pid_t wait(int* status); 

参数:

status ,整型指针,指向的地址即存放退出子进程的退出状态,不关心可以设为 NULL。

等待方式是阻塞式等待,一旦子进程退出,父进程就会立刻收到 SIGCHLD 信号。

注意:

父进程调用 wait,是等待任意一个已经退出的子进程,只要是有子进程退出了,那么就获取子进程退出状态然后返回。

应用示例:

#include <sys/wait.h>
int main()
{
pid_t pid = fork();
......
if(0 == id)
{
/*子进程代码区域*/
    sleep (5);
}
else (id > 0)
{
/*父进程代码区域*/
    int status = 0;
    wait(&status);
}
return 0;
......
}
waitpid函数
#include<sys/types.h> 
#include<sys/wait.h>
pid_t waitpid( pid_t pid, int *status ,int options);

参数详解:

pid:希望等待退出的子进程的进程号;

status:整型指针,指向的地址即存放退出子进程的推出状态,不关心可以设为 NULL;

options:设置为 0,代表阻塞式等待,如果设置为 WNOHANG, waitpid 发现没有已经退出的子进程可以收集,就返回 0,此时是非阻塞式等待。

返回值:

返回 0 表示没有已经退出的子进程;返回正值,表示有已经退出的子进程。

编程要求

在主函数的最开始会初始化一个全部变量g_i4event0

本关的编程任务是补全右侧代码片段中两段BeginEnd中间的代码,具体要求如下:

创建子进程,创建完后,将 g_i4event 置为 1;

子进程等待 3s ,然后子进程退出;

父进程等待一秒,将 g_i4event 置为 2;然后等待子进程退出,父进程获取子进程退出的状态后将 g_i4event 置为 3;

process_wait采用 wait 函数,process_waitpid 采用 waitpid 函数。

测试说明

测试过程:

用户补全框架内的代码,实现功能性代码;

接着根据程序的输出判断程序是否正确;

不要在代码中使用printf等进行输出,以免引起结果判断错误。

答案: 

根据编程要求的步骤编写即可

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

int g_i4event;
int process_wait(void) {
    /********Begin********/
    pid_t pid=fork();
    int status;
    if(pid==0){
        // 子进程
        sleep(3);//子进程等待3s 
        exit(0);//子进程退出
    }else if(pid>0){
        // 父进程
        g_i4event=1;//创建子进程,创建完后,将 g_i4event置为1
        sleep(1);//父进程等待一秒
        g_i4event=2;//将g_i4event置为2
        wait(&status);//等待子进程退出,process_wait采用wait函数
        g_i4event=3;//g_i4event置为3
    }
    /*********End*********/
    return 0;
}

int process_waitpid(void) {
    /********Begin********/
    pid_t pid=fork();
    int status;
    if(pid==0){
        // 子进程
        sleep(3);//子进程等待3s 
        exit(0);//子进程退出
    }else if(pid>0){
        // 父进程
        g_i4event=1;//创建子进程,创建完后,将 g_i4event置为1
        sleep(1);//父进程等待一秒
        g_i4event=2;//将g_i4event置为2
        waitpid(pid,&status,0);//等待子进程退出,process_waitpid采用waitpid函数
        g_i4event=3;//g_i4event置为3
    }
    /*********End*********/
    return 0;
}

第2关: 进程退出控制

任务描述

试想这样的场景:

你的朋友和你一起到逛超市,逛的途中你们分散了,你们各自走出超市出口的先后顺序不同有三种情况。

整个过程如下:

你的朋友先出去;

你先出去,你没有等你的朋友;

你先出去,你在出口等你的朋友。

以进程退出的三种可能性代替这三种情况:

父进程先退出;

子进程先退出,父进程没有调用等待函数;

子进程先退出,父进程调用了等待函数。

本关任务:掌握上面提到的进程退出的三种情况。

相关知识

在 Linux 系统中,当进程退出时,必须释放它所拥有的资源,并通过某种方式告诉父进程。进程的退出一般是显示或隐式地调用了eixt(),或者接受了某种信号。不管是由于什么原因退出,最终都**调用了do_exit**。

进程退出的种类

Linux 下进程的退出分为正常退出异常退出两种。

正常退出 a. 在main()函数中执行 return ; b. 调用exit()函数。

异常退出 a. 调用 abort 函数; b. 进程收到某个信号,而该信号使程序终止。

不管是哪种退出方式,系统最终都会执行内核中的同一代码。这段代码用来关闭进程用到的文件描述符,释放它所占用的内存和其他资源。

进程退出方式的比较

exit 和 return 的区别: exit 是一个函数,有参数。 exit 执行完后把控制权交给系统。

return 是函数执行完后的返回,执行完后把控制权交给调用函数。

exit 和 abort 的区别: exit 是正常终止进程, abort 是异常终止。

应用示例:

int main()
{
......
//exit (0);
return 0;
}
父子进程退出的顺序

父进程先于子进程终止: 此种情况下,父进程会变成孤儿进程。当父进程先退出时,系统会让 init 进程接管子进程。

子进程先于父进程终止,而父进程又没有调用 wait 函数 此种情况子进程进入僵死状态,并且会一直保持下去直到系统重启。子进程处于僵死状态时,内核只保存进程的一些必要信息以备父进程所需。此时子进程始终占有着资源,同时也减少了系统可以创建的最大进程数。

子进程先于父进程终止,而父进程调用了 wait 函数 此时父进程会等待子进程结束。

编程要求

在主函数的最开始会初始化一个全部变量 g_i4event 为 0。

本关的编程任务是补全右侧代码片段中三段BeginEnd中间的代码,具体要求如下:

father_son中,创建子进程后,将 g_i4event 置为 1;父进程睡眠一秒后将 g_i4event 置为 2,接着直接退出;子进程睡眠两秒后直接退出;

son_father_nowait中,创建子进程后,将 g_i4event 置为 1;子进程睡眠一秒后直接退出;父进程睡眠两秒后将 g_i4event 置为 3 直接退出;

son_father_wait中,创建子进程后,将 g_i4event 置为 1;子进程睡眠一秒后直接退出;父进程等待子进程退出,然后睡眠一秒,随后将 g_i4event 置为 4 退出。

测试说明

测试过程:

用户补全框架内的代码,实现功能性代码;

接着根据程序的输出判断程序是否正确;

不要在代码中使用printf等进行输出,以免引起结果判断错误。

答案: 

根据编程要求步骤编写即可

#include <stdio.h>
#include <stdlib.h>
#include <string.h>  
#include <unistd.h> 
#include<sys/types.h> 
#include<sys/wait.h>
int g_i4event;
int father_son(void)
{
    /********Begin********/
    pid_t pid= fork();
    if(pid==0){
        //子进程
        sleep(2);//子进程睡眠两秒
        exit(0);//退出
    }else if(pid>0){
        //父进程
        g_i4event=1;//father_son中,创建子进程后,将g_i4event置为1
        sleep(1);//父进程睡眠一秒
        g_i4event=2;//将g_i4event置为2
    }
    /*********End*********/
}
int son_father_nowait(void)
{
    /********Begin********/
	pid_t pid=fork();
    if(pid==0){
        //子进程
        sleep(1);//子进程睡眠一秒
        exit(0);//退出
    }else if(pid>0){
        //父进程
        g_i4event=1;//on_father_nowait中,创建子进程后,将g_i4event置为1
        sleep(2);//父进程睡眠两秒
        g_i4event=3;//将g_i4event置为3
    }
    /*********End*********/
}
int son_father_wait(void)
{
    /********Begin********/
	pid_t pid=fork();
    int status;
    if(pid==0){
        //子进程
        sleep(1);//子进程睡眠一秒
        exit(0);//退出
    }else if(pid>0){
        //父进程
        g_i4event=1;//son_father_wait中,创建子进程后,将g_i4event 置为1
        wait(&status);//等待子进程退出
        sleep(1);//睡眠一秒
        g_i4event=4;//将g_i4event置为4
    }
    /*********End*********/
}

第3关:system系统调用

任务描述

熟读本关的相关知识,了解 system 函数的执行过程和返回值。

本关任务:

执行touch test.dat命令;

检查命令是否执行成功。

相关知识

为了简化执行命令的复杂程度, Linux 系统提供 system 系统调用,原理是通过 fork 的方式产生一个子进程,在这个子进程中执行系统调用过程中参数里面设定的 command 。

system函数
#include <stdlib.h>
int system(const char *command);

功能:利用 fork 创建子进程,然后用execl来执行/bin/sh sh -c command指令。system 函数的主进程必须等待子进程执行完后再退出,并返回执行状态。

参数: command:需要执行的命令的字符串,比如需要查看当前目录下的文件信息,则将 command 设定为ls -l

返回值:

出现异常时返回 -1;

子进程正常退出,返回 0 或者其他值。

为了更好的理解system()函数返回值,需要了解其执行过程,实际上system()函数执行了三步操作:

fork一个子进程;

在子进程中调用 exec 函数去执行 command ;

在父进程中调用 wait 去等待子进程结束。对于 fork 失败,system()函数返回 -1。如果 exec 执行成功,则返回 command 通过 exit 或 return 返回的值。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
    ......
system("date");//打印当前系统时间
......
}

有兴趣的读者可以查看system函数的源码:

int system(const char * cmdstring)
 {
     pid_t pid;
     int status;
     if(cmdstring == NULL){        
          return (1);
     }
     if((pid = fork())<0){
             status = -1;
     }
     else if(pid == 0){
         execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
         _exit(127); //子进程正常执行则不会执行此语句
        }
     else{
             while(waitpid(pid, &status, 0) < 0){
                 if(errno != EINTER){
                     status = -1;
                     break;
                 }
             }
         }
         return status;
 }
编程要求

在主函数的最开始会初始化一个全部变量 g_i4event 为 0。

本关的编程任务是补全右侧代码片段中BeginEnd中间的代码,具体要求如下:

执行“touch test. dat ”命令;

检查命令是否执行成功,如果执行成功,则将 g_i4event 设为 1;

执行成功返回 0,执行失败返回 -1。

测试说明

测试过程:

用户补全框架内的代码,实现功能性代码;

接着根据程序的输出判断程序是否正确;

不要在代码中使用printf等进行输出,以免引起结果判断错误。

答案:

根据编程要求步骤编写即可

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_i4event;
int do_system(void)
{
    /********Begin********/
	int result=system("touch test.dat");
    if(result==-1){
        return -1;
    }else{
        g_i4event=1;
        return 0;
    }
    /*********End*********/
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐