-- linux爱好者,业余时间热衷于分析linux内核源码 -- 目前主要研究云计算和虚拟化相关的技术,主要包括libvirt/qemu,openstack,opennebula架构和源码分析。 -- 第五届云计算大会演讲嘉宾 微博:@Marshal-Liu
分类: LINUX
2009-12-20 13:03:41
1 名词解释:
(1)页框:物理内存的描述,必须牢牢记住,页框就是物理内存
(2)页描述符:描述每一个页框的状态信息,所有的也描述符都保存在mem_map[ ]数组中,每个描述符32个字节
(3)节点:系统物理内存被划分为多个节点,每个节点内cpu访问页面的时间是相同的,对应的数据结构:节点描述符
(4)管理区:每个节点又分为多个管理区 对应的数据结构: 管理区描述符
2 页表管理
重点介绍内核页表的管理,主要分为两个阶段:启动阶段映射8M的页表和剩余页表的映射阶段
(1)启动阶段8M页表的映射过程
(2)剩余页表的映射过程
几个比较重要的地址转换:
虚拟地址转换成物理地址: virt_to_phsy(address){ __pa(address) }
虚拟地址转换页描述符的地址: virt_to_page( kaddr ) { return mem_map + __pa(kaddr) >>12 }
3 用户进程的地址空间
从内核看来,整个4G的地址空间是这样的。
进程可用的地址空间是被一个叫mm_struct(进程地址空间描述符)结构体来管理的,同一个进程内的多个线程是共享这个数据结构的。
同时,对于用户进程来说,每一个进程有一个独一无二的mm_struct,但是内核线程确不是必须的。下面是操作mm_struct的一些函数。
当然如果在进程创建的时候指定子进程共享父进程的虚拟地址空间的话,比如:
if ( clone_flags & CLONE_VM )
{
atomic_inc(&old_mm->mm_user)
mm = &oldmm;
goto good_mm;
}
还有一点许哟阿注意的就是系统中的第一个mm_struct是需要静态初始化的,以后的所有的mm_strcut都是通过拷贝生成的。
mmap函数,内存映射函数
该函数的主要功能是在进程地址空间中创建一个线性区。有两种类型的内存映射:共享型和私有型。二者的主要区别可以理解成是否对其他进程可见。共享型每次对线性区的读写都会修改
磁盘文件,一个进程修改共享型的线性区,其他映射这一线性区的所有进程都是可见的。与内存映射相关的数据结构:
(1)与所映射的文件相关的索引节点对象
(2)所映射文件的address_space对象
(3)不同进程对同一文件进行不同映射所使用的文件对象
(4)对文件进行每一不同映射所使用的vm_area_struct
(5)对文件进行映射的线性区所分配的每个页框对应的描述符
从图上能看出一个文件对应一个inode,对应一个address_space,对应多个struct file, 对应多个vm_area_file ,对应多个page(页框),当然也对应多个page(页描述符)、
mm_struct 内存描述符中的两棵树: 当前进程内所有线性区的一个链表和所有线性区的红黑树
mmap和mm_rb都可以访问线性区。事实上,它们都指向了同一个vm_area_struct结构,只是链接的方式不同
mmap指向的线性区链表用来遍历整个进程的地址空间
红黑树mm_rb用来定位一个给定的线性地址落在进程地址空间中的哪一个线性区中
另外,mmap_cache用来缓存最近用过的线性区
address_space中的两棵树:基数和优先级搜索数。
address_space的page_tree指向了组织构成这个文件的所有的页描述符的基树
address_space的i_mmap指向了组织构成这个文件的所有的线性区描述符的基树
要注意一个问题:
(1)共享内存映射的页通常保存在也高速缓存中,私有内存映射的页只要还没有修改,也保存在页高速缓存中。当进程试图修改一个私有映射的页时,内核 就把该页框进行复制,并在进程页表中用复制的页替换原来的页,这就是写时复制的基础。复制后的页框就不会放在页告诉缓存中了,原因是它不再是表示磁盘上那 个文件的有效数据。
(2)线性区的开始和结束地址都是4K对齐的
进程获得新线性区的一些典型情况:
刚刚创建的新进程
使用exec系统调用装载一个新的程序运行
将一个文件(或部分)映射到进程地址空间中
当用户堆栈不够用的时候,扩展堆栈对应的线性区
创建内存映射:
要想创建一个映射,就要调用mmap, mmap()最终会调用do_mmap()
static inline unsigned long do_mmap(struct file *file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flag,unsigned long offset)
file:要映射的文件描述符,知道映射哪个文件才行
offset:文件内的偏移量,指定要映射文件的一部分,当然也可以是全部
len: 要映射文件的那一部分的长度
flag:一组标志,显示的指定映射的那部分是MAP_SHARED或MAP_PRIVATE
prot: 一组权限,指定对线性区访问的一种或多种访问权限
addr: 一个可选的线性地址,表示从这个地址之后的某个位置创建线性区
基本的过程是:
(1)先为要映射的文件申请一段线性区,调用内存描述符的get_unmapped_area()
(2)做一些权限和标志位检查
(3)将文件对象的地址struct file地址赋值给线性区描述符vm_area_struct.vm_file
(4)调用mmap方法,这个方法最后调用generic_file_mmap()
其他线性区处理函数
(1)find_vma() : 查找一个线性地址所属的线性区或后继线性区
(2)find_vmm_interrection(): 查找一个与给定区间重叠的线性区
(3)get_unmapped_area() : 查找一个空闲的线性区
(4)insert_vm_struct () : 向进程的内存描述符中插入一个线性区
缺页异常处理程序
(1)背景知识
内核中的函数以直接了当的方式获得动态内存,内核是操作系统中优先级最高的成分,内核信任自己,采用面级内存分配和小内存分配以及非连续线性区得到内存
用户态进程分配内存时,请求被认为是不紧迫的,用户进程不可信任,因此,当用户态进程请求动态内存时,并没有立即获得实际的物理页框,而仅仅获得对一个
新的线性地址区间的使用权这个线性地址区间会成为进程地址空间的一部分,称作线性区(memory areas)。 这样,当用户进程真正向这些线性区写的时候,就会
产生缺页异常,在缺页异常处理程序中获得真正的物理内存。
(2)缺页异常处理程序需要区分引起缺页的两种情况:编程错引起的缺页和属于进程的地址空间尚未分配到物理页框
简单流程图:
详细流程图:
linux为什么要分为三个区:ZONE_DMA ZONE_NORMAL ZONE_HIGHMEM?
(1)isa总线的历史遗留问题,只能访问内存的前16M的空间
(2)大容量的RAM使得线性地址空间太小,并不是所有的物理空间都能映射到唯一的线性地址空间
如何确定某个页框属于哪个节点或管理区?
是由每个页框描述符中的flag的高位索引的,比如page_zone()函数就是接收页描述符的地址作为参数,返回页描述符中flag的高位,并到zone_table[ ]数组中确定相应的管理区描述符的地址
slab算法是用来满足对以页框为单位的请求而设置的,简单介绍以下slab算法的原理
对于以页为单位的请求发送到管理区分配器,然后管理区分配器搜索它所管辖的管理区,找一个满足请求的分配区,然后再由这个管理区中的伙伴系统去处理,为了加快这个
过程,每个分区中还提供了一个每cpu页框高速缓存,来处理单个页框的请求。
这个过程中有四个请求页框的函数和宏:
(1)alloc_pages, alloc_page返回分配的第一个页框的页描述符的地址
(2)__get_free_pages , __get_free_page 返回分配的第一个页框的线性地址
其实二者是相同,因为有专门用来处理线性地址到页描述符地址转换的函数 virt_to_page() 实现从线性地址到页描述符地址的转换