文章每周持续更新。您的“三连冠”是对我最大的肯定。可以在微信搜索公众号“后端技术派”立即阅读(一般比博客更早更新一两篇文章)。今天就带大家学习一下Linux的内存管理。对于精通CURD的商科学生来说,内存管理似乎离我们很远,但是这个知识点虽然冷门(估计很多人学了之后永远没有机会用到),但绝对是基础的基础,就像武术中练内功一样,学了不会立竿见影,但对你以后的发展工作大有裨益,因为你会站得更高。本文所有示例图片均为本人绘制。画图比码字要花更多的时间,但是理解图比文字更直观。需要高清示例图的同学,文末有获取方式。更功利一点,在面试的时候,你不经意间流露出你知道这些知识,你可以说一二三,这可能会让面试官对你更感兴趣。更近了一步。前提约定:本文讨论技术内容的前提,操作系统环境为32位x86架构的Linux系统。即使虚拟地址在现代操作系统中,内存仍然是计算机中非常宝贵的资源。看你的电脑有多少T固态硬盘,再看内存的大小。为了充分利用和管理系统内存资源,Linux采用了虚拟内存管理技术,利用虚拟内存技术让每个进程都有一个4GB的虚拟地址空间,互不干扰。进程的初始分配和运行都是基于这个“虚拟地址”。只有当进程真正需要访问内存资源时,才会建立虚拟地址和物理地址的映射,并将其转移到物理内存页中。打个不恰当的比方。这个原理其实和现在的XX网盘是一样的。如果你的网盘空间是1TB,你真以为这么大的空间一下子就给你了?那还太年轻了,当你把东西放进去时,你会得到分配的空间,你得到的实际空间和你放进去的一样多,但你和你的朋友看起来都有1TB的空间。虚拟地址的好处是防止用户直接访问物理内存地址,防止一些破坏性操作,保护操作系统。每个进程分配4GB的虚拟内存,用户程序可以使用4GB的进程虚拟内存,比实际物理内存要大。地址空间分为“用户空间”和“内核空间”物理地址两部分在上面的章节中,我们已经知道无论是用户空间还是内核空间,使用的地址都是虚拟地址。当进程需要真正访问内存时,会将内核的“请求分页机制”产生的“缺页异常”转移到物理内存页上。将虚拟地址转换为内存的物理地址,这涉及到使用MMU内存管理单元(MemoryManagementUnit)对虚拟地址进行段和页(segmentpage)地址转换。切分和分页的具体过程这里就不说了。具体可以参考任意一本计算机组成原理教材的描述。Linux内核会将物理内存划分为三个管理区,即:ZONE_DMADMA内存区。包含0MB到16MB之间的内存页面帧,可以通过DMA使用旧的基于ISA的设备,直接映射到内核的地址空间。ZONE_NORMAL普通内存区域。包含16MB到896MB之间的内存页框,常规页框,直接映射到内核的地址空间。ZONE_HIGHMEM高端内存区域。包含896MB以上的内存页框没有被直接映射,这些内存页框可以通过永久映射和临时映射来访问。用户空间用户进程可以访问“用户空间”,每个进程都有自己独立的用户空间,虚拟地址范围为0x00000000到0xBFFFFFFF,总容量为3G。用户进程通常只能访问用户空间的虚拟地址,只有在执行陷阱操作或系统调用时才能访问内核空间。进程和内存进程(被执行的程序)占用的用户空间按照“访问属性一致的地址空间存放在一起”的原则划分为5个不同的内存区域。访问属性是指“可读、可写、可执行等”。代码段代码段用于存放可执行文件的操作指令,以及可执行程序在内存中的映像。代码段需要防止被非法访问运行时修改,所以只允许读操作,不可写。program.TheBSSsegmentBSS段包含对于程序中未初始化的全局变量,内存中的bss段全部设置为0heap堆用于存放进程运行过程中动态分配的内存段。它的大小不固定,可以动态扩容或缩容,当进程调用malloc等函数分配内存时,新分配的内存被动态添加到堆中(堆被扩容);当通过free等函数释放内存时,释放的内存从堆中移除(堆减少)stackstack是用户为了保存程序而临时创建的局部变量,即定义在function(但不包括声明为static的变量,static表示将变量存储在数据段中)。另外,当函数被调用时,它的参数也会被压入调用进程的栈中,而在调用结束后,函数的返回值也会被存回栈中。由于栈的先进先出特性,栈对于调用场景的保存/恢复特别方便。从这个意义上说,我们可以把栈看成是一块存储和交换临时数据的内存区域。上述内存区中的数据段、BSS段、堆在内存中通常是连续存放的,位置也是连续的,而代码段和栈往往是独立存放的。在i386架构中,堆和栈这两个区域是相对的,栈向下扩展,堆向上扩展。也可以在linux下使用size命令查看编译后的程序各个内存区的大小:[lemon~]#size/usr/local/sbin/sshdtextdatabssdechexfilename19245321241242689623638402411c0/usr/local/sbin/sshd内核空间在x8632位系统中,Linux内核地址空间是指虚拟地址从0xC0000000到0xFFFFFFFF开始的高端内存地址空间,总容量1G,包括运行在内核空间的内核映像、物理页表、驱动程序等。DirectMappingAreaDirectMemoryRegion:从内核空间起始地址开始,最大896M内核空间地址范围为直接内存映射区。直接映射区的896MB“线性地址”直接映射到“物理地址”的前896MB,也就是说线性地址和分配的物理地址是连续的。内核地址空间的线性地址0xC0000001对应的物理地址为0x00000001,它们之间有一个偏移量。PAGE_OFFSET=0xC0000000该区域的线性地址与物理地址之间存在线性转换关系。“线性地址=PAGE_OFFSET+物理地址”也可以用virt_to_phys()函数将内核虚拟空间中的线性地址转换为物理地址。高端内存线性地址空间内核空间线性地址范围从896M到1G,容量为128MB的地址范围是高端内存线性地址空间。为什么叫高端内存线性地址空间?给大家解释一下:前面提到内核空间总大小为1GB,从内核空间起始地址开始的896MB线性地址可以直接映射到物理地址大小为896MB的地址范围.退一步讲,即使将内核空间中的1GB线性地址映射到物理地址,也最多只能寻址1GB的物理内存地址范围。请问你的内存条现在有多大?醒醒,是0202,一般PC的内存都大于1GB!因此,内核空间将最后的128M地址范围取出来,划分为以下三个高端内存映射区,对整个物理地址范围进行寻址。在64位系统上,不存在这个问题,因为可用的线性地址空间远大于可安装内存。动态内存映射区vmallocRegion该区域由内核函数vmalloc分配,其特点是:线性空间是连续的,但对应的物理地址空间不一定是连续的。vmalloc分配的线性地址对应的物理页可能在低端内存,也可能在高端内存。永久内存映射区PersistentKernelMappingRegion这个区域可以访问高端内存。访问方式是使用alloc_page(_GFP_HIGHMEM)分配一个高端内存页或者使用kmap函数将分配的高端内存映射到这个区域。FixedmappingareaFixingkernelMappingRegion这个区域和4G的顶部只有一个4k的隔离区,每个地址入口都有特定的用途,比如ACPI_BASE等等。上面的复习有点过分了,先别急着进入下一节,先复习一下上面的内容吧。如果你仔细阅读了上面的章节,我在这里又画了一张图。现在你的脑海中应该有了这样一张内存管理的全局图。内存数据结构为了让内核管理系统中的虚拟内存,需要从中抽象出内存管理数据结构。内存管理操作,如“分配、释放等”。都是基于这些数据结构的操作。这里有两个管理虚拟内存区域结构的数据。用户空间内存数据结构我们在前面的“进程与内存”一章提到过,Linux进程可以分为五个不同的内存区域,分别是:代码段、数据段、BSS、堆、栈,以及内核管理这些的方式areas是的,将这些内存区域抽象成vm_area_struct的内存管理对象。vm_area_struct是描述进程地址空间的基本管理单元。一个进程往往需要多个vm_area_struct来描述其用户空间虚拟地址,需要用“链表”和“红黑树”来组织各个vm_area_struct。链表用于需要遍历所有节点的时候,而红黑树适合定位地址空间中的特定内存区域。内核使用这两种数据结构来实现对内存区域的各种操作的高性能。用户空间进程地址管理模型:内核空间动态分配内存数据结构在内核空间一章中,我们提到了“动态内存映射区”。该区域由内核函数vmalloc分配。特点是:线性空间是连续的,但对应的物理地址空间不一定是连续的。vmalloc分配的线性地址对应的物理页可能在低端内存,也可能在高端内存。vmalloc分配的地址限制在vmalloc_start和vmalloc_end之间。vmalloc分配的每块内核虚拟内存对应一个vm_struct结构,不同内核空间虚拟地址之间有一个4k大小的防越界空闲空间。和用户空间的虚拟地址特性一样,这些虚拟地址与物理内存没有简单的映射关系,必须经过内核页表才能转换为物理地址或物理页。分配物理页。综上所述,Linux内存管理是一个非常复杂的系统。本文所描述的只是冰山一角。它会从宏观的角度向你展示内存管理的全貌,但一般来说,当你和面试官聊天时,这些知识就足够了。当然,也希望大家能够通过阅读,了解更深层次的原理。希望这篇文章可以作为索引式的学习指南。当你想深入学习某个点时,可以在这些章节中找到切入点,以及这个知识点在宏内存管理中的位置。我在创作这篇文章的过程中还画了很多插图,可以作为知识索引。个人感觉看图片比看文字更清晰。大家可以在我的公众号“后端技术派”后台回复“内存管理”,获取这些图片的高分辨率原图。老规矩,谢谢阅读。文章的目的是分享对知识的理解。我会反复验证技术文章,最大程度保证准确性。如果文章中有明显的错误,欢迎大家指出。让我们在讨论中共同学习。今天的技术分享就到这里,我们下期再见。原创并不容易。看到这里,如果对我有一点收获,动动手指“点赞”“关注”就是对我最大的支持了。我的更多精彩文章:非常详细的LinuxC/C++学习路线总结!已经拿到腾讯offer的面试官又来找你造飞机了。我告诉你微服务接口怎么设计?面试中被问到的微服务、服务治理、RPC、下一代微服务框架……这篇文章带你一探究竟!Linux下“进程”出问题别慌,资深程序员教你6招搞定!面试官:你说你熟悉MySQL事务?那么让我问你10个问题。我用大数据分析一线城市1000多个职位招聘需求,告诉你如何科学找工作。腾讯后台开发面试笔试C++知识点参考笔记你还能这样玩吗?我用VsCode画类图、流程图、时序图、状态图。别太酷了!面试官:你知道多少种redis分布式锁?我知道三种!最详细的个人博客搭建教程GithubPages+Jekyll简约风格的博客搭建不易,求点赞关注支持。可以在微信搜索公众号“后端技术学院”回复“资料”里面有我为你准备的各种编程学习资料。文章每周持续更新,我们下期再见!
