//task_struct结构体定义在include/linux/sched.h文件
//关于进程的地址空间,指向进程的地址空间。(链表和红黑树)
struct mm_struct *mm, *active_mm;
//mm 进程所拥有的用户空间内存描述符,内核线程无的mm为NULL
//active_mm active_mm 指向进程运行时所使用的内存描述符,对于普通进程而言,这两个指针变量的值相同。但是内核线程 kernel thread是没有进程地址空间的,所以内核线程的tsk->mm域是空(NULL)。但是内核必须知道用户空间包含了什么, 因此它的active_mm成员被初始化为前一个运行进程的active_mm值。
一个进程的虚拟地址空间主要由两个数据结来描述。一个是最高层次的:mm_struct,一个是较高层次的:vm_area_structs。最高层次的mm_struct结构描述了一个进程的整个虚拟地址空间。较高层次的结构vm_area_truct描述了虚拟地址空间的一个区间。
每一个进程都会有自己独立的mm_struct,这样每一个进程都会有自己独立的地址空间,这样才能互不干扰。
//mm_struct 定义在include/linux/mm_types.h
//这里我们提取mm_struct结构体中的 mmap和pgd 进行分析
//指向虚拟区间(VMA)的链表
struct vm_area_struct * mmap;
//指向页全局目录
pgd_t * pgd;
//Linux通过类型为vm_area_struct的结构体对象实现线性区,该结构定义了内存VM内存区域。
//分析部分代码
struct vm_area_struct {
//vm_area_struct结构所描述的虚存空间以vm_start、vm_end成员表示,它们分别保存了该虚存空间的首地址和末地址后 第一个字节的地址,以字节为单位,所以虚存空间范围可以用[vm_start, vm_end)表示。
unsigned long vm_start;
unsigned long vm_end;
//每个任务的VM区域的链接列表,按地址排序
struct vm_area_struct *vm_next, *vm_prev;
struct mm_struct *vm_mm; //我们所属的address space
pgprot_t vm_page_prot; // 此VMA的访问权限
unsigned long vm_flags; //vm_flags主要保存了进程对该虚存空间的访问权限,然后还有一些其他的属性。
/*
对于具有地址空间(address apace)和后备存储(backing store)的区域,
链接到address_space->i_mmap间隔树,或者链接到address_space-> i_mmap_nonlinear列表中的vma。
*/
union {
struct {
struct rb_node rb;
unsigned long rb_subtree_last;
} linear;
struct list_head nonlinear;
} shared;
//处理匿名文件共享内存映射的情况,映射到同一物理内存页的映射都保存在一个链表中
struct anon_vma *anon_vma;
/* 用于处理此结构体的函数指针 */
const struct vm_operations_struct *vm_ops;
/* 后备存储(backing store)的信息: */
//处理有后备文件内存映射的情况,获得该映射在文件的页偏移量,以及打开文件file实例的信息
unsigned long vm_pgoff; /* 以PAGE_SIZE为单位的偏移量(在vm_file中),*不是* PAGE_CACHE_SIZE*/
struct file * vm_file; /* 我们映射到文件(可以为NULL)*/
void * vm_private_data; /* 是vm_pte(共享内存) */
};
//该结构体定义在/include/linux/mm.h
//Linux利用了面向对象的思想,即把一个虚拟区间看成一个对象,用vm_area_structs描述了这个对象的属性,其中的vm_operation结构描述了在这个对象上的操作
struct vm_operations_struct {
void (*open)(struct vm_area_struct * area);
void (*close)(struct vm_area_struct * area);
struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int unused);
};
//vm_operations结构中包含的是函数指针;其中,open、close分别用于虚拟区间的打开、关闭,而nopage用于当虚存页面不在物理内存而引起的“缺页异常”时所应该调用的函数。
页目录表 pgd
每个进程有一个属于自己的页目录表,可通过 CR3 寄存器找到
页表含有220(1M)个表项,而每项占用4Byte。如果作为一个表来存放的话,它们最多将占用4MB的内存。因此为了减少内存占用量,80x86使用了两级表。由此,高20位线性地址到物理地址的转换也被分成两步来进行,每步使用(转换)其中的10bit。
mem_map是一个struct page的数组,管理着系统中所有的物理内存页面。在系统启动的过程中,创建和分配mem_map的内存区域。UMA体系结构中,free_area_init()函数在系统唯一的struct node对象contig_page_data中node_mem_map成员赋值给全局的mem_map变量。
//出于节省内存的考虑,struct page中使用了大量的联合体union
//在mem_map[]结构中找到对应的struct page结构体
struct page {
unsigned long flags; /* 原子标志,有些情况下会异步更新 */
atomic_t _count; /* 使用计数,见下文。 */
union {
atomic_t _mapcount; /* 内存管理子系统中映射的页表项计数,
* 用于表示页是否已经映射,还用于限制逆向映射搜索。
*/
unsigned int inuse; /* 用于SLUB分配器:对象的数目 */
};
union {
struct {
unsigned long private; /* 由映射私有,不透明数据:
*如果设置了PagePrivate,通常用于buffer_heads;
*如果设置了PageSwapCache,则用于swp_entry_t;
* 如果设置了PG_buddy,则用于表示伙伴系统中的阶。
*/
struct address_space *mapping; /* 如果最低位为0,则指向inode
* address_space,或为NULL。
* 如果页映射为匿名内存,最低位置位,
* 而且该指针指向anon_vma对象:
* 参见下文的PAGE_MAPPING_ANON。
*/
};
...
struct kmem_cache *slab; /* 用于SLUB分配器:指向slab的指针 */
struct page *first_page; /* 用于复合页的尾页,指向首页 */
};
union {
pgoff_t index; /* 在映射内的偏移量 */
void *freelist; /* SLUB: freelist req. slab lock */
};
struct list_head lru; /* 换出页列表,例如由zone->lru_lock保护的active_list!
*/
#if defined(WANT_PAGE_VIRTUAL)
void *virtual; /* 内核虚拟地址(如果没有映射则为NULL,即高端内存) */
#endif /* WANT_PAGE_VIRTUAL */
};
通常会把内核空间中大于896M的空间称作内核空间中的高端内存。内核可以用三种不同的机制将页框映射到高端内存:永久内核映射、临时内核映射和非连续内存分配。
非连续内存的线性地址空间是从VMALLOC_START~VMALLOC_END,共128MB大小。当内核需要用vmalloc类的函数进行非连续内存分配时,就会申请一个vm_struct结构来描述对应的vmalloc区,若分配多个vmalloc的内存区,那么相邻两个vmalloc区之间的间隔大小至少为4KB,即至少是一个页框大小PAGE——SIZE。 如下图所示
//vm_struct结构体在include/linux/vmalloc.h中定义
struct vm_struct {
struct vm_struct *next; //指向下一个vm_struct区,所有非连续区组成一个单链表
void *addr; //代表每个内存区的起始地址,即指向申请的内存区的第一个内存单元(线性地址) unsigned long size; //当前所申请的内存区大小加4KB(安全区)
unsigned long flags; //标识内存区类型
struct page **pages; //指向nr_pages页描述符指针数组的指针
unsigned int nr_pages; //所申请的内存区大小对应的页框数
phys_addr_tphys_addr; //该字段一般为0,除非内存已经被申请用作映射一个硬件设备的I/O共享内存
const void *caller; //当前调用vmalloc类的函数的返回地址
};
/*
内核中用get_vm_area函数来创建一个新的非连续区结构,在该函数的实现中又会调用kmallloc函数和kfree函数
分别为vm_struct结构分配和释放所需的内存。
vmalloc给内核分配一个非连续的内存区,其原型为:
void *vmalloc(unsigned longsize)
函数首先把size参数取为页面的大小(即4KB)的倍数,然后进行有效性检查,若有大小适合的可用内存,就调用
get_vm_area()获得一个内存区的结构,最后会调用vmalloc_area_pages()进行真正的的非连续内存的分配,该函数
实际上建立了非连续内存到物理页面的映射。
*/
//这里只举出图中出现的类型
enum zone_type {
//内存首部16MB,即低端范围的物理内存,某些工业标准体系结构(ISA)设备需要用到ZONE_DMA;
#ifdef CONFIG_ZONE_DMA
ZONE_DMA,
#endif
//标记了使用32位地址字可寻址, 适合DMA的内存域。
#ifdef CONFIG_ZONE_DMA32
ZONE_DMA32,
#endif
//16MB~896MB,该部分的内存由内核直接映射到线性地址空间的较高部分;
//许多内核操作只有通过ZONE_NORMAL才能完成,因此ZONE_NORMAL是影响系统性能最为重要的管理区。
ZONE_NORMAL,
//896MB~末尾,将保留给系统使用,是系统中预留的可用内存空间,不能被内核直接映射。
#ifdef CONFIG_HIGHMEM
ZONE_HIGHMEM,
#endif
};
//struct zone结构体在中定义
struct zone {
//各种级别的水线需要保留的内存页面数量,在页面分配器和kswapd页面回收中会用到。
unsigned long watermark[NR_WMARK];
//保留的页框池。linux为了尽量减少内存分配失败的情况发生,内核为原子内存分配请求保留了一个页框池,该页框池只有在内存不足的时候才使用。ZONE_DMA和ZONE_NORMAL内存管理区将一定数量的页框贡献给保留内存,这个数目与两个管理区的相对大小成比例。
long lowmem_reserve[MAX_NR_ZONES];
//该管理区的父节点。
struct pglist_data *zone_pgdat;
//如果用于slab的页达到这个值就缓存收缩。
unsigned long min_slab_pages;
//zone 中被伙伴系统管理的页面数量。
unsigned long managed_pages;
//对应于伙伴系统中MIGRATE_RESEVE链的页块的数量
int nr_migrate_reserve_block;
//...
//空闲区域位图,由伙伴分配器使用。(下面进行介绍)
struct free_area free_area[MAX_ORDER];
/*
* 将zone中的页框PG_reserved置位。表示该页不可用。
* 同时初始化页框中其他值。
*/
void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone,
unsigned long start_pfn, enum memmap_context context)
for (pfn = start_pfn; pfn < end_pfn; pfn++) {
if (context == MEMMAP_EARLY) {
if (!early_pfn_valid(pfn))
continue;
if (!early_pfn_in_nid(pfn, nid))
continue;
if (!update_defer_init(pgdat, pfn, end_pfn,&nr_initialised))
break; }
if (!(pfn & (pageblock_nr_pages - 1))) {
//创建page
struct page *page = pfn_to_page(pfn);
//初始化page
__init_single_page(page, pfn, zone, nid);
set_pageblock_migratetype(page, MIGRATE_MOVABLE);
}
else {
__init_single_pfn(pfn, zone, nid);
}
//在一个循环中建立和初始化了该zone地址范围内的所有page,函数pfn_to_page()用于创建page, //__init_single_page()用于初始化page
//页面和zone映射的api
static inline struct zone *page_zone(const struct page *page){
return &NODE_DATA(page_to_nid(page))->node_zones[page_zonenum(page)];
}
static inline void set_page_zone(struct page *page, enum zone_type zone){
page->flags &= ~(ZONES_MASK << ZONES_PGSHIFT);
page->flags |= (zone & ZONES_MASK) << ZONES_PGSHIFT;
}
{
//zone中定义的
struct free_area free_area[MAX_ORDER];
//用于管理该zone的伙伴系统信息。伙伴系统将基于这些信息管理该zone的物理内存。该数组中每个数组项用于管理一个空闲内存页块链表,同一个链表中的内存页块的大小相同,并且大小为2的数组下标次方页。MAX_ORDER定义了支持的最大的内存页块大小。
//free_area结构体的详解
struct free_area {
structlist_head free_list[MIGRATE_TYPES];
unsignedlong nr_free;
};
//nr_free:其中nr_free表示内存页块的数目,对于0阶的表示以1页为单位计算,对于1阶的以2页为单位计算,n阶的以2的n次方为单位计算。
//free_list:用于将具有该大小的内存页块连接起来。由于内存页块表示的是连续的物理页,因而对于加入到链表中的每个内存页块来说,只需要将内存页块中的第一个页加入该链表即可。因此这些链表连接的是每个内存页块中第一个内存页,使用了struct page中的struct list_head成员lru。free_list数组元素的每一个对应一种属性的类型,可用于不同的目地,但是它们的大小和组织方式相同。
cat /proc/buddyinfo
实现目的:
基本原理
slab分配器为每种对象分配一个高速缓存,这个缓存可以看做是同类型对象的一种储备。每个高速缓存所占的内存区又被划分多个slab,每个 slab是由一个或多个连续的页框组成。每个页框中包含若干个对象,既有已经分配的对象,也包含空闲的对象。
//每个高速缓存通过kmem_cache结构来描述,这个结构中包含了对当前高速缓存各种属性信息的描述。
//所有的高速缓存通过双链表组织在一起,形成 高速缓存链表cache_chain。
//每个kmem_cache结构中并不包含对具体slab的描述,而是通过kmem_list3结构组织各个 slab。
struct kmem_list3 {
//该结构将当前缓存中的所有slab分为三个集合:空闲对象的slab链表slabs_free,非空闲对象的slab链表 slabs_full以及部分空闲对象的slab链表slabs_partial。
struct list_head slabs_partial;
struct list_head slabs_full;
struct list_head slabs_free;
unsigned long free_objects;
unsigned int free_limit;
unsigned int colour_next;
spinlock_t list_lock;
struct array_cache *shared;
struct array_cache **alien;
unsigned long next_reap;
int free_touched;
}
//slab描述符
//每个slab有相应的slab描述符
struct slab {
struct list_head list;
unsigned long colouroff;
void *s_mem;
unsigned int inuse;
kmem_bufctl_t free;
unsigned short nodeid;
}