染色机制:
页的统一偏移,可能映射到硬件cache的同一块,而每个slab中的开始部分,是访问频率最高的,所以使用染色机制,使每个slab中起始部分偏移不一样,从而充分利用硬件cache
cache结构中的colour为最大偏移调整计数,colour_off为每次调整的数值,colour_next为计数值,0到colour
对齐
字对齐,高速缓存行对齐
缓存区创建
kmem_cache_create()
1. 错误检查
2. 从cache_cache中分配一个对象做为新建的这个cache
3. 完成对齐,字对齐,高速缓存行对齐
4. >512,选off slab方式,反之为on slab
5. 找出一个slab的大小(页的倍数)满足3个条件,至少可以容纳一个对象,内存浪费不至于太大,slab块不至于太大
6. 可能因为剩余空间够大,能放入slab_t和kmem_bufctl_t,于是改回on slab方式
7. 设置染色相关数据。colour与剩余空间大小有关
8. 设置参数,slab链表构造(指向自身),firstnotfull指向这个slab
9. 将这个cache插入cache_sizes的指针表,并插入管理chche的链表cache_chain
缓存区释放,扩充与收缩
对象分配与释放
内核空间
2005-07-07
物理内存区:
简单的说:可以减去一个偏移或使用virt_to_phys()函数完成。
由get_free_page或kmalloc函数所分配的连续内存都陷于物理映射区域,所以它们返回的内核虚拟地址和实际物理地址仅仅是相差一个偏移量(page_offset),你可以很方便的将其转化为物理内存地址,同时内核也提供了virt_to_phys()函数将内核虚拟空间中的物理映射区地址转化为物理地址。要知道,物理内存映射区中的地址与内核页表是有序对应,系统中的每个物理页框都可以找到它对应的内核虚拟地址(在物理内存映射区中的)。
虚拟内存分配区
它的问题相对要复杂些,这是因为其分配的内核虚拟内存空间并非直接操作页框,而是分配的是vm_struct结构。该结构逻辑上连续但对应的物理内存并非连续,也就是说它vamlloc分配的内核空间地址所对应的物理地址并非可通过简单线性运算获得。
如何映射?
每个非连续区的描述符
struct vm_struct{ n unsigned long flags;
void *addr;
unsigned long size;
struct vm_struct *next; n};
vmlist
物理内存区 8m 内存区4k内存区4k
vm_struct->get_vm_area()
nstruct vm_struct *get_vm_area(unsigned long size)
{
};
函数首先调用kmalloc()为描述符分配一个内存区,然后查找一个可用的的线性地址区间,如果存在,就初始化这个描述符的所有域,返回内存区的起始地址。
vm_struct->get_vm_area->vmalloc()
分配内存区
void * vmalloc(unsigned long size)
{ n void *addr;
struct vm_struct *area;
size=(size+page_size-1)&page_mask;
if(!seze||size>(num_physpages<<page_shift))
return null;
area=get_vm_area(size);
if(!area)
return null;
if (vmalloc_area_pages(unsigned long)addr,size)
{
vfree(addr);
return null;
}
return addr;
}
vm_struct->get_vm_area()->vmalloc()->vmalloc_area_pages()
inline int vmalloc_area_pages(unsigned long address,unsigned long size,int gfp_mask,pgprot_t prot)
{
pgd_t *dir;
unsigned long end=address+size;
int ret;
dir=pgd_offset_k(address);//导出内存区起始地址在页目录中的目录项;
spin_lock(&init_mm.page_table_lock);
do{ pmd_t *pmd;
pmd=pmd_alloc(&init_mm,dir,address);//为新的内存区创建一个中间页目录项
ret=-enomem;
if(alloc_area_pmd(pmd,address,end-address,gfp_mask,prot)) n break;
address=(address+pgdir_size)&pgdir_mask;
dir++; n ….
}while(address && (address<end)); n nreturn ret; n} nalloc_area_pmd为新的中间页目录分配所有相关页表,并更新页的总目录,调用pte_alloc_kernel()函数来分配一个新的页表,之后调用alloc_area_pte()为页表项分配具体的物理页面 n
至此,完成了非连续内存区到物理页面的映射
固定映射
vaddr=_fix_to_virt(_end_of_fixed_addresses-1)&pmd_mask; nfixrange_init(vaddr,0,pgdbase);
枚举类型_end_of_fixed_addresses用作索引, _fix_to_virt宏返回给定索引的虚地址。函数fixrange_init()为这些虚地址创建合适的页表项,再由set_fixmap()函数完成地址的映射
vaddr=pkmap_base(0xfe000000)4g-32mb
fixrange_init(vaddr,vaddr+page_size*last_pkmap,pgd_base);
pgd=swapper_pg_dir+_pgd_offset(vaddr);
pmd=pmd_offset(pgd,vaddr);
pte=pte_offset(pmd,vaddr);
highmem映射nlinux内核使用highmem接口将高位内存动态映射到内核地址空间的一小部分(这部分就是4m专门保留用来实现这个目的),以此提供间接访问。这部分内核地址空间就是所谓的kmap段
include/linux/highmem.h
void *kmap(struct page *page)
{ return page_address(page);}
#define page_address(page) ((page)->virtual)
管理区zone
为了对物理页面进行有效的管理,linux把物理页面划为3个区
专供dma使用的zone_dma区(小于16mb)
常规的zone_normal区(大于16mb小于896mb)
内核不能直接映射的zone_highme区(大于896mb)
【图】
n2)在标准配置下, 物理区最大长度为896m,系统的物理内存被顺序映射在物理区中,在支持扩展页长(pse)和全局页面(pge)的机器上,物理区使用4m页面并作为全局页面来处理. 当系统物理内存大于896m时,超过物理区的那部分内存称为高端内存,低端内存和高端内存用highmem_start_page变量来定界,内核在存取高端内存时必须将它们映射到"高端页面映射区".
3) linux保留内核空间最顶部128k区域作为保留区,紧接保留区以下的一段区域为专用页面映射区,它的总尺寸和每一页的用途由fixed_address枚举结构在编绎时预定义,用__fix_to_virt(index)可获取专用区内预定义页面的逻辑地址.在专用页面区内为每个cpu预定义了一张高端内存映射页,用于在中断处理中高端页面的映射操作.(高端内存映射区属于固定内存区的一种,见下页代码)
4) 距离内核空间顶部32m, 长度为4m的一段区域为高端内存映射区,它正好占用1个页帧表所表示的物理内存总量, 它可以缓冲1024个高端页面的映射.在物理区和高端映射区之间为虚存内存分配区, 用于vmalloc()函数,它的前部与物理区有8m隔离带, 后部与高端映射区有8k的隔离带.