-
-
[原创]LINUX0.11内存管理详解
-
发表于:
2019-1-18 13:15
5908
-
我们回顾一下,在内核模块运行之前首先运行链接在内核头部的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用于复制父进程产生一个子进程,它本质上是一个内联汇编函数。
进程退出后自动将占用的页面的mem_map减1并清空自身页表,释放占用的页面。
当前进程发生缺页故障的时候,遍历进程表尝试找到一个共用一个可执行文件的进程,如果找到这样一个进程则在该进程中寻找是否有所缺页面,如果有就尝试共享该页面,这样就避免了从磁盘读取的开销和内存开销。这部分在前面的内存共享机制已经介绍。
在C语言中malloc负责动态内存分配,这些内存通常都是一些几十至几百字节的小内存块,在页式管理的基础上还要引入针对小内存块的管理,否则将引发严重的内存浪费问题。
linux0.11中将常用内存块划分为16、32、64、 128…、 4096大小块(chunk),也就是malloc分配的内存最大不能超过一个页面的大小,而对于非2的整数次幂的内存块则选择能满足要求的最小块。
用一个页面划分为相同大小的块,这样的页面称为“桶页面”。每个“桶页面”都需要一个桶页面描述符bucket_dest来描述:
同时为了管理所有的通描述符链表,引入了一个_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中。
空闲块结构
桶结构初始化完毕
首先遍历bucket_dir查找合适的链表,如果到达bucket_dir最后一个元素则表示申请的内存超过4096字节,报错死机。
lib/malloc.c
关中断,这样时钟中断就进不来也就不会引发进程调度,避免了条件竞争的发生。然后在链表中寻找桶页面的空闲块。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)