在学习Unix环境编程和Unix网络编程中,都遇到了fork()函数,于是想写一篇文章来说明一下自己的感悟,方便以后查吧。如果有错误的地方,还需要大佬们多多指点。
示例采用Unix环境编程上1-7的例子
#include "apue.h"
#include <sys/wait.h>
int main(void)
{
char buf[MAXLINE];
pid_t pid;
int status;
while (fgets(buf,MAXLINE,stdin) != NULL)
{
if (buf[strlen(buf)-1]=='\n')
{
buf[strlen(buf)-1]=0;
}
if ((pid=fork())<0)
{
/* code */
err_sys("fork error !");
}
else if (0==pid)
{
execlp(buf,buf,(char *)0);
err_ret("couldn't execute : %s\n",buf);
exit(127);
}
if ((pid=waitpid(pid,&status,0))<0)
{
printf("%d\n",pid);
err_sys("waitpid error\n");
}
}
return 0;
}
注意上面的代码不能直接运行,需要下载apue3e的源代码,因为apue.h头文件是他们提供给我们的,包括err_sys()等函数
从while条件开始,fgets从标准输入中获取字符串,存放在buf里面,最大字符数为MAXLINE,apue.h头文件中定义MAXLINE为4096大小。一般来说我们敲命令是以Enter键结尾,在内部进行判断并且替换最后的’\n’,execlp函数的参数需要的是字符串(以‘\0’结尾)。下一个if是一个关键点,pid=fork()这个是文章的重点。
在fork函数中,将创建一个与创建进程完全一样的进程,所有的寄存器的值都是一样的,只有一个是例外的,那就是存返回值的寄存器。需要记住一点是在fork函数执行结束后就有两个进程了,除非没有创建成功。存返回值的那个寄存器为什么不一样?肯定是用来标识创建进程啊,不然我(父进程)怎么知道我创建的进程是哪一个。所以在父进程中返回的是子进程(fork中创建的进程)的pid(进程ID),而在子进程中返回的是0,切记不是-1。如果返回-1,说明fork失败。
既然子进程是父进程的副本,那么就应该包含父进程所有的一切,包括当时的值,在fork函数执行之前的值,以及执行的是从fork函数后面的代码,而不是从头开始运行。所以在父进程中会判断pid是否为0,答案是否定的,现在pid的值是子进程的进程ID。接下来再执行pid=waitpid()函数,作用是等到这个存在这个pid的时候就终结它,已知在我们之前,子进程可能执行成功了,所以父进程等在这里杀死他,也可能子进程自己就结束运行了,这是无关紧要的。
再说说子进程中的情况,子进程创建后。咦,返回pid居然为0,又进行else if 判断;哇,真的这么巧,刚好可以判断成功,这也是本章示例故意这么做的,为的是进行下一步操作,要不要这样判断,还是要看读者当时的需求。这里也间接告诉我们,是在fork函数中创建的子进程,而不是其他地方,并子进程从fork函数返回的地方开始运行。
这是我在一个文本中写入的命令行,利用重定向到标准输入中。
但是这里还有一个小bug,命令行只能是简单的,也就是一个指令,还没有试用复杂的,还是等测完之后再说吧。
我们都看到了这三个命令确实都运行了,第一次产生子进程,返回到子进程的pid为0,执行else if 中的语句,excelp函数把文本的一行作为参数,效果就像在命令行中执行这一行一样。确实如此,excelp函数执行时会把调用它的进程的所有寄存器内容都清空,像是一个新进程来执行命令,pid是没有变的!!!
父进程在那里等着杀死子进程,也有可能在子进程本身还有没执行结束,就被父进程杀死了。在else if 中也对命令有没有执行成功做出了判断,没有成功会打印信息,并以结束代码127自己终止掉。
下面会改写程序,我们看看fork函数到底做了什么?
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#define MAXLINE 4096
int main(void)
{
char buf[MAXLINE];
pid_t pid;
int status;
while (fgets(buf,MAXLINE,stdin) != NULL)
{
if (buf[strlen(buf)-1]=='\n')
{
buf[strlen(buf)-1]=0;
}
if ((pid=fork())<0)
{
/* code */
printf("fork error !");
}
else if (0==pid)
{
//execlp(buf,buf,(char *)0);
printf("couldn't execute : %s\n",buf);
//exit(127);
}
}
return 0;
}
改写代码,不让父进程帮我们终止,或者等他自己结束,就看看fork
刚好 1 2 4,很符合树的结构嘛,开始我也这么认为,但是他有8个进程。
①——>②(ls)
①——>③(who)
②——>④(who)
①——>⑤(ifconfig)
②——>⑥(ifconfig)
③——>⑦(ifconfig)
④——>⑧(ifconfig)
箭头右边都是子进程。这并不是树的结构,他们只有平行关系,即使你是我创建,我们两个地位是相等的,你要来杀我,就来waitpid我吧!!!