1、Linux内核分析,韩晓峰,内存管理,主要内容,页 区 获得、释放页 kmalloc()、kfree() vmalloc() slab分配器,内核与用户空间不同,它不具备像用户空间那样设置的使用内存的能力,不支持简单便捷的内存分配方式。,页,内核把物理页作为内存管理的基本单位。尽管处理器的最小可寻址单位通常为字(甚至字节),但内存管理单元(MMU)通常以页为单位进行处理。 从虚拟内存的角度来看,页就是最小单位。,体系结构不同,支持的页大小也不同。大多数32位体系结构支持4KB的页,而64位体系结构一般会支持8KB的页。 在支持4KB页大小并有1GB物理内存的机器上,物理内存会被划分为26214
2、4个页。,内核用struct page结构表示系统中的每个物理页,该结构位于linux/mm.h中:,struct pagepage_flags_t flags;atomic_t _count;atomic_t _mapcount;unsigned long private;statct address_space *mapping;pgoff_t index;struct list_head lru;void *virtual; ,较重要的域: flag 存放页的状态 _count存放页的引用计数 virtual是页的虚拟地址。,系统中的每个物理页都要分配一个这样的结构体 假设struct
3、page占用40字节的内存,系统的物理页为4KB大小,系统有128MB物理内存。 那么系统中又有page结构消耗的内存只不过是1MB多些。,区,由于硬件限制,有些页位于内存中特定的物理地址上,所以不能将其用于一些特定的任务。 内核把页划分为不同的区(zone)。,ZHONE_DMA这个区包含的页用来执行DMA操作 ZONE_NORMAL这个区包含的都是正常映射的页 ZONE_HIGHMEMM这个区包含“高端内存”,其中的页并不能永久的映射到内核地址空间,三种区的定义在linux/mmzone.h中 区的实际使用和分布式与体系结构相关的。 ZONE_HIGHMEM所在的内存是高端内存,其余的内存
4、就是所谓的地段内存。,在x86上 ZONE_DMA为小于16MB的物理内存 ZONE_NORMAL为16896MB之间的物理内存 ZONE_HIGHMEM为高于896MB的所有物理内存。,Linux把系统的页划分为区,形成不同的内存池,可以根据用途进行分配。区的划分没有物理意义,只是为了内核管理页的一种逻辑上的分组。,每个区都用struct zone表示,定义在linux/mmzone.h文件中。,结构体中lock域是一个自旋锁,防止该结构被并发访问name域是一个以NULL结束的字符串,表示这个区的名字。内核启动期间初始化这个值,代码位于mm/page_alloc.c中 名字分别为“DMA”
5、,“Normal”,“HighMem”,获得页、释放页,获得页,内核提供了一种请求内存的底层机制,并提供了对它进行访问的几个接口,所有接口都以页尾单位分配内存,定义在linux/gfp.h中,struct page * alloc_pages(unsigned int gfp_mas, unsigned int order)该函数分配2的order次方个连续的无力也,并返回一个指针,指向第一个页的page结构体。,void *page_address(struct page *page)函数返回一个指针,指向给定物理页当前所在的逻辑地址。,unsigned long _get_free_pag
6、es(unsigned int gfp_mask, unsigned int order)这个函数与alloc_page()作用相同,直接返回所请求的第一个页的逻辑地址。,如果只需要一个页,这可使用封装好的函数: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)该函数把分配好的页都填充为0,这样可以清除随机的垃圾数据。,释放页
7、,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值,都可能导致系统崩溃。 内核是不会检查这些参数指定的范围是否正确。,获得8个页的例子,unsigned long page;page=_get_free_pages(GFP_KERNEL,3); if(!page)retu
8、rn -BNOMEM; free_pages(page,3);,kmalloc(),kmalloc(),kmalloc()函数与用户空间的malloc()一族函数非常类似,只是多了一个flags参数。 使用此函数可以获得以字节为单位的一块内核内存。kmalloc()在linux/slab.h中声明,void *kmalloc(size_t size, int flags)这个函数返回一个执行内存块的指针,其内存块至少要有size大小。分配的内存区在屋里上式连续的。出错返回NULL,必须判断返回值再进行使用。,分配结构体例子: struct bird *ptr;ptr=kmalloc(sizeo
9、f(struct bird),GFP_KERNEL);if(!ptr)/* 处理错误. */,kfree(),kfree(),kfree()声明在linux/slab.h中。void kfree(const void *ptr) kfree()函数释放由kmalloc()分配出来的内存块。如果指定释放的内存不应该操作,则会导致严重后果。,char *buf;buf=kmalloc(BUF_SIZE,GFP_ATOMIC); if(!buf)/* 内存分配出错处理 */kfree(buf);,vmalloc(),vmalloc(),vmalloc()函数的工作方式类似于kmalloc,只不过vm
10、alloc分配的内存虚拟地址是连续的,而物理地址无需连续。这也是用户空间分配函数malloc的工作方式。 kmalloc函数确保页在物理地址上是连续的,虚拟地址自然也是连续的。,由于使用vmalloc需要把物理上不连续的页转换为虚拟地址空间上连续的页,必须专门建立页表项,所以vmalloc仅在不得已时才会使用一般是为了获得大块内存时,例如当模块被动态的插入到内核中时,就把模块装载到由vmalloc分配的内存上。,vmalloc()函数在linux/vmalloc.h中声明,在mm/vmalloc.c中定义。用法与用户空间的malloc()相同:void* vmalloc(unsigned lo
11、ng size),该函数返回一个指针,指向逻辑上连续的一块内存区,函数可能睡眠,因此不能从中孤单上下文中进行调用,也不能从 其他不允许阻塞的情况下进行调用。,释放vfree(),要释放通过vmallo()获得的内存,使用 void vfree(void *addr)函数会释放从addr开始的内存块,addr是由vmalloc()分配的内存块地址。,char *buf;buf=vmalloc(16*PAGE_SIZE); if(!buf)/* 分配内存错误 */vfree(buf);,slab层,分配和释放数据结构是所有内核中最普遍的操作之一。 为了便于数据的频繁分配和回收,slab分配器扮演了
12、通用数据结构缓存层的角色。,slab分配器基本原则: 频繁使用的数据结构也会频繁分配和释放,因此需要缓存他们。 频繁分配和回收必然会导致内存碎片,使用空闲链表的缓存会连续的存放 使用空闲链表提高分配和释放的性能。 对多处理器进行优化,slab层把不同的对象划分为所谓高速缓存(cache)组,其中每个高速缓存都存放不同类型的对象。 每种对象类型对应一个高速缓存。 这些高速缓存又被划分为slab。slab由一个或多个物理上连续的页组成。 每个slab都包含一些对象成员,这里的对象指的是被缓存的数据结构。,每个slab处于三种状态: 满没有空闲的对象 部分满slab中有分配和未分配的对象 空没有分配
13、出任何对象,对象分配: 当内核的某一部分需要一个新的对象时,先从部分满的slab中进行分配。 如果没有部分满的slab,就从空的slab中进行分配。 如果没有空的slab,就要创建一个slab。,高速缓冲、slab、对象关系,每个高速缓存都是用kmem_cache_s结构来表示。这个结构包含三个链表slabs_full、slabs_partial和slabs_empty,均存放在kmem_list3结构内。,slab描述符struct slab用来描述每个slab struct slabstruct list_head list; /*链表*/unsigned long colouroff;v
14、oid *s_mem; /*slab中第一个对象*/unsigned int inuse; /*已分配对象数*/kmem_bufctl_t free; /*第一空闲对象*/ ;,相关系统调用,内存相关系统调用,Linux 采用malloc,calloc,realloc和free函数对动态内存进行分配。 动态内存分配是编制高效率程序的基础,能够高效率使用内存和重要的系统资源,malloc(配置内存空间) #include void * malloc(size_t size); 函数说明malloc()用来配置内存空间,其大小由指定的size决定。 返回值若配置成功则返回一指针,失败则返回NULL
15、。,calloc(配置内存空间) #include void *calloc(size_t nmemb,size_t size); calloc()用来配置nmemb个相邻的内存单位,每一单位的大小为size,并返回指向第一个元素的指针。这和使用下列的方式效果相同:malloc(nmemb*size);,不过,在利用calloc()配置内存时会将内存内容初始化为0。 返回值若配置成功则返回一指针,失败则返回NULL。,realloc函数改变以前分配的内存大小,可以使用realloc调整以前由malloc和calloc调用获得的内存大小。 #include void *realloc(void
16、*ptr,size_t size); ptr:必须是malloc和calloc返回的指针,size可以大于原来的内存块的大小,也可以小于.,realloc不对增加的内存块做初始化。 realloc如果不能大于内存块,就返回NULL,而且保持原来的数据不动 realloc的第一个参数如果为NULL,他和malloc作用一样 realloc的第二个参数如果为0,则释放原来的内存块,free释放一块内存 #include void free(void* ptr); ptr必须是malloc和calloc放回的指针。,内存映射,Linux允许任何一个进程把一个磁盘文件映像到内存中,在磁盘文件和内存中的
17、映像创建一个逐字节的对应关系。 在内存映像文件上的I/O操作跳过了内核缓冲,速度快许多。,Linux用mmap,munmap,msync,mprotect、mlock、munlock、mlockall和munlockall。这些系统调用管理内存映像文件。,mmap(建立内存映射) #include #include void *mmap(void *start,size_t length,int port,int flags,int fd,off_t offset); mmap()用来将某个文件内容映射到内存中,对该内存区域的存取即是直接对该文件内容的读写。,参数start指向欲对应的内存起始
18、地址,通常设为NULL,代表让系统自动选定地址,对应成功后该地址会返回。 参数length代表将文件中多大的部分对应到内存。,参数prot代表映射区域的保护方式 有下列组合 PROT_EXEC 映射区域可被执行 PROT_READ 映射区域可被读取 PROT_WRITE 映射区域可被写入 PROT_NONE 映射区域不能存取,参数flags会影响映射区域的各种特性 MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。 MAP_SHARED对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。,MAP_PRIVATE
19、 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。 MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。,MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。 MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。,参数fd为open()返回的文件描述词,代表欲映射到内存的文件。 参数offset为文件映射的偏移
20、量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。 返回值若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED则返回-1,错误原因存于errno中,munmap函数,munmap接触内存映像并把内存释放返回给操作系统。 #include #include int munmap(void *start, size_t length); start指向要解除映像的内存区的起始位置 length指出要解除映像的内存区的大小。 执行成功返回0,失败返回-1并设置errno变量。,msync函数,msync函数把映像文件写入磁盘 #include # incl
21、ude int msync(const void *start,size_t length, int flags); start 指定的地址开始 Length写入指定的长度,Flags参数: MS_ASYNC:调度一次写入操作然后返回 MS_SYNC:在msync返回前写入数据 MS_INVALIDATE:让映像到同一文件的映像无效,以便用新数据更新它们,mprotech函数,mprotech函数修改在内存映像上的保护模式。 #include #include int mprotect(const void *start,size_t len,int prot); mprotech把自star
22、t开始的内存区的保护模式修改为prot指定的值。执行成功返回0,失败返回-1并且设置errno变量.,mremap函数,mremap函数用于改变一个被映像的文件的大小。 #include void *mremap(void *old_addr,size_t old_len, size_t new_len,unsigned lang flags); mremap函数调整自old_addr开始的内存区的大小,把原来的大小old_len调整到new_len。,参数flags:指出如果必要是否在内存中移动内存区。MREMAP_MAYMOVE允许改动地址,如果每没有这个值,则调整内存区大小的操作执行失败。如果执行成功mremap返回内存区调整后的地址,如果执行失败则返回NULL.,