这里只列举几个比较麻烦的数据结构
struct list_head {
struct list_head *next, *prev;
} //linux通用的双向链队列,下面多处用到,这里列出源代码
linux对内存再用页式管理,对于页,就有个数据结构page加以描述。在内核中有个全局量mem-map指向的是一个page的数组,整个数组描述了整个物理内存,大家注意32位地址线,内存大小是2的32次方。由于页大小是4k也就是2的12次方。每4K的页就有一个page。那么2的32次方共有2的20次方个页,则mem-map指向的数组大小就是2的20次方。其中明显对于页的物理地址,一定是4K的倍数,因为它大小是4K,那么这个数组就有个对应,数组下标后面12个0(2进制)对应了页面的物理地址,他们在数值上是相等的。那么我们在page数据结构中就没有必要存贮它的物理地址。page数据结构定义位置include/linux/mm.h
struct page{
struct list_head list; //通过使用它进入下面的数据结构free_area_struct结构中的双向链队列
struct address_space * mapping; //用于内存交换的数据结构
unsigned long index;//当页面进入交换文件后
struct page *next_hash; //自身的指针,这样就可以链接成一个链表
atomic t count; //用于页面交换的计数,若页面为空闲则为0,分配就赋值1,没建立或恢复一次映射就加1,断开映射就减一
unsigned long flags;//反应页面各种状态,例如活跃,不活跃脏,不活跃干净,空闲
struct list_head lru;
unsigned long age; //表示页面寿命
wait_queue_head_t wait;
struct page ** pprev_hash;
struct buffer_head * buffers;
void * virtual
struct zone_struct * zone; //指向所属的管理区
}
(对于每个项的说明,我会慢慢补上)
对内存,仅仅用page数据结构来描述肯定远远不够,对于整个内存,我们在此之上分了管理区的概念,每个管理区管理数个页面。这个数据结构是 zone_struct定义位置是在include/linux/mmzone.h
typedef struct free_area_struct {
struct list_head free_list; //linux 中通用的双向链队列
unsigned int * map;
} free_area_t;
typedef struct zone_struct{
spinlock_t lock;
unsigned long offset; //表示该管理区在mem-map数组中,起始的页号
unsigned long free pages;
unsigned long inactive_clean_pages;
unsigned long inactive_dirty_pages;
unsigned pages_min, pages_low, pages_high;
struct list_head inactive_clean_list; //用于页面交换的队列,基于linux页面交换的机制。这里存贮的是不活动“干净”页面
free_area_t free_area[MAX_ORDER];
//一组“空闲区间”队列,free_area_t定义在上面,其中空闲下标表示的是页面大小,例如:数组第一个元素0号,表示所有区间大小为2的0次方的页面链接成的双向队列,1号表示所有2的1次方页面链接链接成的双向队列,2号表示所有2的2次方页面链接成的队列,其中要求是这些页面地址连续
char * name;
unsigned long size;
struct pglist_data * zone_pgdat; //用于指向它所属的存贮节点,及下面的数据结构
unsigned long zone_start_paddr;
unsigned long zone_start_mapnr;
struct page * zone_mem_map;
} zone_t;
我们知道内存的地位并不是“平等的”,例如主内存和图形卡上的静态内存ram就不是“平等地位”,因此我们页面管理机制做了修正,管理区不是内存管理中最高层的概念,前述的page数组mem-map也不是全局,而是从属于具体的节点,在zone_struct 上面有了一层代表存贮节点的数据结构pglist_data,定义于include/linux/mmzone.h 中
typedef struct pglist_data {
zone_t node_zones[MAX_NR_ZONES]; //该节点最多的3个页面管理区,MAX_NR_ZONE值是3,在linux中分为3个管理区,也可能是2个ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM
zonelist_t node_zonelist[NR_GFPINDEX]; //具体说明见下面的数据结构
struct page * node_mem_map; //用于指向属于该存储节点的page数组
unsigned long * valid_addr_bitmap;
struct bootmem_data * bdata;
unsigned long node_start_paddr;
unsigned long node_start_mapnr;
unsigned long node_size;
int node_id;
struct pglistdata * node_next; //用于形成一个单链队列
} pg_data_t;
typedef struct zonelist_struct {
zone_t * zones [MAX_NR_ZONES+1]; //指针数组,指向每个具体的页面管理区
int gfp_mask;
} zonelist_t; //用于描述内存分配策略的数据结构,比如内存中最小适应法则的分配策略,我们就可以将区放入zones数组中,按照区内空闲页面从小到大的原则,但我们要实现这个法则,我们只需要从头到尾便利这个数组,那么找到第一个大于你要求的页面个数的区就是最小适应区
这些都是用于物理空间管理的,虚拟空间管理由下面的数据结构描述(我刚开始把虚拟空间弄成了虚拟内存,越看越糊涂,虚拟空间指的是,在linux中,4G内存被分为2块,搞地址1G内存叫系统空间,所有进程共享,下面的3G便是虚拟空间,每个进程都占有虚拟空间3G,在进程自己看来是这样的,但实际上,他远远没这么多内存用),vm_area_struct数据结构,定义于 include/linux/mm.h,进程虚存空间的描述,他被进一步组织到mm_struct中,它描述是地址连续的空间
struct vm_area_struct {
struct mm_struct * vm_mm; //下面说明
unsigned long vm_start;
unsigned long vm_end; //start和end决定了虚存空间,个人理解应该是mem-map表的下标,其中 start包含,end不包含,同时区间划分不单单是地址连续,还要保证权限的统一
struct vm_area_struct * vm_next; //将属于同一进程的虚存区间按照虚存地址高低链接起来
pgrot t_vm_page_prot;
unsigned long vm_flags; //存贮该区间的权限
short vm_avl_height;
struct vm_area_struct * vm_avl_left;
struct vm_area_struct * vm_avl_rigth; //用于生成 avl树,提高搜索效率
struct vm_area_struct * vm_next_share;
struct vm_area_struct ** vm_pprev_share;
struct vm_operations_struct * vm_ops; //下面说明
unsigned long vm_pgoff;
struct file * vm_file;
unsigned long vm_raend;
void * vm_private_data; //这些属性都是用于记录页面与文件关系,具体情况具体分析
}
定义于include/linux/mm.h
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 write_access);
} //open, close,nopage用于虚存空间打开,关闭和建立印射
定义于include/linux/sched.h,进程所有的虚存空间的数据结构描述
struct mm_struct{
struct vm_area_struct * mmap; //建立虚存空间的单链队列
struct vm_area_struct * mmap_avl; //建立虚存空间的AVL树
struct vm_area_struct * mmap_cache; //最近一次使用的虚存空间,由于内存访问总是带有局部性,命中率有35%
pgd_t * pgd; //指向进程页面目录,在载入进程时候这个值会被载入到cr3寄存器中
atomic_t mm_users;
atomic_t mm_count;
//这2个变量比较令人费解,mm_users记录是虚存空间的使用者,而mm_count记录的是mm_struct使用的计数。首先虚存空间是可以多个进程使用的,比如如果父进程调用vfork创建子线程,此时2者使用的是同一个虚存空间。线程是没有自己的虚存空间。其次一个mm_struct对应一个虚存,有几个使用者那么就有几个进程通过指针共享了这个mm_struct,按理说这2个计数应该是一个,不应该分成2个。大部分可能性2者是相同的。但又特殊情况,比如内核线程是没有虚存空间,他是需要暂借调用者的虚存空间,只是虚存空间使用者和mm_struct使用计数就不统一
int map_count;
struct semaphore mmap_sem; //用于进程的间的互斥访问
spinlock_t page_table_lock;
struct list_head mmlist;
unsigned long start_code, end_code, start_data, end_data
unsigned long start_brk, brk, start_stack;
unsigned long arg_start, arg_end, env_start, env_end;
unsigned long rss, total_vm, locked_vm;
unsigned long def_flags;
unsigned long cpu_vm_mask;
unsigned long swap_cnt;
unsigned long swap_address;
mm_context_t context;
}
在操作系统中,有个进程控制块(PCB)的概念,具体到linux里面对应的数据结构是task_struct,它内部就有个mm_struct指针,mm_struct是整个用户空间的抽象。待续。。。