在之前的项目中,内存办理运用的是段式存储的方法,而这次将会完成页式存储。

布景常识

页式存储是内存办理的重点,也是其间运用更为广泛的方法。

接下来咱们先从理论层面了解一下它的机制。内容摘录自操作系统理论课课程材料。

咱们先来看一张图:

【操作系统】GeekOS完成记录(六)project4:实现页式存储

  • 内存地址空间被划分为固定大小的很多页,比方红框框所示的内容便是一,容纳它的空间便是一个页框
  • 左边能够看做是物理地址,右边则是映射出的虚拟地址,中心起到映射这两者关系的表格称为页表
  • 页表实际上是一个索引表,每一行中,key值代表物理页号,value值代表逻辑页框号。知道了页号,就能知道对应的页框号,就能找到相应映射的方位。

关于映射的细节,咱们再接着看下两张图:

【操作系统】GeekOS完成记录(六)project4:实现页式存储

【操作系统】GeekOS完成记录(六)project4:实现页式存储
从中咱们能够看出,对于单个地址,物理地址和逻辑地址的页内偏移量是共同的,但是页号和页框号不共同。

当然,为了加速查找,还能够再加入一个快表,它实际上是一个cache。

项目要求

接下来,让咱们逐条拆解项目要求并完成。

运用页表

Init_VM()

  • 使内存仅能从内核模式拜访;创建页表目录及页表实体

【操作系统】GeekOS完成记录(六)project4:实现页式存储

以下是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);
}
  • 如何树立页目录及页表?

【操作系统】GeekOS完成记录(六)project4:实现页式存储

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()有如下注释阐明:

【操作系统】GeekOS完成记录(六)project4:实现页式存储
阐明晰它的用法

  • 设置用户进程的代码段和数据段,置基址为0x80000000

【操作系统】GeekOS完成记录(六)project4:实现页式存储

//paging.h
#define USER_VM_START 0x80000000
#define USER_VM_END 0xFFFFFFFF
#define USER_VM_LEN 0x80000000
#define DEFAULT_STACK_SIZE 4096

【操作系统】GeekOS完成记录(六)project4:实现页式存储

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()
    【操作系统】GeekOS完成记录(六)project4:实现页式存储
    它其实便是答应页被强制换出。

这种机制很灵活,但存在很大的不确定性,所以文档主张不要用这种方法来申请页表或页目录的内存。

这个部分的内容并不需求咱们编写,咱们只需了解即可:

【操作系统】GeekOS完成记录(六)project4:实现页式存储

【操作系统】GeekOS完成记录(六)project4:实现页式存储

操控缺页中止

【操作系统】GeekOS完成记录(六)project4:实现页式存储
处理缺页中止需求考虑几大内容:缺页地址、过错码、原因、可采取的处理方法。

【操作系统】GeekOS完成记录(六)project4:实现页式存储

【操作系统】GeekOS完成记录(六)project4:实现页式存储

【操作系统】GeekOS完成记录(六)project4:实现页式存储

以上是文档要求及根据其上的提示可直接获取到的一些信息。

以下是缺页中止的几种状况、其原因以及处理措施:

【操作系统】GeekOS完成记录(六)project4:实现页式存储

相应代码及注释如下:

/*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;
    }
}

换出页

【操作系统】GeekOS完成记录(六)project4:实现页式存储

这里触及到了页的置换问题。当页框满时,缓存再想装新页就得把原来的页置换出去,最常用的置换算法是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;
}

页换进

【操作系统】GeekOS完成记录(六)project4:实现页式存储
当页面分页到磁盘时,内核将 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);

在内核和用户内存间复制数据

【操作系统】GeekOS完成记录(六)project4:实现页式存储

内核不得在用户内存页上读取或写入数据,条件是该页在可能产生线程切换的任何时分都设置了 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,以下是显现输出的部分内容:

【操作系统】GeekOS完成记录(六)project4:实现页式存储