Linux应用编程学习记录(五)

        现在来学习一下关于进程的一些操作。

1.  使用fork()函数创建进程

A)函数一定是在程序中被调用的,而调用fork函数的程序在运行中是一个进程。在这个进程中,执行fork的效果,是把自己完完全全复制一遍。这个新的进程是原来进程的子进程,他俩构成了父子进程的关系。并且是同时执行的,具体谁先谁后要由调度算法来决定。

B)子进程是由父进程将自己复制一份而产生的,因此不仅程序逻辑与父进程一致(因为代码完全一致),就连运行状态都与父进程一致。子进程被创建出来后,会以为自己就是父进程,而继续执行fork后面的代码。这就麻烦了,我们其实是希望程序能够识别出自己是不是子进程的。换句话说,我们创建出新的进程,不是为了干一件与父进程一模一样的工作,我们需要在fork之后有分支!这就需要用到fork函数的返回值了。

C)关于fork函数的返回

        如果函数执行成功,那么就意味着子进程创建成功。这时候函数不仅会向父进程返回一个值,也会向子进程返回一个值。向父进程返回的是子进程的PID号,向子进程返回的是0。如果执行失败,即子进程创建失败,那么此时只会向父进程返回数值,为一个小于0的错误码。

        对于父进程很好理解,它执行了fork函数,然后检查自己刚刚执行的fork函数的返回值,再对返回值进行判断。对于子进程,它一开始以为自己就是父进程,它也检查“自己刚刚执行的fork函数的返回值”,结果发现是0。(以为自己是爸爸,结果让所有人大吃一惊..... )

D)说了那么多,贴一段代码最实在了:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    pid_t pid;
    pid = fork();
    if (pid == 0)
        printf("I am child, my PID is %d\n", getpid());
    else if (pid > 0)
        printf("I am parent, my PID is %d\n", getpid());
    else
        printf("create new process failed!\n");
    return 0;
}

在父进程中,会进入pid > 0的分支,而在子进程中,会进入pid == 0的分支。

2.  exec族函数

        这是一系列函数,具体包含6个。它们的功能是一致的,只是具体用法不同。

A)该函数的使用背景如下

        当我们用fork函数创建新进程后,只能通过对fork函数的返回值进行判断来搞一些分支操作。在分支中,可以做一些差异化处理。但本质上,子进程与父进程的代码是一模一样的。如果只能这样用多进程,实在是太不灵活了。那么我们能不能在创建了新的进程后,让它去执行另一个程序呢?当然是可以的,这就要使用exec族函数了。它可以将子进程所执行的程序完全替换成新程序。来一张图吧:假设要子进程执行的程序是/bin目录下名为ls的程序。

        注意看,执行exec函数后,子进程的PID和PPID号都没有变,只是执行的代码换掉了。

B)exec族函数的返回值

        exec族函数只有在执行失败的情况下才会有返回值,返回值是-1。如果一切正常,不发生返回。

C)来具体看看exec族的6个函数吧

        注意观察这些函数的名字,会发现它们都是在exec的后面,加上了l、p、e、v这些字母。他们分别代表了不同的含义,为了方便记忆,下面来分析一下。

首先,来看看字母 l 和 v 。对于所有的exec族函数,在命名时必须从 l 和 v 中选一个。它俩代表执行函数时参数的传递方式。

什么是参数传递?调用执行某个程序,其实就好像是你在命令行中输入   

./可执行文件  参数1 参数2

        有些程序没有参数,直接执行。有些函数却需要参数,那么这个参数怎么传递给程序呢?这就涉及到参数的传递方式了。exec族函数支持两种参数传递方式

其一,参数列表形式,即以枚举的形式一个个把参数列出来,注意最后一个参数必须是NULL。

        比如,execl("函数名","参数1","参数2"...NULL);

其二,参数向量形式,这就有点像main函数的第二个参数char *argv[],它是由若干字符型指针构成的数组,每个指针其实都指向着一个参数,当然这些参数被当成字符串处理,取走的是字符串的首地址。

上述两种方式,对应到函数名上就是加l或者加v的区别:加l就是列表,加v就是向量。

其次,来看看字母 p 。它决定了程序文件的查找方式。如果exec族函数的命名中带p,则你可以只写程序名,不写它所在的目录,系统会自动在PATH环境变量所指定的各个目录下去搜索你指定的程序。如果exec族函数的命名中不带p,则你必须把程序的路径写的清清楚楚。

        当然,使用带有字母p命名的exec族函数,也可以在调用函数时写清楚路径。这么一看的话,带字母p的exec函数岂不是万金油?

最后,来看看字母 e 。它决定了新进程的环境变量,如果不带字母e。则新进程继承了environ这个全局变量所指的环境变量。而如果你希望用新的环境变量来替代,那么就使用带字母e的exec族函数。

        这时exec函数的声明中会有char *const envp[]这样一个参数,它是由若干个字符型指针所构成的数组。要想使用它来传递参数,显然你需要先准备好它,并且注意,最后一个环境变量必须是NULL。

贴上我的测试代码吧:

首先是父进程:

我使用execl函数,在子进程中装载another.o这个可执行程序。

execl函数的第一个参数是,可执行程序的详细目录及其程序名。我在测试中一开始将/home/book简写成~,发现execl函数不能识别;

execl函数的其他参数是,所谓的参数列表。用枚举的方式一个个列出来,这个列表的第一个参数必须是可执行程序的程序名,最后一个必须是NULL,我的子进程也没有其他参数需求,因此参数列表就是“another.o” "(char *)0";

execl函数不用特别指定环境变量。于是主进程像上面那样写就行了。

下面是子进程,简单打印一句话,说明执行成功即可:

猜你喜欢

转载自blog.csdn.net/K23428/article/details/82113321
今日推荐