【Linux的内存管理】

为什么需要内存管理

    • 分段和分页
      • 内存分段
      • 内存分页
    • 分页情况下,虚拟内存如何映射到物理地址
    • 页表原理
      • 多级页表
    • TLB快表
    • 段页式内存管理
    • 需要为什么进程地址空间
    • Linux的进程虚拟地址空间
    • 管理进程地址空间
      • 如何分配虚拟内存
      • 虚拟内存的管理
        • 程序编译后的二进制文件如何映射到虚拟内存空间中
    • 内核的虚拟地址
      • 虚拟内核空间进行动态映射
    • 物理内存地址
    • cpu读取内存的数据
    • Linux的物理和虚拟内存的页都是4KB的大小

在计算机中运行的进程,都是二进制代码&数据&内核的数据结构组成,这些内容都需要物理内存进行保存。许多的进程直接使用这些物理内存,操作系统是非常难对指向物理内存进行管理的,如果一个进出现了问题,就有可能会影响其他的进程。所以有了进程地址空间,而且是每一个进程独有的且和其他的进程地址空间隔离,即使该进程出现了问题也不会造成其他进程的问题。在Linux中,如果是32位的相同,进程地址空间可以表示的区域有4GB的大小。其中1GB的内核空间是所有进程共享的。有了进程地址空间,相当于为每个进程提供了一个自己独立的内存区域,且每个区域都是4GB的大小,就可以访问到比真实的物理地址大的空间。
在这里插入图片描述

分段和分页

在Linux中。

  • 分段:可以为每一个每一个进程分配不同的线性地址空间,不同的段有着不同的功能,有代码段,数据段,堆区栈区。每一段都是连续的空间。
  • 分页:将整个虚拟和物理内存的空间划分成许多连续的小块空间,这样的小块空间就是页,在物理内存中为页框,在Linux中,每一个页的大小的空间为4KB。

内存分段

分段机制下的虚拟地址有两部分组成,段的选择因子和段内的偏移量。

  • 段的选择因子:段的选择因子有段号,段号,段寄存器通过段号找到对应的段表,段表有对应的段描述符,段描述符有段的完整信息,段的基地址、段的界限和特权等级等。
  • 段内的偏移量:偏移量在0和段的界限之间,段基地址加上偏移量再通过虚拟到物理地址的转化,就可以访问到物理地址了。
    在这里插入图片描述

分段会产生内存碎片问题,产生的原因是,申请段大小的空间,申请多少就会有多少,然后不同的段大小是不一样的,这就会导致有些区域空间无法申请成功,操作系统就会再其他可以申请的区域申请,就会导致无法申请的区域浪费掉,这就是外部的内存碎片问题。

内存分页

分段能连续分配空间,但因为有些空间不足,无法申请,但是这种情况又无法避免,只能减少。内存不足的情况下,如果进程需要物理内存,操作系统会将一部分的内存数据交换出去,让当前的进程有一定的物理空间使用。然而数据的交互是需要耗时的,这时候我们就需要减少内存的交换,面对这种情况,

  • 我们可以将虚拟和物理的内存进行分页,页与页之间是紧密排列的,所以不会有外部碎片。
  • 页与页之间是紧密排列的,所以不会有外部碎片。有了分页,内存需要置换的话,不需要把一整个段置换,只需要将需要的页进行置换即可,可以提高效率。

分页情况下,虚拟内存如何映射到物理地址

虚拟和物理地址的转化,是由一个页表来进行的。提供虚拟和物理地址的映射。虚拟地址由页号和偏移量。

  • 页号:页号作为页表的索引,提高页号找到对应物理地址。
  • 偏移量:通过偏移量,更加页号找到的物理地址进行偏移量的计算来获取目标的地址。
    在这里插入图片描述

页表原理

页表的存在,让虚拟和物理地址有了联系。一个页或页框是4KB的大小,虚拟和物理地址是4GB的大小,就会有4 * 1024 * 1024 / 4个页或页框(一百多万的页),一个页表的大小就要4MB的大小,如果进程过多的话,采用这样的页表方式,将会浪费物理内存。

多级页表

采用多级页表就可以解决页表占用空间过多的问题。第一个页表大小为4KB,有1024个页表项,而页表项不存储虚拟地址,而是存储二级页表的页号,二级页表也是一个4KB大小的页表,也有1024个页表项,在极端场景下就能表示4GB的空间大小。32位情况下,前10比特位表示一级页表,中间10位表示二级页表,后12位表示物理地址的偏移量。
在这里插入图片描述

一级页表4KB+二级页表4MB的情况 > 一个页表4MB:这让看,会发现使用二级页表会比直接使用一个页表还多4KB,极端情况是这样的。但一个进程在运行的时候,并不是一定需要4GB的空间大小,有些进程还不一定把他的全部数据加载到物理内存当中。这时候,一级页表创建的时候,有些页表项不会被使用到,二级页表就不会存在,如果需要的话,再创建二级页表。如果只使用了20%的二级的页表,一级加二级页表所占的内存只需要0.804MB,远比只使用一个页表更节省空间。

TLB快表

如果每次都虚拟地址都通过页表找到对应的物理地址,效率会很慢,所以计算机科学家在CPU芯片中加入了一个Cache,就是TLB快表,用于存放将虚拟地址映射至物理地址的标签页表条目。有了TLB的缓存,避免每次都要查询页表项,每次CPU访问某个地址的时候,现在TLB缓存中查询,如果查询到有对应的虚拟到物理的映射,则直接通过MMU内存管理单元进行虚拟到物理的访问,减少了页表查询的消耗。
在这里插入图片描述

  • 首先,CPU进行虚拟地址寻址时,首先会在MMU的TLB快表缓存当中寻找是否有虚拟地址到物理地址的页表条目,如果存在,直接通过MMU访问物理地址。
  • 如果TLB缓存没有,则会发生硬件中断,将外设的资源加载到内存当中,页表重新映射虚拟地址到物理地址的页表条目,然后再将该页表条目缓存在TLB,方便下次的快速查找。

段页式内存管理

分段可以让内存空间分为多个有目的逻辑段,不同的数据存放在不同的段空间,分页将多个分段的空间划分为大小一致的许多连续的空间,可以在磁盘和物理内存进行交换的数据减少,提高效率。就可以使用段号,段内页号,页号偏移进行虚拟到物理地址的映射。

  • 访问段表查询段号,得到段内的页表地址。
  • 通过页表找到物理页号。
  • 在通过偏移量的条件下,得到物理地址。

需要为什么进程地址空间

  • 进程的地址空间其实并没有进程需要的数据,数据而是在物理内存当中,所以进程地址空间的内存也是虚拟内存。
    在这里插入图片描述
  • 如果所有的进程都直接使用物理内存,如果其中一个进程修改其他进程的物理内存的数据,就会有可能导致进程的崩溃,所以,为了不同的进程安全,则使用进程地址空间作为间接的访问真实的物理内存,并通过进程地址空间和物理空间的转化,来对内存进行访问。进程之间互不干涉。在操作系统中,通过CPU的MMU对虚拟地址对物理地址的转化来找到真实的物理内存。
  • 如果可执行二进制代码运行多个进程,地址都是物理地址的话,就会指向同一块物理内存,导致程序出错或崩溃。如果重新为每一个进程直接分配物理内存,那会非常的复杂,调试的时候,程序员就难以区分地址。
    在这里插入图片描述
  • 使用了虚拟内存,直接可以让程序员看到的是连续的地址,虽然底层物理的内存是不连续的。每个进程的地址空间互相独立,互补干扰,其中的一个进程崩溃,不会影响其他的进程,即使虚拟地址一样,通过虚拟转物理的技术,不同进程的物理地址是不会发生冲突的。
    在这里插入图片描述

Linux的进程虚拟地址空间

在这里插入图片描述

  • 在Linux中,进程地址空间不是一开始就是在最小地址初开始的,而是在0x0804 8000 地址开始。在0x0000 0000 到 0x0804 8000是一段不可访问的空间。因为数值较小,通常会被认为是一个不合法的地址,比如C语言的无效指针NULL,指向的就是这块区域。
  • 代码区:包括二进制可执行代码;
  • 数据区:包括已初始化的静态常量和全局变量;
  • BSS:未初始化的数据区;
  • 堆区:内核使用start_brk标识堆的起始位置,brk标识堆的结束位置。当申请新的内存空间时,只需要将brk指针增加到对应的大小,回收时减少对应大小即可。
  • 共享映射区:包括动态库,共享内存,堆空间申请;
  • 栈:存放局部变量的数据,和函数调用的上下文。有固定的大小,可以调整。在内核中,使用start_stack标识栈的起始地址,RSP寄存器保存栈顶指针,RBP寄存器保存栈基地址;
  • 内核空间:每一个进程都共用同一个1GB的内核地址空间;

管理进程地址空间

Linux中的进程使用一个结构体mm_struct来描述进程地址空间的,mm_struct是进程控制块task_struct的一个结构。每个进程的mm_struct都是独立的互不干扰。task_size其实是用户态可以访问的空间,task_struct就划分了用户态和内核态了。

struct task_struct{
	unsigned long task_size;//可以访问的大小 0xc0000000
	struct mm_struct *mm;
}

如何分配虚拟内存

根据mm_struct不同的定义进行划分。
在这里插入图片描述

  • start_code和end_code标识代码段开始和结尾,就是存放二进制代码的区域。
  • start_data和start_data是定义了的数据段的开始和结束的区域,后边紧跟着BSS未初始化的数据。
  • start_brk就是动态申请空间的堆区域的开始,brk标识申请的空间的结束位置,低地址往高地址增加。
  • mmap就是内存的映射区的开始,高地址往低地址增加。运行时所依赖的动态链接库就是加载到该内存区域。
  • start_stack是栈的起始位置,栈使用的空间是由高地址往低地址使用,结束的位置的至就在寄存器的栈顶指针。

虚拟内存的管理

mm_struct对虚拟空间进行划分,现在使用新的结构体vm_area_struct对这些区域进行管理。每个vm_area_struct对应一个划分的区域。然后使用类似链表的结构进行组织。

struct mm_struct{
	struct vm_area_struct  *mmap; /* list of VMAs */
}
struct vm_area_struct {
	struct mm_struct * vm_mm;	/* The address space we belong to. */
	unsigned long vm_start;		/* Our start address within vm_mm. */
	unsigned long vm_end;		/* The first byte after our end address
					   within vm_mm. */

	/* linked list of VM areas per task, sorted by address */
	struct vm_area_struct *vm_next;
	struct anon_vma *anon_vma;	/* Serialized by page_table_lock */
	unsigned long vm_pgoff;		/* Offset (within vm_file) in PAGE_SIZE
					   units, *not* PAGE_CACHE_SIZE */
	struct file * vm_file;		/* File we map to (can be NULL). */
	}
  • vm_mm标识该虚拟内存管理属于哪个空间,进行指针的回指到属于那块虚拟内存区域;
  • vm_start标识虚拟划分空间的起始地址,vm_end标识结束地址;
  • vm_next标识下一个vm_area_struct 的划分,内存的划分管理使用链表进行管理;
  • anon_vma标识的是匿名映射区,当进程动态申请的空间大于128kb的时候,则会在该区域申请空间,而不是通过堆区域的brk的指针往上增长,而是调用mmap申请空间。当mmap为文件申请空间的时候,vm_file属性就用来关联被映射的文件,这样虚拟内存就可以和映射的文件关联起来。vm_pgoff则标识映射到虚拟内存中的文件内容的偏移量。

在这里插入图片描述

程序编译后的二进制文件如何映射到虚拟内存空间中
  • 编译后的文件是elf格式的可执行文件,当执行elf格式的文件时,会将二进制代码加载到内存当中。
  • 通过内核函数load_elf_binary将elf的文件映射到虚拟地址空间。当fork创建子进程,exec进行进程替换的时候,执行二进制程序的时候,建立虚拟内存映射。初始化虚拟内存所需要的数据。

内核的虚拟地址

在32位的计算机下,虚拟内核空间在0xc000000到0xffffffff的区域,每个进程都是共有这块虚拟内存空间的。在内核低地址的896MB的空间,是一块直接映射的区域。直接映射到物理内存的0~896MB的区域。虽然是直接映射的区域,但虚拟内核空间还是使用了页表进行虚拟到物理的映射。只是说每次的映射的物理内存的地址是不会改变的。
在这里插入图片描述

  • 在这段896MB的物理内存当中,第1MB在系统启动的时候就已经被占用,1MB之后存放的内核的代码段,数据段,BSS段等。内核的elf格式的代码在启动的时候加载到内存当中。
  • 高端内存:在剩下的4GB- 896MB的3200MB的物理内存就是高端内存,是无法直接映射到内核虚拟空间剩下的128MB的空间的。所以只能动态的映射到虚拟内存的128内存当中,哪些使用先映射,使用完毕的话,可以让其他要映射的覆盖上去。

虚拟内核空间进行动态映射

虚拟内存采用函数vmalloc调用申请空间的,申请的虚拟空间是连续的,但物理空间不一定是连续的。然后通过页表的映射来进行虚拟到物理的转化。
在这里插入图片描述
malloc和vmalloc的区别

  • malloc用于用户申请堆上空间,是C标准库的函数。vmalloc是Linux内核提供的函数,用于内核中分配虚拟地址连续但物理地址不连续的空间。
  • malloc申请的大小没有特别的限制,但受到系统可用内存的限制。vmalloc申请的空间也没有大小的限制,适用于较大内存的场景。
  • malloc申请的的内存在虚拟地址是连续的,但物理地址是否连续取决内存管理器的实现和系统内存碎片的情况。malloc和vmalloc申请的空间都不会自动的初始化。
  • vmalloc在内核中执行,可能会阻塞,在调用vmalloc时一般不会被中断,系统调用的执行被视为一个原子操作,即在执行期间不会被中断。这是为了确保在系统调用服务例程执行期间对内核数据结构的一致性和完整性。系统调用执行的时间相对较短,内核会采取一些机制来防止在其执行期间被中断。
  • malloc调用是C标准库的函数调用,如果申请的空间大于堆可用的空间,malloc就会调用brk()或mmap()系统调用。用户态->内核态,如果申请的空间在堆区域满足,则直接在用户态就可以申请到,不需要到内核态。

物理内存地址

内存也叫随机访问存储器(RAM),分为静态SRAM和动态DRMA。

  • SRAM用于CPU的三级缓存的高速缓存,越靠近CPU的SRAM运行速率越快,但相应的容量也会越少。
    在这里插入图片描述
  • DRAM常用于主存上,运行速率相对高速缓存是慢的,但容量也比高速缓存大。内存是由多个存储器模块组成,存储器模块又包含着8个DRAM芯片。每一个DRAM芯片是由一个二维矩阵组成的。矩阵中每个元素称为supercell超单元,大小位一个字节,每个单元都有坐标。提高行列坐标进行寻找。存储控制器将物理地址转化位DRAM芯片的坐标,然后找到对应的位置将数据发到存储控制器。
    在这里插入图片描述

cpu读取内存的数据

  • 1:CPU获取到虚拟地址,通过MMU内存管理单元对虚拟地址进行物理地址的转化,将物理地址作为地址信号放在系统总线上传输,随后在I/O桥将系统总线上的地址喜欢转化为存储中小的电子信号。
  • 2:主存的存储控制器收到电子信号的物理地址,存储控制器找到对应存储模块的DRAM地址,然后获取数据。
  • 3:存储控制器读取的数据放到存储总线上,存储总线通过I/O桥,I/O将数据信号转化为系统总线的数据信号进行传递。
  • 4:CPU 芯片感受到系统总线上的数据信号,将数据从系统总线上读取出来并拷贝到寄存器中。

在这里插入图片描述

Linux的物理和虚拟内存的页都是4KB的大小

页的大小规定为2的整数次幂,因为有利于计算机的位运算,提高运行的效率。如果内存不足的情况下,有些进程需要使用到内存,操作系统就会将一些不太使用的物理页进行换入换出,还有内存和磁盘文件都会有交互,交互都是要消耗时间的。如果内存和磁盘直接传输小块数据是速度比较快的。所以内核默认采用4KB的大小。

  • 页表过小:虽然传输的效率会变快,内部的碎片也会减少,但需要的页表项就会增多,如果要页表的换入换出,就会涉及过多的页表,频繁的换入换出会更耗时,降低效率。
  • 页表过大:大的页表会减少页表一定的内存消耗,但会导致内部碎片增多。页大小过大,搜索页表会加快一定速率,但搜索页内的内容,要比页小的耗时。大的页和磁盘的每次交互或者换入换出的消耗更多的时间。综合来看4KB的大小更合理。

Linux中,使用struct page对物理页进行管理内核如何描述物理内存页。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/884906.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

node-rtsp-stream、jsmpeg.min.js实现rtsp视频在web端播放

1. 服务地址(私有):https://gitee.com/nnlss/video-node-server 2.node-rtsp-stream 需要安装FFMPEG; 3.给推拉流做了开关,可借助http请求,有更好方式可联系; 4.存在问题: 1&…

王道-计组

4 设相对寻址的转移指令占4字节,其中第1、第2字节是操作码,第3、第4字节是相对位移量(用补码表示)。设当前PC的内容为2008H,要求转移到2001H的地址,则该转移指令第3、第4字节的内容应为______ 答案:A 解析:由于指令占4字节,取指令之后(PC)+4。第3、第4字节的内容为:2…

【从0开始自动驾驶】用python做一个简单的自动驾驶仿真可视化界面

【从0开始自动驾驶】用python做一个简单的自动驾驶仿真可视化界面 废话几句废话不多说,直接上源码目录结构init.pysimulator.pysimple_simulator_app.pyvehicle_config.json 废话几句 自动驾驶开发离不开仿真软件成品仿真软件种类多https://zhuanlan.zhihu.com/p/3…

Debian与Ubuntu:深入解读两大Linux发行版的历史与联系

Debian与Ubuntu:深入解读两大Linux发行版的历史与联系 引言 在开源操作系统的领域中,Debian和Ubuntu是两款备受瞩目的Linux发行版。它们不仅在技术上有着密切的联系,而且各自的发展历程和理念也对开源社区产生了深远的影响。本文将详细介绍…

10分钟,AI如何精准写出社会热点文?一篇爆款文章的背后你敢信?

本文背景 很多小伙伴们反馈,用AI输出的文章经常被平台判定为“疑似AI创作”,一但被判定,系统就不会给推荐流量。 到底在这个充斥着AI的大环境下,应该怎样完成AI文章的写作呢?特别是做流量主项目的小伙伴们,…

探索甘肃非遗:Spring Boot网站开发案例

1 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大,随着当前时代的信息化,科学化发展,让社会各行业领域都争相使用新的信息技术,对行业内的各种相关数据进行科学化,规范化管理。这样的大环境让那些止步不前&#…

【Android 源码分析】Activity短暂的一生 -- 目录篇 (持续更新)

1. 前言 忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。                                                                                  …

Library介绍(二)

时序弧(timing arc) 描述2个节点延迟信息的数据,可以分为net delay和cell delay两大类。 Net delay: drive cell output pin和drived cell input pin之间的net delay,取决于net rc和drive cell驱动能力及drived cell的load。 C…

Java五子棋

目录 一:案例要求: 二:代码: 三:结果: 一:案例要求: 实现一个控制台下五子棋的程序。用一个二维数组模拟一个15*15路的五子棋棋盘,把每个元素赋值位“┼”可以画出棋…

Rust和Go谁会更胜一筹

在国内,我认为Go语言会成为未来的主流,因为国内程序员号称码农,比较适合搬砖,而Rust对心智要求太高了,不适合搬砖。 就个人经验来看,Go语言简单,下限低,没有什么心智成本&#xff0c…

【代码】Zotero|用文章标题更新 Zotero 的参考文献引用条目信息的 Quicker 动作

如题。 目前只支持期刊和会议文章,并且只支持谷歌学术或 DBLP 能搜到的文章,知网的不支持,如果有人有需要我可以去试着写,但我很懒我看大家也没这个需求。 很早就写完了,一直忘记推了。 刚写完的时候心情是很激动的&a…

【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(下)

系列文章目录 【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(上) 【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(下) 文章目录 系列文章目录前言一、装饰器语法6.Builder语法:(1&…

理解Python闭包概念

闭包并不只是一个python中的概念,在函数式编程语言中应用较为广泛。理解python中的闭包一方面是能够正确的使用闭包,另一方面可以好好体会和思考闭包的设计思想。 1.概念介绍 首先看一下维基上对闭包的解释: 在计算机科学中,闭包…

eNSP的AR设备启动错误40解决方案之一

解决方法: 打开控制面板------>程序------>启用或关闭Windows功能,(取消勾选)Window虚拟机监控程序平台 和 虚拟机平台(使用虚拟机时要打开这两功能!!!!)

第八届蓝桥杯嵌入式省赛程序设计题解析(基于HAL库)

一.题目分析 (1).题目 (2).题目分析 1.按键功能分析----过程控制 a. 选择按键按下的个数和目标层数(每个按键都要在一秒之内按下,否则就结束) b. 当升降机到达目标平台,LED灯熄灭 c.…

【YOLO目标检测车牌数据集】共10000张、已标注txt格式、有训练好的yolov5的模型

目录 说明图片示例 说明 数据集格式:YOLO格式 图片数量:10000(2000张绿牌、8000张蓝牌) 标注数量(txt文件个数):10000 标注类别数:1 标注类别名称:licence 数据集下载:车牌数据…

excel导出图片---HSSFWorkbook--SXSSFWorkbook

1 概述 平时在工作中,excel导出图片经常会用到,但奈何HSSFWorkbook导出数据数量有限制问题,所以企业里大多都用SXSSFWorkbook格式,很少用HSSFWorkbook。所以今天以这两种格式分别记录下,图片的导出过程。 2 HSSFWork…

C++:模拟实现vector

目录 成员变量与迭代器 size capacity empty 迭代器有关函数 实现默认成员函数的前置准备 reserve ​编辑 ​编辑 push_back 构造函数 无参构造 迭代器区间构造 n个val来进行构造 析构函数 拷贝构造函数 赋值重载 增删查改 clear resize pop_back inser…

Java SE 总结

Java SE(Standard Edition)是Java编程语言的标准版本,提供了基础的编程环境和API,适用于开发和运行Java应用程序。下面是Java SE的几个重要方面的知识回顾与总结。 1. Java环境基础 具体可参考这里对三者的介绍 传送门 1.1 JVM…