《Chapter 12 内存管理》
MMU:内存管理单元,管理内存,地址转换,通常以页为单位管理。
从虚存角度看,页是最小单位。
struct page {
unsigned long flag; //页是否脏,是否被锁定在内存
atomic_t _count; //页引用计数
atomic_t _mapcount; //
unsigned long private;
struct address_space *mapping;
pdoff_t index;
struct list_head lru;
void *virtual;
}
一个页的用途:由页缓存使用(mapping域指向与这个页关联的address_space)、作为私有数据(由private指向)、作为进程页表中的映射。
virtual域是页的虚拟地址(页在虚存中的地址)。
struct page是与物理页相关。
页的拥有者:用户进程空间、动态分配的内核数据、静态代码段、页高速缓存 等。
内存分区(ZONE):
某些硬件特性导致内存页面不能被一视同仁。
Linux使用了四种区:ZONE_DMA、ZONE_DMA32、ZONE_NORMAL、ZONE_HIGHMEM。
区的使用与体系结构相关,例如,x86下,ISA设备不能使用32位的地址空间,只能使用0~16M的空间,所以ZONE_DMA包含在0~16M这个范围内。
struct zone;
获得页:
//获取2^order个连续的物理页。
struct page* alloc_page(gfp_t gfp_mask, unsigned int order);
//将struct page转为对应的逻辑地址
void* page_address(struct page* page);
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);
struct page* alloc_page(gfp_t gfp_mask);
unsigned long __get_free_page(gfp_t gfp_mask);
unsigned long get_zeroed_page(gfp_t 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);
释放页时只能释放属于自己的页,这个需要函数调用者保证。
kmalloc:
按字节分配,物理上连续。
void* kmalloc(size_t sizem gfp_t flag);
分配器标志:行为修饰符、区修饰符、类型。
void kfree(const void *ptr)
vmalloc:类似于kmalloc,但是vmalloc分配的内存虚拟地址是连续的,物理地址可能不连续。它分配非连续的物理页,然后通过‘修正’页表,将内存映射到逻辑地址空间的连续区域中。大多数情况下,只有硬件设备需要得到物理地址连续的内存。
vmalloc性能差于kmalloc(页表转换),且可能导致TLB抖动,所以vmalloc在不得已时才使用(例如,大块内存)。
slab:
一种对象对应于一种高速缓存,每一个高速缓存又被划分成slab,一个slab是由一个或连续几个物理页组成的。slab中存放数据结构。slab可能是满的,部分满的,空的。
例如struct inode由inode_cachep分配。
高速缓存永kmem_cache结构体来表示。这个结构体有三个链表 slab_full, slab_partial, slab_empty。
struct slab {
struct list_head list; //满(/部分/空)链表
unsigned long coloroff; //着色偏移?
void *s_mem; //第一个对象
unsigned int inuse; //已分配的对象个数
kmem_bufctl_t free; //第一个空闲对象
}
相关接口:
构造高速缓存:
struct kmem_cache *kmem_cache_create(const char* name,
size_t size, //每个元素的大小
size_t align,
unsigned long flags,
void (*ctor)(void*));
ctor是构造器,在高速缓存分配新页的时候调用。事实上,Linux不使用,设为NULL即可。
销毁高速缓存:
int kmem_cache_destroy(struct kmem_cache *cachep);
从高速缓存中分配/释放一个对象:
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);
void *kmem_cache_free(struct kmem_cache *cachep, void *objp);
内核栈可以是一页或者两页,历史上,中断处理程序和进程共享一个内核栈,当内核栈只有一页时,中断使用自己的栈(中断栈)。
高端内存的映射:
将一个struct page(对应一个物理页)映射到虚存(返回虚拟地址):
void* kmap(struct page *pg) //可能睡眠
解除映射:
void* kunmap(struct page *pg)
临时映射(不睡眠)和解除映射
void* kmap_atomic(struct page* pg, enum km_type type)
void* kunmap_atomic(struct page* pg, enum km_type type)
每CPU数据:
使用每CPU数据的原因:
- 减少了数据锁定
- 减少缓存失效
接口:
编译时定义每个CPU变量
DEFINE_PER_CPU(type, name); //为每个处理器都定义了一个类型为type变量名为name的变量。
DECLARE_PER_CPU(type, name);
get_cpu_var(name)++; //加1,同时禁止抢占
put_cpu_var(name); //重新允许抢占
运行时每CPU数据:
void *alloc_percpu(tpye); //一个宏
viud *__alloc_percpu(size_t size, size_t align);
void free_percpu(const void*);
get_cpu_var(ptr); //返回一个void类型的指针,指向ptr
put_cpu_var(ptr); //重新激活内核抢占