• 系统编程-进程-探究父子进程的数据区、堆、栈空间/ 当带缓存的C库函数遇上fork


     

    1. test1

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    /******全局变量位于数据区, 用于数据区测试*******/
    int globvar = 6;
    char buf[] = "a write to stdout!
    ";
    
    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!! 
    ");
        return -1;
        }
        printf("before fork!!, pid = %d
    ", 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!! 
    ");
        }
        else if (pid == 0){
        
            globvar++;
            var++;
            printf("son: pid = %d 
    ", getpid() );
        }
        else{
            sleep(1);
            printf("father: pid = %d 
    ", getpid() );
        }
    
        /***********
    	父子进程内, globvar, var 打印出来的值是不一样的,
                因为父进程中有这俩变量的一份物理内存,子进程中会分配这俩变量的另一份物理内存,
                也就是说,经过本次测试得到:在物理内存上,父子进程有自己的数据区、栈。
                而打印他们的地址值却是一样的,这说明子进程复制了父进程的虚拟地址空间。
    
    	事实上的结论是:子进程会复制父进程的虚拟地址空间。在物理内存上,父子进程共享代码段,但是有各自的数据段、栈、堆。
    
        ***********/
        printf("pid = %d, glob = %d,   var = %d 
    ", getpid(), globvar, var);
        printf("pid = %d, &glob = %ld, &var = %ld 
    ", 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 
    ", getpid(), 
                             (unsigned long)&p[0], p[0]);
    	//printf("
    ___ *p=%d 
    ", 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简介

    .

    /************* 社会的有色眼光是:博士生、研究生、本科生、车间工人; 重点大学高材生、普通院校、二流院校、野鸡大学; 年薪百万、五十万、五万; 这些都只是帽子,可以失败千百次,但我和社会都觉得,人只要成功一次,就能换一顶帽子,只是社会看不见你之前的失败的帽子。 当然,换帽子决不是最终目的,走好自己的路就行。 杭州.大话西游 *******/
  • 相关阅读:
    python 列表与字符串互相转化
    python爬虫——BeautifulSoup详解(附加css选择器)
    python——requests库
    用代理池 + redis 刷博客浏览量(2)
    scrapy爬取知乎用户信息并存入mongodb
    python 爬虫 计算博客园浏览量,刷浏览量(1)
    python SocketServer模块创建TCP服务器·
    【XSY1986】【BZOJ1455】罗马游戏
    【模板】左偏树
    CF464D World of Darkraft
  • 原文地址:https://www.cnblogs.com/happybirthdaytoyou/p/14440552.html
Copyright © 2020-2023  润新知