内存管理
1.页
内核把物理页作为内存管理的基本单位。大多数 32 位体系结构支持 4KB 的页,而 64 位体系结构一般会支持 8KB 的页。内核用 struct page 结构表示系统中的每个物理页。该结构位于<linux/mm.h>中。
struct page { page_flags_t flags; /* 表示页的状态,每一位表示一种状态,定义在<linux/page_flags.h> */ atomic_t _count; /* 存放页的引用计数,0代表没有被引用 */ atomic_t _mapcount; unsigned long private; strcut address_space *mapping; pgoff_t index; struct list_head lru; void *virtual; /* 页在虚拟内存中的地址,动态映射物理页 */ }
2.区
由于硬件的限制,内核并不能对所有的页一视同仁。Linux必须处理如下两种由于硬件存在缺陷而引起的内存寻址问题:
1)一些硬件只能用某些特定的内存地址来执行DMA(直接内存访问)。
2)一些体系结构其内存的物理寻址范围比虚拟寻址范围大得多。这样,就有一些内存不能永久地映射到内核空间上。
由于存在这种限制,内核把具有相似特性的页划分为不同的区(ZONE):
1)ZONE_DMA——这个区包含的页能用来执行DMA操作。
2)ZONE_NORMAL——这个区包含的都是能正常地映射网页。
3)ZONE_HIGHMEM——这个区包含“高端内存”,其中的页并能不永久地映射到内核地址空间。
Linux把系统的页划分为区,形成不同的内存池,这样就可以根据用途进行分配。注意,区的划分没有任何物理意义,这只是内核为了管理页而采取的一种逻辑上的分组。用于DMA的内存必须从ZONE_DMA中进行分配,但是一般用途的内存却既能从ZONE_DMA分配,也能从ZONE_NORMAL分配。
3.获得页
内核提供了一种请求内存的底层机制,并提供了对它进行访问的几个接口。所有这些接口都以页为单位分配内存,定义于<linux/gfp.h>。最核心的函数是:
struct page *alloc_pages( unsigned int gfp_mask, unsigned int order );
该函数分配 2order 个连续的物理页,并返回一个指向第一页的 page 结构体指针,如果出错就返回 NULL。
void *page_address( struct page *page );
把给定的页转换成它的逻辑地址。如果无须用到 struct page,可以调用:
unsigned long __get_free_pages( unsigned int gfp_mask, unsigned int order );
这个函数与 alloc_pages 作用相同,不过它直接返回所请求的第一个页的逻辑地址。因为页是连续的,因此其他页也会紧随其后。
如果只需要一页,可以用以下两个函数:
struct page *alloc_page( unsigned int gfp_mask ); unsigned long _get_free_page( unsigned int gfp_mask );
如果需要让返回页的内容全为0,可以使用下面这个函数
unsigned long get_zeroed_page(unsigned int gfp_mask );
当不再需要页时可以使用以下函数来释放它。
void __free_pages( struct page *page, unsigned int order ); void free_pages( unsigned long addr, unsigned int order ); void free_page( unsigned long addr );
释放页时要谨慎,只能释放属于你的页。传递了错误的 struct page 或地址,用了错误的 order 值都可能导致系统崩溃。请记住,内核是完全依赖自己的。
4.kmalloc
kmalloc 与 malloc 一族函数非常类似,只不过它多了一个 flags 参数。kmalloc在<linux/slab.h>中声明:
void *kmalloc( size_t size, int flags );
这个函数返回一个指向内存块的指针,其内存块至少要有 size 大小。所分配的内存正在物理上是连续的。在出错时,它返回 NULL。除非没有足够的内存可用,否则内核总能分配成功。在对 kmalloc 调用之后,你必须检查返回的是不是 NULL,如果是,要适当地处理错误。
在低级页分配函数还是 kmalloc 中,都用到了 gfp_mask(分配器标志)。这些标志可分为三类:行为修饰符、区修饰符及类型。
1)行为修饰符表示内核应当如何分配所需的内存。在某些特定情况下,只能使用某些特定的方法分配内存。例如,中断处理程序就要求内核在分配内存的过程中不能睡眠(因为中断处理程序不能被重新调度)。
2)区修饰符指明到底从哪一区中进行分配。
3)类型标志组合了行为修饰符和区修饰符,将各种可能用到的组合归纳为不同类型,简化了修饰符的使用。
行为修饰符
可以同时指定这些分配标志。例如
ptr = kmalloc( size, __GFP_WAIT | __GFP_IO | __GFP_FS );
区修饰符
指定以上标志中的一个就可以改变内核试图进行分配的区。如果没有指定任何标志,则内核从ZONE_DMA或ZONE_NORMAL进行分配,优先从ZONE_NORMAL进行分配。不能给 _get_free_pages 或 kmalloc 指定 __GFP_HIGHMAM,因为这两个函数返回的都是逻辑地址,而不是 page 结构,这两个函数分配的内存当前有可能还没有映射到内核的虚拟地址空间。因此,也可能根本就没有逻辑地址。只有 alloc_pages 才能分配高端内存。
类型标志(括号里说明该类型等价的行为修饰符组合)
内核中最常用的标志是 GFP_KERNEL。这种分配可能会引起睡眠,它使用的是普通优先级,因此这个标志只用在可以重新安全高度的进程上下文中。另一个全然相反的标志是 GFP_ATOMIC。因为这个标志表示不能睡眠的内存分配,因此想要满足调用者获取内存的请求将会受到很严格的限制。
kmalloc 的另一端就是 kfree,kfree声明于<linux/slab.h>中
void kfree( const void *ptr );
kfree 函数释放由 kmalloc 分配出来的内存块。调用 kfree( NULL ) 是安全的。
5.vmalloc
vmalloc 的工作方式是类似于 kmalloc,只不过前者分配的内存虚拟地址是连续的,而物理地址则无需连续。用户空间分配函数工作方式的区别:
1)由 malloc 返回的页在进程的虚拟地址空间内是连续的,但是,这并不保证它们在物理RAM中也连续。
2)kmalloc 确保页在物理地址上是连续的(虚拟地址自然也是连续的)。
3)vmalloc 只确保页的虚拟地址空间内是连续的。
大多数情况下,只有硬件设备需要得到物理地址连续的内存。但由于通过 vmalloc 获得的页必须一个一个地进行映射,这就会导致比直接内存映射大得多的TLB抖动。因为这个原因,很多内核代码都用 kmalloc 来获得内存。