系统编程-进程-探究父子进程的数据区、堆、栈空间/ 当带缓存的C库函数遇上fork
1. test1
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
/******全局变量位于数据区, 用于数据区测试*******/
int globvar = 6;
char buf[] = "a write to stdout!\n";
char gstring[] = "hello string";
int main(void)
{
/******局部变量位于栈, 用于栈测试*******/
int var;
pid_t pid;
var = 88;
if ( write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1 ){
printf("write error!! \n");
return -1;
}
printf("before fork!!, pid = %d\n", getpid() );
fflush(stdout); // 注意该行代码产生的效果
FILE* fp = fopen("s.txt", "wb+");
/******当带缓存的C库函数遇上fork: C库函数的缓存建立在堆上, 这就相当于用于堆测试*******/
fprintf(fp, "1st , string: %s, pid:%d ", gstring, getpid());
/* 父进程中,fork返回新创建子进程的进程ID. 子进程中,fork返回0. 出现错误,fork返回负值. */
if ((pid = fork()) < 0){
printf("fork error!! \n");
}
else if (pid == 0){
globvar++;
var++;
printf("son: pid = %d \n", getpid() );
}
else{
sleep(1);
printf("father: pid = %d \n", getpid() );
}
/***********
父子进程内, globvar, var 打印出来的值是不一样的,
因为父进程中有这俩变量的一份物理内存,子进程中会分配这俩变量的另一份物理内存,
也就是说,经过本次测试得到:在物理内存上,父子进程有自己的数据区、栈。
而打印他们的地址值却是一样的,这说明子进程复制了父进程的虚拟地址空间。
事实上的结论是:子进程会复制父进程的虚拟地址空间。在物理内存上,父子进程共享代码段,但是有各自的数据段、栈、堆。
***********/
printf("pid = %d, glob = %d, var = %d \n", getpid(), globvar, var);
printf("pid = %d, &glob = %ld, &var = %ld \n", getpid(), (long int)&globvar, (long int)&var);
fprintf(fp, "2nd , string: %s, pid:%d ", gstring, getpid());
/*********
这里的代码,父子进程都会执行到。
对于父进程而言,之前fprintf一次,现在又一次,合计是两次,相信大家对这点都不会搞错。
对子进程而言,由于父进程fork子进程之前,已经向缓存区内写入了字符串,所以子进程复制了父进程的这份缓存区,
只是我们不方便修改这子进程内复制来的堆空间内的数据。
在子进程即将退出之际,这里再次使用fprintf,实际上相当于这是子进程第二次向该缓冲区内写东西了。
在程序退出后,缓存内的数据会被写入文件,那么合计,s.txt文件内最终记录的应该有4条打印语句。
***********/
exit(0);
}
运行:
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/IO缓存
函数+fork# gcc fork3.c
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/IO缓存
函数+fork# ./a.out
a write to stdout!
before fork!!, pid = 5102
son: pid = 5103
pid = 5103, glob = 7, var = 89
pid = 5103, &glob = 6295696, &var = 140732929243848
father: pid = 5102
pid = 5102, glob = 6, var = 88
pid = 5102, &glob = 6295696, &var = 140732929243848
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/IO缓存
函数+fork#
2. test2
鉴于test1实验代码不便于修改子进程内由父进程复制得到的堆空间。
我们再来写个实验代码test2吧。
#include <stdio.h> #include <unistd.h> #include <stdlib.h> pid_t pid; int main(void) { int* p = (int*)malloc(sizeof(int)*100); if( (pid = fork()) < 0){ perror("fork "); }else if(pid > 0){ p[0] = 99; }else{ sleep(1); p[0] = 100; } printf("pid=%d, p=0x%lx, *p=%d \n", getpid(), \ (unsigned long)&p[0], p[0]); //printf("\n___ *p=%d \n", 0[p]); 等价于p[0] return 0; }
运行:
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/IO缓存函数+fork# ./a.out
pid=10424, p=0xc1a010, *p=99
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/IO缓存函数+fork# pid=10425, p=0xc1a010, *p=100
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/IO缓存函数+fork#
经过test2的实验,可以看到,
fork前申请了堆空间,fork后,子进程内和父进程内对该获取到的堆指针进行打印,都是同一个值,表明子进程复制了父进程的虚拟内存的堆区,
然而父子进程打印p[0]却又有不同的值,表明父子进程内的堆实际上拥有各自独立的物理内存。
PS: 编写代码期间不小心犯错了,没加括号,C语言运算符优先级 小于符号的优先级 要高于 赋值符号!
下面是错误代码展示:
对于严谨思维,可以预测到test2的执行是:父进程先打印退出,之后延时1秒后,子进程才打印退出。
如果ubuntu内的现象不符合这个,例如两条语句同时打印出来,那么可以推测发生了预期异常!
结论:
子进程会复制父进程的虚拟地址空间。在物理内存上,父子进程共享代码段,但是有各自的数据区、栈、堆。
我的关联博文:
系统编程-进程-fork深度理解、vfork简介
.