一直以来对Linux内核的内存管理很是不解,最近在读毛德操老师和胡希明老师的<<Linux内核源代码情景分析>>,有所收获,借此机会写一篇小文权作读书笔记,以供日后不断反思学习之用

Linux虚拟地址空间和物理地址空间

在32位的Linux系统里面每个进程都有自己独立的大约3GB的虚拟地址空间,而真实的物理内存是硬件固定的,所以就需要页表建立虚拟地址空间到物理地址空间 的对应.

在Linux中物理地址空间被划分为大小为4KB的一个个页框,这个页框在内核中通过Page结构体表示.并且物理内存也被划分为ZONE_DMA, ZONE_NORMAL, ZONE_HIGH三个区块,其中ZONE_DMA是为IO提供DMA通道的地址区域,一般进程使用的物理内存是属于ZONE_NORMAL/ZONE_HIGH区域.

可以使用的物理内存被划分成一个个页框后,被放入到11个free_area_strcut链表中,这11个链表分别管理着1, 2, …., 2^10大小的连续的空闲页框链表,并且通过伙伴算法进程管理,具体可以戳这里,以后所有的物理页获取与释放都是通过伙伴算法来管理的

进程的内存管理

首先介绍一下几个重要的数据结构: task_struct, mm_struct, vm_area_struct. 这里task_struct可以看做是进程的档案,每个进程内核都会为它维护一个task_struct,在task_struct里面有很多进程的信息,其中就包括虚拟地址空间这块的mm_strcut成员,这这个成员它记录这进程的pgd,以及通过链表和AVL树管理的vm_area_struct节点,这里首先解释pgd的作用: pgd就是指向该进程在页全局目录表的位置,用来MMU地址转换的,一般进程切换时候会把它加载到cr3寄存器用来MMU转换; 其次vm_area_struct是进程虚拟地址空间的一个区间,因为虚拟地址空间很大,所以一般都是分段使用的,比如代码段,数据段,堆栈段,共享段等等,这些都是一个个vm_area_struct记录的,每个段的读写权限不同

我们进程在使用一个虚拟地址时候,一般是先会通过MMU进程地址映射,如果发现存在则验证读写权限,如果不存在则触发缺页异常然后进入到内核态,接着会在mm_strcut里面的vm_area_struct的AVL树里面查找是否存在并验证权限是否正确,比如读写权限不一致,比如找不到一个合法的vm_area_struct区包含该虚拟地址等等,这些都会使得内核将SIGSEGV信号放入到进程的task_struct里面,待进程从内核态切换到用户态时候检查到这个信号就会终止运行出现Segment Fault这个臭名昭著的信息. 当然,进程栈的扩张是这个流程的意外情况,这时候会根据所需修改相应栈的vm_area_struct终止地址,然后申请物理页并且然进程对应页表项上填写映射信息

另外进程在用户空间通常会使用malloc进程内存申请,这个malloc实质上会调用brk或者mmap系统调用,一般来说当malloc的空间小于128K时候调用brk使得堆的虚拟地址空间扩大一下,即通过修改对应vm_area_struct节点,而大于128K时候则调用mmap从进程虚拟地址空间中找出一块出来建立一个vm_area_struct,这时候只是分配的虚拟地址并没有对应物理页框,这是因为申请的不一定会用,之后使用时候才会触发缺页异常然后分配对应物理页

这里需要单独说明一下mmap系统调用,mmap一般来说是将磁盘上一个文件映射到进程虚拟地址空间上,以方便读写文件,这里只是会为该文件建立一个vm_area_struct然后设置好file_ops调用方便在进程读写该文件时候引起缺页异常时候指引找到对应文件,然后将文件内容读入到某个物理页上再在页表上建立好映射关系

内核的内存管理

内核也同样需要使用物理内存,内核的内存通常是通过slab分配器获得的,这个slab分配器分为两种,一种是管理专属对象的缓冲,比如task_struct这些常见的对象,另一种则是通用的对象缓冲,根据空间的大小分类管理

kmem_cache结构体可以想象成一个对象缓冲的分配入口,一般它有三个指针指向三种slab,分别是满.部分满和空闲slab,每个slab使用一个或者多个页框并且在里面分配一个个具体对象,我们以task_struct的kmem_cache为例,那么每个slab都可以存放多个task_struct对象,我们当需要申请一个task_struct时候则会从对应kmem_cache里面的部分满slab链表里面找一个task_struct对象返回,如果这是该slab最后一个没使用的task_struct则将此slab移动到满链表中.通用对象的slab也是一样,只不过根据所需空间大小找对应kmem_cache

slab分配器的优点在于可以减少内存空洞,以及缓存对象通过在kmem_cache里面设定的初始化函数一次初始化所有对象,避免不必要的不断的初始化工作,kmalloc就是通过slab分配器获得一个通用的空间大小

页面调度

物理内存是有限的,所以就需要不断的换进和换出物理页,内核使用的页框一般来说是背会被换出的,进程的页框则通过LRU调度算法换出, 页面换出时候是将页面换到磁盘的交换区部分,然后原先使用该物理页的进程的页表映射也要改为磁盘的页面

- EOF -

声明:本文采用BY-NC-SA协议进行授权.转载请注明: Linux内核源代码情景分析之存储管理



comments powered by Disqus

Hitwebcounter.com Free