自学内容网 自学内容网

头歌 进程管理之二(wait、exec、system的使用)

第1关:进程等待

任务描述


通过上一个实训的学习,我们学会了使用fork创建子进程,在使用fork创建子进程的时候,子进程和父进程的执行顺序是无法预知的。本关我们将介绍如何使得fork创建出来的子进程先执行,随后父进程再执行。

本关任务:学会在多进程中,学会进程的等待处理。

相关知识


在上一个实训的学习,我们知道使用fork可以创建一个进程,并且创建的子进程其执行顺序可能在父进程前,也可能在父进程后。因此,这是由于fork创建的进程是由操作系统调度器来决定执行顺序的。如果,当子进程在父进程前结束,则内核会把子进程设置为一个特殊的状态。这种状态的进程叫做僵死进程(zombie)。尽管子进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存。但是仍然保留了一些信息(如进程号pid退出状态 运行时间等)。只有父进程获取了子进程的这些信息后,子进程才会彻底的被销毁,否则一直保持僵死状态。如果系统中产生大量的僵尸进程,将导致系统没有可用的进程号,从而导致系统不能创建新的进程。

Linux处理僵死进程的方法之一是使用进程等待的系统调用wait和waitpid来使得父进程获取子进程的终止信息。

wait函数和waitpid函数是系统调用函数,因此使用man 2 函数名来查看其使用方法。

wait函数使用方法


wait函数的具体的说明如下:

需要的头文件如下:

#include <sys/types.h>
#include <sys/wait.h>


wait函数格式如下:

pid_t wait(int *status);


参数说明:
参数status是一个整数指针,当子进程结束时,将子进程的结束状态字存放在该指针指向的缓存区。利用这个状态字,需要时可以使用一些由 Linux 系统定义的宏来了解子程序结束的原因。这些宏的定义与作用如下:

函数返回值说明: 调用成功时,返回值为被置于等待状态的进程的 pid;执行失败返回-1并设置错误代码errno。


案例演示1:


编写一个程序,使用fork函数与wait函数结合创建一个新进程,使得新创建的子进程在父进程前执行。详细代码如下所示:

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{
    pid_t pid;
    pid = fork();
    if(pid == -1)
    {
        //创建进程失败
        printf("创建进程失败(%s)!\n", strerror(errno));
        return -1;
    }
    else if(pid == 0)
    {
        //子进程
        sleep(2);
        printf("This is child process\n");
        exit(1);
    }
    else
    {
        //父进程
        int status;
        if(wait(&status) != -1)
        {
            if(WIFEXITED(status))
                printf("子进程正常退出,退出代码:%d\n", WEXITSTATUS(status));
        }
        printf("This is parent process\n");
        
        exit(0);
    }
}

将以上代码保存为waitProcess.c文件,编译执行。可以看到执行waitProcess程序后,尽管子进程使用sleep睡眠了2秒,还是子进程先执行,然后父进程才执行。这就是wait的作用,使得父进程一直等待子进程执行结束后才继续执行。

waitpid函数使用方法


waitpid函数的具体的说明如下:

需要的头文件如下:

#include <sys/types.h>
#include <sys/wait.h>


waitpid函数格式如下:

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


参数说明: pid:用于指定所等待的进程。其取值和相应的含义如下所示:

参数option则用于指定进程所做操作。其取值和相应的含义如下所示:

参数status是一个整数指针,当子进程结束时,将子进程的结束状态字存放在该指针指向的缓存区。

函数返回值说明: 调用成功时,返回收集到的子进程的进程pid;当设置选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;执行失败返回-1并设置错误代码errno。


案例演示1:


编写一个程序,使用fork函数与waitpid函数结合创建一个新进程,使得新创建的子进程在父进程前执行。详细代码如下所示:

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{
    pid_t pid;
    pid = fork();
    if(pid == -1)
    {
        //创建进程失败
        printf("创建进程失败(%s)!\n", strerror(errno));
        return -1;
    }
    else if(pid == 0)
    {
        //子进程
        sleep(2);
        printf("This is child process\n");
        exit(1);
    }
    else
    {
        //父进程
        int status;
        if(waitpid(-1, &status, 0) != -1)
        {
            if(WIFEXITED(status))
                printf("子进程正常退出,退出代码:%d\n", WEXITSTATUS(status));
        }
        printf("This is parent process\n");
        
        exit(0);
    }
}

将以上代码保存为waitpidProcess.c文件,编译执行。可以看到执行waitpidProcess程序后,尽管子进程使用sleep睡眠了2秒,还是子进程先执行,然后父进程才执行。waitpid函数可以实现与wait函数相同的功能。

编程要求


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

补全waitProcess函数,等待子进程结束,并且返回子进程的退出的代码。


测试说明


本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。

开始你的任务吧,祝你成功!

解答:

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
 
 
/************************
 * 返回值: 调用成功且子进程正常退出返回退出代码,否则返回-1
*************************/
int waitProcess()
{
int status = -1;
/********** BEGIN **********/
 pid_t pid;
    pid = fork();
    if(pid == -1)
    {
        //创建进程失败
        return -1;
    }
    else if(pid == 0)
    {
        //子进程
        sleep(2);
        printf("This is child process\n");
        exit(1);
    }
    else
    {
        //父进程
        int status;
        if(waitpid(-1, &status, 0) != -1)
        {
            if(WIFEXITED(status))
                return WEXITSTATUS(status);
        }
 
        
        exit(0);
    }

/********** END **********/

return status;
}

第2关:进程创建操作-exec函数族

任务描述


在上一个实训中我们学习使用fork函数创建新进程,本关我们将介绍如何另一种创建新进程的库函数。

本关任务:学会使用C语言在Linux系统中使用exec族的系统调用创建一个新的进程。

相关知识


在上一个实训中提到,fork函数创建的子进程可以通过调用exec函数族来正确退出。其原理是,使用exec函数族可以执行一个新的程序,并且以新的子进程来完全替换原有的进程地址空间。

exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。

通常exec函数族用来与fork函数结合一起使用。使用fork函数创建一个子进程,然后在子进程中使用exec函数族来执行一个新的程序。当在由fork创建的子进程中使用exec函数族来执行新程序时,子进程的地址空间会被新执行的程序完全覆盖,并且此时fork的父进程与子进程地址空间被分离开,也就是使用exec函数族创建的新程序不会对fork的父进程造成任何影响。

exec函数族是库函数,因此使用man 3 exec来查看其使用方法。

使用exec函数族创建进程


exec函数族的具体的说明如下:

需要的头文件如下:

#include <unistd.h>


函数族格式如下:

int execl(const char *path, const char *arg, ... /* (char  *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);
int execle(const char *path, const char *arg, ...  /*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],  char *const envp[]);


参数说明:
1、函数名中含有字母l的函数,其参数个数不定。其参数由所调用程序的命令行参数列表组成,最后一个NULL表示结束。

2、函数名中含所有字母v的函数,则是使用一个字符串数组指针argv指向参数列表,这一字符串数组和含有l的函数中的参数列表完全相同,也同样以NULL结束。
3、函数名中含有字母p的函数可以自动在环境变量PATH指定的路径中搜索要执行的程序。因此它的第一个参数为file表示可执行函数的文件名。而其他函数则需要用户在参数列表中指定该程序路径,其第一个参数path 是路径名。路径的指定可以是绝对路径,也可一个是相对路径。但出于对系统安全的考虑,建议使用绝对路径而尽量避免使用相对路径。
4、函数名中含有字母e的函数,比其他函数多含有一个参数envp。该参数是字符串数组指针,用于制定环境变量。调用这两个函数时,可以由用户自行设定子进程的环境变量,存放在参数envp所指向的字符串数组中。这个字符串数组也必须由NULL结束。其他函数则是接收当前环境。


函数返回值说明: 只有当函数执行失败时,exec函数族才会返回-1并设置错误代码errno。当执行成功时,exec函数族是不会返回任何值。


案例演示1:


编写一个程序,使用fork函数与exec函数族结合创建一个新进程,并在子进程中执行touch testFile命令创建一个testFile文件,在父进程中返回进程ID。详细代码如下所示:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{
    pid_t pid;
    pid = fork();
    if(pid == -1)
    {
        //创建进程失败
        printf("创建进程失败(%s)!\n", strerror(errno));
        return -1;
    }
    else if(pid == 0)
    {
        //子进程
        if(execlp("touch", "touch", "testFile",  NULL) < 0)
        {
            //执行execlp函数失败
            exit(-1);
        }
    }
    else
    {
        //父进程
        printf("当前进程为父进程:pid(%d),ppid(%d)\n", getpid(), getppid());
    }
    
    //如果执行execlp成功,则以下代码只会被父进程执行
    exit(0);
}

将以上代码保存为execlProcess.c文件,编译执行。可以看到执行execlProcess程序后,在当前目录下创建了一个名为testFile的文件。

编程要求


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

补全execlProcess函数,使用fork函数创建进程,并在子进程中调用创建一个名为testDir的目录,在父进程中输出"Parent Process"字符串。


测试说明


本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。

开始你的任务吧,祝你成功!

解答:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
 
/************************
 * 提示: 在子进程中如果执行exec函数失败要使用exit函数正确退出子进程
*************************/
int execlProcess()
{
pid_t pid = vfork();
if(pid == -1)
{
printf("创建子进程失败(%s)\n", strerror(errno));
return -1;
}
else if(pid == 0)
{
//子进程
/********** BEGIN **********/
if(execlp("touch", "touch", "testFile",  NULL) < 0)
        {
            //执行execlp函数失败
            exit(-1);
        }

/********** END **********/
}
else
{
//父进程
/********** BEGIN **********/
 printf("Parent Process");

/********** END **********/
}
}

第3关:进程创建操作-system

任务描述


在上一关中我们学习使用exec函数族执行新的程序,本关我们将介绍如何另一种执行新的程序的库函数。

本关任务:学会使用C语言在Linux系统中使用system库函数来执行一个新的程序。

相关知识


在上一关中我们学习了如何使用vfork结合exec函数族来执行一个新的程序。在Linux系统中提供了另一个库函数来实现执行一个新的程序。那就是system函数。

system函数是一个和操作系统相关紧密的函数。用户可以使用它来在用户自己的程序中调用系统提供的各种命令。因此,system函数使用起来是很方便的,用户可以在自己的程序中很方便地实现一些功能。

执行系统的命令行,其实也是调用程序创建一个进程来实现的。实际上,system函数的实现正是通过调用fork、exec和waitpid函数来完成的。详细的实现思路是:首先使用fork创建一个新的进程,并且在子进程中通过调用exec函数族来执行一个新程序,在父进程中通过waitpid函数等待子进程的结束,同时也获取子进程退出代码。

system函数是库函数,因此使用man 3 system来查看其使用方法。

使用system函数执行程序一个新程序


system函数的具体的说明如下:

需要的头文件如下:

#include <stdlib.h>


函数格式如下:

int system(const char *command);


参数说明:
command:需要被执行的命令;

函数返回值说明:
执行成功,返回值是执行命令得到的返回状态,如同wait的返回值一样。执行失败时返回的值分为以下几种情况:执行system函数时,它将调用fork、exec和waitpid函数。其中任意一个调用失败都可以使得system函数的调用失败。如果调用fork函数出错,则返回值为-1,errno被设置为相应错误;如果调用exec时失败,则表示shell无法执行所设命令,返回值为shell操作的返回值;如果调用waitpid函数失败,则返回值也为-1,errno被置为相应值。

案例演示1:


编写一个程序,使用system函数来执行touch testFile命令创建一个testFile文件。详细代码如下所示:

#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
    int ret = system("touch testFile");
    if(ret == -1)
    {
        printf("执行 touch testFile 命令失败(%s)\n", strerror(errno));
        return -1;
    }
    
    return 0;
}

将以上代码保存为system.c文件,编译执行。可以看到执行system程序后,在当前目录下创建了一个名为testFile的文件。

编程要求


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

补全createProcess函数,使用system函数创建一个名为testDir的目录((使用system('mkdir testDir')创建目录) 调用成功返回命令的状态码,失败返回-1)。


测试说明


本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。

开始你的任务吧,祝你成功!

解答:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
 
/************************
 * 返回值: 调用成功返回命令的状态码,失败返回-1
*************************/
int createProcess()
{
int ret = -1;
/********** BEGIN **********/
 ret=system("mkdir testDir");
     if(ret == -1)
    {
        return -1;
    }

/********** END **********/

return ret;
}


原文地址:https://blog.csdn.net/2302_79714797/article/details/144298406

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!