【Linux的内存管理】

news2024/12/24 20:25:38

为什么需要内存管理

    • 分段和分页
      • 内存分段
      • 内存分页
    • 分页情况下,虚拟内存如何映射到物理地址
    • 页表原理
      • 多级页表
    • 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.coloradmin.cn/o/2178183.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《Zeotero的学习》

学习视频链接 Zeotera的安装 官网点击download,选择合适的版本进行下载,并安装插件。 下载完成之后,点击安装包,一路默认就可以。如果不想下载在C盘,可以在步骤中选择自定义路径。 Zeotero的注册 官网进行注册&am…

AIGC: 从两个维度快速选择大模型开发技术路线

在当今人工智能飞速发展的时代,大模型开发技术路线的选择至关重要。本文将从两个维度出发,为大家快速介绍不同的大模型开发技术路线,帮助你在开发过程中做出明智的决策。 一、两个维度解析 传入大模型的信息 低要求:传入的信息相…

【D3.js in Action 3 精译_025】3.4 让 D3 数据适应屏幕(中)—— 线性比例尺的用法

当前内容所在位置(可进入专栏查看其他译好的章节内容) 第一部分 D3.js 基础知识 第一章 D3.js 简介(已完结) 1.1 何为 D3.js?1.2 D3 生态系统——入门须知1.3 数据可视化最佳实践(上)1.3 数据可…

Activity的生命周期分析

目录 Activity的生命周期全面分析 典型情况下的生命周期分析 异常情况下的生命周期分析 情况1:资源相关的系统配置发生改变导致Activity被杀死并重新创建 Activity的生命周期全面分析 在Android开发中,Activity的生命周期是非常重要的概念。它描述了…

数仓建模:DataX同步Mysql数据到Hive如何批量生成建表语句?| 基于SQL实现

目录 一、需求 二、实现步骤 1.数据类型转换维表 2.sql批量生成建表语句 三、小结 如果觉得本文对你有帮助,那么不妨也可以选择去看看我的博客专栏 ,部分内容如下: 数字化建设通关指南 专栏 原价99,现在活动价39.9&#x…

前端使用xlsx-js-style导出Excel,带样式,并处理合并单元格边框显示不全和动态插入表头解决

一、在学习之前,先给出一些学习/下载地址: xlsx-js-style下载地址 https://github.com/gitbrent/xlsx-js-style 或者 https://www.npmjs.com/package/xlsx-js-style SheetJS中文教程: https://xlsx.nodejs.cn/docs/csf/cell 二、先看样…

双指针---(部分地更新)

双指针 复写零 给你一个长度固定的整数数组 arr ,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。 注意:请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上述修改,不要从函数返回任何东西。 …

Tableau 瀑布图应用示例

通过探索 10 个示例,将瀑布图的应用拓展到更深层次的业务分析! 作为一种直观展示数据变化的图表,瀑布图被广泛应用在业务分析中。同时,借助 Tableau 2024.2 中的 Viz Extensions,如今我们可以快速在 Tableau 中实现瀑布…

Vue3-TS-Lodash:理解Lodash / 常用方法积累

一、Lodash官网 Lodash 简介 | Lodash中文文档 | Lodash中文网 二、理解Lodash Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库。它提供了大量的函数来帮助你处理数组、数值、对象、字符串等,使你的代码更加简洁、易读和高效。Lodash 的设计哲学是…

25基于python的文本冒险岛游戏(源码+游戏简介+python代码学习攻略)校园招聘面试

基于python的文本冒险岛游戏(源代码游戏简介python代码学习)资源-CSDN文库https://download.csdn.net/download/m0_72216164/89817518 开头附上工作招聘面试必备问题噢~~包括综合面试题、无领导小组面试题资源文件免费!全文干货。 工作招聘无…

HarmoneyOS--Ability(能力)、窗口、通知

标题 文章目录 一、什么是Ability?二、使用步骤(单例和多例)三、窗口四、通知 一、什么是Ability? 开发模式提供的开发功能抽象的描述。 其中重要的是UiAbility,界面组件能力,负责所有界面的处理。 通过配置可以变更单例,多例,指定实例,在module.json5中进行配置 如: 单例:l…

FreeRTOS的中断管理

前言 FreeRTOS的任务有优先级,MCU的硬件中断有中断优先级,这是两个不同的概念,FreeRTOS的任务管理要用到硬件中断,使用FreeRTOS时候也可以使用硬件中断,但是硬件中断ISR的设计要注意一些设计原则,在本节中我…

RVC变声器入门

主要参考资料: RVC变声器官方教程:10分钟克隆你的声音!一键训练,低配显卡用户福音!: https://www.bilibili.com/video/BV1pm4y1z7Gm/?spm_id_from333.337.search-card.all.click&vd_sourcedd284033cd0c4d1f3f59a2…

RocketMQ消息发送之广播模式

前言 在前面的文章中我们回顾了RocketMQ的顺序消息和乱序消息,以及里面包含的乱序消息和全局消息,RocketMQ支持多种消息类型和消费模式 今天这篇文章主要介绍RocketMQ的广播消息。希望文章能为正在学习RocketMQ相关知识的大佬们提供帮助! 广…

全自动ai生成视频MoneyPrinterTurbo源码 在线ai生成视频源码

介绍: 现在短视频这么火爆,流量就是金钱。 如果能全自动的生成短视频,是不是很容易带来流量,赚到马内。 MoneyPrinter 这个开源项目就可以自动生成短视频,而且质量还不错,不是那种低质的营销视频。 使用…

画个心,写个花!Python Turtle库带你玩转创意绘图!

文章目录 前言一、Turtle 库基础介绍二、画布设置三、画笔属性设置1.画笔颜色设置2.画笔粗细与速度设置3.画笔形状设置 四、画笔移动函数五、画笔控制函数六、实战案例一:“花”字绘制七、实战案例二:心型图案绘制总结 前言 Python 的 turtle 库是一种简…

【MySQL内置数据库】 mysql

目录 统计 columns_priv component db default_roles engine_cost func general_log global_grants gtid_executed help_category help_keyword help_relation help_topic innodb_index_stats innodb_table_stats ndb_binlog_index password_history plugin…

【RocketMQ】SpringBoot整合RocketMQ

🎯 导读:本文档详细介绍了如何在Spring Boot应用中集成Apache RocketMQ,并实现消息生产和消费功能。首先通过创建消息生产者项目,配置POM文件引入RocketMQ依赖,实现同步消息发送,并展示了如何发送普通字符串…

STM32+ADC+扫描模式

1 ADC简介 1 ADC(模拟到数字量的桥梁) 2 DAC(数字量到模拟的桥梁),例如:PWM(只有完全导通和断开的状态,无功率损耗的状态) DAC主要用于波形生成(信号发生器和音频解码器) 3 模拟看门狗自动监…

Ract vs Vue 你更喜欢谁?

React 和 Vue 是当今最受欢迎的两个前端框架,各自有其独特的特点和优势。以下是对这两个框架的详细比较和分析,以帮助你了解它们的异同和适用场景: React 简介 React 是由 Facebook 开发和维护的一个开源 JavaScript 库,主要用于…