系统编程-进程-探究父子进程的数据区、堆、栈空间/ 当带缓存的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简介

 

.

posted @ 2021-02-24 11:55  一匹夫  阅读(571)  评论(0编辑  收藏  举报