第4步 Linux内存管理.doc_第1页
第4步 Linux内存管理.doc_第2页
第4步 Linux内存管理.doc_第3页
第4步 Linux内存管理.doc_第4页
第4步 Linux内存管理.doc_第5页
已阅读5页,还剩7页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Linux内存管理 文档修订记录 版本 时间修订者 修订内容 1.0 2010-03-21 朱康挺 创建此文档 1.1 2010-03-24 朱康挺 补充:请页机制中flags叙述 每次修改与增加内容者,需在文档修订记录中进行记录 内存是Linux内核所管理的最重要的资源之一,内存管理系统是操作系统中最为重要的部分。因为系统的物理内存有限,是一种稀缺资源,其远少于系统所需要的内存数量。作为操作系统的核心,内存管理必须能够克服物理内存的局限,使用户进程在透明方式下,拥有比实际物理内存大得多的内存。其核心策略就是使用虚拟内存。Linux成功地实现了以虚拟内存为核心的内存管理策略,强大的分页机制,公平的交换方式,各类有效的高速缓存,以及以页保护为主的保护措施等。 虚存管理可提供以下的功能: 广阔的地址空间:系统的虚拟内存可以比系统的实际内存大很多倍。 进程的保护:系统中的每一个进程都有自己的虚拟地址空间。这些虚拟地址空间是完全分开的,这样一个进程的运行不会影响其他进程。并且,硬件上的虚拟内存机制是被保护的,内存不能被写入,这样可以防止迷失的应用程序覆盖代码的数据。 内存映射:内存映射用来把文件映射到进程的地址空间。在内存映射中,文件的内容直接连接到进程的虚拟地址空间。 公平的物理内存分配:内存管理系统允许系统中每一个运行的进程都可以公平地得到系统的物理内存。 共享虚拟内存:Linux实现的虚拟内存允许两个进程之间互相共享内存,例如:共享的库。在这种情形下,库代码仅存在于一个进程,而不需要为每个应用都复制一份。Linux虚拟内存的实现需要6种机制的支持:地址映射机制、内存分配回收机制、缓存和刷新机制、请求页机制、交换机制和内存共享机制。内存管理程序通过映射机制把用户程序的逻辑地址映射到物理地址。当用户程序运行时,如果发现程序中要用的虚地址没有对应的物理内存,就发出了请求页要求。如果有空闲的内存可供分配,就请求分配内存(于是用到了内存的分配和回收),并把正在使用的物理页记录在缓存中(使用了缓存机制)。如果没有足够的内存可供分配,那么就调用交换机制;腾出一部分内存。另外,在地址映射中要通过TLB(翻译后援存储器)来寻找物理页;交换机制中也要用到交换缓存,并且把物理页内容交换到交换文件中,也要修改页表来映射文件地址。一、 内存分页机制在Linux中,每个进程都有一套独立于其它进程的虚拟地址空间,这个地址空间有4GB大小,其中03GB为用户态空间,用户态进程可以直接访问。从3GB4GB为内核态空间,存放内核访问的代码和数据,用户态进程不能直接访问。用户进程可通过中断或系统调用访问内核态空间。为了效率起见,虚拟地址空间被分成以固定长度为单位的页(一般4KB)。分页单元认为所有的RAM被分成固定长度的页框(Page frame)(有时叫做物理页)。每一个页框包含一页(Page),也就是说一个页框的长度与一个页的长度一致。页框是主存的一部分,因此也是一个存储区域。区分页与页框是很重要的,前者只是一个数据块,可以被存放在任何页框或磁盘中。将这些页映射成页框的数据结构称为页表 (page table)。页表存储在主存中,可由内核在启用分页单元前对其进行恰当的初始化。图1.1显示了页到页框的映射。图1.1 页表将页转换成页框在ARM-Linux系统中,32位的虚拟地址被分成3个域:页目录:最高的10位页表: 中间的10位偏移量:最低的12位虚拟地址的转换由两步完成,每一步都基于一种转换表。第一种转换表称为页目录表(page directory),第二种转换表称为页表(Page table)。正在使用的页目录表的物理地址存放在处理器的cr3寄存器中,虚拟地址内的页目录域决定了它指向页目录表中的哪一项,即指向哪个页表。接下来,地址的页表域决定页表中的一项,此项含有此页所在页框的物理地址。偏移量域,决定了本页框内的相对位置(见图1.2)。由于它是一个12位长的域故每一页含有4096字节的数据。页目录城和页表域都是10位长,因此页目录表和页表都可以多达1024项。因此一个页目录表可以寻址到高达l024*1024*40964G个存储单元。图1.2 内存分页机制分页举例:假如内核已给一个正在运行的进程分配的线性地址空间范围是0x20000000到0x2003ffff。这个空间由64页组成。我们不必关心包含这些页的页框的物理地址,实际上,其中的一些页在主存中也许不连续。我们只关注页表项中的几个域。让我们从分配给进程的线性地址的最高10位(分页单元解释成页目录域)开始。这两个地址都以2开头,后面跟着0,因此高10位有相同的值,即0x080或十进制的128。因此,这两个地址的页目录域都指向进程页目录的第l 29项。相应的页目录项中必须包含分配给进程的页表的物理地址(参见图1.3)。如果没有给这个进程分配其它的线性地址,页目录表的其余1023项都填为0。中间10位的值(即页表域的值)范围从0到0x03f(0到63)。因而只有页表的前64个表项是有意义的其余960表项都填为0。图1.3页目录与页表假设进程需要读线性地址0x20021406中的字节。这个地址由分页单元按下面的方法处理:页目录域的0x80用于选择页目录的第0x80页目录项,此目录项指向进程的页所在的页表。页表域的策0x2l顶用于选择页表的第0x2l表项,此表项指向所需页的页框。最后,偏移量0x406用于在目标页框中读偏移量为0x406中的字节。内核态虚拟空间从3GB到3GB+4MB的一段(对应进程页目录第768项指引的页表),被映射到物理地址0x00x3FFFFF(4MB)。因此,进程处于内核态时,只要通过访问3GB3GB+4MB就可访问物理内存的低4MB空间。所有进程从3GB4GB的线性空间都是一样的,由同样的页目录项,同样的页表,映射到相同的物理内存段。Linux以这种方式让内核态进程共享代码和数据。二、物理页面内核必须记录每个页框当前的状态。例如,内核必须能区分哪些页框用于进程的页,而哪些页框包含的是内核代码或数据。同理,内核还必须能够确定动态内存中页框是否空闲。这种状态信息被保存在一个描述符数组中,每个页框对应数组中的一个元素。这种类名为struct page的描述符具有如下形式:Page(mem_map_t)代表物理上的一页。这里看一下其中比较重要的域。flags域:用于存放页的状态。其每一位表示一种状态,故它可同时表示出32种不同的状态。_count域:存放页的引用次数。当计数值为0时,说明当前内核并没有引用该页,于是,在新的分配中就可以使用它。virtual域:页的虚拟地址。通常情况下,它就是页在虚拟内存中的地址。有些内存(即所谓的高端内存)并不永久地映射到内核地址空间上。在这种情况下,其值为NULL,需要的时候,必须动态地映射这些页。 内核用这一结构来管理系统中的所有的页,因为内核需要知道一个页是否空闲(也就是页有没有被分配)。如果页已分配,内核还需知道谁拥有该页。拥有者可能是用户空间进程、动态分配的内核数据、静态内核代码,或页高速缓存等。请页机制内核提供了一种请求内存的底层机制,并提供了对它进行访问的几个接口。所有这些接口都以页为单位分配内存。 Struct page *alloc_pages(unsigned int flags,unsigned int order)该函数是最核心的函数,其分配2order(即1order)个连续的物理页,并返回一个指针,该指针指向第一个页的page结构体;如果出错,就返回NULL。 void *page_address(struct page *page)该函数将给定的页转换成它的虚拟地址。其返回一个指针,指向给定物理页当前所在的虚拟地址。 unsigned long_get_free_pages(unsigned int flags,unsigned int order)该函数与alloc_pages()作用相同,不过它直接返回所请求的第一个页的虚拟地址,因为页是连续的,故其它页会紧随其后。 struct page *alloc_page(unsigned int flags)该函数与alloc_pages()工作方式相同,只不过传递给order的值为0,即分配1页。 unsigned long_get_free_page(unsigned int flags)该函数与_get_free_pages()工作方式相同,只不过传递给order的值为0,即分配1页。unsigned long get_zeroed_page(unsigned int flags)该函数与_get_free_page()工作方式相同,只不过把分配好的页都填充成0.void _free_pages(struct page *page, unsigned long order)void free_pages(unsigned long addr, unsigned int order);void free_page(unsigned long addr)当你不再需要页时可用以上的一簇函数来释放.释放页时要小心,只能释放属于你的页。传递了错误的struct page或地址,用了错误的order值,这些都可能导致系统崩溃。 在你需要以页为单位的一簇连续物理页时,尤其是在你只要一、两页时,上面这些低级页函数很有用。kmalloc()kmalloc()函数与用户空间的malloc()一簇函数非常类似,只不过它多了个flags参数。kmalloc()函数是一个简单的接口,用它可获得以字节为单位的一块内核内存。若你需要整个页,前面讨论的页分配接口可能是更好的选择。然而,对于大多数内核分配来说,kmalloc()接口用得更多。void *kmalloc(size_t size,int flags)该函数返回一个指向内存块的指针,其内存块至少要有size大小(分配的内存可能比你请求的多,但你无法知道到底多了多少。因为内核分配器本质上是基于页的)。所分配的内存区在物理上是连续的。在出错时,它返回NULL。除非没有足够的内存可用,否则内核总能分配成功。在对kmalloc()调用后,你必须检查返回的是不是NULL。若是,需进行适当的处理。下面给一示例,假定存在一个aaa结构体,现需为其动态分配足够的空间:struct aaa *ptr;ptr = kmalloc(sizeof(struct aaa),GFP_KERNEL);if(!ptr)/* 进行处理 */如果kmalloc()调用成功,ptr则指向一个内存块,内存块的大小至少为所请求的大小。GFP_KERNEL标志表示在试图获取内存并返回给kmalloc()的调用者的过程中,内存分配器将要采取的行为。kfree()void kfree(const void *ptr)kfree()函数释放由kmalloc()分配出来的内存块。如果想要释放的内存不是由kmalloc()分配的,或想要释放的内存早就被释放了,调用该函数将导致严重后果。与用户空间类似,分配和回收要注意配对使用,以避免内存泄漏和其他bug。注意,调用kfree(NULL)是安全的。下面看一个在中断处理程序中分配内存的例子。在此例中,中断处理程序想分配一个缓冲区来保存输入数据。BUF_SIZE预定义为以字节为单位的缓冲区长度,它应该是大于两个字节的。char *buf;buf = kmalloc(BUF_SIZE,GFP_ATOMIC);if(!buf) /* 内存分配错误 */之后,当不再需要该内存时,别忘记释放它:kfree(buf);vmalloc()vmalloc()函数的工作方式类似于kmalloc(),只不过前者分配的内存虚拟地址连续,而物理地址则无需连续。这也是用户空间分配函数的工作方式:由malloc()返回的页在进程的虚拟空间内是连续的,但是,这并不保证它们在物理RAM中也连续。kmalloc()函数确保页在物理地址上是连续的(虚拟地址自然也是连续的)。vmalloc()函数只确保页在虚拟地址空间内是连续的。它通过分配非连续的物理内存块,再“修正”页表,把内存映射到逻辑地址空间的连续区域中,就能办到。 大多数情况下,只有硬件设备需要得到物理地址连续的内存。在很多体系结构上,硬件设备存在于内存管理单元以外,它根本不理解什么是虚拟地址。因此,硬件设备用到的任何内存区都必须是物理上连续的块,而不仅仅是虚拟地址连续的内存块。但在你的编程中,你根本察觉不到这种差异。对内核而言,所有内存看起来都是逻辑上连续的。 尽管仅仅在某些情况下才需物理上连续的内存块,但是,很多内核代码都用kmalloc()来获得内存,而不是vmalloc()。这主要是出于性能的考虑。后者为了把物理上不连续的页转换为虚拟地址空间上连续的页,必须专门建立页表项。而且,通过vmalloc()获得的页必须一个一个地进行映射(因为它们物理上不连续)。故vmalloc()仅在不得已时才会使用一般为了获得大块内存时,例如,当模块被动态插入到内核中时,就把模块装载到由vmalloc()分配的内存上。void *vmalloc(unsigned long size)该函数返回一个指针,指向逻辑上连续的一块内存区,其大小至少为size。在发生错误时,函数返回NULL。函数可能睡眠,因此,不能从中断上下文中进行调用,也不能从其它不允许阻塞的情况下进行调用。要释放由vmalloc()获得的内存,使用下面函数:void vfree(void *addr)该函数会释放从addr开始的内存块,其中addr是以前由vmalloc()分配的内存块的地址,这个函数也可睡眠,因此,不能从中断上下文中调用。它无返回值。 这个函数用起来比较简单:char *buf;buf = vmalloc(16*PAGE_SIZE):/* 获得16页 */if(!buf)/* 错误,不能分配 */* buf现在指向虚拟地址连续的一块内存区,其大小至少为16*PAGE_SIZE */在用完后,一定要释放:vfree(buf); 三、 进程地址空间Linux操作系统采用虚拟内存技术,系统中的所有进程之间以虚拟方式共享内存。对每个进程来说,它们好像都可以访问整个系统的所有物理内存。如图2.1所示,各进程拥有自己的3GB用户空间,内核占用最高的1GB作为系统空间,系统空间由所有进程共享。图2.1虚拟空间内核使用内存描述符结构体表示进程的地址空间,该结构包含了和进程地址空间有关的全部信息。内存描述符由mm_struct结构体表示。mm_struct标示了一个进程。内存区域由vm_area_struct结构体描述,内存区域在内核中也经常被称为虚拟内存区域或VMA。vm_area_struct结构体描述了指定地址空间内连续空间上的一个独立内存范围。内核将每个内存区域作为一个单独的内存对象管理,每个内存区域都拥有一致的属性,比如访问权限等,另外,相应的操作也一致。这种面向对象的方法使VMA结构体可以代表多种类型的内存区域。下面给出该结构的定义:每个内存描述符对应着进程地址空间中的唯一区间。vm_start至vm_end便是该内存区域。用户进程的虚拟内存结构如图2.2所示,图中左边的进程结构task_struct中的mm指向一个mm_strut结构体,而右边的几个VMA结构表示此用户进程所占有的虚拟内存块。图2.2 用户进程的虚拟内存结构下图显示了虚拟内存管理中的几个数据结构之间的关系。从图中可以看到进程地址空间的使用以及向物理地址的映射情况。图2.3数据结构之间的关系图补充:请页机制中flags叙述最一般使用的标志是GFP_KERNEL, 意思是这个分配(内部最终通过调用 _get_free_pages 来进行, 它是 GFP_ 前缀的来源) 代表运行在内核空间的进程而进行的. 换句话说, 这意味着调用函数代表一个进程在执行一个系统调用。 使用 GFP_KENRL 意味着 kmalloc 能够使当前进程在少内存的情况下睡眠来等待一页。一个使用 GFP_KERNEL 来分配内存的函数必须是可重入的并且不能在原子上下文中运行。当当前进程睡眠, 内核采取正确的动作来定位一些空闲内存, 或者通过刷新缓存到磁盘或者交换出去一个用户进程的内存。GFP_KERNEL 并不一直是使用的正确分配标志; 有时 kmalloc 从一个进程的上下文的外部调用。例如,这类的调用可能发生在中断处理、 tasklet和内核定时器中。在这个情况下, 当前进程不应当被置为睡眠, 并且驱动应当使用一个 GFP_ATOMIC 标志来代替。 内核正常地试图保持一些空闲页以便来满足原子的分配。当使用 GFP_ATOMIC 时, kmalloc 能够使用甚至最后一个空闲页. 如果这最后一个空闲页都不存在,则分配失败。其他用来补充 GFP_KERNEL 和 GFP_ATOMIC 的标志(尽管它们 2 个涵盖大部分设备驱动的需要)均定义在 , 并且每个标志用一个双下划线做前缀, 例如_GFP_DMA。另外, 有符号代表常常使用的一些标志的组合; 这些符号缺乏前缀并且有时被称为分配优先级。这些符号包括: GFP_ATOMIC用来从中断处理和进程上下文之外的其他代码中分配内存。 从不睡眠。 GFP_KERNEL内核内存的正常分配。可能睡眠。 GFP_USER用来为用户空间页来分配内存;它可能睡眠。 GFP_HIGHUSER如同 GFP_USER, 但是从高端内存分配。 GFP_NOIOGFP_NOFS这个标志功能如同 GFP_KERNEL, 但是它们增加了限制。 一个GFP_NOFS 分配不允许进行任何文件系统调用, 而 GFP_NOIO 根本不允许任何 I/O 初始化。它们主要地用在文件系统和虚拟内存代码, 那里允许一个分配睡眠, 但是递归的文件系统调用是一个坏主意。上面列出的这些分配标志可以是下列标志的相或来作为参数, 这些标志决定这些分配如何进行:_GFP_DMA这个标志要求分配的是能够进行 DMA 的内存区。_GFP_HIGHMEM这个标志指示分配的内存可以位于高端内存。_GFP_COLD正常地, 内存分配器尽力返回缓冲热的页-可能在处理器缓冲区中找到的页。 相反, 这个标志请求一个冷页, 它在一段时间没被使用。它对分配页作 DMA 读是有用的, 此时在处理器缓冲区中出现是无用的。_GFP_NOWARN这个标志很少用到。当一个分配无法满足时,阻止内核发出警告(使用 printk )。_GFP_HIGH这个标志标识了一个高优先级请求, 它允许消耗甚至被内核保留给紧急状况的最后的内存页。_GFP_REPEAT_GFP_NOFAIL_GFP_NORETRY这些标志决定当有困难满足一个分配时分配器如何动作。 _GFP_REPEAT 意思是 更尽力些尝试 ,通过重复尝试 - 但是分配可能仍然失败。 _GFP_NOFAIL 标志告诉分配器不要失败;它尽最大努力来满足要求。使用 _GFP_NOFAIL 是强烈不推荐的;可能从不会有有效的理由在一个设备驱动中使用它。最后,_GFP_NORETRY 告知分配器如果得不到请求的内存则立即放弃。附录1. 内存空间Linux进程所能访问的内存空间达到4GB:用户空间与内核空间。用户空间地址分布从0到3GB(PAGE_OFFSET),内核空间为3GB4GB。内核空间中,从3G到vmalloc_start这段地址是物理内存映射区,该区域包含内核镜像、物理页表等。物理内存映射区后是vmalloc区,在物理内存映射区与vmalloc_start间还存在一个8M的gap来防止跃界。vmalloc_end的位置接近4G(最后位置系统会保留一片128k大小的区域用于专用页面映射)。2. 内存操作函数kmalloc和get_free_page申请的内存位于物理内存映射区,且在物理上是连续的,它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系,virt_to_phys()可以实现内核虚拟地址转化为物理地址:将虚拟地址减去3G,得到PAGE_OFFSET。与之对应的函数为phys_to_virt(),将内核物理地址转化为虚拟地址(virt_to_phys和phys_to_virt都定义在includeasm-i386io.h中)。而vmalloc申请的内存则位于vmalloc_startvmalloc_end之间,与物理地址没有转换关系,虽然在逻辑上也是连续的,但在物理上它们不要求连续。我们用下面的程序来演示kmalloc、get_free_page和vmalloc的区别:#include #include #include MODULE_LICENSE(GPL); unsigned char *pagemem;unsigned char *kmallocmem;unsigned char *vmallocmem;int _init mem_module_init(void)pageme

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论