【Linux学习笔记】11. Linux父子进程中变量地址相同——虚拟内存

Linux父子进程地址相同问题——虚拟内存

在进行进程控制编程的时候,使用fork()创建子进程时有这样的一个实例:

int main()
{
    int num = 0;
    pid_t pid = fork();
    if( -1 == pid) { perror("fork"); exit(1);}
    else if( 0 == pid) num ++;	//子进程动作
    else num ++;	//父进程动作
    return 0;
}

这段代码中,num变量在父子进程中的地址相同,但是却相互独立

按照常规的思路来说,这两个是不同进程中的变量,并且在两个进程中互相不受影响,即在一个进程中改变这个变量的值不会影响到另一个进程。既然这个变量都互相独立了,那么它们的地址应该不同。

但是当在父子进程中分别输出printf("&num = %d", &num);的时候,发现其地址却一样。

这就涉及到了Linux系统的一个领域的知识:虚拟内存

关于虚拟内存

通过虚拟内存机制,每个进程都以为自己占用了全部内存,进程访问内存时,操作系统都会把进程提供的虚拟内存地址转换为物理地址,再去对应的物理地址上获取数据。内存管理单元 MMU(Memory Management Unit)专门用来将翻译虚拟内存地址。CPU 还为页表寻址设置了缓存策略,由于程序的局部性,其缓存命中率能达到 98%。

  • 虚拟内存空间中的地址叫做“虚拟地址”
  • 实际物理内存空间中的地址叫做“实际物理地址”或“物理地址”

由于存在两个内存地址,因此一个应用程序从编写到被执行,需要进行两次映射。

  1. 第一次是映射到虚拟内存空间
  2. 第二次时映射到物理内存空间

在计算机系统中,第两次映射的工作是由硬件和软件共同来完成的。承担这个任务的硬件部分叫做存储管理单元MMU,软件部分就是操作系统的内存管理模块了。

在映射工作中,为了记录程序段占用物理内存的情况,操作系统的内存管理模块需要建立一个表格,该表格以虚拟地址为索引,记录了程序段所占用的物理内存的物理地址。这个虚拟地址/物理地址记录表便是存储管理单元MMU把虚拟地址转化为实际物理地址的依据。

虚拟内存的页、物理内存的页框及页表

页与页框是Linux实现虚拟内存技术的基础。

  • Linux把虚存空间分成若干个大小相等的存储分区,Linux把这样的分区叫做页。

为了换入、换出的方便,物理内存也就按也得大小分成若干个块。

  • 由于物理内存中的块空间是用来容纳虚存页的容器,所以物理内存中的块叫做页框。

在Linux中,页与页框的大小一般为4KB。

物理内存和虚拟内存被分成了页框与页之后,其存储单元原来的地址都被自然地分成了两段,并且这两段各自代表着不同的意义:高位段分别叫做页框码和页码,它们是识别页框和页的编码;低位段分别叫做页框偏移量和页内偏移量,它们是存储单元在页框和页内的地址编码。

在这里插入图片描述

图片来自知乎

为了使系统可以正确的访问虚存页在对应页框中的映像,在把一个页映射到某个页框上的同时,就**必须把页码和存放该页映像的页框码填入一个叫做页表的表项中。**这个页表就是映射记录表。

也就是说:处理器遇到的地址都是虚拟地址。虚拟地址和物理地址都分成页码(页框码)和偏移值两部分。在由虚拟地址转化成物理地址的过程中,偏移值不变。而页码和页框码之间的映射就在一个映射记录表——页表中。

写时拷贝发生的请页与交换

我们知道,在fork()创建进程时,系统会给创建出来的子进程分配地址空间,然后把父进程堆栈上的数据都拷贝到子进程中。这个拷贝的动作为写时拷贝。只要操作到内存时才会进行这个拷贝动作。

因此,在开头提到的那段代码中,若子进程没有去访问修改num变量,则不会进行这个拷贝动作,在这个时候两个进程中的num都是存储在同一个物理内存的地址上。若此时我们在子进程中执行了num ++;,则会发生堆栈数据的拷贝,在从内存管理的角度上看就是发生了请页操作

关于请页

虚存页面到物理页框的映射叫做页面的加载。

  • 当处理器试图访问一个虚存页面时,首先到页表中去查询该页是否已映射到物理页框中,并记录在页表中。
  • 如果在,则MMU会把页码转换成页框码,并加上虚拟地址提供的页内偏移量形成物理地址后去访问物理内存;
  • 如果不在,则意味着该虚存页面还没有被载入内存,这时MMU就会通知操作系统:发生了一个页面访问错误(页面错误),接下来系统会启动**“请页”机制**,即调用相应的系统操作函数,判断该虚拟地址是否为有效地址。
  • 如果是有效的地址,就从虚拟内存中将该地址指向的页面读入到内存中的一个空闲页框中,并在页表中添加上相对应的表项,最后处理器将从发生页面错误的地方重新开始运行;如果是无效的地址,则表明进程在试图访问一个不存在的虚拟地址,此时操作系统将终止此次访问。
关于交换

当然,也存在这样的情况:在请页成功之后,内存中已没有空闲物理页框了

这时,系统必须启动**“交换”机制**,即调用相应的内核操作函数,在物理页框中寻找一个当前不再使用或者近期可能不会用到的页面所占据的页框。

找到后,就把其中的页移出,以装载新的页面。

对移出页面根据两种情况来处理:如果该页未被修改过,则删除它;如果该页曾经被修改过,则系统必须将该页写回辅存。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vqLz1Lg3-1651819848443)(https://cdn.jsdelivr.net/gh/Chen-Mxn/mx-picgo-image/20220505194825.jpg)]

图片来自知乎

为了公平地选择将要从系统中抛弃的页面,**Linux系统使用最近最少使用(LRU)页面的衰老算法。**这种策略根据系统中每个页面被访问的频率,为物理页框中的页面设置了一个叫做年龄的属性。页面被访问的次数越多,则页面的年龄最小;相反,则越大。而年龄较大的页面就是待换出页面的最佳候选者。

关于父子进程变量地址相同的解释

回到一开始提到的那段代码,其具体过程如下:

当程序执行到fork()时创建子进程,子进程会完全复制父进程的栈空间,包括页表,但没有复制物理页面,所以这时虚拟地址相同,物理地址也相同。

但是会把父子共享的页面标记为“只读”(类似mmap的private的方式),在父子进程都没有访问内存时,两个进程用同一个页面。

直到其中任何一个进程要对共享的页面“写操作”,这时内核会复制一个物理页面给这个进程使用,并修改页表。同时把原来的只读页面标记为“可写”,留给另外一个进程使用。

这就是所谓的 “写时复制”“写时拷贝”

关于虚拟内存的介绍参考:知乎文章

修改时间:2022.05.05

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值