03 内存管理¶
一 虚拟内存¶
进程持有的虚拟地址会通过 CPU 芯片中的内存管理单元(MMU) 的映射关系,来转换变成物理地址。
1.1 内存分段¶
- 四个段:代码分段、数据分段、栈段、堆段
- 分段机制下的虚拟地址由两部分组成,段选择因子(段号)和段内偏移量
- 段表:段的基地址、界限、特权等级 问题:
- 内存碎片(外部内存碎片)
- 内存交换效率低
1.2 内存分段¶
- Linux:4KB
- 页表:存储在内存里的,CPU的内存管理单元MMU 负责将虚拟内存地址转换成物理地址
- 缺页异常:
- 内存碎片:无外部,有内部内存碎片
- 页面置换:最近未被使用LRU,一次一个或几个页,内存交换的效率就相对比较高。
- 分段机制下的虚拟地址由两部分:页号+页内偏移量
- 页表:页表由虚拟页号对应物理页号,含物理页每页所在物理内存的基地址,
- 简单分页的问题:32位--每个进程都有一个4MB内存存页表,那100个进程就是巨大的开销
- 多级页表: 100 多万个「页表项」的单级页表再分页
- 二级页表虚拟地址由:一级页表+二级页表+页内偏移组成
- 为什么不会更大?程序一般不会使用大量空间,需要时才会创建对应的二级页表。
- 64位已经发展到4级目录了
- TLB快表(页表缓存):程序是有局部性,把最常访问的几个页表项存储到访问速度更快的硬件(CPU)。
- 是一种Cache,装在CPU里,和MMU交互
- 命中率很高,CPU在寻址时,会先查TLB
1.3 段页式内存管理¶
- 地址结构:段号、段内页号和页内位移
- 内存:每一个程序一张段表,每个段又建立一张页表
1.4 Linux内存布局¶
- 早期IntelCPU:(段+偏移)逻辑地址-段式内存管理->线性(虚拟)地址-页式内存管理->物理地址
- Linux 系统中的每个段都是从 0 地址开始的整个 4GB 虚拟空间,屏蔽了处理器中的逻辑地址概念
- Linux分内核空间和用户空间:
- 进程在用户态时,只能访问用户空间内存;进入内核态后,才可以访问内核空间的内存;
- 内核地址:每个进程都各自有独立的虚拟内存。但是每个虚拟内存中的内核地址,关联的都是相同的物理内存。切换到内核态后,就可以很方便地访问内核空间内存
- 用户空间内存:从下到上
- 保留区:非合法地址,比如无效指针的NULL指向这块
- 代码段:二进制可执行代码+常量
- 数据段:已初始化的静态变量+全局变量
- BSS段:未初始化的~
- 堆段:动态分配的内存,动态分配
- 文件映射区(保留区):动态库,共享内存,动态分配
- 栈段
内存满了,发生什么(缺页中断过程)?¶
- 内存分配过程:缺页中断,进程从用户态切换到内核态,内核的缺页中断函数会进行处理。缺页中断函数会做如下处理:
- 查看有无空闲内存:有则直接分配物理内存,建立内部映射关系
- 回收内存工作:没有则触发回收
- 后台内存回收(异步):
- 直接内存回收:如果后台跟不上进程内存申请的速度,会同步并阻塞进程执行。
- 如果回收后,仍然无法满足。则触发OOM机制(out of memory)
- OOM:据算法选择一个占用物理内存较高的进程杀死,还不够久继续杀,直到释放足够的内存位置。
- 内存紧张时,会自动进行回收。
- 文件页:内核缓存磁盘数据和文件数据。(干净页)大部分都可以释放了,(脏页)要用再重新读取。被应用修改过还没写入磁盘的,那就写了再释放。
- 匿名页:没有文件这样的载体。但是会用Swap机制写入磁盘等待再次读取,换入换出。
- 页的回收倾向可以手动调控。
- 页的回收算法LRU:两个链表:活跃和不活跃两个链表,优先回收不活跃的。
预读失效和缓存污染问题¶
- 文件缓存:读取的文件会被存储在Page Cache中, 加速访问,大小有限。
- 同样是LRU管理文件页
预读失效? - 提前加载进来的页,并没有被访问,白读了。 - LRU前列却没读,还要等待淘汰地址,Cache命中率大大降低。、 - 解决:只有真正读的时候,再放到lru活跃链表里。
缓存污染: - 数据访问一次,就活跃头部了,之前的热点数据全部淘汰。后续不再读那就是缓存污染。
Linux LRU机制总结¶
活跃和不活跃链表: - 真正读了第二次,就到活跃头部。 - 活跃末尾被淘汰,降级为不活跃头部 - 不活跃降级才是淘汰。
虚拟内存管理¶
PCB:task_struct
- pid
- file_struct *files
- mm_struct
每次创建会携带一个mm_struct(COW) - 共享:变成线程了。是否共享地址空间几乎是Linux进程和线程之间的本质区别。
内核线程和用户态线程的本质区别:就是内核线程没有相关的内存描述符 mm_struct,所以内核线程之间调度是不涉及地址空间切换的。
mm_struct内容: - task_size:定义用户态地址空间和内核态地址空间分界线。 - 进程虚拟内存空间:data、text、brk、stack的起始、args和env的起始、mmap的起始,以及根据权限区分的VMA区域,还有rbtree,mmap分配VMA存储到一颗RBTREE上 - 内核内存空间: - 直接映射区:直接映射到物理地址(不过还是走的页表)。存进程相关描述符,task_struct、mm_struct、vm_area_struct之类的。以及内核栈(容量小,固定) - 8M空洞 - vmalloc映射区:分配页 - 永久映射区:允许建立与物理高端内存的长期映射关系 - 固定映射区:有些模块需要使用虚拟内存并映射到指定的物理地址上,使用该地址的模块在初始化的时候,将这些固定分配的虚拟地址映射到指定的物理地址上去。 - 临时映射区:缓存页