lab2
特权级管理
linux和μcore只使用0和3
段选择子
DPL 段描述符 中描述特权 门
RPL段寄存器 DS ES FS GS(寄存器最低两位)
CPL 段寄存器 CS SS (寄存器最低两位)
操作系统对于特权的合法性检查
访问门时:CPL<=DPL[门]&CPL>=DPL[段](低优先级可以访问高优先级的代码)
访问段时:MAX(CPL,RPL)<=DPL[段](发出请求的一方优先级高于目标)
特权级切换
通过中断实现特权级转换
TSS(任务状态段)存储信息
TSS 全称task state segment,是指在操作系统进程管理的过程中,任务(进程)切换时的任务现场信息。
分配tss内存->初始化tss->将ts描述符填写入GDT中->设置Tss选择子
MMU 内存管理单元
段描述符:基址与限制
段选择子里的隐藏部分:基址一直存放在隐藏部分,知道选择子发生变化,才会更新基址
μcore映射两次:页将虚拟地址0xC0000000映射到物理0地址
段时又将其映射回去
页机制概述
二级页表:pde pte
页表项:
### 使能页机制:
设置cr0的第31位置1
建立页表
地址映射主要是页机制,段机制主要是用于安全管理方面
练习
1
主要是实现了一个ffma算法,这里利用的是链表结构维护一个空闲页面链表(双向),需要注意很多标志位的东西
typedef struct {
list_entry_t free_list; // the list header
unsigned int nr_free; // # of free pages in this free list
} free_area_t;
free_area结构
初始化函数就是将空闲链表初始化,指针指向自己,空闲内存页数目置零。
static void
default_init(void) {
list_init(&free_list);
nr_free = 0;
}
初始化整个内存空间
static void
default_init_memmap(struct Page *base, size_t n) {
assert(n > 0);
struct Page *p = base;
for (; p != base + n; p ++) {
assert(PageReserved(p));
p->flags = p->property = 0;
set_page_ref(p, 0);
}
base->property = n;
SetPageProperty(base);
nr_free += n;
list_add_before(&free_list, &(base->page_link));
}
这里需要看一下page结构:(这里的page结构只是页描述符)
struct Page {
int ref; // page frame's reference counter
uint32_t flags; // array of flags that describe the status of the page frame
unsigned int property; // the num of free block, used in first fit pm manager
list_entry_t page_link; // free list link
};
ref就是有多少个link指向它,flag用于存储一些标志位,property就是这个空闲块里面有多少空闲页,而后的就是一个指针,用于组织链表。
/* Flags describing the status of a page frame */
#define PG_reserved 0 // the page descriptor is reserved for kernel or unusable
#define PG_property 1 // the member 'property' is valid
flag信息
property意思是描述符中的property是否被使用。
这里的话因为结构体以及真实的页存储在不同的物理空间内,而且页描述符结构体中也没有记录页地址的数据项。不过好在初始化的内存空间是连续的,结构体数组也是同样连续的,所以直接通过下标访问即可达到描述符以及真实页面的映射。理解了这个的话后续的操作就简单了。
初始化内存空间:
static void
default_init_memmap(struct Page *base, size_t n) {
assert(n > 0);
struct Page *p = base;
for (; p != base + n; p ++) {
assert(PageReserved(p));
p->flags = p->property = 0;
set_page_ref(p, 0);
}
base->property = n;
SetPageProperty(base);
nr_free += n;
list_add_before(&free_list, &(base->page_link));
}
操作过程代码表现的很直观了,这里不做赘述。
alloc:
static struct Page *
default_alloc_pages(size_t n) {
assert(n > 0);
if (n > nr_free) {
return NULL;
}
struct Page *page = NULL;
list_entry_t *le = &free_list;
// TODO: optimize (next-fit)
while ((le = list_next(le)) != &free_list) {
struct Page *p = le2page(le, page_link);
if (p->property >= n) {
page = p;
break;
}
}
if (page != NULL) {
if (page->property > n) {
struct Page *p = page + n;
p->property = page->property - n;
SetPageProperty(p);
list_add_after(&(page->page_link), &(p->page_link));
}
list_del(&(page->page_link));
nr_free -= n;
ClearPageProperty(page);
}
return page;
}
由于是双向链表,利用的算法是ffma,所以第一个满足大小的内存块就能被直接alloc,只需比较需要的n以及property。alloc前还需要考虑是否需要分块操作,分块后的内存加入freelist中,只需要注意一些page结构体内部的一些标志位就可以了。
free:
static void
default_free_pages(struct Page *base, size_t n) {
assert(n > 0);
struct Page *p = base;
for (; p != base + n; p ++) {
assert(!PageReserved(p) && !PageProperty(p));
p->flags = 0;
set_page_ref(p, 0);
}
base->property = n;
SetPageProperty(base);
list_entry_t *le = list_next(&free_list);
while (le != &free_list) {
p = le2page(le, page_link);
le = list_next(le);
// TODO: optimize
if (base + base->property == p) {
base->property += p->property;
ClearPageProperty(p);
list_del(&(p->page_link));
}
else if (p + p->property == base) {
p->property += base->property;
ClearPageProperty(base);
base = p;
list_del(&(p->page_link));
}
}
nr_free += n;
le = list_next(&free_list);
while (le != &free_list) {
p = le2page(le, page_link);
if (base + base->property <= p) {
assert(base + base->property != p);
break;
}
else if(p + p->property <= base)
{
assert(p + p->property != base);
}
le = list_next(le);
}
list_add_before(le, &(base->page_link));
}
和alloc操作逆过程类似,但是释放的时候需要考虑向前合并以及向后合并的过程,同样需要注意的也是page描述符中的数据项填写,代码本身很好理解,但是最后的内存释放成功判断有些出入
while (le != &free_list) {
p = le2page(le, page_link);
if (base + base->property <= p) {
assert(base + base->property != p);
break;
}
/*******/
else if(p + p->property <= base)
{
assert(p + p->property != base);
}
/*******/
le = list_next(le);
}
这里就是内存释放后的检测代码,两段注释间为本人后续添加的代码,因为个人觉得答案中代码有些问题,因为在答案代码中,只是检查了释放内存块的尾部与freelist中各个free内存的头部地址大小比较,用于判断是否出现了未合并情况,但是这个检查不够完全,忽视了freelist中的别的块与释放内存块的头部的比较,而且不理解为何会判断了后就要直接break,可能原先的代码逻辑已经将另外的情况排除,但个人觉得检查还是充分较好。简而言之,要确保合法的内存释放后应该不存在重叠以及未合并现象。
2
练习2主要就是二级页表中页表项的获取:
pte_t *
get_pte(pde_t *pgdir, uintptr_t la, bool create) {
/* LAB2 EXERCISE 2: YOUR CODE
*
* If you need to visit a physical address, please use KADDR()
* please read pmm.h for useful macros
*
* Maybe you want help comment, BELOW comments can help you finish the code
*
* Some Useful MACROs and DEFINEs, you can use them in below implementation.
* MACROs or Functions:
* PDX(la) = the index of page directory entry of VIRTUAL ADDRESS la.
* KADDR(pa) : takes a physical address and returns the corresponding kernel virtual address.
* set_page_ref(page,1) : means the page be referenced by one time
* page2pa(page): get the physical address of memory which this (struct Page *) page manages
* struct Page * alloc_page() : allocation a page
* memset(void *s, char c, size_t n) : sets the first n bytes of the memory area pointed by s
* to the specified value c.
* DEFINEs:
* PTE_P 0x001 // page table/directory entry flags bit : Present
* PTE_W 0x002 // page table/directory entry flags bit : Writeable
* PTE_U 0x004 // page table/directory entry flags bit : User can access
*/
pde_t *pdep = &pgdir[PDX(la)]; // (1) find page directory entry
struct Page * p;
if (!(*pdep & PTE_P)) { // (2) check if entry is not present
if(! create){
p = alloc_page();
} // (3) check if creating is needed, then alloc page for page table
// CAUTION: this page is used for page table, not for common data page
set_page_ref(p,1); // (4) set page reference
uintptr_t pa = page2pa(p); // (5) get linear address of page
memset(KADDR(pa),0,PGSIZE) ; // (6) clear page content using memset
*pdep = pa | PTE_U | PTE_W | PTE_P;//set flag
}
return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)];//addr in page table or page directory entry[pagetable index] -> to kernal va
}
主要难点就是二级页表这个东西比较抽象,而且变量名的缩写比较容易混杂,根据提示基本可以写出,但是需要注意数据类型以及各个宏(macro)的用法。
3
页表项的删除,和2差不多,看着提示就基本写出来了。
static inline void
page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) {
/* LAB2 EXERCISE 3: YOUR CODE
*
* Please check if ptep is valid, and tlb must be manually updated if mapping is updated
*
* Maybe you want help comment, BELOW comments can help you finish the code
*
* Some Useful MACROs and DEFINEs, you can use them in below implementation.
* MACROs or Functions:
* struct Page *page pte2page(*ptep): get the according page from the value of a ptep
* free_page : free a page
* page_ref_dec(page) : decrease page->ref. NOTICE: ff page->ref == 0 , then this page should be free.
* tlb_invalidate(pde_t *pgdir, uintptr_t la) : Invalidate a TLB entry, but only if the page tables being
* edited are the ones currently in use by the processor.
* DEFINEs:
* PTE_P 0x001 // page table/directory entry flags bit : Present
*/
#if 0
if (0) { //(1) check if this page table entry is present
struct Page *page = NULL; //(2) find corresponding page to pte
//(3) decrease page reference
//(4) and free this page when page reference reachs 0
//(5) clear second page table entry
//(6) flush tlb
}
#endif
if( *ptep & PTE_P )
{
struct Page * p = pte2page(*ptep);
page_ref_dec(p);
if(p->ref == 0)
free_page(p);
*ptep = 0;
tlb_invalidate(pgdir, la);
}
}