首页
社区
课程
招聘
[原创]LINUX0.11内存管理详解
发表于: 2019-1-18 13:15 5745

[原创]LINUX0.11内存管理详解

2019-1-18 13:15
5745

一、初始化后内存布局

我们回顾一下,在内核模块运行之前首先运行链接在内核头部的head.s模块,该模块的最后设置了内存分页,其中0~4KB、4~8KB、8~12KB、12~16KB分别为页目录和页表0~页表3。然后进入main.c模块,它负责内核运行的初始化,完成内存功能区的划分和页面引用字节图mem_map的初始化。


kernel/main.c

由于内存可用大小为16MB且未定义RAMDISK,所以主内存起始于4MB边界。确定内存边界后调用mem_inti对页面引用字节图进行初始化。


kernel/main.c

1MB以下的内存没有参与映射,这可以理解,内核部分是绝对不允许映射到用户空间的否则后果太可怕了。高速缓冲区的页面映射值设置为100,表示这一部分也不会被用到,因为缓冲区这部分功能固定属于公共区域,每个进程都可以读取其中的数据,所以没有必要映射到各自的进程空间。但是可以不把缓冲区放进mem_map数组,不理解他为什么这么做。最后把分给进程使用的主内存区页面映射值全部设置为0,表示目前为止还没有进程引用主内存。


完成初始化之后的内存布局

二、进程空间管理

fork用于复制父进程产生一个子进程,它本质上是一个内联汇编函数。


include/sys/unistd.h
fork定义在main.c的第23行,它展开后实际上就是以上这个样子,通过调用号和80h中断调用sys_fork。

kernel/system_call.s
sys_fork的函数调用路径是copy_proces—>copy_mem—>copy_page_tables,这些函数都在memory.c文件中,copy_page_tables通过写时复制机制在子进程产生的初期进行父子进程物理页面共享,一旦父进程或者子进程需要修改页面就会引发写保护异常,这时候才申请并映射一页空闲内存,将共享的那部分页面复制到新的页面并对新的页面进行修改,这一部分在前面文章中已经分析过。所以在某一时刻父子进程空间的关系如下:

进程退出后自动将占用的页面的mem_map减1并清空自身页表,释放占用的页面。

当前进程发生缺页故障的时候,遍历进程表尝试找到一个共用一个可执行文件的进程,如果找到这样一个进程则在该进程中寻找是否有所缺页面,如果有就尝试共享该页面,这样就避免了从磁盘读取的开销和内存开销。这部分在前面的内存共享机制已经介绍。

三、动态内存管理

在C语言中malloc负责动态内存分配,这些内存通常都是一些几十至几百字节的小内存块,在页式管理的基础上还要引入针对小内存块的管理,否则将引发严重的内存浪费问题。

3.1桶结构初始化

linux0.11中将常用内存块划分为16、32、64、 128…、 4096大小块(chunk),也就是malloc分配的内存最大不能超过一个页面的大小,而对于非2的整数次幂的内存块则选择能满足要求的最小块。

用一个页面划分为相同大小的块,这样的页面称为“桶页面”。每个“桶页面”都需要一个桶页面描述符bucket_dest来描述:

lib/malloc.c
为了方便管理,相同块大小的桶结构通过next指针连成链表,使用_bucket_dir结构描述这个链表:

lib/malloc.c

同时为了管理所有的通描述符链表,引入了一个_bucket_dir结构的数组用于保存所有链表头:


lib/malloc.c

bucket_dir[]的最后一个元素为{0,0},表示这里已经到最后一个。


lib/malloc.c

在首次调用malloc或者发现没有空闲桶描述符的时候会调用init_bucket_desc分配一个专用的页面来存放桶描述符,因为如果也像正常申请内存那样申请空间来存放桶描述符的话将会导致死循环。这里还很好地解决了多进程条件竞争的问题,按道理在执行init_bucket_desc之前是要检查free_bucket_desc指针是否为空,(该指针指向空闲桶描述符,也就是没有分配桶页面的描述符),在这里如果A进程get_free_page的时候阻塞,此时另一个进程B执行了init_bucket_desc,当A唤醒后直接将专用页插入B的专用页之前,这样就相当于重新申请了一个专用页。


发生条件竞争时的情况



lib/malloc.c

为bucket_dest分配了专用的页面并初始化之后,get_free_page申请一个桶页面,桶页面是真正用于存放数据的地方,被划分成了同样大小的块,每个块的头部为指向下一个空闲块的指针,在初始化桶页面的时候,所以空闲块都连成链表,最后一个指向NULL。设置桶描述符相关成员,并且把桶描述符插入_bucket_dir中。


空闲块结构


桶结构初始化完毕

3.2桶内存块分配

首先遍历bucket_dir查找合适的链表,如果到达bucket_dir最后一个元素则表示申请的内存超过4096字节,报错死机。


lib/malloc.c

关中断,这样时钟中断就进不来也就不会引发进程调度,避免了条件竞争的发生。然后在链表中寻找桶页面的空闲块。


lib/malloc.c
如果不存在空闲块则重新分配桶描述符和桶页面,并将新的桶描述符插入bucket_dir。

lib/malloc.c
打开中断并且返回空闲页面的地址。

lib/malloc.c

3.3桶内存块回收

free_s接收两个参数,第一个参数是内存地址,第二个参数是释放内存大小,指定内存大小可以减去搜索桶描述符的步骤。free未指定释放内存大小。


include/linux/kernel.h

如果调用的是free则size=0,需要从第一个桶目录项的第一个描述符开始遍历搜索,bdesc记录了要释放的内存所在页面的桶描述符,prev记录了该桶描述符的前一个描述符,如果prev=0则表示该描述符是链表的第一个描述符。


lib/malloc.c

将空闲块插入桶页面中的空闲链表中,页面引用数-1,如果桶页面所有块都空闲则释放桶页面并将对应的描述符从链表中删除,打开中断。


lib/malloc.c

进程管理和内存管理是操作系统的核心,这部分弄明白了其他也好理解了。









[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

上传的附件:
收藏
免费 5
支持
分享
最新回复 (2)
雪    币: 21449
活跃值: (62288)
能力值: (RANK:125 )
在线值:
发帖
回帖
粉丝
2
感谢分享!
2019-1-18 13:25
2
雪    币: 6
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2019-1-30 10:17
0
游客
登录 | 注册 方可回帖
返回
//