子进程的执行起点
第一次知道fork()时,就有点疑惑子进程到底是从哪里开始执行的。既然fork之后是相互独立的关系,子进程也该从头开始,但是如果从头开始又会遇到已经在父进程调用过的fork,那这样会一直fork下去没有尽头。
机器不会骗人,在一个fork调用前加一句输出语句我们来看看结果。
int x = 1;
printf("before fork x = %d ",x);
//fflush(stdout);
pid_t pid = fork();
if (pid == 0) {
printf("Child has x = %d\n", ++x);
}
else {
printf("Parent has x = %d\n", --x);
}
printf("Bye from process %d with x = %d\n", getpid(), x);
输出了2次before fork,于是我怀着子进程从头开始执行的信念改良了一下代码:printf("before fork x = %d \n",x);
,再次编译运行结果如下:
我傻了,说好的遇到\n即刻输出呢,第二个before fork被吃了吗?(其实这里代码的确改良了,是我的脑子没改良过来)
我被第一个结果误导的脑子在看到这篇博文后终于纠过来了:https://www.cnblogs.com/QingCHOW/p/4588575.html
这篇博客里有几个点很关键,对我们正确理解fork()也很有帮助:
1、子进程继承了父进程的PC-程序计数器
2、子进程继承了fork()语句执行时当前的环境(包括缓冲区),而不是程序的初始环境。
而我的初始程序出现这样的误导性结果,正是因为没有\n,父进程里的before fork被放在缓冲区里继承给子进程了,所以子进程在遇到\n后会把历史遗留的before fork一并输出来。(为我的莽撞自罚一杯)
所以,子进程是从父进程调用fork后接下来的指令开始执行的。(pc计数器是关键)
CSAPP教材课后题分析(进程图,运行结果)
先把几个题的代码发一下(函数名fork813代表是课后作业8.13题)
(之前在edge浏览器复制代码的时候编辑器崩了,而我还没保存,一切重来,一瓶苦酒入喉)
(果然换个浏览器就没有复制不了长代码的问题了)
#include"csapp.h"
int counter = 1;
pid_t Fork(void)//简单包装一下fork()
{
pid_t pid;
if ((pid = fork()) < 0)
printf("Fork error");
return pid;
}
void end(void) {//8.18要用
printf("2"); fflush(stdout);
}
void fork813()
{
int x = 3;
if (Fork() != 0)
printf("x=%d\n", ++x);
printf("x=%d\n", --x);
exit(0);
}
void fork814()//这里对于作业里的doit(),剩余代码在main中的对应case里
{
if (Fork() == 0) {
Fork();
printf("hello\n");
exit(0);
}
return;
}
void fork815()//这里对于作业里的doit(),剩余代码在main中的对应case里
{
if (Fork() == 0) {
Fork();
printf("hello\n");
return;
}
return;
}
void fork816() {
if (Fork() == 0) {
counter--;//全局变量,初值在第二行定义了
printf("in child,counter=%d\n", counter);
exit(0);
}
else
{
wait(NULL);
printf("finaly,counter=%d\n", ++counter);
}
exit(0);
}
void fork818() {
if (Fork() == 0)
atexit(end);
if (Fork() == 0) {
printf("in child:0"); fflush(stdout);
}else {
printf("in parent:1"); fflush(stdout);
}
exit(0);
}
void fork819(int n) {
int i;
for (i = 0; i < n; i++)
Fork();
printf("hello\n");
exit(0);
}
int main(int argc, char *argv[])
{
int option = 0;
int n = 4;
if (argc > 1)
option = atoi(argv[1]);
switch(option) {
case 813: fork813();
break;
case 814: fork814();
printf("hello\n");
exit(0);
break;
case 815: fork815();
printf("hello\n");
exit(0);
break;
case 816: fork816();
break;
case 818: fork818();
break;
case 819:
fork819(n);
break;
default:
printf("Unknown option %d\n", option);
break;
}
return 0;
}
8.13到8.15的输出,再对照一下13,14的进程图(随手在题目旁边画的,字丑画质渣见谅)
根据观察,每个程序,无论进程总数是2个还是2个以上,最后上图这个只出现两次,且第一个头头(我太菜了,我不知道它叫什么)输出的内容就是最开始的父进程(也就是进程图最底下那条线)的输出,后面的一代子进程和二代,三代子进程都算做一个大的子进程在第二个头头输出。(不知道其他系统是不是这个效果)
然后是16,18,19的运行结果。
(16题我脑子又想岔了好一阵儿,总觉得该输出2,时刻谨记:相同但独立的地址空间,独立独立独立)
接下来详细讲一下8.19吧,其实fork的原理搞清楚了这题也不难,就是第一次做的时候选错了分析方向有点被自己绕晕。
放一下进程图和分析草稿(依然是手写见谅
子进程等比数列求和加上最原先的的1个祖宗父进程,一共有为2^n个进程,也即hello的输出数量。现在对照运行结果,给n的赋值为4,数了一下的确是16个hello输出,没毛病。
遗留疑问(好的,其实是我眼神的问题)
这里有个很迷的地方,就是8.19明明应该有4个进程,输出应该有6个数字,但运行却只有5个数字,检查了一下,少的还就是最祖宗的那个进程,想不通。
没问题了,谁能想到呢,原来真相只有一个,是我眼神不好:
下次没有换行没有空格的输出一定要睁大眼睛。。。