志在指尖
用双手敲打未来

linux内存管理(机制详解)

linux内存管理

内存(memory)在Linux系统中是一种牵涉面极广的资源,上至应用程序、下至kernel和driver,无不为之魂牵梦绕。加上它天然的稀缺性,导致内存办理(MemoryManagement,简称MM)是linuxkernel中十分重要又十分复杂的一个子系统。
重要性就不多说了,Kernel自有尺度。关于复杂性(鉴于Linuxkernel优异的笼统才能),应该不会被普通人(Linux系统的使用者、应用工程师、驱动工程师、轻量级的内核工程师)感知到才对。事实的确如此,Kernel屏蔽掉了大多数的完成细节,尽量以简略、易用的办法向其它模块供给memory服务。
不过呢,这个世界上没有完美的存在,kernel的内存办理也是如此,因为两方面的原因:一、众口难调,内存办理有关的需求实在太复杂了;二、CPU、Device和Memory之间纠结的三角恋(参阅下面图片),导致它也(不得不)供给了许多啰里噜苏的、不易了解的功用(困扰了许多从入门级到资深级的linux软件工程师)。
根据上面的原因,本站内存办理子系统发布了许多分析文章,以帮助咱们了解内存办理有关的概念。不过到目前为止,还短少一篇索引类的文章,从整体出发,了解Kernel内存办理所需求面对的软硬件局面、所要处理的问题,以及各个内存办理子模块的功用和意义。这便是本文的意图。
2.内存有关的需求总结
在嵌入式系统中,从需求的角度看内存,是十分简略的,能够总结为两大类(参阅上面的图片1):
1)CPU有拜访内存的需求,包含从内存中取指、从内存中取数据、向内存中写数据。相应的数据流为:
CPUMMU(Optional)Memory
2)其它外部设备有拜访内存的需求,包含从内存“读取”数据、向内存“写入”数据。这儿的“读取”和“写入”加了引号,是因为在大部分情况下,设备不像CPU那样有智能,不具备直接拜访内存的才能。总结来说,设备有三种途径拜访内存:
a)由CPU从中中转,数据流如下(本质上是CPU拜访内存):
DeviceCPU<——————–Memory
b)由第三方具有智能的模块(如DMA控制器)中转,数据流如下:
DeviceDMAControllerMemory
c)直接拜访内存,数据流如下:
DeviceIOMMU(Optional)———>Memory
那么Linuxkernel的内存办理模块怎么了解并满意上面的需求呢?接下来咱们将逐个梳理。
3.软件(Linuxkernel内存办理模块)的角度看内存
3.1CPU视角
咱们先从CPU的需求说起(以当时具有MMU功用的嵌入式Linux渠道为例),看看会向kernel的内存办理模块提出哪些需求。
?看到内存
关于内存以及内存办理,最初始的需求是:Linuxkernel的中心代码(首要包含启动和内存办理),要能看到物理内存。
在MMU使能之前,该需求很简略满意。
但MMU使能之后、Kernel的内存办理机制ready之前,Kernel看到的是虚拟地址,此刻需求一些简略且有用的机制,建立虚拟内存到物理内存的映射(能够是部分的映射,但要能够满意kenrel此刻的需求)。
办理内存
看到内存之后,下一步便是将它们办理起来。根据不同的内存形状(在物理地址上是否连续、是否具有NUMA内存、是否具有可拔插的内存、等等),可能有不同的办理模型和办理办法。
向内核线程/用户进程供给服务
将内存办理起来之后,就能够向其它人(kernel的其它模块、内核线程、用户空间进程、等等)供给服务了,首要包含:
以虚拟地址(VA)的办法,为应用程序供给远大于物理内存的虚拟地址空间(VirtualAddressSpace)
每个进程都有独立的虚拟地址空间,不会相互影响,从而可供给十分好的内存保护(memoryprotection)
供给内存映射(MemoryMapping)机制,以便把物理内存、I/O空间、KernelImage、文件等对象映射到相应进程的地址空间中,方便进程的拜访
供给公正、高效的物理内存分配(PhysicalMemoryAllocation)算法
供给进程间内存同享的办法(以虚拟内存的办法),也称作SharedVirtualMemory
等等
更为高档的内存办理需求
欲望是无止境的,在内存办理模块供给了基础的内存服务之后,Linux系统(包含kernel线程和用户进程)现已能够正常work了,更为高档的需求也产生了,例如:
内存的热拔插(memoryhotplug)
内存的size超过了虚拟地址可寻址的空间怎么办(highmemory)
超大页(hugetlbpage)的支撑
使用磁盘作为交换页以扩大可用内存(各种swap机制和算法)
在NUMA系统中经过移动物理页面方位的办法进步内存的拜访功率(Pagemigration)
内存泄漏的查看
内存碎片的整理
内存不足时的处理(oomkill机制)
等等
3.2Device视角
正常情况下,当软件活动只需求CPU参加时(例如简略的数学运算、图像处理等),上面3.1所触及内容现已足够了,无论是用户空间程序,还是内核空间程序,都能够愉快的运行了。
不过,当软件操作一些特别的、能够以自己的办法拜访memory的硬件设备的时分,费事就呈现了:软件经过CPU视角获得memory,并不能直接被这些硬件设备拜访。于是这些硬件设备就提出了需求:
内存办理模块需求为这些设备供给一些特别的获取内存的接口,这些接口能够按照设备所期望的办法组织内存(因此能够被设备拜访),也能够从头映射成CPU视角的办法,以便CPU能够拜访。
这便是咱们在编写设备驱动的时分会经常遇到的DMAmapping功用,其间DMA是DirectMemoryAccess的所需,表明(memory)能够被设备直接拜访。
另外,在某些应用场景下,内存数据可能会在多个设备间活动,为了进步功率,不能为每个设备都供给一份拷贝,因此内存办理模块需求供给设备间内存同享(以及相互转化)的功用。
4.软件结构
根据上面章节的需求,Linuxkernel从虚拟内存(VM)、DMAmapping以及DMAbuffersharing三个角度,对内存进行办理,如下图所示:
其间VM是内存办理的首要模块,也是咱们一般意义上所讲的狭义“内存办理”,代码首要分布在mm/以及arch/xxx/mm/两个目录下,其间arch/xxx/mm/*供给渠道相关部分的完成,mm/*供给渠道无关部分的完成。
DMAmapping是内存办理的辅佐模块,注要供给dma_alloc_xxx(申请可供设备直接拜访的内存—-dma_addr)和dma_map_xxx(是在CPU视角的虚拟内存和dma_addr之间转化)两类接口。该模块的详细完成依赖于设备拜访内存的办法,代码首要分别在drivers/base/*(通用完成)以及arch/xxx/mm/(渠道相关的完成)。
最终是DMAbuffersharing的机制,用于在不同设备之间同享内存,一般包含两种办法:
传统的、使用CPU虚拟地址中转的办法,例如scatterlist;
dmabuffersharingframework,位于drivers/dma-buf/dma-buf.c中。
有关这些模块的详细描述,本文后续的文章将会逐个展开(有些现已有了),这儿就先结束吧.linux

linux内存管理机制详解

内存处理的首要工作就是对物理内存进行组织,然后对物理内存的分配和收回。但是Linux引进了虚拟地址的概念。
虚拟地址的效果
假设用户进程直接操作物理地址会有以下的害处:
1、用户进程能够直接操作内核对应的内存,损坏内核工作。
2、用户进程也会损坏其他进程的工作
CPU中存放器中存储的是逻辑地址,需求进行映射才华转化为对应的物理地址,然后获取对应的内存。
通过引进逻辑地址,每个进程都具有独自的逻辑地址规划。
当进程恳求内存的时分,会为其分配逻辑地址和物理地址,并将逻辑地址和物理地址做一个映射。
所以,Linux内存处理触及到了以下三个部分:
1、物理内存
物理内存的组织
Linux中内存分为3个等级,从下到上依次为:
1、Page:一个page的大小为4k,Page是内存的一个最基本的单位。
2、Zone:Zone中供应了多个队伍来处理page。
Zone分为3种
2.1、ZONE_DMA:用来存放DMA读取IO设备的数据,内核专用
2.2、ZONE_NORMAL:用来存放内核的相关数据,内核专用
2.3、ZONE_HIGHMEM:高端内存,用来用户进程存放数据
3、Node节点,一个CPU对应着一个Node,一个Node包含一个Zone_DMA、ZONE_NORMAL、ZONE_HIGHMEM。
一起当一个CPU对应的内存用光后,能够恳求其他CPU对应的内存。
物理内存的分配
Linux将内存分配分为两种:
1、大内存
大内存运用伙伴系统分配
伙伴系统的做法是将ZONE中的Page分组,然后组装为多个链表。
链表中存放的是页块的调集
页块对应着有不同的大小,别离为1、2、4、8…1024个页。
当恳求(2i-1,2i]大小的page的时分,会直接恳求2i个页,假设对应的链表中有对应的页块,就直接分配。假设对应的链表没有,就往上找2i+1,假设2i+1存在,就将其分为2个2i页块,将其间1个2i加入到对应的链表中,将别的一个分配出去。
例如,要恳求一个128个页的页块时,先查看128个页的页块链表是否有空闲块。假设没有,则查256个页的页块链表;假设有空闲块的话,则将256个页的页块分红两份,一份运用,一份刺进128个页的页块链表中。假设仍是没有,就查512个页的页块链表;假设有的话,就分裂为128、128、256三个页块,一个128的运用,剩下两个刺进对应页块链表。
2、小内存分配
小内存分配运用slub分配,比方对象等数据
slub就是将几个页独自拎出来作为缓存,里边维护了链表。每次直接从链表中获取对应的内存,用完之后也不必清空,就直接挂到链表上,然后等候下次运用。
2、如何组织虚拟地址
虚拟地址对应的是虚拟空间,虚拟空间只不过是一个虚拟地址的调集,用来映射物理内存。
虚拟空间分为用户态和内核态。
32位系统中将虚拟空间按照1:3的比例分配给内核态和用户态
64位系统中别离给内核态和用户态分配了128T。
用户态结构
每个进程都会对应一个用户态虚拟空间,里边存放了Text(代码)的内存虚拟地址规划、Data(数据)的内存虚拟地址规划、BSS(全局变量)的内存虚拟地址规划、堆的虚拟地址规划、栈的虚拟地址规划,以及mmap内存映射区。
其间mmap用于恳求动态内存的时分的映射,堆和栈都是动态变化的。
一个进程对应的用户态中的各个方面的虚拟地址信息都通过一个struct来存储在内存中,当创建进程的时分会为其分配内存存储对应的虚拟地址信息
内核态结构
Linux中的内核程序共用一个内核态虚拟空间。其间分为了以下几部分:
1、直接映射区
896M,内核空间直接映射到对应的ZONE_DMA和ZONE_NORMAL中。为什么叫做直接映射呢?逻辑地址直接减去对应的差值就能够得到对应的物理地址。固定死了。
2、动态映射
为什么要引进动态映射呢?
因为一切物理内存的分配都需求内核程序进行恳求,用户进程没有这个权限。所以内核空间一定要能映射到一切的物理内存地址。
那么假设都选用直接映射的话,1G大小逻辑地址的内核空间只能映射1G大小的物理内存。
所以引进了动态映射,动态映射就是内核空间的逻辑地址能够映射到物理内存中的ZONE_HIGHMEM(高端内存)中的任何一个地址,并且在对应的物理内存运用完之后,能够再映射其他物理内存地址。
动态映射分为三种
1、动态内存映射:运用完对应的物理内存后,就能够映射其他物理内存了。
2、永久内存映射:一个虚拟地址只能映射一个物理地址。假设需求映射其他物理地址,需求解绑。
3、固定内存映射:只能被某些特定的函数来调用引用物理地址。
动态内存映射和直接映射的差异
动态映射和直接映射的差异就是逻辑地址到物理地址的转化规则。
直接映射
直接映射的规则是死的,一个逻辑地址对应的物理地址是固定的。通过逻辑地址加或许减去一个数,就能够得到对应的物理地址。
动态映射
动态映射是动态的绑定,每个逻辑地址对应的物理地址是动态的,通过页表进行查询
用户空间映射:
用户空间选用动态映射,每个虚拟地址能够被映射到一个物理地址,映射到ZONE_HIGHMEM。
为什么用户空间不选用直接映射呢?
因为物理内存是多个进程一切的,每个进程都有一个用户空间。假设选用直接映射的话,对应的物理地址是会冲突的。其用户空间的逻辑地址大小都为3G,所以存在逻辑地址相同,但是对应的物理地址不同。需求通过页表来转化,一个进程会对应一个页表。
3、如何将虚拟地址映射到物理内存
虚拟地址通过页表将虚拟地址转化为物理地址
每个进程都对应着一个页表
内核只有一个页表
虚拟空间和物理内存都按照4k来分页,一个虚拟空间中的页和物理内存中页是一一对应的。
页表映射
如上图所示,将虚拟地址中的页号通过页表转化为对应的物理页号,然后通过页内偏移量就能够得到对应的物理地址了。
但是1个进程就需求一个页表,一个4G的内存条,就需求1M个页表记载来描绘,假设1个页表记载需求4个字节,那么就需求4MB。而且页表记载是通过下标来对应的,通过虚拟页号来乘以对应的页表项大小来核算得到对应的地址的。
所以Linux将4M分为1K个4K,一个4K对应着一个page,用来存储对应的实在的页表记载。将1K个page分隔存放,就不要求连续的4M了。
假设将4M分红1K个离散的page的话,怎样虚拟地址对应的页表号呢?
运用指针,存储1K个地址,别离指向这1K个page,地址的大小为4个字节,也就是32位,完全能够标明整个内存的地址规划。
1K*4个字节,正好是一个page4k,所以也就是运用1个page来存储对应的页表记载索引。
所以我们的虚拟地址寻觅进程如下:
1、找到对应的页表记载索引方位,因为有1K个索引,所以用10位就能够标明晰
2、通过索引能够找到对应的实在的页表地址,对应的有1K个页表记载,所以用10位就能够标明晰
3、1个页有4K,通过12位就能够标明其页内偏移量了。
所以虚拟地址被分为了三部分
1、10位标明索引偏移
2、10位标明页表记载偏移
3、12位标明页内偏移
尽管这种方法增加了索引项,进一步增加了内存,但是减少了连续内存的运用,通过离散的内存就能够存储页表。
这是关于32位系统,64位选用了5级页表
映射流程图
用户态恳求内存时,只会恳求对应的虚拟地址,不会直接为其分配物理内存,而是等到实在访问内存的时分,发生缺页间断,然后内核才会为其分配,然后为其树立映射,也就是树立对应的页表项。
TLB
TLB就是一个缓存,放在CPU中。用来将虚拟地址和对应的物理地址进行缓存。
当查询对应的物理地址的时分,首要查询TLB,假设TLB中存在对应的记载,就直接返回。假设不存在,就再去查询页表。
虚拟内存
虚拟内存指的是将硬盘中划出一段swap分区当作虚拟的内存,用来存放内存中暂时用不到的内存页,等到需求的时分再从swap分区中将对应的内存页调入到内存中。硬盘此刻相当于一个虚拟的内存。
从逻辑上能够工作更大内存的程序,因为程序工作的时分并不需求把一切数据都加载到内存中,只需求将当时工作必要的相关程序和数据加载到内存中就能够了,当需求其他数据和程序的时分,再将其调入。
相较于实在的内存加载,虚拟内存需求将数据在内存和磁盘中不断切换,这是一个耗时的操作,所以速度比不上实在的内存加载。
总结
虚拟空间和物理内存都分为内核空间和用户空间。
虚拟地址需求通过页表转化为物理地址,然后才华访问。
用户虚拟空间只能映射物理内存中的用户内存,无法映射到物理内存中的内核内存,也就是说,用户进程只能操效果户内存。
内核空间只能被内核恳求运用,用户进程只能操效果户空间的物理内存和虚拟空间。
当用户进程调用系统调用的时分,会将其对应的代码和数据工作在内核空间中。
所以当调用内核空间读取文件或许网络数据的时分,首要会将数据拷贝到内存空间,然后在将数据从内核空间拷贝到用户空间。因为用户进程不能访问内核空间。

未经允许不得转载:IT技术网站 » linux内存管理(机制详解)
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!

 

志在指尖 用双手敲打未来

登录/注册IT技术大全

热门IT技术

C#基础入门   SQL server数据库   系统SEO学习教程   WordPress小技巧   WordPress插件   脚本与源码下载