页式映射的数据结构
物理内存 - PGD,PMD,PT的数据结构
#if CONFIG_X86_PAE typedef struct { unsigned long pte_low, pte_high; } pte_t; typedef struct { unsigned long long pmd; } pmd_t; typedef struct { unsigned long long pgd; } pgd_t; #define pte_val(x) ((x).pte_low | ((unsigned long long)(x).pte_high << 32)) #else typedef struct { unsigned long pte_low; } pte_t; typedef struct { unsigned long pmd; } pmd_t; typedef struct { unsigned long pgd; } pgd_t; #define pte_val(x) ((x).pte_low) #endif #define PTE_MASK PAGE_MASK typedef struct { unsigned long pgprot; } pgprot_t; #define pmd_val(x) ((x).pmd) #define pgd_val(x) ((x).pgd) #define pgprot_val(x) ((x).pgprot)
内核代码中对pmd_t, pgd_t, pte_t的定义只是用struct包装了一层unsigned long,目的是为了进行类型检查。 pgprot_t: 由于pte_t指向一个页面的地址,而页面是4K对齐的,所以pte_t的低12位是0. 这12位被用来描述所指向页面的9种属性。
#define _PAGE_PRESENT 0x001 #define _PAGE_RW 0x002 #define _PAGE_USER 0x004 #define _PAGE_PWT 0x008 #define _PAGE_PCD 0x010 #define _PAGE_ACCESSED 0x020 #define _PAGE_DIRTY 0x040 #define _PAGE_PSE 0x080 /* 4 MB (or 2MB) page, Pentium+, if present.. */ #define _PAGE_GLOBAL 0x100 /* Global TLB entry PPro+ */ #define _PAGE_PROTNONE 0x080 /* If not present */
实际中pgprot_t的值总是小于0x1000 pte_t的值总是大于0x1000 两者结合起来就是内存中要存储的pte_t的值。 #define __mk_pte(page_nr,pgprot) __pte(((page_nr) << PAGE_SHIFT) | pgprot_val(pgprot))
物理内存 - 全局变量mem_map
一个连续的大数组struct page mem_map[N];每一个page代表一个页面。 而一个pte对于MMU来说是一个页面的高20位地址,而对于kernel来说一个一个页面的编号,正好是mem_map的下标。
#define pte_page(x) (mem_map+((unsigned long)(((x).pte_low >> PAGE_SHIFT))))
page结构:
typedef struct page { struct list_head list; struct address_space *mapping; unsigned long index; struct page *next_hash; atomic_t count; unsigned long flags; /* atomic flags, some possibly updated asynchronously */ struct list_head lru; unsigned long age; wait_queue_head_t wait; struct page **pprev_hash; struct buffer_head * buffers; void *virtual; /* non-NULL if kmapped */ struct zone_struct *zone; } mem_map_t;
各个变量的顺序是由讲究的,尽力使联系紧密的在CPU的同一个cache line中。
物理内存 - ZONE
系统所有的page数据结构是最低层描述物理内存的结构了,做为内存分配时候的批发仓库。 这些page结构被分成了ZONE。 ZONE_DMA:1) 是磁盘IO所必须的,如果系统把page分配光了,就不能进行IO了。2) DMA的操作不经过CPU,也就是不经过MMU,没有内存映射机制,所以要单独管理。3) DMA的内存需要连续的物理页面。
typedef struct zone_struct { spinlock_t lock; unsigned long offset; unsigned long free_pages; unsigned long inactive_clean_pages; unsigned long inactive_dirty_pages; unsigned long pages_min, pages_low, pages_high; struct list_head inactive_clean_list; free_area_t free_area[MAX_ORDER]; // MAX_ORDER = 10 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; #define ZONE_DMA 0 #define ZONE_NORMAL 1 #define ZONE_HIGHMEM 2 #define MAX_NR_ZONES 3
free_area_t就是有名的buddy算法的数据结构。 offset 是在mem_map的偏移。表示该zone_t管辖的页面的起始下标。一个页面被一个zone管辖后就不会改变了。 free_area_struct的free_list是一个双向链表的挂钩。而page结构中第一个成员struct list_head list就是通过free_list挂入了一个free_area。
typedef struct free_area_struct { struct list_head free_list; unsigned int *map; } free_area_t;
物理内存 - NUMA非均质存储结构
质地相同的内存称为一个node。一个node的内存是独立的,由独立的mem_map数组,分配连续物理页面的时候不跨node。
typedef struct pglist_data { zone_t node_zones[MAX_NR_ZONES]; //当前节点的最多3个的‘管理区’。相反,在zone_t中的zone_pgdat指向‘管理区’所属于的node zonelist_t node_zonelists[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 pglist_data *node_next; } pg_data_t;
分配策略:一个zonelist_t代表一个分配策略,最多有NR_GFPINDEX256个策略,分配内存时候要指定分配的策略。
typedef struct zonelist_struct { zone_t * zones [MAX_NR_ZONES+1]; // NULL delimited int gfp_mask; } zonelist_t;
每个zonelist_t代表着一个分配策略。zone_t指向一个管理区,可以跨node。 分配内存时候根据指定的策略从node_zonelist选出一个策略,然后从zonelist_t中的zones从下标0开始的‘管理区’开始分配。 比如:DMA内存分配。到当前节点找到分配DMA策略的zonelist_t,然后从第0个‘管理区’开始分配,这个管理区有可能是一个指向本地DMA内存的zone。 如果分配失败,就从第1个‘管理区’分配,这个管理区有可能代表全局的DMA内存的管理结构zone。
虚拟内存 - vma
vma是从进程的角度。
struct vm_area_struct { struct mm_struct * vm_mm; /* VM area parameters */ unsigned long vm_start; unsigned long vm_end; /* linked list of VM areas per task, sorted by address */ struct vm_area_struct *vm_next; pgprot_t vm_page_prot; unsigned long vm_flags; /* AVL tree of VM areas per task, sorted by address */ short vm_avl_height; struct vm_area_struct * vm_avl_left; struct vm_area_struct * vm_avl_right; /* For areas with an address space and backing store, * one of the address_space->i_mmap{,shared} lists, * for shm areas, the list of attaches, otherwise unused. */ struct vm_area_struct *vm_next_share; struct vm_area_struct **vm_pprev_share; struct vm_operations_struct * vm_ops; unsigned long vm_pgoff; /* offset in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */ struct file * vm_file; unsigned long vm_raend; void * vm_private_data; /* was vm_pte (shared mem) */ };
描述一个进程中一段连续的虚拟地址空间,且属性相同。 vm_next以链表形式串起来,按照vm_start升序排列。vm_avl_left, vm_avl_right以平衡树形式串起来。 vm_next_share, vm_pprev_share, vm_file和文件相关。 vm_ops是回调
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); };
vm_mm是这个vma所属于的mm_struct结构:
struct mm_struct { struct vm_area_struct * mmap; /* list of VMAs */ struct vm_area_struct * mmap_avl; /* tree of VMAs */ struct vm_area_struct * mmap_cache; /* last find_vma result */ 35%的命中率 pgd_t * pgd; atomic_t mm_users; /* How many users with user space? */ atomic_t mm_count; /* How many references to "struct mm_struct" (users count as 1) */ int map_count; /* number of VMAs */ struct semaphore mmap_sem; spinlock_t page_table_lock; struct list_head mmlist; /* List of all active mm's */ 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; /* number of pages to swap on next pass */ unsigned long swap_address; /* Architecture-specific MM context */ mm_context_t context; };
一个进程一个mm_struct,描述一个进程所用内存的细节。 vma是按照vm_start从小到大排序。vma的查找插入操作在mmap.c中。比较特殊的是插入操作,用一个指针就搞定了往list中插入节点。
pprev = &mm->mmap; //2级指针,记录要插入位置的前面一个vma中的next的地址,妙! while (*pprev && (*pprev)->vm_start <= vmp->vm_start) pprev = &(*pprev)->vm_next; vmp->vm_next = *pprev; *pprev = vmp;
当mmap中的vma个数大于32时启用avl