Linux 内核源代码情景分析 chap2 存储管理
Posted 枯萎的海风
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 内核源代码情景分析 chap2 存储管理相关的知识,希望对你有一定的参考价值。
几个重要的数据结构和函数
1. 物理地址管理
1.1 pgd_t, pmd_t, pte_t
页面目录PGD, 中间目录PMD 和 页面表PT 分别是由 pgd_t, pmd_t, pte_t 构成的数组, 下面给出他们的定义:
==================== include/asm-i386/page.h 36 50 ====================
36 /*
37 * These are used to make use of C type-checking..
38 */
39 #if CONFIG_X86_PAE
40 typedef struct { unsigned long pte_low, pte_high; } pte_t;
41 typedef struct { unsigned long long pmd; } pmd_t;
42 typedef struct { unsigned long long pgd; } pgd_t;
43 #define pte_val(x) ((x).pte_low | ((unsigned long long)(x).pte_high << 32))
44 #else
45 typedef struct { unsigned long pte_low; } pte_t;
46 typedef struct { unsigned long pmd; } pmd_t;
47 typedef struct { unsigned long pgd; } pgd_t;
48 #define pte_val(x) ((x).pte_low)
49 #endif
50 #define PTE_MASK PAGE_MASK
根据定义, 我们知道pgd_t, pmd_t, pte_t 实际上就是长整数。
我们知道, 物理页面都是跟4K 字节的边界对齐的, 因而, 物理页面起始的高 20 bit 可以看成是物理页面的序号, 余下的 低 12 bit 可以用来表征页面的状态信息和访问权限, 就像 PGD 中所做的那样。然而, 内核中并没有在 pte_t 中定义有关的位段, 而是在page.h 中另行定义了一个用来说明页面保护的结构 pgprot_t
typedef struct { unsigned long pgprot; } pgprot_t;
这个结构的值与i386 MMU 的页面表项的低12bit 相对应, 表征所映射页面的当前的状态和访问权限。
实际操作中,pgprot 数值小于 0x1000, 而pte 中数值大于 0x1000, 通过 __mk_pte 宏 可以得到实际用于页面表中的表项:
==================== include/asm-i386/pgtable-2level.h 61 61 ====================
61 #define __mk_pte(page_nr,pgprot) __pte(((page_nr) << PAGE_SHIFT) | pgprot_val(pgprot))
==================== include/asm-i386/page.h 56 58 ====================
56 #define pgprot_val(x) ((x).pgprot)
58 #define __pte(x) ((pte_t) { (x) } )
1.2 mem_map
在内核中有一个全局的指针 mem_map, 他指向一个page 数据结构的数组。而每个page 结构代表了一个物理页面, 整个的这个 page 数组代表了系统中的全部的物理页面。
也就是说, 页面表项的高20 bit 对应了一个物理页面的编号, 通过这个编号, 我们可以在这个mem_map 所对应的page 数组中找到相应的 代表这个物理页面的 page 数据结构, 而通过在这个高 20 bit 数据后面添加 12 个 0 之后, 就可以得到物理页面的起始地址了。
1.3 pgprot 中的P标志位
在映射过程中, MMU 首先检查的是 P 标志位, (就是表项中最低位), 他标志着 所映射的物理页面是否在内存中。只有 P 为 1, 才完成映射, 否则会产生缺页异常。
1.4 pte_page, virt_to_page
内核中使用 pte_page 从页面表项获取物理页面结构地址,
用 virt_to_page 从虚拟地址找到相应物理页面的page 结构
==================== include/asm-i386/pgtable-2level.h 59 59 ====================
59 #define pte_page(x) (mem_map+((unsigned long)(((x).pte_low >> PAGE_SHIFT))))
==================== include/asm-i386/page.h 117 117 ====================
117 #define virt_to_page(kaddr) (mem_map + (__pa(kaddr) >> PAGE_SHIFT))
1.5 物理页面的page 结构(mem_map_t)
==================== include/linux/mm.h 126 148 ====================
126 /*
127 * Try to keep the most commonly accessed fields in single cache lines
128 * here (16 bytes or greater). This ordering should be particularly
129 * beneficial on 32-bit processors.
130 *
131 * The first line is data used in page cache lookup, the second line
132 * is used for linear searches (eg. clock algorithm scans).
133 */
134 typedef struct page {
135 struct list_head list;
136 struct address_space *mapping;
137 unsigned long index;
138 struct page *next_hash;
139 atomic_t count;
140 unsigned long flags; /* atomic flags, some possibly updated asynchronously */
141 struct list_head lru;
142 unsigned long age;
143 wait_queue_head_t wait;
144 struct page **pprev_hash;
145 struct buffer_head * buffers;
146 void *virtual; /* non-NULL if kmapped */
147 struct zone_struct *zone;
148 } mem_map_t;
内核中通常使用 page 或者 map 来表示这个数据结构。
ps: 这个结构中的各个成分的次序是有讲究的, 目的是使得联系紧密的若干成分, 在执行被装入高速缓存的同一缓冲线中(16 字节)。
index 表明页面在文件中的序号, 或者去向。
上面提到了, mem_map 是内核中指向一个page结构的数组, 相当于一个物理页面的仓库。在系统初始化的时候, 被建立起来。 而这个仓库主要被划分为两个部分: ZONE_DMA 和 ZONE_NORMAL 。
1.6 ZONE_DMA
ZONE_DMA 管理区内的页面 主要是提供给 DMA 使用的。由于 DMA 交换不通过CPU 需要确保有一定的空间,以及有些外设的特殊要求, 或者 当DMA 所需要的缓冲区大小超过一个物理页面大小的时候, 要求这两个页面在物理上连续, (这是无法通过MMU 来保证的)。 基于这些原因, DMA 所用的物理地址需要单独划分一个区域。
1.7 管理区数据结构 zone_struct
==================== include/linux/mmzone.h 11 58 ====================
11 /*
12 * Free memory management - zoned buddy allocator.
13 */
14
15 #define MAX_ORDER 10
16
17 typedef struct free_area_struct {
18 struct list_head free_list;
19 unsigned int *map;
20 } free_area_t;
21
22 struct pglist_data;
23
24 typedef struct zone_struct {
25 /*
26 * Commonly accessed fields:
27 */
28 spinlock_t lock;
29 unsigned long offset;
30 unsigned long free_pages;
31 unsigned long inactive_clean_pages;
32 unsigned long inactive_dirty_pages;
33 unsigned long pages_min, pages_low, pages_high;
34
35 /*
36 * free areas of different sizes
37 */
38 struct list_head inactive_clean_list;
39 free_area_t free_area[MAX_ORDER];
40
41 /*
42 * rarely used fields:
43 */
44 char *name;
45 unsigned long size;
46 /*
47 * Discontig memory support fields.
48 */
49 struct pglist_data *zone_pgdat;
50 unsigned long zone_start_paddr;
51 unsigned long zone_start_mapnr;
52 struct page *zone_mem_map;
53 } zone_t;
54
55 #define ZONE_DMA 0
56 #define ZONE_NORMAL 1
57 #define ZONE_HIGHMEM 2
58 #define MAX_NR_ZONES 3
在这个管理区结构 zone_t 中 存在一组空闲区间队列 free_area_t, 这是因为, 我们常常需要按块来分配物理空间中的连续的页面。 于是有了 1, 2, 4, 8 , 16 。。。。等大小的块结构。
offset 表明分区在mem_map 中的起始页面编号。
1.8 NUMA
传统计算机结构中, 整个物理空间都是均匀一致的, cpu 访问这个空间中的任何一个地址所需要的时间都是相同的, 我们将它称为是 UMA (均质存储结构)。
然而, 这种情况是理想的,现实中一般都是 NUMA 结构。
为了支持 NUMA 结构, 管理区不在作为最高的机构,
他设置了多个存储节点, 对每个存储节点, 采用类似 UMA 时候的管理方式, ie, 在管理区结构 zone_struct 以及 page 结构数组的上方 多了一层代表着存储节点的 pglist_data 数据结构。
==================== include/linux/mmzone.h 79 90 ====================
79 typedef struct pglist_data {
80 zone_t node_zones[MAX_NR_ZONES];
81 zonelist_t node_zonelists[NR_GFPINDEX];
82 struct page *node_mem_map;
83 unsigned long *valid_addr_bitmap;
84 struct bootmem_data *bdata;
85 unsigned long node_start_paddr;
86 unsigned long node_start_mapnr;
87 unsigned long node_size;
88 int node_id;
89 struct pglist_data *node_next;
90 } pg_data_t;
若干存储节点的pglist_data数据结构通过指针 node_next 形成了一个单链队列。
node_zones 表征节点的管理区, node_mem_map 表征指向 page 的结构数组。
相应的 zone_t 结构中也有一个指针 zone_pgdat 指向所属节点的pglist_data 结构。
==================== include/linux/mmzone.h 71 74 ====================
71 typedef struct zonelist_struct {
72 zone_t * zones [MAX_NR_ZONES+1]; // NULL delimited
73 int gfp_mask;
74 } zonelist_t;
这个 zonelist_t 结构用 zones 数组 来表征不同页面的分配策略。
1.9 小结
物理空间的管理方面, 顶层的是 存储节点, 下方是管理区 和 page 数组。 我们可以这么去理解, 原先的UMA 结构在现在的模型中仅仅只是 NUMA 结构下方的一个存储节点而已。
我们拿到一个物理地址, 先得到他所在的存储节点位置 pglist_data, 继而 从这个结构中可以得到node_mem_map 就是我们所需要的 page 数组了, 通过 pte 表项的前 20 bit 可以定位得到这个 page 了。
2. 虚拟空间管理
2.1 虚拟空间的特殊性
虚拟空间管理和物理空间不同, 他没有一个总的物理页面的大仓库, 而是以进程为基础, 每个进程都有各自的虚拟存储空间(用户空间)。
物理空间管理, 我们主要是从 供 的角度管理, 而虚拟空间的管理, 更多的是从需求的角度来切入了。
2.2 vm_area_struct
这是一个对虚存区间抽象的一个重要的数据结构, 每一个离散的虚存空间都有一个 vma 与之相对应:
==================== include/linux/mm.h 35 69 ====================
35 /*
36 * This struct defines a memory VMM memory area. There is one of these
37 * per VM-area/task. A VM area is any part of the process virtual memory
38 * space that has a special rule for the page-fault handlers (ie a shared
39 * library, the executable area etc).
40 */
41 struct vm_area_struct {
42 struct mm_struct * vm_mm; /* VM area parameters */
43 unsigned long vm_start;
44 unsigned long vm_end;
45
46 /* linked list of VM areas per task, sorted by address */
47 struct vm_area_struct *vm_next;
48
49 pgprot_t vm_page_prot;
50 unsigned long vm_flags;
51
52 /* AVL tree of VM areas per task, sorted by address */
53 short vm_avl_height;
54 struct vm_area_struct * vm_avl_left;
55 struct vm_area_struct * vm_avl_right;
56
57 /* For areas with an address space and backing store,
58 * one of the address_space->i_mmap{,shared} lists,
59 * for shm areas, the list of attaches, otherwise unused.
60 */
61 struct vm_area_struct *vm_next_share;
62 struct vm_area_struct **vm_pprev_share;
63
64 struct vm_operations_struct * vm_ops;
65 unsigned long vm_pgoff; /* offset in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */
66 struct file * vm_file;
67 unsigned long vm_raend;
68 void * vm_private_data; /* was vm_pte (shared mem) */
69 };
内核中这个结构的变量名通常都是 vma
vm_start 和 vm_end 标记了一个虚存空间, vm_page_prot 和 vm_flags 表征这个虚存空间的权限等信息, 一个区间内的所有页面都应该有相同的访问权限和保护属性。
利用 vm_next 指针将同一个进程空间内所有的虚存地址的高低次序连接在一起。由于, 通常涉及到给定一个虚拟地址, 需要找出他所在的区间的操作, 如果仅仅只是链表的话, 效率不高, 于是这里还引入了AVL 树。
vm_avl_height, vm_avl_left, vm_avl_right 的三个成分就是用于 AVL 树, 表示本区间在 AVL 树中的相应位置的。
使用 vm_next_share, vm_pprev_share, vm_file 表征虚存空间与磁盘文件之间的关联。
vm_ops 指向一个 vm_operation_struct 数据结构的指针
==================== include/linux/mm.h 115 124 ====================
115 /*
116 * These are the virtual MM functions - opening of an area, closing and
117 * unmapping it (needed to keep files on disk up-to-date etc), pointer
118 * to the functions called when a no-page or a wp-page exception occurs.
119 */
120 struct vm_operations_struct {
121 void (*open)(struct vm_area_struct * area);
122 void (*close)(struct vm_area_struct * area);
123 struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int write_access);
124 };
这个结构由3个函数指针构成, 分别用于虚存空间的打开, 关闭 和 建立映射。
2.3 vm_mm
==================== include/linux/sched.h 203 227 ====================
203 struct mm_struct {
204 struct vm_area_struct * mmap; /* list of VMAs */
205 struct vm_area_struct * mmap_avl; /* tree of VMAs */
206 struct vm_area_struct * mmap_cache; /* last find_vma result */
207 pgd_t * pgd;
208 atomic_t mm_users; /* How many users with user space? */
209 atomic_t mm_count; /* How many references to "struct mm_struct" (users count as 1) */
210 int map_count; /* number of VMAs */
211 struct semaphore mmap_sem;
212 spinlock_t page_table_lock;
213
214 struct list_head mmlist; /* List of all active mm‘s */
215
216 unsigned long start_code, end_code, start_data, end_data;
217 unsigned long start_brk, brk, start_stack;
218 unsigned long arg_start, arg_end, env_start, env_end;
219 unsigned long rss, total_vm, locked_vm;
220 unsigned long def_flags;
221 unsigned long cpu_vm_mask;
222 unsigned long swap_cnt; /* number of pages to swap on next pass */
223 unsigned long swap_address;
224
225 /* Architecture-specific MM context */
226 mm_context_t context;
227 };
这是一个比vm_area_t 更高层次上使用的数据结构。每个进程只有一个 mm_struct 结构。, 在每个进程的进程 控制块, task_struct 结构中, 有一个指针指向该进程的 mm_struct 结构。
一个进程只有一个 mm_struct, 但是 一个 mm_struct 可以被多个进程共享, ex. vfork(), clone()
2.4 小结
ps:
mm_struct 结构和他下属的各个 vm_area_struct 只是表明了他对虚存空间的需求, page , zone_struct 等结构则说明了对页面的供应, 而 PGD, PMD, PT 等则是他们两者之间的桥梁
以上是关于Linux 内核源代码情景分析 chap2 存储管理的主要内容,如果未能解决你的问题,请参考以下文章