linux 内存看一篇就够了(多图)(建议收藏)

正文

内存模块

1 linux内存总体布局:内存分成用户态和内核态

4G进程地址空间解析

内核地址空间

进程地址空间
 

2 地址转换和页表

2.1 地址转换

虚拟内存是指程序使用的逻辑地址。每个进程4G。所有进程共享物理内存4G,所以逻辑地址和物理地址不是一一对应,需要地址转换.

页表由3部分组成:页目录,页面,页内偏移
32bit只有3级 0 -11位:页内偏移OFFSET 12-21位:页面表偏移PT(PTE 页表项.指向一张具体的物理内存页) 22-31位:页面目录偏移PGD
寻址过程如下:
1)操作系统从寄存器CR3获得当前页面目录指针(基地址);
2)基地址+页面目录偏移->页面表指针(基地址);
3)页面表指针+页面表偏移->内存页基址;
4)内存页基址+页内偏移->具体物理内存单元。
页号=逻辑地址/页面大小;页面偏移量=逻辑地址%页面大小 。
页目录保存页表项的地址,页表项保存物理地址,最后1项保存4k页内偏移。
进程页表长这样子:

举例说明地址映射:比如要访问线性地址0xBFC0 4FFE(1byte),2进制形式

1).mmu先取线性地址高10位(31-22)=767,x4=3068=0xBFC,+cr3=0x3800 0bfc,即页目录项在内存的地址,此地址的内容0x392f f000即页表首地址(页对齐最后12位清0)
2).mmu取线性地址下面10位数(21-12)=4,x4=16=0x10,+0x392f f000=0x392f f010,即页表项在内存的地址,此地址的内容0x3080 4000即物理页面的首地址(页对齐最后12位清0)
3).mmu取线性地址最后12位数(11-0)=4094=0xffe作为低12位,0x3080 4000的前20位作为高20位,组成一个新的32位数0x3080 4ffe即线性地址0xBFC0 4FFE对应的物理地址

2.2 内核页表和进程页表的区别和联系?

进程页表访问:虚拟内存与物理内存的对应。 内核页表:建立物理内存和disk的对应.address_space。
mmap的时候,vmalloc分配内核内存,更改内核页表,然后拷贝内核页表到进程页表.
无论进程页表还是内核页表都在内核中运行,都由内核修改.
经典问题:两个进程虚拟地址相同,物理地址不同。本质就是进程页表的内容不同。

3 内存的分配

3.1 伙伴系统 and slab

伙伴系统分配的最小单位是页(4k). 伙伴系统是一个内存池.
它把所有的空闲页框分组为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续的页框.
把空闲的页以2的n次方为单位进行拆分or合并.分配从链表上获取和归还.
比如:k 链表(32)没有空闲块,需要在k+1链表(64)查找,如果仍然没有。在k+2链表(128)查找,找到以后分成1个64,2个32发给k和k+1链表.
链表回收过程相反,临近的空闲块组合,连接到空闲链表上.

例如:假设ZONE_NORMAL 有16页内存,此时有人申请一页内存,剩下15页.Buddy算法会把剩下的15页拆分成8+4+2+1,放到不同的链表中去。

slab:内核分配内存通常很小,所以引入了slab的方法. Buddy解决外部内存碎片,slab 解决内部内存碎片.
伙伴系统(buddy system)是以页为单位管理和分配内存.slab是以byte为单位分配内存.
slab分配器是基于对象类型进行内存管理的,每一种对象被划分为一类,例如索引节点对象是一类,进程描述符又是一类,等等 slab分成2种cache,普通cache和专用cache. 每种cache分成3种链表full,paritition,empty.
slab 使用kmalloc分配.

伙伴系统就是内存池.slab是从伙伴系统批发的内存建立的小内存池. 3.2 内存分配

内存分配就是向伙伴系统申请内存,然后更改页表形成地址转换.
缺页中断:malloc分配内存即产生缺页中断,缺页中断有两个情况,一种有足够内存,直接分配。另一种情况没有内存,需要先置换后分配.

(1) 进程分配malloc

malloc 分配的最小单位是页.小于128k用brk.malloc 大于128k用mmap.

1): brk原理:brk是堆顶指针,会产生内存空洞.比如:先分配256k,再分配128k,释放前面的256k,因为brk指向的栈顶128k没有回收,所以释放的256k页不会归还os.
brk释放:当brk释放空间少于128k则不会归还os,而是malloc采用链表管理这些空闲内存.
比如:malloc(1) 分配1个字节,os也会分配4k。剩下的4k-1字节全由malloc的空闲内存管理链表管理.

2): mmap

堆和栈之间,独立分配,直接释放. 采用匿名映射分配内存. 缺点在堆栈之间造成内存空洞。
写拷贝:malloc分配内存仅仅分配虚拟内存并没有分配物理内存,根据内存的写拷贝原则,只有访问内存的时候才正式分配物理内存。即malloc分配虚拟内存,memset的时候才真正分配物理内存.

(2) 内核分配内存

1 ) vmalloc
适用场景:在内核中不需要连续的物理地址,而仅仅需要内核空间里连续的虚拟地址的内存块. 比如:将文件读入内核内存.
适用区域: vmalloc 机制则在高端内存映射区分配物理内存.
通过更改内核页表实现内核内存和物理地址的地址转换. vmalloc是从伙伴系统分配内存.

2 ) kmalloc
kmalloc是实地址映射,不需要地址转换.
kmalloc基于slab分配器,slab缓冲区建立在一个连续的物理地址的大块内存之上,所以缓冲对象也是物理地址连续的。

3 ) malloc,vmalloc,kmallc 分配流程

3.3 置换和回收

(1)两种情况

一种释放,一种PFRA(类似jvm 垃圾回收内存机制)

(2)页分类(按有无文件背景页面主要分两种):

文件页(file-backed page):有文件背景页面。可以直接和硬盘对应的文件进行交换。
匿名页(anonymous page):无文件背景页面.如进程堆,栈,数据段使用的页等,无法直接跟磁盘交换,但是可以跟swap区进行交换.

(3)哪些内存可以回收

a ) 属于内核的大部分页框是不能够进行回收的,比如内核栈、内核代码段、内核数据段以及大部分内核使用的页框。
b ) 进程使用的页框可以进行回收的,比如进程代码段,进程数据段,进程堆栈,进程访问文件时映射的文件页,进程间共享内存使用的页.
c ) 伙伴系统分配的页面使用者使用free_pages之类的函数主动释放的,页面释放后被直接放归伙伴系统 slab中分配的对象(使用kmem_cache_alloc函数),也是由使用者主动释放的(使用kmem_cache_free函数).

(4) 页回收方式

a ) 页回写:如果一个很少使用的页的后备存储器是一个块设备(例如文件映射),则可以将内存直接同步到块设备,腾出的页面可以被重用 页交换:如果页面没有后备存储器,则可以交换到特定swap分区,再次被访问时再交换回内存。
b ) 页丢弃:如果页面的后备存储器是一个文件,但文件内容在内存不能被修改(例如可执行文件),那么在当前不需要的情况下可直接丢弃。
页面该回收: 磁盘高速缓存的页面,但是如果页面是脏页面,则丢弃之前必须将其写回磁盘 回收匿名映射的页面,只好先把页面上的数据转储到磁盘,这就是页面交换(swap)。 所有的磁盘高速缓存页面都可回收,所有的匿名映射页面都可交换。

(5) 页回收算法-LRU

磁盘高速缓存页面(包括文件映射页面)的链表、匿名映射页面的链表 当Linux系统内存有盈余时,内核会尽量多地使用内存作为page cache,提高系统性能,page cache会被加入到文件类型的LRU链表中,当系统内存紧张时,会按一定的算法来回收内存.
下面简单了解下: LRU链表按zone来配置,每个zone中都有一整套LRU链表.page交换调度策略使用.page可能被调度到active_list或者inactive_list队列里.就是使用lru这个list_head. LRU每个zone有两个链表,一个active,一个non-active.
进行页面回收的时候,一是将active链表中最近最少使用的页面移动到inactive链表、二是尝试将inactive链表中最近最少使用的页面回收.

(6) 页回收时机

直接页面回收(主动触发):“内存严重不足”事件的触发。 周期性回收(被动触发):kswapd进程以水线为触发点,按LRU链表来进行回收。系统会调用函数balance_pgdat(),它主要调用的函数是 shrink_zone() 和 shrink_slab()。

(7) 反向映射(比如共享文件或者内存)

本质是逆向映射。
正向映射是建立进程页表和物理内存关系用于访问。
逆向映射是回收的时候,查看反向指针rmap,没有页表引用就回收.
多个引用的回收 PFRA处理页面回收的过程中,LRU的inactive链表中的某些页面可能就要被回收了。
如果页面没有被映射,直接回收到伙伴系统即可(对于脏页,先写回、再回收)。
否则,还有一件麻烦的事情要处理。因为用户进程的某个页表项正引用着这个页面呢,在回收页面之前,还必须给引用它的页表项一个交待 内核建立了从页面到页表项的反向映射。通过反向映射可以找到一个被映射的页面对应的vma,通过vma->vm_mm->pgd就能找到对应的页表。然后通过page->index得到页面的虚拟地址。再通过虚拟地址从页表中找到对应的页表项。

(8) OOM( out of memory )killer

如果操作系统在进行了内存回收操作之后仍然无法回收到足够多的页面以满足上述内存要求,那么操作系统只有最后一个选择,那就是使用 OOM( out of memory )killer,它从系统中挑选一个最合适的进程杀死它,并释放该进程所占用的所有页面

4 内存实现

5 内存与文件系统

页缓存和address_space

比如:进程A将文件A读入内核页缓存,然后address_space管理这些页缓存。进程B也要读文件A,它会现在address_space中查找如果存在则直接返回,如果不存在则从文件系统读入. 有了address_space可以多个进程共享读取文件.

如果两个进程访问一个文件,其中一个进程写文件,那么采用匿名文件方式.

6 共享内存

1 mmap 有2个功能:
(1) malloc 底层实现. 参见上文
(2) 映射文件读写. 本质是进程可以访问内核内存,减少了read,write函数所需要的内核拷贝到用户态.

2 shm*() 函数。就是匿名文件

7 内存零拷贝技术

8 源码实现

1) 结构体定义
内存描述符由mm_struct结构体表示

struct mm_struct
{
    struct vm_area_struct *mmap;
    rb_root_t mm_rb;
    atomic_t mm_users;
    atomic_t mm_count;
    struct list_head mmlist;
    ...
}; 
  • mm_users:代表正在使用该地址的进程数目;
  • mm_count: 代表mm_struct的主引用计数,当该值为0说明没有任何指向该mm_struct结构体的引用,结构体会被撤销。
  • mmap和mm_rb:描述的对象都是相同的
    • mmap以链表形式存放, 利于高效地遍历所有元素
    • mm_rb以红黑树形式存放,适合搜索指定元素
  • mmlist:所有的mm_struct结构体都通过mmlist连接在一个双向链表中,该链表的首元素是init_mm内存描述符,它代表init进程的地址空间。

物理内存是通过分页机制实现的.
物理页在系统中由也结构struct page描述,所有的page都存储在数组mem_map[]中,可通过该数组找到系统中的每一页。
2) malloc 实现
如何实现一个malloc:CodingLabs - 如何实现一个malloc
上面的blog 推理非常清晰,struct仅仅加一个变量,变成下一种情况。图画的非常准确.
9 valgraind
内存问题分析的利器——valgraind的memcheck 内存问题分析的利器——valgrind的memcheck_方亮的专栏
Valgrind使用简介
10 内存分析命令
1) 概述
a) 内存指标概念

故内存的大小关系:VSS >= RSS >= PSS >= USS
b) 内存分析命令
常用的内存调优分析命令:dumpsys meminfo;procrank;cat /proc/meminfo;free;showmap;vmstat

小结

A 区:虚实转换; B 区:Buddy伙伴系统和slab系统; C 区:内核空间分类; D 区:单个进程的内存管理 task_struct 说明
页表就是索引,伙伴系统就是内存池.
other: 内核用struct page结构体表示每个物理页,struct page结构体占40个字节.假定系统物理页大小为4KB,对于4GB物理内存,1M个页面,故所有的页面page结构体共占有内存大小为40MB.
用户空间对应进程,所以当进程切换,用户空间也会跟着变化;
内核空间是由内核负责映射,不会跟着进程变化;内核空间地址有自己对应的页表,用户进程各自有不同额页表

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值