您的位置:首页 > 汽车 > 新车 > 福建龙岩疫情最新消息_喀什网站建设公司_短网址生成网站_网络营销策划怎么写

福建龙岩疫情最新消息_喀什网站建设公司_短网址生成网站_网络营销策划怎么写

2024/11/13 10:00:40 来源:https://blog.csdn.net/2301_79465388/article/details/142725787  浏览:    关键词:福建龙岩疫情最新消息_喀什网站建设公司_短网址生成网站_网络营销策划怎么写
福建龙岩疫情最新消息_喀什网站建设公司_短网址生成网站_网络营销策划怎么写

标题:[Linux] Linux初识进程地址空间

个人主页@水墨不写bug

(图片来源于AI) 


目录

一、什么是进程地址空间

二、为什么父子进程相同地址的变量的值不同

三、初识虚拟地址、页表


一、什么是进程地址空间

         其实,在很久之前,也就是去年的时候,我在《C语言:动态内存规划》(可转跳的这篇文章)一文中,就提到了内存分布:不同的变量类型会存储在不同的内存区域。

        但是这个内存真的是真正的内存吗?一个进程都有一个这样的内存区域吗?这样的内存区域是如何组织的?这是本文想要回答的问题。

        我们在动态内存规划中讲解过,C/C++语言的不同变量存在于不同的区域:

         但是,实际上我们对这个图片并没有非常只管的认识,我们只知道不同数据类型存储在不同的区域,我们甚至不知道这个整体的结构到底存储在哪里!接下来,一切都要从一个现象说起。


        我们在Linux下,可以使用系统调用fork()创建一个子进程

        子进程的会"继承"父进程的代码和数据,所以我们可以通过一个if条件判断把父进程和子进程将要执行的代码分开:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int g_val = 0;int main()
{pid_t id = fork();if(id < 0)//返回值小于0,表示创建子进程失败,打印错误信息后返回{perror("fork");return 1;}else if(id == 0)//child子进程只能执行这里的代码{ printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);}else//parent父进程只能执行这里的代码{ printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);}sleep(100);return 0;
}

        上述的代码就通过if判断,把子进程和父进程将要执行的代码分离开,这样就能单独编写父子进程需要执行的任务了。

        我们运行上面的代码,得到结果:

         我们发现,父子进程的全局变量g_val的值是相同的,地址也是相同的!这符合我们的预期,因为子进程会按照父进程为模板,父子都没有对这个变量做出任何修改。

        但是如果我们把这个代码稍微修改一下:(在子进程中故意修改全局的g_val变量

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int g_val = 0;int main()
{pid_t id = fork();if(id < 0){perror("fork");return 0;}else if(id == 0)//child,子进程肯定先跑完,也就是子进程先修改,完成之后,父进程再读取{ g_val=100;printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);}else//parent{ sleep(3);printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);}sleep(100);return 0;
}

        这个时候再运行程序,得到结果:

        这个结果发生了在语言层面上根本无法回答的问题:

同一个地址处的变量的值不相同!! 

二、为什么父子进程相同地址的变量的值不同

         其实,我们以前在C/C++中打印出来并看到的地址全都是虚拟地址,并不是真正的地址。

        我们可以推测:

        变量内容不一样,所以父子进程输出的变量绝对不是同一个变量。但地址值是一样的,说明,该地址绝对不是物理地址!
        我们其实访问的都是虚拟地址,对于物理地址,用户一律是看不到的,是由操作系统统一管理的。于是操作系统负责讲把虚拟地址转化为物理地址。

        具体如何操作呢?这就需要结合进程地址空间,同时,需要引入一个新的概念:页表。

三、初识虚拟地址、页表

         页表,浅层次的理解,就是一个能把虚拟地址映射到物理地址的表。

        操作系统通过PCB(也就是Linux的task_struct)来组织管理进程,进程地址空间本质是PCB内部的一个结构体,简单一点理解就是PCB内部含有维护本进程虚拟地址空间的信息

        在进程正常运行的情况下,PCB是链接在CPU的运行队列当中的         这样,我们就对进程地址空间有了全新的直观的认识了。CPU会根据《Linux的O(1)调度算法来调度进程》(可转跳的这篇文章),这是之前的故事了,这里不再赘述。

         言归正传,页表是一个把虚拟地址映射到物理地址的结构:

         我们创建的全局变量g_val就存储在数据段,g_val自然会通过页表映射到一段物理地址:

        当我们fork创建子进程,子进程会有和父进程一样的代码和数据:

 

        但是,由于进程间具有独立性,所以注定子进程改变全局变量g_val不能影响到父进程:这就好比你写的代码遇到空指针挂了,你的VS不会跟着挂一样。

        于是,解决这一问题的方法:写时拷贝就会发生。

        当子进程想要该表父进程的g_val时,会发生写时拷贝,在物理地址空间上重新开辟一块空间,复制拷贝一份g_val专门给子进程使用,这在保证进程的独立性的同时也在最大程度上节约了空间资源。         到这里,你也许就会明白为什么相同的地址的值不同了:因为父子进程页表的内容是相同的。不同的是页表的映射关系不同了。


完~

未经作者同意禁止转载

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com