Linux内核设计与实现之内存管理(十一)

第11章 内存管理
11.1 页
1)内核把物理页作为内存管理的基本单位
2)内存管理单元MMU以页为单位进行处理
3)MMU是管理内存并把虚拟地址转换成物理地址的硬件
4)MMU以页为单位管理系统中的页表
5)从虚拟内存角度看,页是最小单位
6)内核用struct page 结构表示系统中的每个物理页,该结构位于<linux/mm.h>
struct page
{
page_flags_t flags; //存放页的状态(是否脏的,是否被锁在内存中,定义在<linux/page-flags.h>)
atomic_t _count; //存放页的引用计数,page_count()
atomic_t _mapcount;
unsigned long private;
struct address_space *mapping;
pgoff_t index;
struct list_head lru;
void *virtual; //页的虚拟地址
};
7)page结构与物理页相关,并非与虚拟页相关,该结构对页的描述只是暂时的。内核仅仅用这个数据结构描述当前时刻在相关的物理页中存放的东西。内核用着个数据结构来管理系统中的所有页。系统中的每个物理页都有分配一个这样的结构体
11.2 区
1)硬件限制,内核不能对所有的页一视同仁,内核把页划分为不同的区(zone),用于对具有相似特性的页进行分组
2)硬件缺陷引起的内存寻址问题:一些硬件只能用某些特定的内存地址来执行DMA;一些体系结构的内存的物理地址大于虚拟地址寻址范围,导致一些内存不能永久的映射到内核空间上
3)区的划分linux/mmzone.h
A) ZONE_DMA: 包含的页用来执行DMA操作
B) ZONE_NORMAL:包含的页都是能正常映射的页
C)ZONE_HIGHMEM:包含高端内存,其中的页不能永久的映射到内核地址空间
4)x86体系结构上,ISA设备不能在整个32位的地址空间中执行DMA,ISA只能访问物理内存的前16MB,ZONE_DMA范围0~16MB
5)ZONE_HIGHMEM能否直接映射页取决体系结构,x86上,它为高于896M的所有物理内存
6)x86每个区
ZONE_DMA小于16MB
ZONE_NORMAL 16~896MB
ZONE_HIGHMEM 大于896M
7)区的划分没有物理意义,只是内核为了管理页而采用的一种逻辑上的分组
8)struct zone linux/mmzone.h
11.3 获取页
1)通过内核实现的接口在内核内分配和释放内存
2)请求内存的底层机制,以页为单位分配内存 linux/gfp.h
a)Struct page *alloc_pages(unsigned int gfp_mask, unsigned int order);
该函数分配2~order(1<<order)个物理页,并返回一个指针,该指针指向第一页的page结构体,出错,返回NULL
b)void *page_address(struct page *page);
该函数把给定的页转换成它的逻辑地址,返回一个指向给定物理页当前所在的逻辑地址的指针
C)unsigned long __get_free_pages(unsigned int gfp_mask, unsigned int order)
该函数与allo_pages()作用相同,但它直接返回所请求的第一个页的逻辑地址
D)只需要一页
Struct page *alloc_page(unsigned int gfp_mask)
Unsigned long __get_free_page(unsigned int gfp_mask)
11.3.1 获取填充为0的页
Unsigned long get_zeroed_page(unsigned int gfp_mask);
该函数同_get_free_page(),不同之处是该函数把分配好的页都填充成0.
11.3.2 释放页
Void __free_pages(struct page *page, unsigned int order);
Void free_pages(unsigned long addr, unsigned int order);
Void free_page(unsigned long addr);
11.4 以字节为单位的分配 kmalloc() <linux/slab.h>
1)kmalloc()同用户空间的malloc(),只不过多了一个flags参数
2)void *kmalloc(size_t size, int flags);
该函数返回一个指向内存块的指针,其内存块至少要有size大小;所分配的内存区在物理上是连续的;出错时,返回NULL
3)GFP_KERNEL标志表示在试图获取内存并返回给该函数的调用者的过程中,内存分配器将要采取的行为
11.4.1 gfp_mask标志
1)行为修饰符:表示内核应该如何分配所需的内存
1.__GFP_WAIT
2.__GFP_HIGH
3.__GFP_IO
4.__GFP_FS
5.__GFP_COLD
6.__GFP_NOWARN
7.__GFP_REPEAT
8.__GFP_NOFAIL
9.__GFP_NORETRY
10.__GFP_NO_GROW
11.__GFP_COMP
2)区修饰符:表示从哪儿分配内存
1.__GFP_DMA
2.__GFP_HIGHMEN
3)类型标志:组合行为修饰符和区修饰符,将各种可能用到的组合归纳为不同的类型,简化修饰符的使用
1.GFP_ATOMIC __GFP_HIGH
2.GFP_NOIO __GFP_WAIT
3.GFP_NOFS (__GFP_WAIT | __GFP_IO)
4.GFP_KERNEL (__GFP_WAIT | __GFP_IO | __GFP_FS)
5.GFP_USER (__GFP_WAIT | __GFP_IO | __GFP_FS)
6.GFP_HIGHUSER (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HIGHMEM)
7.GFP_DMA __GFP_DMA
4)什么时候用GFP_KERNEL和GFP_ATOMIC
进程上下文,可以睡眠 GFP_KERNEL
进程上下文,不可以睡眠 GFP_ATOMIC
中断处理程序 GFP_ATOMIC
软中断 GFP_ATOMIC
Tasklet GFP_ATOMIC
需要用于DMA的内存,可以睡眠 (GFP_DMA | GFP_KERNEL)
需要用于DMA的内存,不可以睡眠 (GFP_DMA | GFP_ATOMIC)
11.4.2 释放kfree() <linux/slab.h>
Void kfree(const void *ptr)
11.5 vmalloc()
1)分配的内存虚拟地址连续,物理地址不一定连续,同用户空间分配函数的工作方式
2)kmalloc()确保页在物理地址上是连续的
3)硬件设备需要得到物理地址连续的内存;软件使用的内存块虚拟内存连续即可;内核看所有内存都是逻辑上连续的
4)内核代码中,常用kmalloc(),
5)void *vmalloc(unsigned long size); <mm/vmalloc.h>
该函数返回一个指针,指向逻辑上连续的一块内存,其大小至少是size;出错时,返回NULL;函数可能睡眠,不能从中断上下文中进行调用,也不能从其他不允许阻塞的情况下进行调用。
6)void vfree(void *addr) 该函数也可以睡眠
11.6 slab层
1)空闲链表:相当于高速缓存以便快速存储频繁使用的对象
2)空闲链表面临的主要问题:不能全局控制。当可用内存紧缺时,内核无法通知每个空闲链表,让其收缩缓存的大小以便释放出一些内存。实际上,内存根本就不知道存在任何空闲链表。为了弥补这一缺陷,linux内核提供了slab层。Slab分配器扮演通用数据结构缓存层的角色
3)slab分配器试图在几个原则之间寻找一个平衡
A)频繁使用的数据结构也会频繁分配和释放,应该缓存它们
B)频繁分配和回收必然会导致内存碎片。空闲链表的缓存会连续地存放,释放的数据结构又会放回空闲链表,因此不会导致碎片
C)回收的对象可以立即投入下一次分配。对于频繁的分配和释放,空闲链表可以提高其性能
D)如果分配器知道对象大小、页大小和总的高速缓存的大小,它会做出更明智的决策
E)如果让部分缓存专属于单个处理器,那么分配和释放就可以在不加SMP锁的情况下进行
F)如果分配器与NUMA相关,它就可以从相同的存储节点为请求者进行分配
G)对存放的对象进行着色,以防止多个对象映射到相同的高速缓存行
4)slab层的设计
1.它把不同的对象划分为所谓的高速缓存组
2.每个高速缓存存放不同类型的对象
3.每个对象类型对于应该高速缓存。一个高速缓存用于存放进程描述符,而另一个高速缓存存放索引节点对象
4.kmalloc()接口建立在slab层上,使用了一组通用高速缓存
5.slab由一个或多个物理上连续的页组成,每个高速缓存可以由多个slab组成
6.slab的对象成员:别缓存的数据结构
6.slab的状态:满、部分满、空。一个满的slab没有空闲的对象
7.inode结构:磁盘索引节点在内存中的体系,这些结构会被频繁地创建和释放,所以用slab分配器来管理它们。Struct inode由inode_cachep高速缓存进行分配
8. 高速缓存:kmem_cache_s结构,包含三个链表slabs_full、slabs_partial和slabs_empty.
9.slab:struct slab,slab描述符要么在slab之外另行分配,要么放在slab自身最开始的地方
10.slab分配器可以创建新的slab,通过__get_free_pages()低层内核页分配器进行的
11.Static void *kmem_getpages(kmem_cache_t *cachep, int flags, int nodeid)
Kmem_freepages()
12.slab层的管理是在每个高速缓存的基础上,通过提供给整个内核一个接口来完成的,通过接口就可以创建和销毁新的高速缓存,并在高速缓存内分配和释放对象
11.7 slab分配器的接口
1)创建一个新的高速缓存
Kmem_cache_t *kmem_cache_create(const char *name, size_t size, size_t align, unsigned long flags, void (ctor)(void, kmem_cache_t *, unsigned long), void (dtor)(void, kmem_cache_t *, unsigned long));
该函数返回一个指向所创建高速缓存的指针,不能在中断上下文中调用,因为它可能会睡眠
2)销毁一个高速缓存
Int kem_cache_destroy(kmem_cache_t *cachep);
3)创建高速缓存后,从中获取对象
Void *kmem_cache_alloc(kmem_cache_t *cachep, int flags)
4)释放一个对象,并把它返回给原先的slab
Void kmem_cache_free(kmem_cache_t *cachep, void *objp);
11.8 在栈上的静态分配
1)内核栈大小固定,用户空间可以动态增长
2)每个进程的内核栈大小既取决于体系结构,也与编译时的选项有关
3)每个进程都有两页的内核栈,32位和64位体系结构的页面大小分别是4KB和8KB,内核栈大小分别是8KB和16KB
4)中断处理程序曾经使用过它们所中断的进程的内核栈,导致中断处理程序也要放在内核栈中,同时会把更严格的约束条件加在可怜的内核栈上。为了矫正这个问题,增加一个附加选项:中断栈。中断栈为每个进程提供一个用于中断处理程序的栈。无限的递归和alloca()是不允许的
5)具体函数中的所有局部变量所占空间之和不要超过几百字节。在栈上进行大量静态分配,比如分配大型数组和大型结构体,是危险的。动态分配是一个明智的选择

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值