5 Interlude: Process API
5.1 The fork() System Call
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
printf("hello world (pid:%d)\n", (int) getpid());
int rc = fork();
if (rc < 0) {
// fork failed
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) {
// child (new process)
printf("hello, I am child (pid:%d)\n", (int) getpid());
} else {
// parent goes down this path (main)
printf("hello, I am parent of %d (pid:%d)\n",
rc, (int) getpid());
}
return 0;
}
首先是对这个的编译,unistd.h为Linux/Unix系统中内置头文件,unix std么。所以win是不能编译的,换到linux去了。
(主要是受到CSAPP关于异常控制流的视频启发,突然想道自己还写过一篇关于这个的,fork这个函数就把它看成是系统调用,创建个与父进程拷贝过来几乎一模一样的子进程,除了pid不一样,甚至连当前PC都一样,然后fork对子进程返回值是0,对父进程返回值是子进程的pid,据此就有上面的根据rc判断是父进程还是子进程的控制逻辑了)
把这命名为p1.c,然后用gcc p1.c编译它,会默认输出个a.out,我们输入个./a.out就能执行了
cbh@ubuntu:~/Desktop/Oprator SYstems Three Easy Pieces$ ls
p1.c
cbh@ubuntu:~/Desktop/Oprator SYstems Three Easy Pieces$ gcc p1.c
cbh@ubuntu:~/Desktop/Oprator SYstems Three Easy Pieces$ ls
a.out p1.c
cbh@ubuntu:~/Desktop/Oprator SYstems Three Easy Pieces$ ./a.out
hello world (pid:10524)
hello, I am parent of 10525 (pid:10524)
hello, I am child (pid:10525)
第一次玩gcc,肯定要好好玩玩的哈哈,后来的那个gcc a.c还真成功了,只不过报了好多错没法运行便是
此时我们再看看目录
a.c居然还是惊人的绿色,区别于p1.c
目前玩够了,现在好好的用gcc,后面加个-o 能够重命名为你想要的,否则就自动变成a.out了
现在问题来了,我们在回头看看代码,标准的if-else if-else 结构,为啥会输出三段?不应该只输出两段么,或者四段啊?
emm首先,fork的英文意思是
n. | 叉(挖掘用的园艺工具); 餐叉; (道路、河流等的)分岔处; 岔路; 叉状物; 车叉子; |
---|---|
v. | 分岔; 岔开两条分支; 走岔路中的一条; 叉运; 叉掘; |
我们可以把fork()理解成以父进程为模板创建一个子进程,返回值是子进程先不理fork()的具体机理因为我也不懂,这不是这节的重点,我就理解成复制一个和父进程一模一样的子进程包括执行位置。现在输出三段而不是两段或者四段的理由想必大家已经知道了。
作者说有时候子进程会先执行,就是i am child 在 i am parent 前面,可是我试了好多次都没试出来
5.2 The wait() System Call
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
printf("hello world (pid:%d)\n", (int) getpid());
int rc = fork();
if (rc < 0) { // fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) { // child (new process)
printf("hello, I am child (pid:%d)\n", (int) getpid());
} else { // parent goes down this path (main)
int rc_wait = wait(NULL);
printf("hello, I am parent of %d (rc_wait:%d) (pid:%d)\n",
rc, rc_wait, (int) getpid());
}
return 0;
}
上面是p2.c的代码,下面我们来试试它
好了,加了wait后子进程就先输出了,那,wait(NULL)又是什么意思呢,先放着吧,我们后面再看
5.3 Finally, The exec() System Call
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
printf("hello world (pid:%d)\n", (int) getpid());
int rc = fork();
if (rc < 0) { // fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) { // child (new process)
printf("hello, I am child (pid:%d)\n", (int) getpid());
char *myargs[3];
myargs[0] = strdup("wc"); // program: "wc" (word count)
myargs[1] = strdup("p3.c"); // argument: file to count
myargs[2] = NULL; // marks end of array
execvp(myargs[0], myargs); // runs word count
printf("this shouldn’t print out");
} else { // parent goes down this path (main)
int rc_wait = wait(NULL);
printf("hello, I am parent of %d (rc_wait:%d) (pid:%d)\n",
rc, rc_wait, (int) getpid());
}
return 0;
}
上面是代码,下面是运行结果
简单的分析
好了,两个例子基本也看完了,相信大家也看得差不多了,我们在这姑且再分析一下这个程序的小细节
1.wait(NULL)是干嘛的
用途:等待创建的子进程执行完成后执行
引出的小问题
1.如果有多个子进程,那么返回的值是哪个进程号(PID)?
2.在不使用fork()的情况下,单独使用wait()会怎么样?
2.strdup(“wc”)又是干嘛的
这个函数用途,是新建一个区域,把"wc"复制到这块区域来,然后返回这个区域的指针
自己的尝试
一.多个子进程wait(NULL)的情况
1.多加一个fork()
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
printf("hello world (pid:%d)\n", (int) getpid());
int rc = fork();
int rc1=fork(); //new add code compaerd with p3.c!!!Pay Attention!!!
if (rc < 0) { // fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) { // child (new process)
printf("hello, I am child (pid:%d)\n", (int) getpid());
char *myargs[3];
myargs[0] = strdup("wc"); // program: "wc" (word count)
myargs[1] = strdup("p3.c"); // argument: file to count
myargs[2] = NULL; // marks end of array
execvp(myargs[0], myargs); // runs word count
printf("this shouldn’t print out");
} else { // parent goes down this path (main)
int rc_wait = wait(NULL);
printf("hello, I am parent of %d (rc_wait:%d) (pid:%d)\n",
rc, rc_wait, (int) getpid());
}
return 0;
}
结果非常有意思
父进程(4054)分别创造两个子进程(fork创建4055,其rc为0)(fork1创建4057,其rc为4055),其中进程(4055)又会创造一个子进程4056(因为fork1,其rc也为0),感觉可以用来当考试题恶心人了…
(在后面的分析中可以发现我这段是在胡扯。。。)
为了阐述为什么这个rc为0我做了个小例子,貌似gcc会默认局部变量为0而不是VS中未初始化,注意看这个0输入在哪…在第二行首端,因为我没输出换行符
2.直接wait(NULL)
这回自己学乖了加了个换行符,结果是-1,估计是不用等待就输出-1
3.综合上面两个下
我们还有个问题没解决,wait(NULL)在有多个子进程,那么返回的值是哪个进程号(PID)?让我们来多加几个fork看看结果,子进程调用顺序咱不知道,反正我们可以肯定的是,进程号最小的肯定是父进程
在这里我们加了3个fork(),总共会有7个进程生成
好了,惊讶的事情来了,我们测试了5个案例(一如既往左边是测试代码),第一组父进程11064与wait(NULL)返回值之差是3,第二组是1,第三组是3,第四组是1,第五组也是1。再给大家补两组好玩的
第一组差是3,但是仔细看看他的位置和含有-1行的位置,是不是感觉很混乱?以为只有1和3?我们再看看第四组,差值是4哈哈,所以wait(NULL)在有多个子进程,那么返回的值是哪个进程号(PID)这个问题感觉很难在实际中得出解答。
下面我们看看我们搜索得到的一些回复
stackoverflow :wait(NULL)
will block parent process until any of its children has finished.
博客园 :父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
感觉自己好像懂了点什么,一旦父进程wait能够捕捉到一个子进程变成zombie(僵尸)状态,就触发它销毁它,也得到这个子进程的PID,看上去好像是一一对应的关系,现在我们再来实践下
真的是这样呢,好耶!