本来打算返校上机之前再继续写code review的(拖延症),不过看来返校还遥遥无期,所以就先写了⑧。
Lab2文件树如下,新增文件已用*标出,本lab相关的主要是内存管理方面的文件。
![](http://img.e-com-net.com/image/info8/2b246dbcfe82488397e9aa432bbc4e31.gif)
1 . 2 ├── boot 3 │ ├── Makefile 4 │ └── start.S 5 ├── drivers 6 │ ├── gxconsole 7 │ │ ├── console.c 8 │ │ ├── dev_cons.h 9 │ │ └── Makefile 10 │ └── Makefile 11 ├── gxemul 12 │ ├── elfinfo 13 │ ├── r3000 14 │ ├── r3000_test 15 │ ├── test 16 │ └── view * 17 ├── include 18 │ ├── args.h * 19 │ ├── asm 20 │ │ ├── asm.h 21 │ │ ├── cp0regdef.h 22 │ │ └── regdef.h 23 │ ├── asm-mips3k 24 │ │ ├── asm.h 25 │ │ ├── cp0regdef.h 26 │ │ └── regdef.h 27 │ ├── env.h 28 │ ├── error.h 29 │ ├── kclock.h 30 │ ├── mmu.h 31 │ ├── pmap.h 32 │ ├── printf.h 33 │ ├── print.h 34 │ ├── queue.h 35 │ ├── sched.h 36 │ ├── stackframe.h 37 │ ├── trap.h 38 │ ├── types.h 39 │ └── unistd.h * 40 ├── include.mk 41 ├── init 42 │ ├── init.c 43 │ ├── main.c 44 │ └── Makefile 45 ├── lib 46 │ ├── env.c * 47 │ ├── genex.S * 48 │ ├── Makefile 49 │ ├── printBackUp 50 │ ├── print.c 51 │ └── printf.c 52 ├── Makefile 53 ├── mm * 54 │ ├── Makefile * 55 │ ├── pmap.c * 56 │ └── tlb_asm.S * 57 ├── readelf 58 │ ├── kerelf.h 59 │ ├── main.c 60 │ ├── Makefile 61 │ ├── readelf.c 62 │ ├── testELF 63 │ └── types.h 64 └── tools 65 └── scse0_3.lds
一些实用宏
链表操作的宏在./include/queue.h
中给出,虽然文件名是queue.h
但其实定义了双向链表、双向尾队列和循环队列三种数据结构。
![](http://img.e-com-net.com/image/info8/2b246dbcfe82488397e9aa432bbc4e31.gif)
1 /* 2 * ./include/queue.h 3 */ 4 5 #ifndef _SYS_QUEUE_H_ 6 #define _SYS_QUEUE_H_ 7 8 /* 9 * This file defines three types of data structures: lists, tail queues, 10 * and circular queues. 11 * 12 * A list is headed by a single forward pointer(or an array of forward 13 * pointers for a hash table header). The elements are doubly linked 14 * so that an arbitrary element can be removed without a need to 15 * traverse the list. New elements can be added to the list before 16 * or after an existing element or at the head of the list. A list 17 * may only be traversed in the forward direction. 18 * 19 * A tail queue is headed by a pair of pointers, one to the head of the 20 * list and the other to the tail of the list. The elements are doubly 21 * linked so that an arbitrary element can be removed without a need to 22 * traverse the list. New elements can be added to the list before or 23 * after an existing element, at the head of the list, or at the end of 24 * the list. A tail queue may only be traversed in the forward direction. 25 * 26 * A circle queue is headed by a pair of pointers, one to the head of the 27 * list and the other to the tail of the list. The elements are doubly 28 * linked so that an arbitrary element can be removed without a need to 29 * traverse the list. New elements can be added to the list before or after 30 * an existing element, at the head of the list, or at the end of the list. 31 * A circle queue may be traversed in either direction, but has a more 32 * complex end of list detection. 33 * 34 * For details on the use of these macros, see the queue(3) manual page. 35 */ 36 37 /* 38 * List declarations. 39 */ 40 41 /* 42 * A list is headed by a structure defined by the LIST_HEAD macro. This structure con‐ 43 * tains a single pointer to the first element on the list. The elements are doubly 44 * linked so that an arbitrary element can be removed without traversing the list. New 45 * elements can be added to the list after an existing element or at the head of the list. 46 * A LIST_HEAD structure is declared as follows: 47 * 48 * LIST_HEAD(HEADNAME, TYPE) head; 49 * 50 * where HEADNAME is the name of the structure to be defined, and TYPE is the type of the 51 * elements to be linked into the list. 52 */ 53 #define LIST_HEAD(name, type) \ 54 struct name { \ 55 struct type *lh_first; /* first element */ \ 56 } 57 58 /* 59 * Set a list head variable to LIST_HEAD_INITIALIZER(head) 60 * to reset it to the empty list. 61 */ 62 #define LIST_HEAD_INITIALIZER(head) \ 63 { NULL } 64 65 /* 66 * Use this inside a structure "LIST_ENTRY(type) field" to use 67 * x as the list piece. 68 * 69 * The le_prev points at the pointer to the structure containing 70 * this very LIST_ENTRY, so that if we want to remove this list entry, 71 * we can do *le_prev = le_next to update the structure pointing at us. 72 */ 73 #define LIST_ENTRY(type) \ 74 struct { \ 75 struct type *le_next; /* next element */ \ 76 struct type **le_prev; /* address of previous next element */ \ 77 } 78 79 /* 80 * List functions. 81 */ 82 83 /* 84 * Detect the list named "head" is empty. 85 */ 86 #define LIST_EMPTY(head) ((head)->lh_first == NULL) 87 88 /* 89 * Return the first element in the list named "head". 90 */ 91 #define LIST_FIRST(head) ((head)->lh_first) 92 93 /* 94 * Iterate over the elements in the list named "head". 95 * During the loop, assign the list elements to the variable "var" 96 * and use the LIST_ENTRY structure member "field" as the link field. 97 */ 98 #define LIST_FOREACH(var, head, field) \ 99 for ((var) = LIST_FIRST((head)); \ 100 (var); \ 101 (var) = LIST_NEXT((var), field)) 102 103 /* 104 * Reset the list named "head" to the empty list. 105 */ 106 #define LIST_INIT(head) do { \ 107 LIST_FIRST((head)) = NULL; \ 108 } while (0) 109 110 /* 111 * Insert the element "elm" *after* the element "listelm" which is 112 * already in the list. The "field" name is the link element 113 * as above. 114 */ 115 116 117 #define LIST_INSERT_AFTER(listelm, elm, field) do { \ 118 LIST_NEXT((elm), field) = LIST_NEXT((listelm), field); \ 119 if (LIST_NEXT((listelm), field)) { \ 120 LIST_NEXT((listelm), field)->field.le_prev = &LIST_NEXT((elm), field); \ 121 } \ 122 LIST_NEXT((listelm), field) = (elm); \ 123 (elm)->field.le_prev = &LIST_NEXT((listelm), field); \ 124 }while (0) 125 // Note: assign a to b <==> a = b 126 //Step 1, assign elm.next to listelem.next. 127 //Step 2: Judge whether listelm.next is NULL, if not, then assign listelm.pre to a proper value. 128 //step 3: Assign listelm.next to a proper value. 129 //step 4: Assign elm.pre to a proper value. 130 131 132 /* 133 * Insert the element "elm" *before* the element "listelm" which is 134 * already in the list. The "field" name is the link element 135 * as above. 136 */ 137 #define LIST_INSERT_BEFORE(listelm, elm, field) do { \ 138 (elm)->field.le_prev = (listelm)->field.le_prev; \ 139 LIST_NEXT((elm), field) = (listelm); \ 140 *(listelm)->field.le_prev = (elm); \ 141 (listelm)->field.le_prev = &LIST_NEXT((elm), field); \ 142 } while (0) 143 144 /* 145 * Insert the element "elm" at the head of the list named "head". 146 * The "field" name is the link element as above. 147 */ 148 #define LIST_INSERT_HEAD(head, elm, field) do { \ 149 if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ 150 LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\ 151 LIST_FIRST((head)) = (elm); \ 152 (elm)->field.le_prev = &LIST_FIRST((head)); \ 153 } while (0) 154 155 /* 156 * Insert the element "elm" at the tail of the list named "head". 157 * The "field" name is the link element as above. You can refer to LIST_INSERT_HEAD. 158 * Note: this function has big differences with LIST_INSERT_HEAD ! 159 */ 160 #define LIST_INSERT_TAIL(head, elm, field) do { \ 161 if (LIST_FIRST((head)) != NULL) { \ 162 LIST_NEXT((elm), field) = LIST_FIRST((head)); \ 163 while (LIST_NEXT(LIST_NEXT((elm), field), field) != NULL) { \ 164 LIST_NEXT((elm), field) = LIST_NEXT(LIST_NEXT((elm), field), field); \ 165 } \ 166 LIST_NEXT(LIST_NEXT((elm), field), field) = (elm); \ 167 (elm)->field.le_prev = &LIST_NEXT(LIST_NEXT((elm), field), field); \ 168 LIST_NEXT((elm), field) = NULL; \ 169 } else { \ 170 LIST_INSERT_HEAD((head), (elm), field); \ 171 } \ 172 } while (0) 173 174 175 #define LIST_NEXT(elm, field) ((elm)->field.le_next) 176 177 /* 178 * Remove the element "elm" from the list. 179 * The "field" name is the link element as above. 180 */ 181 #define LIST_REMOVE(elm, field) do { \ 182 if (LIST_NEXT((elm), field) != NULL) \ 183 LIST_NEXT((elm), field)->field.le_prev = \ 184 (elm)->field.le_prev; \ 185 *(elm)->field.le_prev = LIST_NEXT((elm), field); \ 186 } while (0) 187 188 /* 189 * Tail queue definitions. 190 */ 191 #define TAILQ_HEAD(name, type) \ 192 struct name { \ 193 struct type *tqh_first; /* first element */ \ 194 struct type **tqh_last; /* addr of last next element */ \ 195 } 196 197 #define TAILQ_ENTRY(type) \ 198 struct { \ 199 struct type *tqe_next; /* next element */ \ 200 struct type **tqe_prev; /* address of previous next element */ \ 201 } 202 203 #endif /* !_SYS_QUEUE_H_ */
其中定义的宏的意义与用法示例如下,
链表的声明相关宏:
LIST_HEAD(name, type)
:定义了链表的表头,其中含有一个指向链表头部的指针。用法:LIST_HEAD(headstruct, Type) head;
LIST_HEAD_INITIALIZER(head)
:用于将表头初始化为NULL,但似乎没有实现,如果实现的话应该是{head->lh_first = NULL;}
,用法:LIST_HEAD_INITIALIZER(&head);
(注意取地址符)LIST_ENTRY(type)
:相当于链表项的索引,含有指向上一个和下一个元素的指针,用法:LIST_ENTRY(Type) entry;
链表操作相关宏:
LIST_EMPTY(head)
:判断链表是否为空,用法示例:if(LIST_EMPTY(&head)) {/*do something*/}
LIST_FIRST(head)
:用于获得链表第一个元素的指针,用法:Type* first = LIST_FIRST(head);
LIST_FOREACH(var, head, field)
:用于遍历链表,用法:LIST_FOREACH(item, &head, field) {/*do something*/}
LIST_INIT(head)
:与LIST_HEAD_INITIALIZER
相同,用法:LIST_INIT(&head);
LIST_INSERT_AFTER(listelm, elm, field)
、LIST_INSERT_BEFORE(listelm, elm, field)
:分别是在链表中指定元素的后方与前方插入元素,listelm是链表中元素、elm是要插入的元素,field是访问时用到的域。LIST_INSERT_HEAD(head, elm, field)
、LIST_INSERT_TAIL(head, elm, field)
:分别是在链表的头部与尾部插入元素elm。LIST_NEXT(elm, field)
:用于访问链表中指定元素的下一个元素。LIST_REMOVE(elm, field)
:用于从链表中移除指定元素
尾队列暂时没有用到所以略过。需要注意的是,这种链表的结构其实非常特殊,不同于普通的链表。以本次的Page
结构体为例,其结构为:
其中pp_link
是由LIST_ENTRY
定义,可以看出LIST_ENTRY
是作为Page
结构体的子结构体使用的,其prev
指针也并不是指向了上一个Page
结构体,而是指向了(指向自身所在的Page
结构体的指针)。
观察代码可以注意到,许多宏都定义为了do{...} while(0)
的形式,定义函数宏有多种方式,例如直接将宏定义为多个语句,或者将宏定义为一个语句块(即由大括号括起的一组语句),也可以像此处一样定义为do-while
的形式。这三种各有优缺点。以SWAP
为例,第一种方式为:
1 #define SWAP(a, b, type) type tmp = *a;\ 2 *a = *b;\ 3 *b = tmp;
第二种方式为:
1 #define SWAP(a, b, type) {type tmp = *a;\ 2 *a = *b;\ 3 *b = tmp;\ 4 }
第三种方式为:
1 #define SWAP(a, b, type) do{\ 2 type tmp = *a;\ 3 *a = *b;\ 4 *b = tmp;\ 5 } while(0)
第一种的优点是方便,但如果跟在一个没有加大括号的if
后边,只有第一个语句属于这个if
,无形之间引入问题。第二种语句块整体的值是最后一个语句的值,相当于一个有返回值的函数宏,然而如果跟在一个没有加大括号的if
后边,如果加了分号(例如if(a != b) SWAP(a, b, int); else ...
),则if
的作用域会被分号提前终结,导致在else
处报错。第三种没有前两者的问题,但整体无返回值,相当于一个返回值为void
的函数宏。
在C语言中,使用宏函数主要的原因应当是出于性能考虑,使用宏而非普通函数,可以避免函数调用过程中的压栈、退栈、保存上下文的过程,可以有效地提高性能。但宏容易产生二义性,使用内联函数可以继承宏函数的优点,同时规避宏函数的不足:
inline
是C++关键字,在函数声明或定义中,函数返回类型前加上关键字inline
,即可以把函数指定为内联函数。这样可以解决一些频繁调用的函数大量消耗栈空间(栈内存)的问题。关键字inline
必须与函数定义放在一起才能使函数成为内联函数,仅仅将inline
放在函数声明前面不起任何作用。inline
是一种“用于实现”的关键字,而不是一种“用于声明”的关键字。
在pmap.h
中定义的函数均为内联函数(后文将提到)。
内存管理相关
内存管理的核心代码位于./mm/pmap.c
、./include/pmap.h
、./include/mmu.h
中,分别包含内存相关操作、物理内存布局与页式内存管理的相关代码。
![](http://img.e-com-net.com/image/info8/2b246dbcfe82488397e9aa432bbc4e31.gif)
1 #ifndef _PMAP_H_ 2 #define _PMAP_H_ 3 4 #include "types.h" 5 #include "queue.h" 6 #include "mmu.h" 7 #include "printf.h" 8 9 10 LIST_HEAD(Page_list, Page); 11 typedef LIST_ENTRY(Page) Page_LIST_entry_t; 12 13 struct Page { 14 Page_LIST_entry_t pp_link; /* free list link */ 15 16 // Ref is the count of pointers (usually in page table entries) 17 // to this page. This only holds for pages allocated using 18 // page_alloc. Pages allocated at boot time using pmap.c's "alloc" 19 // do not have valid reference count fields. 20 21 u_short pp_ref; 22 }; 23 24 extern struct Page *pages; 25 26 static inline u_long 27 page2ppn(struct Page *pp) 28 { 29 return pp - pages; 30 } 31 32 /* Get the physical address of Page 'pp'. 33 */ 34 static inline u_long 35 page2pa(struct Page *pp) 36 { 37 return page2ppn(pp) << PGSHIFT; 38 } 39 40 /* Get the Page struct whose physical address is 'pa'. 41 */ 42 static inline struct Page * 43 pa2page(u_long pa) 44 { 45 if (PPN(pa) >= npage) { 46 panic("pa2page called with invalid pa: %x", pa); 47 } 48 49 return &pages[PPN(pa)]; 50 } 51 52 /* Get the kernel virtual address of Page 'pp'. 53 */ 54 static inline u_long 55 page2kva(struct Page *pp) 56 { 57 return KADDR(page2pa(pp)); 58 } 59 60 /* Transform the virtual address 'va' to physical address. 61 */ 62 static inline u_long 63 va2pa(Pde *pgdir, u_long va) 64 { 65 Pte *p; 66 67 pgdir = &pgdir[PDX(va)]; 68 69 if (!(*pgdir & PTE_V)) { 70 return ~0; 71 } 72 73 p = (Pte *)KADDR(PTE_ADDR(*pgdir)); 74 75 if (!(p[PTX(va)]&PTE_V)) { 76 return ~0; 77 } 78 79 return PTE_ADDR(p[PTX(va)]); 80 } 81 82 /********** functions for memory management(see implementation in mm/pmap.c). ***********/ 83 84 void mips_detect_memory(); 85 86 void mips_vm_init(); 87 88 void mips_init(); 89 void page_init(void); 90 void page_check(); 91 void physical_memory_manage_check(); 92 int page_alloc(struct Page **pp); 93 void page_free(struct Page *pp); 94 void page_decref(struct Page *pp); 95 int pgdir_walk(Pde *pgdir, u_long va, int create, Pte **ppte); 96 int page_insert(Pde *pgdir, struct Page *pp, u_long va, u_int perm); 97 struct Page *page_lookup(Pde *pgdir, u_long va, Pte **ppte); 98 void page_remove(Pde *pgdir, u_long va) ; 99 void tlb_invalidate(Pde *pgdir, u_long va); 100 101 void boot_map_segment(Pde *pgdir, u_long va, u_long size, u_long pa, int perm); 102 103 extern struct Page *pages; 104 105 106 #endif /* _PMAP_H_ */
pmap.h
分为三个部分:变量与数据结构的声明、一些内联函数的定义与pmap.c
中函数的定义。
Page
的结构已经在上一节给出,其pp_link
域链接到其他的Page对象,用于空闲链表中,pp_ref
记录该页面被引用的次数。
pmap.h
中定义的各个函数功能为:
static inline u_long page2ppn(struct Page *pp)
:由页面得到对应的物理页号,因为Page对象在内存中是连续分布的所以可以直接减去首地址得到。static inline u_long page2pa(struct Page *pp)
:由页面得到其对应的物理地址,也就是页大小✖物理页号。static inline struct Page * pa2page(u_long pa)
:由物理地址得到物理页面,是page2pa的逆操作。static inline u_long page2kva(struct Page *pp)
:获得物理页面的内核态虚拟地址,也就是物理地址+ULIM。static inline u_long va2pa(Pde *pgdir, u_long va)
:返回页表pgdir
中虚拟地址va
对应的物理地址。
![](http://img.e-com-net.com/image/info8/2b246dbcfe82488397e9aa432bbc4e31.gif)
1 #ifndef _MMU_H_ 2 #define _MMU_H_ 3 4 5 /* 6 * This file contains: 7 * 8 * Part 1. MIPS definitions. 9 * Part 2. Our conventions. 10 * Part 3. Our helper functions. 11 */ 12 13 /* 14 * Part 1. MIPS definitions. 15 */ 16 #define BY2PG 4096 // bytes to a page 17 #define PDMAP (4*1024*1024) // bytes mapped by a page directory entry 18 #define PGSHIFT 12 19 #define PDSHIFT 22 // log2(PDMAP) 20 #define PDX(va) ((((u_long)(va))>>22) & 0x03FF) 21 #define PTX(va) ((((u_long)(va))>>12) & 0x03FF) 22 #define PTE_ADDR(pte) ((u_long)(pte)&~0xFFF) 23 24 // page number field of address 25 #define PPN(va) (((u_long)(va))>>12) 26 #define VPN(va) PPN(va) 27 28 #define VA2PFN(va) (((u_long)(va)) & 0xFFFFF000 ) // va 2 PFN for EntryLo0/1 29 #define PTE2PT 1024 30 //$#define VA2PDE(va) (((u_long)(va)) & 0xFFC00000 ) // for context 31 32 /* Page Table/Directory Entry flags 33 * these are defined by the hardware 34 */ 35 #define PTE_G 0x0100 // Global bit 36 #define PTE_V 0x0200 // Valid bit 37 #define PTE_R 0x0400 // Dirty bit ,'0' means only read ,otherwise make interrupt 38 #define PTE_D 0x0002 // fileSystem Cached is dirty 39 #define PTE_COW 0x0001 // Copy On Write 40 #define PTE_UC 0x0800 // unCached 41 #define PTE_LIBRARY 0x0004 // share memmory 42 /* 43 * Part 2. Our conventions. 44 */ 45 46 /* 47 o 4G -----------> +----------------------------+------------0x100000000 48 o | ... | kseg3 49 o +----------------------------+------------0xe000 0000 50 o | ... | kseg2 51 o +----------------------------+------------0xc000 0000 52 o | Interrupts & Exception | kseg1 53 o +----------------------------+------------0xa000 0000 54 o | Invalid memory | /|\ 55 o +----------------------------+----|-------Physics Memory Max 56 o | ... | kseg0 57 o VPT,KSTACKTOP-----> +----------------------------+----|-------0x8040 0000-------end 58 o | Kernel Stack | | KSTKSIZE /|\ 59 o +----------------------------+----|------ | 60 o | Kernel Text | | PDMAP 61 o KERNBASE -----> +----------------------------+----|-------0x8001 0000 | 62 o | Interrupts & Exception | \|/ \|/ 63 o ULIM -----> +----------------------------+------------0x8000 0000------- 64 o | User VPT | PDMAP /|\ 65 o UVPT -----> +----------------------------+------------0x7fc0 0000 | 66 o | PAGES | PDMAP | 67 o UPAGES -----> +----------------------------+------------0x7f80 0000 | 68 o | ENVS | PDMAP | 69 o UTOP,UENVS -----> +----------------------------+------------0x7f40 0000 | 70 o UXSTACKTOP -/ | user exception stack | BY2PG | 71 o +----------------------------+------------0x7f3f f000 | 72 o | Invalid memory | BY2PG | 73 o USTACKTOP ----> +----------------------------+------------0x7f3f e000 | 74 o | normal user stack | BY2PG | 75 o +----------------------------+------------0x7f3f d000 | 76 a | | | 77 a ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 78 a . . | 79 a . . kuseg 80 a . . | 81 a |~~~~~~~~~~~~~~~~~~~~~~~~~~~~| | 82 a | | | 83 o UTEXT -----> +----------------------------+ | 84 o | | 2 * PDMAP \|/ 85 a 0 ------------> +----------------------------+ ----------------------------- 86 o 87 */ 88 89 #define KERNBASE 0x80010000 90 91 #define VPT (ULIM + PDMAP ) 92 #define KSTACKTOP (VPT-0x100) 93 #define KSTKSIZE (8*BY2PG) 94 #define ULIM 0x80000000 95 96 #define UVPT (ULIM - PDMAP) 97 #define UPAGES (UVPT - PDMAP) 98 #define UENVS (UPAGES - PDMAP) 99 100 #define UTOP UENVS 101 #define UXSTACKTOP (UTOP) 102 #define TIMESTACK 0x82000000 103 104 #define USTACKTOP (UTOP - 2*BY2PG) 105 #define UTEXT 0x00400000 106 107 108 #define E_UNSPECIFIED 1 // Unspecified or unknown problem 109 #define E_BAD_ENV 2 // Environment doesn't exist or otherwise 110 // cannot be used in requested action 111 #define E_INVAL 3 // Invalid parameter 112 #define E_NO_MEM 4 // Request failed due to memory shortage 113 #define E_NO_FREE_ENV 5 // Attempt to create a new environment beyond 114 // the maximum allowed 115 #define E_IPC_NOT_RECV 6 // Attempt to send to env that is not recving. 116 117 // File system error codes -- only seen in user-level 118 #define E_NO_DISK 7 // No free space left on disk 119 #define E_MAX_OPEN 8 // Too many files are open 120 #define E_NOT_FOUND 9 // File or block not found 121 #define E_BAD_PATH 10 // Bad path 122 #define E_FILE_EXISTS 11 // File already exists 123 #define E_NOT_EXEC 12 // File not a valid executable 124 125 #define MAXERROR 12 126 127 #ifndef __ASSEMBLER__ 128 129 /* 130 * Part 3. Our helper functions. 131 */ 132 #include "types.h" 133 void bcopy(const void *, void *, size_t); 134 void bzero(void *, size_t); 135 136 extern char bootstacktop[], bootstack[]; 137 138 extern u_long npage; 139 140 typedef u_long Pde; 141 typedef u_long Pte; 142 143 extern volatile Pte *vpt[]; 144 extern volatile Pde *vpd[]; 145 146 // translates from kernel virtual address to physical address. 147 #define PADDR(kva) \ 148 ({ \ 149 u_long a = (u_long) (kva); \ 150 if (a < ULIM) \ 151 panic("PADDR called with invalid kva %08lx", a);\ 152 a - ULIM; \ 153 }) 154 155 // translates from physical address to kernel virtual address. 156 #define KADDR(pa) \ 157 ({ \ 158 u_long ppn = PPN(pa); \ 159 if (ppn >= npage) \ 160 panic("KADDR called with invalid pa %08lx", (u_long)pa);\ 161 (pa) + ULIM; \ 162 }) 163 164 #define assert(x) \ 165 do { if (!(x)) panic("assertion failed: %s", #x); } while (0) 166 167 #define TRUP(_p) \ 168 ({ \ 169 register typeof((_p)) __m_p = (_p); \ 170 (u_int) __m_p > ULIM ? (typeof(_p)) ULIM : __m_p; \ 171 }) 172 173 174 extern void tlb_out(u_int entryhi); 175 176 #endif //!__ASSEMBLER__ 177 #endif // !_MMU_H_
mmu.h
同样分为三部分:MIPS中的定义、操作系统规定的内存分布和错误码以及一些实用的函数。
第一部分代码及其解释如下:
1 #define BY2PG 4096 // 页面大小的Byte数,一个页面大小为4kb 2 #define PDMAP (4*1024*1024) // 一个页表管理1024个页面,大小总共4*1024*1024Byte 3 #define PGSHIFT 12 // 页面的偏移位数,4kb对应12位 4 #define PDSHIFT 22 // 页表的偏移位数,同上,即log2(PDMAP) 5 #define PDX(va) ((((u_long)(va))>>22) & 0x03FF) // 取虚拟地址高10位,为页目录号 6 #define PTX(va) ((((u_long)(va))>>12) & 0x03FF) // 取虚拟地址高11~20位,为页表号 7 #define PTE_ADDR(pte) ((u_long)(pte)&~0xFFF) // 页表项取低12位,为页内偏移 8 9 // page number field of address 10 #define PPN(va) (((u_long)(va))>>12) // 物理页号,为虚拟地址偏移12位 11 #define VPN(va) PPN(va) // 虚页号 12 13 #define VA2PFN(va) (((u_long)(va)) & 0xFFFFF000 ) 14 #define PTE2PT 1024 15 //$#define VA2PDE(va) (((u_long)(va)) & 0xFFC00000 ) 16 17 /* Page Table/Directory Entry flags 18 * these are defined by the hardware 19 */ 20 #define PTE_G 0x0100 // 全局位 21 #define PTE_V 0x0200 // 有效位 22 #define PTE_R 0x0400 // 修改位,如果是0表示只对该页面进行过读操作,否则进行过写操作,要引发中断将内容写回内存 23 #define PTE_D 0x0002 // 文件缓存的修改位dirty 24 #define PTE_COW 0x0001 // 写时复制copy on write 25 #define PTE_UC 0x0800 // 未缓存uncached 26 #define PTE_LIBRARY 0x0004 // 共享内存
前数行是对虚拟地址的一些操作,最后是页表项中的一些标记位,通过位运算可以修改或取出标记位。(最后四个标记位此处不用,在文件系统中使用)
第二部分首先是一个内存布局图(见上方完整代码)。从图中可见,内存的低2G空间是用户段,用户代码段并不是从0地址开始,而是在保留了两页之后的地址开始。保留的两页大小是为了向前兼容一些古老的操作系统,它们在运行时要使用这一部分内存。然后从下向上依次是用户栈、不可用区、用户中断时栈、进程控制块、页表、VPT(virtual page table?意义暂时不明)。再以上是内核段。
其余的一些定义为(异常码的解释略去):
1 #define KERNBASE 0x80010000 // 内核基地址 2 3 #define VPT (ULIM + PDMAP ) // 4 #define KSTACKTOP (VPT-0x100) // 内核栈顶 5 #define KSTKSIZE (8*BY2PG) // 内核栈大小 6 #define ULIM 0x80000000 // 用户态地址上限 7 8 #define UVPT (ULIM - PDMAP) // 9 #define UPAGES (UVPT - PDMAP) // 用户页表 10 #define UENVS (UPAGES - PDMAP) // 用户进程控制块 11 12 #define UTOP UENVS // 用户态高地址 13 #define UXSTACKTOP (UTOP) // 用户态异常栈 14 #define TIMESTACK 0x82000000 // 上下文保存栈 15 16 #define USTACKTOP (UTOP - 2*BY2PG) // 用户栈 17 #define UTEXT 0x00400000 // 用户代码段
最后是一些函数与函数宏:
void bcopy(const void *, void *, size_t)
:内存拷贝void bzero(void *, size_t)
:内存清空PADDR(kva)
:内核虚拟地址映射到物理地址(直接清空高位)。KADDR(pa)
:物理地址映射到内核虚拟地址。assert(x)
:支持断言机制。TRUP(_p)
:相当于min(_p, ULIM)
,似乎是为了防止用户读写内核段内存。
pmap.c
代码如下,❗❗长代码段预警❗❗+❗❗剧透预警❗❗
![](http://img.e-com-net.com/image/info8/2b246dbcfe82488397e9aa432bbc4e31.gif)
1 #include "mmu.h" 2 #include "pmap.h" 3 #include "printf.h" 4 #include "env.h" 5 #include "error.h" 6 7 8 /* These variables are set by mips_detect_memory() */ 9 u_long maxpa; /* Maximum physical address */ 10 u_long npage; /* Amount of memory(in pages) */ 11 u_long basemem; /* Amount of base memory(in bytes) */ 12 u_long extmem; /* Amount of extended memory(in bytes) */ 13 14 Pde *boot_pgdir; 15 16 struct Page *pages; 17 static u_long freemem; 18 19 static struct Page_list page_free_list; /* Free list of physical pages */ 20 21 22 /* Overview: 23 Initialize basemem and npage. 24 Set basemem to be 64MB, and calculate corresponding npage value.*/ 25 26 void mips_detect_memory() 27 { 28 /* Step 1: Initialize basemem. 29 * (When use real computer, CMOS tells us how many kilobytes there are). */ 30 maxpa = 0x4000000; 31 basemem = 0x4000000; 32 npage = 0x4000; 33 extmem = 0x0; 34 35 // Step 2: Calculate corresponding npage value. 36 printf("Physical memory: %dK available, ", (int)(maxpa / 1024)); 37 printf("base = %dK, extended = %dK\n", (int)(basemem / 1024), 38 (int)(extmem / 1024)); 39 } 40 41 /* Overview: 42 Allocate `n` bytes physical memory with alignment `align`, if `clear` is set, clear the 43 allocated memory. 44 This allocator is used only while setting up virtual memory system. 45 46 Post-Condition: 47 If we're out of memory, should panic, else return this address of memory we have allocated.*/ 48 static void *alloc(u_int n, u_int align, int clear) 49 { 50 extern char end[]; 51 u_long alloced_mem; 52 53 /* Initialize `freemem` if this is the first time. The first virtual address that the 54 * linker did *not* assign to any kernel code or global variables. */ 55 if (freemem == 0) { 56 freemem = (u_long)end; 57 } 58 59 /* Step 1: Round up `freemem` up to be aligned properly */ 60 freemem = ROUND(freemem, align); 61 62 /* Step 2: Save current value of `freemem` as allocated chunk. */ 63 alloced_mem = freemem; 64 65 /* Step 3: Increase `freemem` to record allocation. */ 66 freemem = freemem + n; 67 68 /* Step 4: Clear allocated chunk if parameter `clear` is set. */ 69 if (clear) { 70 bzero((void *)alloced_mem, n); 71 } 72 73 // We're out of memory, PANIC !! 74 if (PADDR(freemem) >= maxpa) { 75 panic("out of memorty\n"); 76 return (void *)-E_NO_MEM; 77 } 78 79 /* Step 5: return allocated chunk. */ 80 return (void *)alloced_mem; 81 } 82 83 /* Overview: 84 Get the page table entry for virtual address `va` in the given 85 page directory `pgdir`. 86 If the page table is not exist and the parameter `create` is set to 1, 87 then create it.*/ 88 static Pte *boot_pgdir_walk(Pde *pgdir, u_long va, int create) 89 { 90 91 Pde *pgdir_entryp; 92 Pte *pgtable, *pgtable_entry; 93 94 /* Step 1: Get the corresponding page directory entry and page table. */ 95 /* Hint: Use KADDR and PTE_ADDR to get the page table from page directory 96 * entry value. */ 97 pgdir_entryp = pgdir + PDX(va); 98 99 /* Step 2: If the corresponding page table is not exist and parameter `create` 100 * is set, create one. And set the correct permission bits for this new page 101 * table. */ 102 if ((*pgdir_entryp & PTE_V) == 0) { 103 if (create) { 104 *pgdir_entryp = PADDR(alloc(BY2PG, BY2PG, 1)); 105 *pgdir_entryp = *pgdir_entryp | PTE_V | PTE_R; 106 } else { 107 return 0; 108 } 109 } 110 pgtable = (Pte*) KADDR(PTE_ADDR(*pgdir_entryp)); 111 112 /* Step 3: Get the page table entry for `va`, and return it. */ 113 pgtable_entry = pgtable + PTX(va); 114 return pgtable_entry; 115 } 116 117 /*Overview: 118 Map [va, va+size) of virtual address space to physical [pa, pa+size) in the page 119 table rooted at pgdir. 120 Use permission bits `perm|PTE_V` for the entries. 121 Use permission bits `perm` for the entries. 122 123 Pre-Condition: 124 Size is a multiple of BY2PG.*/ 125 void boot_map_segment(Pde *pgdir, u_long va, u_long size, u_long pa, int perm) 126 { 127 int i, va_temp; 128 u_long rsize; 129 u_int PERM; 130 Pte *pgtable_entry; 131 132 PERM = perm | PTE_V; 133 134 /* Step 1: Check if `size` is a multiple of BY2PG. */ 135 rsize = ROUND(size, BY2PG); 136 137 /* Step 2: Map virtual address space to physical address. */ 138 /* Hint: Use `boot_pgdir_walk` to get the page table entry of virtual address `va`. */ 139 for (i = 0; i < rsize; i = i + BY2PG) { 140 pgtable_entry = boot_pgdir_walk(pgdir, va+i, 1); 141 *pgtable_entry = PTE_ADDR(pa) | PERM; 142 } 143 144 } 145 146 /* Overview: 147 Set up two-level page table. 148 149 Hint: 150 You can get more details about `UPAGES` and `UENVS` in include/mmu.h. */ 151 void mips_vm_init() 152 { 153 extern char end[]; 154 extern int mCONTEXT; 155 extern struct Env *envs; 156 157 Pde *pgdir; 158 u_int n; 159 160 /* Step 1: Allocate a page for page directory(first level page table). */ 161 pgdir = alloc(BY2PG, BY2PG, 1); 162 printf("to memory %x for struct page directory.\n", freemem); 163 mCONTEXT = (int)pgdir; 164 165 boot_pgdir = pgdir; 166 167 /* Step 2: Allocate proper size of physical memory for global array `pages`, 168 * for physical memory management. Then, map virtual address `UPAGES` to 169 * physical address `pages` allocated before. For consideration of alignment, 170 * you should round up the memory size before map. */ 171 pages = (struct Page *)alloc(npage * sizeof(struct Page), BY2PG, 1); 172 printf("to memory %x for struct Pages.\n", freemem); 173 n = ROUND(npage * sizeof(struct Page), BY2PG); 174 boot_map_segment(pgdir, UPAGES, n, PADDR(pages), PTE_R); 175 176 /* Step 3, Allocate proper size of physical memory for global array `envs`, 177 * for process management. Then map the physical address to `UENVS`. */ 178 envs = (struct Env *)alloc(NENV * sizeof(struct Env), BY2PG, 1); 179 n = ROUND(NENV * sizeof(struct Env), BY2PG); 180 boot_map_segment(pgdir, UENVS, n, PADDR(envs), PTE_R); 181 182 printf("pmap.c:\t mips vm init success\n"); 183 } 184 185 /*Overview: 186 Initialize page structure and memory free list. 187 The `pages` array has one `struct Page` entry per physical page. Pages 188 are reference counted, and free pages are kept on a linked list. 189 Hint: 190 Use `LIST_INSERT_HEAD` to insert something to list.*/ 191 void 192 page_init(void) 193 { 194 struct Page* page; 195 /* Step 1: Initialize page_free_list. */ 196 /* Hint: Use macro `LIST_INIT` defined in include/queue.h. */ 197 LIST_INIT(&page_free_list); 198 199 /* Step 2: Align `freemem` up to multiple of BY2PG. */ 200 freemem = ROUND(freemem, BY2PG); 201 202 /* Step 3: Mark all memory below `freemem` as used(set `pp_ref` 203 * filed to 1) */ 204 for (page = pages; page2kva(page) < freemem; page++) { 205 page -> pp_ref = 1; 206 } 207 208 /* Step 4: Mark the other memory as free. */ 209 for (page = (pages + PPN(PADDR(freemem))); page2ppn(page) < npage; page++) { 210 page -> pp_ref = 0; 211 LIST_INSERT_HEAD(&page_free_list, page, pp_link); 212 } 213 } 214 215 /*Overview: 216 Allocates a physical page from free memory, and clear this page. 217 218 Post-Condition: 219 If failed to allocate a new page(out of memory(there's no free page)), 220 return -E_NO_MEM. 221 Else, set the address of allocated page to *pp, and returned 0. 222 223 Note: 224 Does NOT increment the reference count of the page - the caller must do 225 these if necessary (either explicitly or via page_insert). 226 227 Hint: 228 Use LIST_FIRST and LIST_REMOVE defined in include/queue.h .*/ 229 int 230 page_alloc(struct Page **pp) 231 { 232 struct Page *ppage_temp; 233 234 /* Step 1: Get a page from free memory. If fails, return the error code.*/ 235 if (LIST_EMPTY(&page_free_list)) { 236 return -E_NO_MEM; 237 } 238 ppage_temp = LIST_FIRST(&page_free_list); 239 LIST_REMOVE(ppage_temp, pp_link); 240 241 /* Step 2: Initialize this page. 242 * Hint: use `bzero`. */ 243 bzero(page2kva(ppage_temp), BY2PG); 244 *pp = ppage_temp; 245 return 0; 246 } 247 248 /*Overview: 249 Release a page, mark it as free if it's `pp_ref` reaches 0. 250 Hint: 251 When to free a page, just insert it to the page_free_list.*/ 252 void 253 page_free(struct Page *pp) 254 { 255 /* Step 1: If there's still virtual address refers to this page, do nothing. */ 256 if (pp -> pp_ref > 0) { 257 return; 258 } 259 260 /* Step 2: If the `pp_ref` reaches to 0, mark this page as free and return. */ 261 if (pp -> pp_ref == 0) { 262 LIST_INSERT_HEAD(&page_free_list, pp, pp_link); 263 return; 264 } 265 266 /* If the value of `pp_ref` less than 0, some error must occurred before, 267 * so PANIC !!! */ 268 panic("cgh:pp->pp_ref is less than zero\n"); 269 } 270 271 /*Overview: 272 Given `pgdir`, a pointer to a page directory, pgdir_walk returns a pointer 273 to the page table entry (with permission PTE_R|PTE_V) for virtual address 'va'. 274 275 Pre-Condition: 276 The `pgdir` should be two-level page table structure. 277 278 Post-Condition: 279 If we're out of memory, return -E_NO_MEM. 280 Else, we get the page table entry successfully, store the value of page table 281 entry to *ppte, and return 0, indicating success. 282 283 Hint: 284 We use a two-level pointer to store page table entry and return a state code to indicate 285 whether this function execute successfully or not. 286 This function have something in common with function `boot_pgdir_walk`.*/ 287 int 288 pgdir_walk(Pde *pgdir, u_long va, int create, Pte **ppte) 289 { 290 Pde *pgdir_entryp; 291 Pte *pgtable; 292 struct Page *ppage; 293 294 /* Step 1: Get the corresponding page directory entry and page table. */ 295 pgdir_entryp = pgdir + PDX(va); 296 297 /* Step 2: If the corresponding page table is not exist(valid) and parameter `create` 298 * is set, create one. And set the correct permission bits for this new page 299 * table. 300 * When creating new page table, maybe out of memory. */ 301 if ((*pgdir_entryp & PTE_V) == 0) { 302 if (create) { 303 if (page_alloc(&ppage) == -E_NO_MEM) { 304 return -E_NO_MEM; 305 } else { 306 *pgdir_entryp = page2pa(ppage); 307 *pgdir_entryp = *pgdir_entryp | PTE_V | PTE_R; 308 ppage->pp_ref++; 309 } 310 } else { 311 *ppte = 0; 312 return 0; 313 } 314 } 315 pgtable = (Pte*) KADDR(PTE_ADDR(*pgdir_entryp)); 316 317 /* Step 3: Set the page table entry to `*ppte` as return value. */ 318 *ppte = pgtable + PTX(va); 319 320 return 0; 321 } 322 323 /*Overview: 324 Map the physical page 'pp' at virtual address 'va'. 325 The permissions (the low 12 bits) of the page table entry should be set to 'perm|PTE_V'. 326 327 Post-Condition: 328 Return 0 on success 329 Return -E_NO_MEM, if page table couldn't be allocated 330 331 Hint: 332 If there is already a page mapped at `va`, call page_remove() to release this mapping. 333 The `pp_ref` should be incremented if the insertion succeeds.*/ 334 int 335 page_insert(Pde *pgdir, struct Page *pp, u_long va, u_int perm) 336 { 337 u_int PERM; 338 Pte *pgtable_entry; 339 PERM = perm | PTE_V; 340 341 /* Step 1: Get corresponding page table entry. */ 342 pgdir_walk(pgdir, va, 0, &pgtable_entry); 343 344 if (pgtable_entry != 0 && (*pgtable_entry & PTE_V) != 0) { 345 if (pa2page(*pgtable_entry) != pp) { 346 page_remove(pgdir, va); 347 } else { 348 tlb_invalidate(pgdir, va); 349 *pgtable_entry = page2pa(pp) | PERM; 350 return 0; 351 } 352 } 353 354 /* Step 2: Update TLB. */ 355 356 /* hint: use tlb_invalidate function */ 357 tlb_invalidate(pgdir, va); 358 359 360 /* Step 3: Do check, re-get page table entry to validate the insertion. */ 361 362 /* Step 3.1 Check if the page can be insert, if can’t return -E_NO_MEM */ 363 if (pgdir_walk(pgdir, va, 1, &pgtable_entry) != 0) { 364 return -E_NO_MEM; 365 } 366 367 /* Step 3.2 Insert page and increment the pp_ref */ 368 *pgtable_entry = page2pa(pp) | PERM; 369 pp->pp_ref = pp->pp_ref + 1; 370 return 0; 371 } 372 373 /*Overview: 374 Look up the Page that virtual address `va` map to. 375 376 Post-Condition: 377 Return a pointer to corresponding Page, and store it's page table entry to *ppte. 378 If `va` doesn't mapped to any Page, return NULL.*/ 379 struct Page * 380 page_lookup(Pde *pgdir, u_long va, Pte **ppte) 381 { 382 struct Page *ppage; 383 Pte *pte; 384 385 /* Step 1: Get the page table entry. */ 386 pgdir_walk(pgdir, va, 0, &pte); 387 388 /* Hint: Check if the page table entry doesn't exist or is not valid. */ 389 if (pte == 0) { 390 return 0; 391 } 392 if ((*pte & PTE_V) == 0) { 393 return 0; //the page is not in memory. 394 } 395 396 /* Step 2: Get the corresponding Page struct. */ 397 398 /* Hint: Use function `pa2page`, defined in include/pmap.h . */ 399 ppage = pa2page(*pte); 400 if (ppte) { 401 *ppte = pte; 402 } 403 404 return ppage; 405 } 406 407 // Overview: 408 // Decrease the `pp_ref` value of Page `*pp`, if `pp_ref` reaches to 0, free this page. 409 void page_decref(struct Page *pp) { 410 if(--pp->pp_ref == 0) { 411 page_free(pp); 412 } 413 } 414 415 // Overview: 416 // Unmaps the physical page at virtual address `va`. 417 void 418 page_remove(Pde *pgdir, u_long va) 419 { 420 Pte *pagetable_entry; 421 struct Page *ppage; 422 423 /* Step 1: Get the page table entry, and check if the page table entry is valid. */ 424 ppage = page_lookup(pgdir, va, &pagetable_entry); 425 426 if (ppage == 0) { 427 return; 428 } 429 430 /* Step 2: Decrease `pp_ref` and decide if it's necessary to free this page. */ 431 432 /* Hint: When there's no virtual address mapped to this page, release it. */ 433 ppage->pp_ref--; 434 if (ppage->pp_ref == 0) { 435 page_free(ppage); 436 } 437 438 /* Step 3: Update TLB. */ 439 *pagetable_entry = 0; 440 tlb_invalidate(pgdir, va); 441 return; 442 } 443 444 // Overview: 445 // Update TLB. 446 void 447 tlb_invalidate(Pde *pgdir, u_long va) 448 { 449 if (curenv) { 450 tlb_out(PTE_ADDR(va) | GET_ENV_ASID(curenv->env_id)); 451 } else { 452 tlb_out(PTE_ADDR(va)); 453 } 454 } 455 456 void 457 physical_memory_manage_check(void) 458 { 459 struct Page *pp, *pp0, *pp1, *pp2; 460 struct Page_list fl; 461 int *temp; 462 463 // should be able to allocate three pages 464 pp0 = pp1 = pp2 = 0; 465 assert(page_alloc(&pp0) == 0); 466 assert(page_alloc(&pp1) == 0); 467 assert(page_alloc(&pp2) == 0); 468 469 assert(pp0); 470 assert(pp1 && pp1 != pp0); 471 assert(pp2 && pp2 != pp1 && pp2 != pp0); 472 473 474 475 // temporarily steal the rest of the free pages 476 fl = page_free_list; 477 // now this page_free list must be empty!!!! 478 LIST_INIT(&page_free_list); 479 // should be no free memory 480 assert(page_alloc(&pp) == -E_NO_MEM); 481 482 temp = (int*)page2kva(pp0); 483 //write 1000 to pp0 484 *temp = 1000; 485 // free pp0 486 page_free(pp0); 487 printf("The number in address temp is %d\n",*temp); 488 489 // alloc again 490 assert(page_alloc(&pp0) == 0); 491 assert(pp0); 492 493 // pp0 should not change 494 assert(temp == (int*)page2kva(pp0)); 495 // pp0 should be zero 496 assert(*temp == 0); 497 498 page_free_list = fl; 499 page_free(pp0); 500 page_free(pp1); 501 page_free(pp2); 502 struct Page_list test_free; 503 struct Page *test_pages; 504 test_pages= (struct Page *)alloc(10 * sizeof(struct Page), BY2PG, 1); 505 LIST_INIT(&test_free); 506 //LIST_FIRST(&test_free) = &test_pages[0]; 507 int i,j=0; 508 struct Page *p, *q; 509 //test inert tail 510 for(i=0;i<10;i++) { 511 test_pages[i].pp_ref=i; 512 //test_pages[i].pp_link=NULL; 513 //printf("0x%x 0x%x\n",&test_pages[i], test_pages[i].pp_link.le_next); 514 LIST_INSERT_TAIL(&test_free,&test_pages[i],pp_link); 515 //printf("0x%x 0x%x\n",&test_pages[i], test_pages[i].pp_link.le_next); 516 } 517 p = LIST_FIRST(&test_free); 518 int answer1[]={0,1,2,3,4,5,6,7,8,9}; 519 assert(p!=NULL); 520 while(p!=NULL) 521 { 522 //printf("%d %d\n",p->pp_ref,answer1[j]); 523 assert(p->pp_ref==answer1[j++]); 524 //printf("ptr: 0x%x v: %d\n",(p->pp_link).le_next,((p->pp_link).le_next)->pp_ref); 525 p=LIST_NEXT(p,pp_link); 526 527 } 528 // insert_after test 529 int answer2[]={0,1,2,3,4,20,5,6,7,8,9}; 530 q=(struct Page *)alloc(sizeof(struct Page), BY2PG, 1); 531 q->pp_ref = 20; 532 533 //printf("---%d\n",test_pages[4].pp_ref); 534 LIST_INSERT_AFTER(&test_pages[4], q, pp_link); 535 //printf("---%d\n",LIST_NEXT(&test_pages[4],pp_link)->pp_ref); 536 p = LIST_FIRST(&test_free); 537 j=0; 538 //printf("into test\n"); 539 while(p!=NULL){ 540 // printf("%d %d\n",p->pp_ref,answer2[j]); 541 assert(p->pp_ref==answer2[j++]); 542 p=LIST_NEXT(p,pp_link); 543 } 544 545 546 547 printf("physical_memory_manage_check() succeeded\n"); 548 } 549 550 551 void 552 page_check(void) 553 { 554 struct Page *pp, *pp0, *pp1, *pp2; 555 struct Page_list fl; 556 557 // should be able to allocate three pages 558 pp0 = pp1 = pp2 = 0; 559 assert(page_alloc(&pp0) == 0); 560 assert(page_alloc(&pp1) == 0); 561 assert(page_alloc(&pp2) == 0); 562 563 assert(pp0); 564 assert(pp1 && pp1 != pp0); 565 assert(pp2 && pp2 != pp1 && pp2 != pp0); 566 567 // temporarily steal the rest of the free pages 568 fl = page_free_list; 569 // now this page_free list must be empty!!!! 570 LIST_INIT(&page_free_list); 571 572 // should be no free memory 573 assert(page_alloc(&pp) == -E_NO_MEM); 574 575 // there is no free memory, so we can't allocate a page table 576 assert(page_insert(boot_pgdir, pp1, 0x0, 0) < 0); 577 578 // free pp0 and try again: pp0 should be used for page table 579 page_free(pp0); 580 assert(page_insert(boot_pgdir, pp1, 0x0, 0) == 0); 581 assert(PTE_ADDR(boot_pgdir[0]) == page2pa(pp0)); 582 583 printf("va2pa(boot_pgdir, 0x0) is %x\n",va2pa(boot_pgdir, 0x0)); 584 printf("page2pa(pp1) is %x\n",page2pa(pp1)); 585 586 assert(va2pa(boot_pgdir, 0x0) == page2pa(pp1)); 587 assert(pp1->pp_ref == 1); 588 589 // should be able to map pp2 at BY2PG because pp0 is already allocated for page table 590 assert(page_insert(boot_pgdir, pp2, BY2PG, 0) == 0); 591 assert(va2pa(boot_pgdir, BY2PG) == page2pa(pp2)); 592 assert(pp2->pp_ref == 1); 593 594 // should be no free memory 595 assert(page_alloc(&pp) == -E_NO_MEM); 596 597 printf("start page_insert\n"); 598 // should be able to map pp2 at BY2PG because it's already there 599 assert(page_insert(boot_pgdir, pp2, BY2PG, 0) == 0); 600 assert(va2pa(boot_pgdir, BY2PG) == page2pa(pp2)); 601 assert(pp2->pp_ref == 1); 602 603 // pp2 should NOT be on the free list 604 // could happen in ref counts are handled sloppily in page_insert 605 assert(page_alloc(&pp) == -E_NO_MEM); 606 607 // should not be able to map at PDMAP because need free page for page table 608 assert(page_insert(boot_pgdir, pp0, PDMAP, 0) < 0); 609 610 // insert pp1 at BY2PG (replacing pp2) 611 assert(page_insert(boot_pgdir, pp1, BY2PG, 0) == 0); 612 613 // should have pp1 at both 0 and BY2PG, pp2 nowhere, ... 614 assert(va2pa(boot_pgdir, 0x0) == page2pa(pp1)); 615 assert(va2pa(boot_pgdir, BY2PG) == page2pa(pp1)); 616 // ... and ref counts should reflect this 617 assert(pp1->pp_ref == 2); 618 printf("pp2->pp_ref %d\n",pp2->pp_ref); 619 assert(pp2->pp_ref == 0); 620 printf("end page_insert\n"); 621 622 // pp2 should be returned by page_alloc 623 assert(page_alloc(&pp) == 0 && pp == pp2); 624 625 // unmapping pp1 at 0 should keep pp1 at BY2PG 626 page_remove(boot_pgdir, 0x0); 627 assert(va2pa(boot_pgdir, 0x0) == ~0); 628 assert(va2pa(boot_pgdir, BY2PG) == page2pa(pp1)); 629 assert(pp1->pp_ref == 1); 630 assert(pp2->pp_ref == 0); 631 632 // unmapping pp1 at BY2PG should free it 633 page_remove(boot_pgdir, BY2PG); 634 assert(va2pa(boot_pgdir, 0x0) == ~0); 635 assert(va2pa(boot_pgdir, BY2PG) == ~0); 636 assert(pp1->pp_ref == 0); 637 assert(pp2->pp_ref == 0); 638 639 // so it should be returned by page_alloc 640 assert(page_alloc(&pp) == 0 && pp == pp1); 641 642 // should be no free memory 643 assert(page_alloc(&pp) == -E_NO_MEM); 644 645 // forcibly take pp0 back 646 assert(PTE_ADDR(boot_pgdir[0]) == page2pa(pp0)); 647 boot_pgdir[0] = 0; 648 assert(pp0->pp_ref == 1); 649 pp0->pp_ref = 0; 650 651 // give free list back 652 page_free_list = fl; 653 654 // free the pages we took 655 page_free(pp0); 656 page_free(pp1); 657 page_free(pp2); 658 659 printf("page_check() succeeded!\n"); 660 } 661 662 void pageout(int va, int context) 663 { 664 u_long r; 665 struct Page *p = NULL; 666 667 if (context < 0x80000000) { 668 panic("tlb refill and alloc error!"); 669 } 670 671 if ((va > 0x7f400000) && (va < 0x7f800000)) { 672 panic(">>>>>>>>>>>>>>>>>>>>>>it's env's zone"); 673 } 674 675 if (va < 0x10000) { 676 panic("^^^^^^TOO LOW^^^^^^^^^"); 677 } 678 679 if ((r = page_alloc(&p)) < 0) { 680 panic ("page alloc error!"); 681 } 682 683 p->pp_ref++; 684 685 page_insert((Pde *)context, p, VA2PFN(va), PTE_R); 686 printf("pageout:\t@@@___0x%x___@@@ ins a page \n", va); 687 }
在进行内存初始化时,mips_detect_memory()
、mips_vm_init()
与page_init()
被依次调用。mips_detect_memory()
用来初始化一些全局变量(此处将物理内存大小设置为64MB,在实际中,内存大小是由硬件得到的,这里只是模拟了检测物理内存大小这个过程)。其余的函数的功能为:
static void *alloc(u_int n, u_int align, int clear)
:申请一块内存,返回首地址。static Pte *boot_pgdir_walk(Pde *pgdir, u_long va, int create)
:从页目录项中找出虚拟地址va
对应的页表项,若create
置位,则不存在时创建。void boot_map_segment(Pde *pgdir, u_long va, u_long size, u_long pa, int perm)
:将虚拟地址va
映射到物理地址pa
。void mips_vm_init()
:创建一个二级页表。void page_init(void)
:将内存分页并初始化空闲页表。int page_alloc(struct Page **pp)
:分配一页内存并把值赋给pp。void page_free(struct Page *pp)
:释放一页内存。int pgdir_walk(Pde *pgdir, u_long va, int create, Pte **ppte)
:建立起二级页表结构后从页目录中找到va对应页表项的函数。int page_insert(Pde *pgdir, struct Page *pp, u_long va, u_int perm)
:将物理页pp映射到va。struct Page * page_lookup(Pde *pgdir, u_long va, Pte **ppte)
:找到虚拟地址va对应的物理页面。void page_decref(struct Page *pp)
:降低物理页面的引用次数,降到0后释放页面。void page_remove(Pde *pgdir, u_long va)
:释放虚拟地址va对应的页面。void tlb_invalidate(Pde *pgdir, u_long va)
:更新TLB。
最后一个函数通过调用tlb_asm.S
中的汇编函数实现TLB的更新。
1 #include2 #include 3 #include 4 5 LEAF(tlb_out) 6 //1: j 1b 7 nop 8 mfc0 k1,CP0_ENTRYHI 9 mtc0 a0,CP0_ENTRYHI 10 nop 11 tlbp 12 nop 13 nop 14 nop 15 nop 16 mfc0 k0,CP0_INDEX 17 bltz k0,NOFOUND 18 nop 19 mtc0 zero,CP0_ENTRYHI 20 mtc0 zero,CP0_ENTRYLO0 21 nop 22 tlbwi 23 NOFOUND: 24 mtc0 k1,CP0_ENTRYHI 25 j ra 26 nop 27 END(tlb_out)
大概就是看一下TLB命不命中,若命中则写TLB,若不命中则直接返回。TLBP
与TLBWI
分别用于查询TLB中的项与写TLB。