在之前的项目中,内存办理运用的是段式存储的方法,而这次将会完成页式存储。
布景常识
页式存储是内存办理的重点,也是其间运用更为广泛的方法。
接下来咱们先从理论层面了解一下它的机制。内容摘录自操作系统理论课课程材料。
咱们先来看一张图:
- 内存地址空间被划分为固定大小的很多页,比方红框框所示的内容便是一页,容纳它的空间便是一个页框。
- 左边能够看做是物理地址,右边则是映射出的虚拟地址,中心起到映射这两者关系的表格称为页表。
- 页表实际上是一个索引表,每一行中,key值代表物理页号,value值代表逻辑页框号。知道了页号,就能知道对应的页框号,就能找到相应映射的方位。
关于映射的细节,咱们再接着看下两张图:
从中咱们能够看出,对于单个地址,物理地址和逻辑地址的页内偏移量是共同的,但是页号和页框号不共同。
当然,为了加速查找,还能够再加入一个快表,它实际上是一个cache。
项目要求
接下来,让咱们逐条拆解项目要求并完成。
运用页表
Init_VM()
- 使内存仅能从内核模式拜访;创建页表目录及页表实体
以下是Init_VM()中的提示:
/*
* Hints:
* - Build kernel page directory and page tables
* - Call Enable_Paging() with the kernel page directory
* - Install an interrupt handler for interrupt 14,
* page fault
* - Do not map a page at address 0; this will help trap
* null pointer references
*/
由于更多细节是在文档的后续内容中描述的,凭仗现有信息暂时只能写出如下结构代码(详细细节用伪代码符号替代了,后续弥补)
void Init_VM(struct Boot_Info *bootInfo)
{
// TODO("Build initial kernel page directory and page tables");
uint_t mem_addr;
//伪代码:树立页目录及页表
Build_page_dir_And_page_table();
//将页中一切位清0,特别是其间的userMode位
memset(g_kernel_pde, 0, PAGE_SIZE);
//地址不应该从0开始,否则可能会陷入空指针异常,后续添加偏移地址核算的内容
mem_addr = 0;
//调用Enable_Paging()
Enable_Paging(g_kernel_pde);
//装置中止操控
Install_Interrupt_Handler(14, Page_Fault_Handler);
}
- 如何树立页目录及页表?
int kernel_pde_entries = whole_pages / NUM_PAGE_DIR_ENTRIES + (whole_pages % NUM_PAGE_DIR_ENTRIES == 0 ? 0 : 1);
/*内核中映射一切物理内存所需的页目录项*/
int whole_pages = bootInfo->memSizeKB / 4;
/*物理内存的页数*/
pte_t *cur_pte;
pde_t *cur_pde_entry;
/*要赋值的pde和pte域*/
/*为内核页分配空间*/
g_kernel_pde = (pde_t *)Alloc_Page();
KASSERT(g_kernel_pde != NULL);
cur_pde_entry = g_kernel_pde;
/*以下是对单个页目录项的操作*/
cur_pde_entry->present = 1;
cur_pde_entry->flags = VM_WRITE;
/*功用:置为大局页
效果:一旦CR3寄存器被载入或产生任务切换(此刻CR3中的值会改变),TLB中的页表或指向页的页目录表项并不失效。该能够防止使TLB中频频运用的页(比方操作系统内核或其他系统代码)失效
留意事项:先设置CR0中的PG标志来设置分页,再设置CR4中的PGE来设置分页实体,才能启用大局页特性
*/
cur_pde_entry->globalPage = 1;
cur_pte = (pte_t *)Alloc_Page();
KASSERT(cur_pte != NULL);
//初始化最终一个页目录表项和对应的页表。留意,页表中的页表项不一定满足1024个
memset(cur_pte, 0, PAGE_SIZE);
cur_pde_entry->pageTableBaseAddr = PAGE_ALLIGNED_ADDR(cur_pte);
int last_pagetable_num;
last_pagetable_num = whole_pages % NUM_PAGE_TABLE_ENTRIES;
//当last_pagetable_num=0时,最终一个页目录项对应的页表是满的
if (last_pagetable_num == 0)
{
last_pagetable_num = NUM_PAGE_TABLE_ENTRIES;
}
for (int i = 0; i < last_pagetable_num; i++)
{
cur_pte->present = 1;
cur_pte->flags = VM_WRITE;
cur_pte->globalPage = 1;
cur_pte->pageBaseAddr = mem_addr >> 12;
cur_pte++;
mem_addr += PAGE_SIZE;
}
在上述代码中,触及的是对单个pde_entry的操作,而对于一切的目录项,可用for循环将这一操作推广到一切操作。以下为兼并了上两部分的终究代码:
void Init_VM(struct Boot_Info *bootInfo){
uint_t mem_addr;
memset(g_kernel_pde, 0, PAGE_SIZE);
mem_addr = 0;
int kernel_pde_entries = whole_pages / NUM_PAGE_DIR_ENTRIES + (whole_pages % NUM_PAGE_DIR_ENTRIES == 0 ? 0 : 1);
int whole_pages = bootInfo->memSizeKB / 4;
pte_t *cur_pte;
pde_t *cur_pde_entry;
g_kernel_pde = (pde_t *)Alloc_Page();
KASSERT(g_kernel_pde != NULL);
for (int i = 0; i < kernel_pde_entries - 1; i++){
cur_pde_entry->present = 1;
cur_pde_entry->flags = VM_WRITE;
cur_pde_entry->globalPage = 1;
cur_pte = (pte_t *)Alloc_Page();
KASSERT(cur_pte != NULL);
cur_pde_entry->present = 1;
cur_pde_entry->flags = VM_WRITE;
cur_pde_entry->globalPage = 1;
cur_pte = (pte_t *)Alloc_Page();
KASSERT(cur_pte != NULL);
memset(cur_pte, 0, PAGE_SIZE);
cur_pde_entry->pageTableBaseAddr = PAGE_ALLIGNED_ADDR(cur_pte);
int last_pagetable_num;
last_pagetable_num = whole_pages % NUM_PAGE_TABLE_ENTRIES;
if (last_pagetable_num == 0)
{
last_pagetable_num = NUM_PAGE_TABLE_ENTRIES;
}
for (int j= 0; j < last_pagetable_num; j++)
{
cur_pte->present = 1;
cur_pte->flags = VM_WRITE;
cur_pte->globalPage = 1;
cur_pte->pageBaseAddr = mem_addr >> 12;
cur_pte++;
mem_addr += PAGE_SIZE;
}
Enable_Paging(g_kernel_pde);
Install_Interrupt_Handler(14, Page_Fault_Handler);
}
}
- 关于最终的
Install_Interrupt_Handler(14, Page_Fault_Handler)
在paging.c中的Page_Fault_Handler()有如下注释阐明: 阐明晰它的用法
- 设置用户进程的代码段和数据段,置基址为0x80000000
//paging.h
#define USER_VM_START 0x80000000
#define USER_VM_END 0xFFFFFFFF
#define USER_VM_LEN 0x80000000
#define DEFAULT_STACK_SIZE 4096
void Switch_To_Address_Space(struct User_Context *userContext)
{
/*
* - If you are still using an LDT to define your user code and data
* segments, switch to the process's LDT
* -
*/
// TODO("Switch_To_Address_Space() using paging");
if (userContext == 0)
{
Print("the userContext is NULL!/n");
return;
}
Load_LDTR(userContext->ldtSelector);
Set_PDBR(userContext->pageDir);
}
- 关于Alloc_Pageable_Page() 它其实便是答应页被强制换出。
这种机制很灵活,但存在很大的不确定性,所以文档主张不要用这种方法来申请页表或页目录的内存。
这个部分的内容并不需求咱们编写,咱们只需了解即可:
操控缺页中止
处理缺页中止需求考虑几大内容:缺页地址、过错码、原因、可采取的处理方法。
以上是文档要求及根据其上的提示可直接获取到的一些信息。
以下是缺页中止的几种状况、其原因以及处理措施:
相应代码及注释如下:
/*static*/ void Page_Fault_Handler(struct Interrupt_State *state)
{
ulong_t address;
faultcode_t faultCode;
KASSERT(!Interrupts_Enabled());
/* Get the address that caused the page fault */
address = Get_Page_Fault_Address();
// Print("Page fault @%lx\n", address);
/* Get the fault code */
faultCode = *((faultcode_t *)&(state->errorCode));
/* rest of your handling code here */
// Print("Unexpected Page Fault received\n");
// Print_Fault_Info(address, faultCode);
// Dump_Interrupt_State(state);
// /* user faults just kill the process */
struct User_Context *userContext = g_currentThread->userContext;
//写过错,缺页状况为堆栈生长到新页
if (faultCode.writeFault)
{
Print("write Fault\n");
int res;
if (!Alloc_User_Page(userContext->pageDir, Round_Down_To_Page(address), PAGE_SIZE))
{
Print("Alloc_User_Page error in Page_Fault_Handler\n");
Exit(-1);
}
return;
}
else
{
//读过错,分两种缺页状况
Print("read fault\n");
//先找到虚拟地址对应的页表项
ulong_t page_dir_addr = PAGE_DIRECTORY_INDEX(address);
ulong_t page_addr = (address << 10) >> 22;
pde_t *page_dir_entry = (pde_t *)userContext->pageDir + page_dir_addr;
pte_t *page_entry = NULL;
// Print("address=%x\n", address);
// Print("userContext->pageDir=%x\n", userContext->pageDir);
// Print("page_dir_entry=%x\n", page_dir_entry);
if (page_dir_entry->present)
{
// Print("page_dir_entry->present=%x\n", page_dir_entry->present);
page_entry = (pte_t *)((page_dir_entry->pageTableBaseAddr) << 12);
page_entry += page_addr;
// Print("page_entry=%x\n", page_entry);
// Print("*page_entry=%x\n", *page_entry);
// Print("page_entry->present=%x\n", page_entry->present);
// Print("page_entry->pageBaseAddr=%x\n", (page_entry->pageBaseAddr) << 12);
}
else
{
//不合法地址拜访的缺页状况
Print_Fault_Info(address, faultCode);
Exit(-1);
}
if (page_entry->kernelInfo != KINFO_PAGE_ON_DISK)
{
// Print("page_entry->kernelInfo=%x\n", page_entry->kernelInfo);
//不合法地址拜访的缺页状况
Print_Fault_Info(address, faultCode);
Exit(-1);
}
//由于页保存在磁盘pagefile引起的缺页
int pagefile_index = page_entry->pageBaseAddr;
void *paddr = Alloc_Pageable_Page(page_entry, Round_Down_To_Page(address));
if (paddr == NULL)
{
Print("no more page/n");
Exit(-1);
}
*((uint_t *)page_entry) = 0;
page_entry->present = 1;
page_entry->flags = VM_WRITE | VM_READ | VM_USER;
page_entry->globalPage = 0;
page_entry->pageBaseAddr = PAGE_ALLIGNED_ADDR(paddr);
//从页面文件中把页读到内存中
Enable_Interrupts();
Read_From_Paging_File(paddr, Round_Down_To_Page(address), pagefile_index);
Disable_Interrupts();
//开释页面文件中的空间
Free_Space_On_Paging_File(pagefile_index);
return;
}
}
换出页
这里触及到了页的置换问题。当页框满时,缓存再想装新页就得把原来的页置换出去,最常用的置换算法是LRU算法(least frequently used)
从文档提示信息可知:
- 需求一个clock,用于LRU算法中记录每个页的拜访次数(以便将最不频频拜访的,即拜访次数最少的逐出)
根据题意在mem.h的page中添加clock字段:
struct Page {
unsigned flags; /* Flags indicating state of page */
DEFINE_LINK(Page_List, Page); /* Link fields for Page_List */
**int clock;**
ulong_t vaddr; /* User virtual address where page is mapped */
pte_t *entry; /* Page table entry referring to the page */
};
- 有一个页办理文件
在
<geekos/vfs.h>
中,有一个struct Paging_Device *Get_Paging_Device(void);
,设置页时会用到。 - 每一页会耗费接连8磁盘块的大小,读/写页设备能够用
Block_Read()``Block_Write()
换出页的部分归纳了第2、3小点后,有三大函数需求弥补,分别是页的初始化、读和写页文件。 依照上面的思路写即可:
//paging.c
void Init_Paging(void)
{
// TODO("Initialize paging file data structures");
pagingDevice = Get_Paging_Device();
if (pagingDevice == NULL)
{
Print("can not find pagefile\n");
return;
}
numPagingDiskPages = pagingDevice->numSectors / SECTORS_PER_PAGE;
//为pagefile中每一页设置标示位
Bitmap = Create_Bit_Set(numPagingDiskPages);
}
void Write_To_Paging_File(void *paddr, ulong_t vaddr, int pagefileIndex)
{
struct Page *page = Get_Page((ulong_t)paddr);
KASSERT(!(page->flags & PAGE_PAGEABLE)); /* Page must be locked! */
// TODO("Write page data to paging file");
if (0 <= pagefileIndex && pagefileIndex < numPagingDiskPages)
{
int i;
for (i = 0; i < SECTORS_PER_PAGE; i++)
{
Block_Write(
pagingDevice->dev,
pagefileIndex * SECTORS_PER_PAGE + i + (pagingDevice->startSector),
paddr + i * SECTOR_SIZE);
}
Set_Bit(Bitmap, pagefileIndex);
}
else
{
Print("Write_To_Paging_File: pagefileIndex out of range!\n");
Exit(-1);
}
}
void Read_From_Paging_File(void *paddr, ulong_t vaddr, int pagefileIndex)
{
block_t *block = (block_t *)paddr;
struct Page *page = Get_Page((ulong_t)paddr);
KASSERT(!(page->flags & PAGE_PAGEABLE)); // Page must be locked!
// TODO("Read page data from paging file");
page->flags = page->flags & ~PAGE_PAGEABLE;
}
页换进
当页面分页到磁盘时,内核将 Find_Space_On_Paging_File() 返回的索引存储在页表条目 (pte_t) 的 pageBaseAddr 字段中,并将KINFO_PAGE_ON_DISK的值存储在条目的 kernelInfo 字段中。
这一部分的内容也是针对缺页操控的。
能够运用存储在 pageBaseAddr 中的值在分页文件中查找该页面的数据。
详细细节如下:
//paging.c
int Find_Space_On_Paging_File(void)
{
KASSERT(!Interrupts_Enabled());
// TODO("Find free page in paging file");
return Find_First_Free_Bit(Bitmap, numPagingDiskPages);
}
其间Find_First_Free_Bit()的效果如名字所示。
//bitset.c
int Find_First_Free_Bit(void *bitSet, ulong_t totalBits)
{
uint_t numBytes = FIND_NUM_BYTES(totalBits);
ulong_t offset;
uchar_t *bits = (uchar_t*) bitSet;
for (offset = 0; offset < numBytes; ++offset) {
if (bits[offset] != 0xff) {
uint_t bit;
for (bit = 0; bit < 8; ++bit) {
if ((bits[offset] & (1 << bit)) == 0)
return (offset * 8) + bit;
}
KASSERT(false);
}
}
return -1;
}
详细对应于Page_Fault_Handler
的如下操作代码:
//由于页保存在磁盘pagefile引起的缺页
int pagefile_index = page_entry->pageBaseAddr;
void *paddr = Alloc_Pageable_Page(page_entry, Round_Down_To_Page(address));
if (paddr == NULL)
{
Print("no more page/n");
Exit(-1);
}
*((uint_t *)page_entry) = 0;
page_entry->present = 1;
page_entry->flags = VM_WRITE | VM_READ | VM_USER;
page_entry->globalPage = 0;
page_entry->pageBaseAddr = PAGE_ALLIGNED_ADDR(paddr);
//从页面文件中把页读到内存中
Enable_Interrupts();
Read_From_Paging_File(paddr, Round_Down_To_Page(address), pagefile_index);
Disable_Interrupts();
//开释页面文件中的空间
Free_Space_On_Paging_File(pagefile_index);
在内核和用户内存间复制数据
内核不得在用户内存页上读取或写入数据,条件是该页在可能产生线程切换的任何时分都设置了 PAGE_PAGEABLE 位。
上述讲的其实是并发导致的数据不共同问题。
完成方案有两个:
- 一个是在接触用户内存时应禁用中止(不完整)
- 加页锁
置PAGE_PAGEABLE为1相当于加锁,操作完成后从头置为0相当于解锁。
这两种方法不能一起运用!
详细代码体现如下:
KASSERT(!(page->flags & PAGE_PAGEABLE));//加锁
page->flags = page->flags & ~PAGE_PAGEABLE;//开释锁
流程回忆
上述项目要求对应的是文档的2,3,4功用概述部分,在依照其要求进行编码的过程中,项目就已经完成得差不多了。最终咱们凭借implementation
中所罗列的函数模块来查验是否有遗漏的当地,一起也将全流程整理一遍。
(以下小点的注释中,中文是我写文章的时分加上去的,目的是总结函数里面的做法,大家写注释的时分要留意标准,别这样写哈)
paging.c
-
Init_VM()
/*
* Initialize virtual memory by building page tables
* for the kernel and physical memory.
* 树立页目录及页表,装置中止操控
*/
void Init_VM(struct Boot_Info *bootInfo)
void Init_Paging(void)
/**
* Initialize paging file data structures.
* All filesystems should be mounted before this function
* is called, to ensure that the paging file is available.
* 经过Get_Paging_Device()获取页文件所在的设备(paingDevice)、占用磁盘块的范围(扇区数,numSectors)
*/
void Init_Paging(void)
-
Find_Space_On_Paging_File()
/**
* Find a free bit of disk on the paging file for this page.
* Interrupts must be disabled.
* 在分页文件中找到一个可用的页面大小的磁盘空间块(调用Find_First_Free_Bit)
* @return index of free page sized chunk of disk space in
* the paging file, or -1 if the paging file is full
*/
int Find_Space_On_Paging_File(void)
-
Free_Space_On_Paging_File()
/**
* Free a page-sized chunk of disk space in the paging file.
* Interrupts must be disabled.
* 开释空间,调用Clear_bit
* @param pagefileIndex index of the chunk of disk space
*/
void Free_Space_On_Paging_File(int pagefileIndex)
-
Write_To_Paging_File()
/**
* Write the contents of given page to the indicated block
* of space in the paging file.
* 如果能写入,调用Block_Write(),并将相应方位1,留意过程中上锁
* @param paddr a pointer to the physical memory of the page
* @param vaddr virtual address where page is mapped in user memory
* @param pagefileIndex the index of the page sized chunk of space
* in the paging file
*/
void Write_To_Paging_File(void *paddr, ulong_t vaddr, int pagefileIndex)
-
Read_From_Paging_File()
/**
* Read the contents of the indicated block
* of space in the paging file into the given page.
* 置页的状况
* @param paddr a pointer to the physical memory of the page
* @param vaddr virtual address where page will be re-mapped in
* user memory
* @param pagefileIndex the index of the page sized chunk of space
* in the paging file
*/
void Read_From_Paging_File(void *paddr, ulong_t vaddr, int pagefileIndex)
uservm.c
-
Destroy_User_Context()
/*
* Destroy a User_Context object, including all memory
* and other resources allocated within it.
*/
void Destroy_User_Context(struct User_Context *context)
{
/*
* Hints:
* - Free all pages, page tables, and page directory for
* the process (interrupts must be disabled while you do this,
* otherwise those pages could be stolen by other processes)
* - Free semaphores, files, and other resources used
* by the process
*/
// TODO("Destroy User_Context data structure after process exits");
if (context == NULL)
{
return;
}
Free_Segment_Descriptor(context->ldtDescriptor);
Set_PDBR(g_kernel_pde);
if (context->pageDir != NULL)
{
pde_t *page_dir = context->pageDir;
//KASSERT(!Interrupts_Enabled());
//Enable_Interrupts();
bool flag;
flag = Begin_Int_Atomic();
pde_t *pdir;
if (page_dir == NULL)
{
return true;
}
for (pdir = page_dir + NUM_PAGE_DIR_ENTRIES / 2; pdir < page_dir + NUM_PAGE_DIR_ENTRIES; pdir++)
{
pte_t *ptable;
pte_t *ptable_first;
if (!pdir->present)
{
continue;
}
ptable_first = (pte_t *)(pdir->pageTableBaseAddr << 12);
for (ptable = ptable_first; ptable < ptable_first + NUM_PAGE_TABLE_ENTRIES; ptable++)
{
if (ptable->present)
{
Free_Page((void *)(ptable->pageBaseAddr << 12));
}
else if (ptable->kernelInfo == KINFO_PAGE_ON_DISK)
{
//当页在pagefile上时,pte_t结构中的pageBaseAddr指示了页在pagefile中的方位
Free_Space_On_Paging_File(ptable->pageBaseAddr);
}
}
Free_Page(ptable_first);
}
//Disable_Interrupts();
Free_Page(page_dir);
End_Int_Atomic(flag);
}
context->pageDir = 0;
Free(context);
}
-
Load_User_Program()
loads an executable file into memory, creating a complete, ready-to-execute user address space.
/*
* Load a user executable into memory by creating a User_Context
* data structure.
* Params:
* exeFileData - a buffer containing the executable to load
* exeFileLength - number of bytes in exeFileData
* exeFormat - parsed ELF segment information describing how to
* load the executable's text and data segments, and the
* code entry point address
* command - string containing the complete command to be executed:
* this should be used to create the argument block for the
* process
* pUserContext - reference to the pointer where the User_Context
* should be stored
*
* Returns:
* 0 if successful, or an error code (< 0) if unsuccessful
*/
int Load_User_Program(char *exeFileData, ulong_t exeFileLength,
struct Exe_Format *exeFormat, const char *command,
struct User_Context **pUserContext)
{
/*
* Hints:
* - This will be similar to the same function in userseg.c
* - Determine space requirements for code, data, argument block,
* and stack
* - Allocate pages for above, map them into user address
* space (allocating page directory and page tables as needed)
* - Fill in initial stack pointer, argument block address,
* and code entry point fields in User_Context
*/
// TODO("Load user program into address space");
struct User_Context *uContext;
uContext = Create_User_Context(); //创建一个UserContext
//处理分页触及的数据
pde_t *pageDirectory;
pageDirectory = (pde_t *)Alloc_Page();
if (pageDirectory == NULL)
{
Print("no more page!\n");
return -1;
}
memset(pageDirectory, 0, PAGE_SIZE);
//将内核页目录复制到用户态进程的页目录中
memcpy(pageDirectory, g_kernel_pde, PAGE_SIZE);
//将用户态进程对应高2G线性地址的页目录表项置为0,用户态进程中高2G的线性地址在GeekOS中为用户空间
memset(pageDirectory + 512, 0, PAGE_SIZE / 2);
uContext->pageDir = pageDirectory;
int i;
int res;
uint_t startAddress = 0;
uint_t sizeInMemory = 0;
uint_t offsetInFile = 0;
uint_t lengthInFile = 0;
for (i = 0; i < exeFormat->numSegments - 1; i++)
{
startAddress = exeFormat->segmentList[i].startAddress;
sizeInMemory = exeFormat->segmentList[i].sizeInMemory;
offsetInFile = exeFormat->segmentList[i].offsetInFile;
lengthInFile = exeFormat->segmentList[i].lengthInFile;
if (!sizeInMemory && !lengthInFile)
{
sizeInMemory = DEFAULT_STACK_SIZE;
lengthInFile = DEFAULT_STACK_SIZE;
}
if (startAddress + sizeInMemory < USER_VM_LEN)
{
//给数据段和代码段分配空间
//在GeekOS的分页机制下,用户地址空间默认从线性地址2G开始
if (!Alloc_User_Page(pageDirectory, startAddress + USER_VM_START, sizeInMemory) ||
!Copy_User_Page(pageDirectory, startAddress + USER_VM_START, exeFileData + offsetInFile, lengthInFile))
{
return -1;
}
}
else
{
Print("startAddress+sizeInMemory > 2GB in Load_User_Program\n");
return -1;
}
}
//处理参数块
uint_t args_num;
uint_t stack_addr;
uint_t arg_addr;
ulong_t arg_size;
Get_Argument_Block_Size(command, &args_num, &arg_size);
if (arg_size > PAGE_SIZE)
{
Print("Argument Block too big for one PAGE_SIZE\n");
return -1;
}
//给参数块在地址空间的尾部分配一页
arg_addr = Round_Down_To_Page(USER_VM_LEN - arg_size);
char *block_buffer = Malloc(arg_size);
KASSERT(block_buffer != NULL);
Format_Argument_Block(block_buffer, args_num, arg_addr, command);
if (!Alloc_User_Page(pageDirectory, arg_addr + USER_VM_START, arg_size) ||
!Copy_User_Page(pageDirectory, arg_addr + USER_VM_START, block_buffer, arg_size))
{
return -1;
}
Free(block_buffer);
//给堆栈在地址空间的尾部分配一页
stack_addr = USER_VM_LEN - Round_Up_To_Page(arg_size) - DEFAULT_STACK_SIZE;
if (!Alloc_User_Page(pageDirectory, stack_addr + USER_VM_START, DEFAULT_STACK_SIZE))
{
return -1;
}
//UserContext中触及分段机制的挑选子,描述符
uContext->ldtDescriptor = Allocate_Segment_Descriptor();
if (uContext->ldtDescriptor == NULL)
{
Print("allocate segment descriptor fail\n");
return -1;
}
Init_LDT_Descriptor(uContext->ldtDescriptor, uContext->ldt, NUM_USER_LDT_ENTRIES);
uContext->ldtSelector = Selector(USER_PRIVILEGE, true, Get_Descriptor_Index(uContext->ldtDescriptor));
//留意,在GeekOS的分页机制下,用户地址空间默认从线性地址2G开始
Init_Code_Segment_Descriptor(&uContext->ldt[0], USER_VM_START, USER_VM_LEN / PAGE_SIZE, USER_PRIVILEGE);
Init_Data_Segment_Descriptor(&uContext->ldt[1], USER_VM_START, USER_VM_LEN / PAGE_SIZE, USER_PRIVILEGE);
uContext->csSelector = Selector(USER_PRIVILEGE, false, 0);
uContext->dsSelector = Selector(USER_PRIVILEGE, false, 1);
//填充UserContext
uContext->entryAddr = exeFormat->entryAddr;
uContext->argBlockAddr = arg_addr;
uContext->size = USER_VM_LEN;
uContext->stackPointerAddr = stack_addr;
*pUserContext = uContext;
DisplayMemory(pageDirectory);
return 0;
}
-
Copy_From_User()
copies data from a user buffer into a kernel buffer.
bool Copy_From_User(void *destInKernel, ulong_t srcInUser, ulong_t numBytes)
{
/*
* Hints:
* - Make sure that user page is part of a valid region
* of memory
* - Remember that you need to add 0x80000000 to user addresses
* to convert them to kernel addresses, because of how the
* user code and data segments are defined
* - User pages may need to be paged in from disk before being accessed.
* - Before you touch (read or write) any data in a user
* page, **disable the PAGE_PAGEABLE bit**.
*
* Be very careful with race conditions in reading a page from disk.
* Kernel code must always assume that if the struct Page for
* a page of memory has the PAGE_PAGEABLE bit set,
* IT CAN BE STOLEN AT ANY TIME. The only exception is if
* interrupts are disabled; because no other process can run,
* the page is guaranteed not to be stolen.
*/
// TODO("Copy user data to kernel buffer");
struct User_Context *userContext = g_currentThread->userContext;
void *user_lin_addr = (void *)(USER_VM_START) + srcInUser;
if ((srcInUser + numBytes) < userContext->size)
{
memcpy(destInKernel, user_lin_addr, numBytes);
return true;
}
return false;
}
-
Copy_To_User()
/*
* Copy data from kernel buffer into user buffer.
* Returns true if successful, false otherwise.
*/
bool Copy_To_User(ulong_t destInUser, void *srcInKernel, ulong_t numBytes)
{
/*
* Hints:
* - Same as for Copy_From_User()
* - Also, make sure the memory is mapped into the user
* address space with write permission enabled
*/
// TODO("Copy kernel data to user buffer");
struct User_Context *userContext = g_currentThread->userContext;
void *user_lin_addr = (void *)(USER_VM_START) + destInUser;
if ((destInUser + numBytes) < userContext->size)
{
memcpy(user_lin_addr, srcInKernel, numBytes);
return true;
}
return false;
}
-
Switch_To_Address_Space()
void Switch_To_Address_Space(struct User_Context *userContext)
{
/*
* - If you are still using an LDT to define your user code and data
* segments, switch to the process's LDT
* -
*/
// TODO("Switch_To_Address_Space() using paging");
if (userContext == 0)
{
Print("the userContext is NULL!/n");
return;
}
Load_LDTR(userContext->ldtSelector);
Set_PDBR(userContext->pageDir);
}
这个project没有给测试样例,我也不知道上述代码会不会有问题。希望对操作系统感兴趣的大佬们能提出宝贵的辅导定见哈!
后续弥补:关于测试验证:
输入rec 4
,以下是显现输出的部分内容: