现在来学习一下关于进程的一些操作。
1. 使用fork()函数创建进程
A)函数一定是在程序中被调用的,而调用fork函数的程序在运行中是一个进程。在这个进程中,执行fork的效果,是把自己完完全全复制一遍。这个新的进程是原来进程的子进程,他俩构成了父子进程的关系。并且是同时执行的,具体谁先谁后要由调度算法来决定。
B)子进程是由父进程将自己复制一份而产生的,因此不仅程序逻辑与父进程一致(因为代码完全一致),就连运行状态都与父进程一致。子进程被创建出来后,会以为自己就是父进程,而继续执行fork后面的代码。这就麻烦了,我们其实是希望程序能够识别出自己是不是子进程的。换句话说,我们创建出新的进程,不是为了干一件与父进程一模一样的工作,我们需要在fork之后有分支!这就需要用到fork函数的返回值了。
C)关于fork函数的返回
如果函数执行成功,那么就意味着子进程创建成功。这时候函数不仅会向父进程返回一个值,也会向子进程返回一个值。向父进程返回的是子进程的PID号,向子进程返回的是0。如果执行失败,即子进程创建失败,那么此时只会向父进程返回数值,为一个小于0的错误码。
对于父进程很好理解,它执行了fork函数,然后检查自己刚刚执行的fork函数的返回值,再对返回值进行判断。对于子进程,它一开始以为自己就是父进程,它也检查“自己刚刚执行的fork函数的返回值”,结果发现是0。(以为自己是爸爸,结果让所有人大吃一惊..... )
D)说了那么多,贴一段代码最实在了:
#include
#include
#include
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函数不用特别指定环境变量。于是主进程像上面那样写就行了。
下面是子进程,简单打印一句话,说明执行成功即可: