《操作系统真相还原》实验记录2.4——内存管理系统

一、位图 bitmap 及其函数的实现

1.1 位图简介

  1. 位图,也就是bitmap,广泛用于资源管理,是一种管理资源的方式、手段。“资源”包括很多,比如内存或硬盘,对于此类大容量资源的管理一般都会采用位图的方式。
  2. 位是指bit,即字节中的位,1 字节中有 8 个位;图是指map,地图本质上就是映射的意思,映射,即对应关系。综合起来,位图就是用字节中的 1 位来映射其他单位大小的资源,位与资源之间是一对一的对应关系
  3. 计算机中最小的数据单位是位,于是,用一组二进制位串来管理其他单位大小的资源是很自然的事,这组二进制位中的每一位与其他资源中的数据单位都是一对一的关系,这实际就成了一种映射,即map,于是这组二进制位就有了更恰当的名字——位图。
    位图与内存的映射关系

1.2 位图的代码说明与实现

1.2.1 代码说明

  1. bitmap.h
    1. 实现了位图的结构体定义。
  2. bitmap.c
    1. 实现了对位图的初始化及其他操作函数。

1.2.2 代码实现

bitmap.h

#ifndef __LIB_KERNEL_BITMAP_H
#define __LIB_KERNEL_BITMAP_H
#include "global.h"
#include "stdbool.h"#define BITMAP_MASK 1
struct bitmap {uint32_t btmp_bytes_len;uint8_t* bits;  // other module create bitmap which type is uint8_t, then ,that module translate it's bitmap's head address to bits.
};void bitmap_init(struct bitmap* btmp);
bool bitmap_scan_test(struct bitmap* btmp, uint32_t bit_idx);
int bitmap_scan(struct bitmap* btmp, uint32_t cnt);
void bitmap_set(struct bitmap* btmp, uint32_t bit_idx, uint8_t value);
#endif

bitmap.c

#include "bitmap.h"
#include "stdint.h"
#include "stdbool.h"
#include "print.h"
#include "interrupt.h"
#include "debug.h"
#include "string.h"void bitmap_init(struct bitmap* btmp) {memset(btmp->bits, 0, btmp->btmp_bytes_len);/*bits is a pointer and is's type is int8_t,it means bits++, the pointer's address will add one byte.*/
}/*judge the bit which index is bit_index in bitmap whether is 1 or 0*/
bool bitmap_scan_test(struct bitmap* btmp, uint32_t bit_idx) {  //pay attention to bit_idx and byte_idx.uint32_t byte_idx = bit_idx / 8;uint32_t bit_odd = bit_idx % 8;return (btmp->bits[byte_idx] & (uint8_t)(BITMAP_MASK << bit_odd));
}/*used to find empty bit's address in bitmap,and desire to find the empty bit's quantities is cnt */
int bitmap_scan(struct bitmap* btmp, uint32_t cnt) {uint32_t idx_byte = 0; //used to record the byte where empty bit is in. while((0xff == btmp->bits[idx_byte]) && (idx_byte < btmp->btmp_bytes_len)) {idx_byte++;}ASSERT(idx_byte < btmp->btmp_bytes_len);if(idx_byte == btmp->btmp_bytes_len) {return -1;  //can't find an empty space in memory pool.}int idx_bit  = 0;while((uint8_t)(BITMAP_MASK << idx_bit) & btmp->bits[idx_byte]) {idx_bit++;}int bit_idx_start = idx_byte * 8 + idx_bit;  //the index of empty bit in bitmap.if(cnt == 1) {return bit_idx_start;}uint32_t bit_left = (btmp->btmp_bytes_len * 8 - bit_idx_start);  //to record how many bits we can judge.uint32_t next_bit = bit_idx_start + 1;uint32_t count = 1;bit_idx_start = -1;  //set to -1 and if can not find continuous bit, then return it.while(bit_left-- > 0) {if(!(bitmap_scan_test(btmp, next_bit))) {count++;}else {count = 0;}if(count == cnt) {bit_idx_start = next_bit - cnt + 1;break;}next_bit++;}return bit_idx_start;
}/*set the bit of bit_idx in btmp to value.*/
void bitmap_set(struct bitmap* btmp, uint32_t bit_idx, uint8_t value) {ASSERT((value == 0) || (value == 1));uint32_t byte_idx = bit_idx / 8;uint32_t bit_odd = bit_idx % 8;if(value) {btmp->bits[byte_idx] |= (BITMAP_MASK << bit_odd);}else {btmp->bits[byte_idx] &= ~(BITMAP_MASK << bit_odd);}
}

二、内存管理系统的内存池初始化及分配页内存实现

  1. 用户程序所占用的内存空间是由操作系统分配的,内存是如何被分配的并且该给用户进程分配多少字节是本节要解决的问题。
  2. 本节将循序渐进地实现内存管理系统,直到函数 malloc() 函数的完成

2.1 内存池规划

  1. 内存地址池的概念是将可用的内存地址集中放到一个“池子”中,需要的时候直接从里面取出,用完后再放回去。由于在分页机制下有了虚拟地址和物理地址,为了有效地管理它们,我们需要同时创建虚拟内存地址池和物理内存地址池
  2. 操作系统为了能够正常运行,不能用户进程申请多少内存就分配多少,必须得给自己预留出足够的内存才行,否则有可能会出现因为物理内存不足,导致内核自己都无法正常运行、自身难保的现象。
  3. 物理内存池
    1. 我们把物理内存分成两个内存池,一部分称为用户物理内存池,此内存池中的物理内存只用来分配给用户进程。另一部分就是内核物理内存池,此内存池中的物理内存只给操作系统使用
    2. 内存池中的内存得按单位大小来获取,这个单位大小是 4KB,称为“页”,故,内存池中管理的是一个个大小为 4KB 的内存块,从内存池中获取的内存大小至少为 4KB 或者为 4KB 的倍数(以后会实现更细粒度的内存管理)。
    3. 为了方便实现,我们把内核与用户的内存池的大小设为一致,即各占一半的物理内存,如下图所示:
      当前物理内存池划分图示
  4. 虚拟内存池
    1. 在分页机制下程序中的地址都是虚拟地址,虚拟地址的范围取决于地址总线的宽度,咱们是在 32 位环境下,所以虚拟地址空间为4GB。除了地址空间比较大以外,分页机制的另一个好处是每个任务都有自己的 4GB 虚拟地址空间,也就是各程序中的虚拟地址不会与其他程序冲突,都可以为相同的虚拟地址,不仅用户进程是这样,内核也是。程序中的地址是由链接器在链接过程中分配的,分配之后就不会再变了,运行时按部就班地送上处理器的 CS 和 EIP 即可。
    2. 虽然程序中的地址是由链接器在链接过程中分配的,但程序(进程、内核线程)在运行过程中也有申请内存的需求,这种动态申请内存一般是指在堆中申请内存,操作系统接受申请后,为进程或内核自己在堆中选择一空闲的虚拟地址,并且找个空闲的物理地址作为此虚拟地址的映射,之后把这个虚拟地址返回给程序
    3. 对于所有任务(包括用户进程、内核)来说,它们都有各自 4GB 的虚拟地址空间,在某一任务中,为了获取哪些虚拟地址是空闲的以及如何跟踪这些空闲的虚拟地址,因此需要为所有任务都维护它们自己的虚拟地址池,即一个任务一个虚拟内存池。
    4. 对于内核来说:
      1. 我们让内核也通过内存管理系统申请内存,为此,它也要有个虚拟地址池,当它申请内存时,从内核自己的虚拟地址池中分配虚拟地址,再从内核物理内存池(内核专用)中分配物理内存,然后在内核自己的页目录表及页表中将这两种地址建立好映射关系
    5. 对于用户进程来说:
      1. 对用户进程而言,它向内存管理系统,即操作系统,申请内存时,操作系统先从用户进程自己的虚拟地址池中分配空闲虚拟地址,然后再从用户物理内存池(所有用户进程共享)中分配空闲的物理内存,然后在该用户进程自己的页表内将这两种地址建立好映射关系
    6. 为方便管理,虚拟地址池中的地址单位也是 4KB,这样虚拟地址便于和物理地址做完整页的映射。有了虚拟地址池和物理地址池后,它们的关系如下图所示:
      虚拟地址池与物理地址池的关联

2.2 分配页内存

  1. 在C语言下是用 malloc() 函数向操作系统申请内存的,此函数可以申请的内存数量比较灵活,在用户眼里,可以申请任意字节尺寸的内存,但本节要做的是实现任意内存分配的基础部分——“整页分配”
    1. 整页分配”这个词是作者自己杜撰的,其意思是先支持一次分配 n 个页的内存,即 n*4096 字节。
  2. 内存管理中,必不可少的操作就是修改页表,这势必涉及到页表项及页目录项的操作,因此又在 memory.h 中定义了一些PG_开头的宏,这是页表项或页目录项的属性,memory.c 中的函数会用到它们,其中memory.h头文件使用到的页目录项及页表项结构如下:
    页目录项及页表项结构
  3. memory.h中使用到的页目录项及页表项内属性说明如下:
    1. PG_P_1 表示 P 位的值为1,表示此页内存已存在。
    2. PG_P_0 表示 P 位的值为0,表示此页内存不存在。
    3. PG_RW_W 表示 RW 位的值为W,即RW=1,表示此页内存允许读、写、执行。
    4. PG_RW_R 表示 RW 位的值为R,即RW=0,表示此页内存允许读、执行。
    5. PG_US_S 表示 US 位的值为S,即US=0,表示只允许特权级别为 0、1、2 的程序访问此页内存,3 特权级程序不被允许。
    6. PG_US_U 表示 US 位的值为U,即US=1,表示允许所有特权级别程序访问此页内存。
  4. 32 位虚拟地址的转换过程复习
    1. (1) 高 10 位是页目录项 pde 的索引,用于在页目录表中定位pde,细节是处理器获取高 10 位后自动将其乘以4(乘以4的原因:一个entry占4字节),再加上页目录表的物理地址,这样便得到了 pde 索引对应的 pde 所在的物理地址,然后自动在该物理地址中,即该 pde 中,获取保存的页表物理地址。
    2. (2) 中间 10 位是页表项 pte 的索引,用于在页表中定位pte。细节是处理器获取中间 10 位后自动将其乘以 4,再加上第一步中得到的页表的物理地址,这样便得到了 pte 索引对应的 pte 所在的物理地址,然后自动在该物理地址,即该 pte 中获取保存的普通物理页的物理地址。
    3. (3) 低 12 位是物理页内的偏移量,页大小是4KB,12 位可寻址的范围正好是4KB,因此处理器便直接把低 12 位作为第二步中获取的物理页的偏移量,无需乘以4用物理页的物理地址加上这低 12 位的和便是这 32 位虚拟地址最终落向的物理地址。
    4. 32位地址经过以上三步拆分,地址最终落在某个物理页内。
  5. 再次强调:页表的作用是将虚拟地址转换成物理地址。
    1. 转换过程中涉及访问的页目录表、页目录项及页表项,都是通过真实物理地址访问的,否则若用虚拟地址访问它们的话,会陷入转换的死循环中不可自拔。

2.3 内存管理系统相关代码说明与实现

2.3.1 代码说明

  1. memory.h
    1. 本代码实现了虚拟内存池结构体定义。
    2. 虚拟内存池结构体struct virtual_addr包含两个成员,一个是vaddr_bitmap,它的类型是位图结构体struct bitmap,用来管理虚拟地址的分配情况。
      1. 虽然多个进程可以拥有相同的虚拟地址,但究其原因,是因为这些虚拟地址所对应的物理地址是不同的。但是,在同一个进程内的虚拟地址必然是唯一的,这通常是由链接器为其分配的,由链接器负责虚拟地址(程序内地址)的唯一性。但进程在运行时可以动态从堆中申请内存,系统为其分配的虚拟地址也属于此进程的虚拟地址空间,也必须要保证虚拟地址的唯一性,所以,用虚拟内存池位图来记录虚拟地址的分配情况
    3. 虚拟内存池结构体struct virtual_addr的另一个成员是vaddr_start,用来记录虚拟地址的起始值,未来在分配虚拟地址时,将以这个地址为起始分配。其他的部分是一些声明,它们都在memory.c中有具体的实现,在此不再说明。
  2. memory.c
    1. 本代码的重点如下:
      1. 物理内存池结构体定义;
      2. 内存池初始化;
      3. 内存请求分配;
    2. 注意
      1. 内存池结构体是对可用内存的整体描述和管理;
      2. 内存池结构体中包含以下内容:
        1. 用于描述和管理可用内存的位图;
        2. 内存池结构体管理的可用内存池的起始内存地址;
        3. 内存池中可用内存的总体大小(虚拟内存池结构体不需要,因为4GB与32MB相比足够大,因此不对其大小做约束)

2.3.2 内存池初始化相关函数调用图

内存池初始化相关函数调用图

2.3.3 分配页内存相关函数调用图

分配页内存相关函数调用图

2.3.4 代码实现

memory.h

#include "memory.h"
#include "stdint.h"
#include "print.h"
#include "debug.h"
#include "string.h"
#include "bitmap.h"#define PG_SIZE 4096  //4kB = 4 * 1024 = 4096 Byte
/*
The kernel stack's bottom is 0xc009f000, 0xc009e000 is kernel main thread's pcb.
one page can present 128MB memory(4KB * 1024 * 8 * 4KB),so, we put bitmap to address of 0xc009a000,
so,our system can offer 4(0x9b000-0x9a000 = 2^12 = 4KB) page of bitmap,these can indicate 512MB memory.
*/
#define MEM_BITMAP_BASE 0xc009a000
/*-------------------------------------------------*/
#define K_HEAP_START 0xc0100000  //pay attention: PAGE_DIR_TABLE_POS equ 0x100000 is an physic address and this is virtual address./*The struct of memory pool, it will be used to create two object to manage kernel memory pool and user memory pool.*/
struct pool {   //used to manage physic memory.struct bitmap pool_bitmap;uint32_t phy_addr_start;uint32_t pool_size;
};#define PDE_IDX(addr) ((addr & 0xffc00000) >> 22)  //get high 10 bit of 32 address
#define PTE_IDX(addr) ((addr & 0x003ff000) >> 22)  //get mid 10 bit of 32 addressstruct pool kernel_pool, user_pool; //manage physic
struct virtual_addr kernel_vaddr;   //manage virtual/*applicate virtual page in pf(pool flag) by quantity of pg_cnt,if successful ,return the start address of virtual page,of fail,return NULL*/
static void* vaddr_get(enum pool_flags pf, uint32_t pg_cnt) {int vaddr_start = 0, bit_idx_start = -1;  //bit_idx is the index in bitmap.uint32_t cnt = 0;if(pf == PF_KERNEL) {bit_idx_start = bitmap_scan(&kernel_vaddr.vaddr_bitmap, pg_cnt);  //pay attention: bitmap_scan()'s parameter is an bitmap structure's address,not an bitmap's address.if(bit_idx_start == -1) {return NULL;}while(cnt < pg_cnt) {bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 1); //indicate these pages have be used.}vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start * PG_SIZE;}else {// User memory pool, we will add it when we do user processes.}return (void*)vaddr_start;
}/*get the pte pointer of virtual address "vaddr"*/
/*remember!! pointer's address is vritual address*/
/*
remember!! pte_ptr() and pde_ptr() don't care whether pte and pde exist, they just caculate virtual address of pte and pde which are related with vaddr
******************************these two function are the key of edit page table.**************************
*/
uint32_t* pte_ptr(uint32_t vaddr) {/*0xffc00000: this is virtual address, and it is point to last PDE, last PDE point to PDT's physic address*/uint32_t* pte = (uint32_t*)(0xffc00000 + ((vaddr & 0xffc00000) >> 10) + PTE_IDX(vaddr) * 4);return pte;
}/*get the pde pointer of virtual address "vaddr"*/
uint32_t* pde_ptr(uint32_t vaddr) {uint32_t* pde = (uint32_t*)((0xfffff000) + PDE_IDX(vaddr) * 4);return pde;
}
/******************************************************************************************* *//*destribute one physic page from physic memory pool which is pointed by m_pool*/
/*if success, return physic address of page,if fail,return NULL.*/
static void* palloc(struct pool* m_pool) {int bit_idx = bitmap_scan(&m_pool->pool_bitmap, 1);if(bit_idx == -1) {return NULL;}bitmap_set(&m_pool->pool_bitmap, bit_idx, 1);uint32_t page_phyaddr = ((bit_idx * PG_SIZE) + m_pool->phy_addr_start);return (void*)page_phyaddr;
}/*add mapping from _vaddr to _page_phyaddr in page table.*/
static void page_table_add(void* _vaddr, void* _page_phyaddr) {uint32_t vaddr = (uint32_t)_vaddr, page_phyaddr = (uint32_t)_page_phyaddr;uint32_t* pde = pde_ptr(vaddr);uint32_t* pte = pte_ptr(vaddr);/**************************************8pay attention!!*******************************/
/*if we want execute *pte, we must make sure we have finished create pde,if we don's create pde and execute *pte, we will make page_fault*/if(*pde & 0x00000001) {  //the attribute of "P",it indicate whether this pte exist.//ASSERT(!(*pte & 0x00000001));  //if we want create pte,so,it's "P" must 0.if(!(*pte & 0x00000001)) {  //check more times to make sure safety.*pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);  //structure of pte.}else {PANIC("pte repeat");*pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);}}else { //the page space of PT all be dectributed from kernel space.uint32_t pde_phyaddr = (uint32_t)palloc(&kernel_pool);  //apply for a page space from kernel pool to create PT.*pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);memset((void*)((int)pte & 0xfffff000), 0, PG_SIZE);   //clean the page space of pde_phyaddrASSERT(!(*pte & 0x00000001));*pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);}
}/*destribute page space and the quantities are pg_cnt.If success,return start virtual address,else return NULL.*/
void* malloc_page(enum pool_flags pf, uint32_t pg_cnt) {ASSERT(pg_cnt > 0 && pg_cnt < 3840);  //3840: kernel or user pyhsic space have about 16MB,we use 15MB to limit. pg_cnt < 15 * 1024 * 1024 / 4096(PG_SIZE) = 3840 (page)
/*************************the principle of malloc_page()***************************/
/*      First: use vaddr_get() to apply vaitual address from virtual memory pool.Second: use palloc() to apply physic page from physic memory pool.Thired: use page_table_add() to finish mapping from virtual address to physic address in PT.
*/void* vaddr_start = vaddr_get(pf, pg_cnt);if(vaddr_start == NULL) {return NULL;}uint32_t vaddr = (uint32_t)vaddr_start, cnt = pg_cnt;struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;  //make sure to use which type of pool to destribute physic page,this pointer(mem_pool) point to pool structure./*virtual address is continuous,but physic address could not be continuous,so we divide these three function from one cycle,and we can apply more virtual page onetime instead of applying virtual page one by one,if we failed to apply virtual page,we can decrease steps to apply physic page.*/while(cnt-- > 0) {void* page_phyaddr = palloc(mem_pool);if(page_phyaddr == NULL) {//if failed, we must rollback all address(virtual/physic page) which are applied, we will achieve it in the future.return NULL;}page_table_add((void*)vaddr, page_phyaddr);  //make mapping in PT.vaddr += PG_SIZE;    //next virtual page.}return vaddr_start;
}/*apply one page memory from pyhsic kernel memory pool, if success, return virtual address,else,return NULL.*/
void* get_kernel_pages(uint32_t pg_cnt) {void* vaddr = malloc_page(PF_KERNEL, pg_cnt);if(vaddr != NULL) {memset(vaddr, 0, pg_cnt * PG_SIZE);  //clean this memory space which you have applied.}return vaddr;
}/*initialize memory pool and ralated structures*/
static void mem_pool_init(uint32_t all_mem) {put_str("mem_pool_init start\n");uint32_t page_table_size = PG_SIZE * 256;  //used to record the Byte's quantity of (PDT add PT). The PDT's 769~1022(kernel space) totally have 254 PDE,these point to 254 page,0 and 769 point to same one page(this page point to low 1MB physic memory),1023 point to PDT,so,totally have 256 page,these page have be occupied.uint32_t used_mem = page_table_size + 0x100000;  //0x100000 is low 1MB physic memory.uint32_t free_mem = all_mem - used_mem;uint16_t all_free_page = free_mem / PG_SIZE;uint16_t kernel_free_pages = all_free_page / 2;uint16_t user_free_pages = all_free_page - kernel_free_pages;uint32_t kbm_length = kernel_free_pages / 8;   //length of kernel Bitmap.uint32_t ubm_length = user_free_pages / 8;uint32_t kp_start = used_mem;  //The low 1MB + PDT + PT are continuous in memory,so,this is kernel pool start address.uint32_t up_start = kp_start + kernel_free_pages * PG_SIZE;kernel_pool.phy_addr_start = kp_start;user_pool.phy_addr_start = up_start;kernel_pool.pool_size = kernel_free_pages * PG_SIZE;user_pool.pool_size = user_free_pages * PG_SIZE;kernel_pool.pool_bitmap.btmp_bytes_len = kbm_length;user_pool.pool_bitmap.btmp_bytes_len = ubm_length;kernel_pool.pool_bitmap.bits = (void*)MEM_BITMAP_BASE;user_pool.pool_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length);put_str("kernel_pool_bitmap_start: ");put_int((int)kernel_pool.pool_bitmap.bits);put_str("  kernel_pool_phy_addr_start: ");put_int(kernel_pool.phy_addr_start);put_str("\n");put_str("user_pool_bitmap_start: ");put_int((int)user_pool.pool_bitmap.bits);put_str("  user_pool_phy_addr_start: ");put_int(user_pool.phy_addr_start);put_str("\n");/*set bitmap to 0*/bitmap_init(&kernel_pool.pool_bitmap);bitmap_init(&user_pool.pool_bitmap);/*initialize kernel virtual address's bitmap,because this is used to control physic-kernel-heap,so, it's size is same to kernel memory pool.*/kernel_vaddr.vaddr_bitmap.btmp_bytes_len = kbm_length;kernel_vaddr.vaddr_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length + ubm_length);put_str("kernel_vaddr_bitmap_start: ");put_int((int)kernel_vaddr.vaddr_bitmap.bits);kernel_vaddr.vaddr_start = K_HEAP_START;put_str("  kernel_vaddr_start: ");put_int(K_HEAP_START);put_str("\n");bitmap_init(&kernel_vaddr.vaddr_bitmap);put_str("mem_pool_init done\n");
}/*memory manage port's initial entry*/
void mem_init() {put_str("mem_init start\n");uint32_t mem_bytes_total = (*(uint32_t*)(0xb00));  //0xb00: total_mem_bytes's address ,this value's defination can see loader.Smem_pool_init(mem_bytes_total);  //initialize memory pool.put_str("mem_init done\n");
}

2.3.5 内存池初始化运行结果

内存池初始化运行结果

2.3.6 分配页内存运行结果

分配页内存运行结果

四、问题分析与解决

4.1 pte repeate

  1. 首先利用x/10 0xc009a3c0查看虚拟地址池位图的分配情况是否正常,通过修改main.c中的get_kernel_pages(1)参数,发现虚拟地址池位图的分配情况正常。
    虚拟地址池位图的分配情况正常
  2. 虚拟地址池位图的分配情况正常即说明vaddr_get()函数运行正常,因此,按照分配内存三大步,接下来我们去查看物理内存页的分配情况。
  3. malloc_page()函数中,进入while(cnt-- > 0){}循环后,第一次调用palloc()函数分配物理内存页,利用x/10 0xc009a000命令观察到物理内存池位图中分配了一页(即置1)。
    成功分配一页物理内存
  4. 成功分配到虚拟地址及物理页地址后,接下来函数page_table_add()将两类地址通过页目录、页表建立映射关系。
  5. 注意,我们的错误便出现在page_table_add()函数中,因此要开始重点分析了。此时向page_table_add()函数传入的两个地址参数值分别如下:
    1. 虚拟页地址:0xc0100000
    2. 物理页地址:0x200000
  6. 根据计算,vaddr的pde和pte分别为:pde = 0xfffffc00pte = 0xfff00000
  7. 需要注意的是,上述两个计算值均为指针类型,即其值均为虚拟地址。,因此,pde虚拟地址的低12位即为相对页目录表物理地址的偏移量,偏移量为0xc00,而页目录表的物理地址为0x100000,因此,pde指针对应的物理地址为0x100000 + 0xc00 = 0x100c00,利用xp 0x100c00即可查看该物理地址处的数据,结果如下
    物理地址0x100c00处四字节的数据
  8. 目前来看,该页目录项中是有数据的(暂且不论是脏数据还是正确的页表映射),且其最低位为1,即代表该PDE映射的页表存在。因此,page_table_add()函数中的if(*pde & 0x00000001)条件判断能够顺利通过。本次错误pte_repeate即发生在该条件判断内,下面继续分析。
  9. 在上文中,我们计算出的pte值为pte = 0xfff00000,经过从虚拟地址到物理地址的转换,该pte指针指向的是第0x300个页目录项中映射的页表的第一个页表项的物理地址
  10. 上文中我们已获取到第0x300个页目录项中的内容,即物理地址0x100c00处四字节的数据,根据页目录项的格式,我们提取出的该目录项映射的页表的高3212位物理地址**,将其低110位补零后得到值为0x101000,由于我们存储页表的位置位于物理地址0x101000以上,因此,该页目录项中的页表物理地址即为第一个页表物理地址”**。
    页目录项及页表项结构
    初始化后的虚拟空间与物理空间的映射情况
  11. 使用命令xp 0x101000查看该页表的第一个页表项中数据,结果如下:
    第一个PT的第一个PTE内容
  12. 可以看到,虽然页表项最低为为1,但其中并不含有物理页的地址。
  13. 我们将PNIC()改为put_str()函数,观察该页表项内容是否会被正常覆盖,结果如下:
    引发page-fault故障
  14. 使用命令xp 0x101000查看该页表的第一个页表项中数据,结果如下:
    尽管引发了page-fault故障,但第一个PT的第一个PTE内容被正常覆盖修改
  15. 现在我们需要探究一下,成功建立虚拟地址与物理地址的映射后,为什么会发生page-fault故障
  16. 调用page_table_add()函数的是malloc_page()函数,因此,我们在malloc_page()函数中的page_table_add()函数调用返回后的位置加一个ASSERT(1==2)断言,用于判断错误究竟是由这两个函数中的哪一个引发,运行结果如下:
    使用ASSERT断言进行错误定位
  17. 由此可以看出,page_table_add()函数没有在运行中产生问题,因此,我们将引发page-fault故障的原因定位在malloc_page()函数。
  18. malloc_page()函数的上级是get_kernel_pages()函数,利用同样的调试方法,我们将引发page-fault故障的原因定位在get_kernel_pages()函数。
    使用ASSERT断言进行错误定位
  19. 经过排查main.c后,我们将引发page-fault故障的原因最终定位在get_kernel_pages()函数中的memset()函数内的while循环上。
  20. 我们由于该循环用作分配的内存初始化,因此先将该while循环注释掉,观察运行结果,结果如下:
    注释掉while循环后的运行结果
  21. 程序虽然顺利返回了分配到的虚拟地址,但这可以说明页表的映射建立成功了吗,答案是否定的,我们需要使用info tab以及page 0xc0100000命令查看页表的映射情况,结果如下:
    当前虚拟地址与物理地址的映射情况
  22. 我们发现,当前虚拟地址0xc0100000对应的页表项为空,且虚拟地址的低端4KB映射到我们分配好的物理地址上去了。
    虚拟地址的低端4KB映射到我们分配好的物理地址上去了
  23. 这就说明,我们在建立映射关系时,页表物理地址的选择可能出现了问题,因此我们检查pte指针,接着发现,在计算pte指针时,PTE_IDX(addr)宏的移位出现了问题,即((addr & 0x003ff000) >> 22)应当为((addr & 0x003ff000) >> 12)
  24. 经过修正后,我们将memset函数中对while循环的注释撤销掉,接着运行程序,结果如下:
    成功返回分配的虚拟地址
    虚拟地址与物理地址的映射结果正常且页表初始化正常
  25. 至此,我们顺利解决了“pte repeate”的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/867669.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

信息安全数学基础-期末(第八章)

群 定义 半群的定义:设S是一个具有结合法的非空集合.如果S中有一个元素e;使得对S中所有元素a,都有 ea=ae=a. 单位元的定义: 性质:设 S是一个有单位元的半群, 则对 S 中的任意可逆元 a, 其逆元 a 是唯的 群的定义: 子群 定义: 同态和同构 定义: 单射、满射、双射: 单射确…

Python/Conda环境配置

Python/conda环境配置 需用: Anaconda Pycharm 均在:U23\00公共空间\软件安装包\Python 步骤 1.安装Anaconda (最好安装在英文路径下,避免不必要的问题) 注意:一定要勾选红框选项!2.打开命令窗 开始--Anaconda—Anaconda Prompt (Anaconda) 初始环境为--base 3.创建环…

Mac电脑必备的菜单栏管理软件 Bartender 5

Mac电脑必备的菜单栏管理软件 Bartender 5 介绍 Bartender 5,是一款菜单栏管理软件,可以帮助用户隐藏、组织和自定义Mac菜单栏中的图标和通知。使用Bartender 5,用户可以将不常用的图标隐藏起来,使菜单栏保持整洁,并只显示重要的通知和信息。此外,Bartender 5还支持自定义…

2024年总结及2025年目标之关键字【稳进】

2024年总结及2025年目标之关键字【稳进】1. 感受 时光荏苒,都731天(2年时间)下来了,从第一年的【坚持】,到第二年的【提速】,定目标,现在回头看,还是那句话【事非经过不知难】,那又怎么样呢,再难不是也过来了吗:D,接下来就是【而今迈步从头越】!读书时间大增,尤其…

现货黄金

可能WXY反弹 短期见顶了 2695-2700阻力 支撑2665-2670

深入解析 Spring AI 系列:以OpenAI与Moonshot案例为例寻找共同点

今天,我们将重点探讨对接的业务逻辑。为了帮助大家更直观地掌握其中的规律性,我将通过对比OpenAI与《月之暗面》中的Moonshot两个案例来阐述这一点。通过这样的对比,大家可以更清晰地看到,这些对接业务的整体框架其实非常相似。换句话说,我们要做的工作只是其中的一小部分…

硬盘检测工具|数据恢复

硬盘检测工具设置 # 在settings中开启如下配置,而后关闭数据恢复

VMware ESXi 8.0U3c macOS Unlocker OEM BIOS Huawei (华为) 定制版

VMware ESXi 8.0U3c macOS Unlocker & OEM BIOS Huawei (华为) 定制版VMware ESXi 8.0U3c macOS Unlocker & OEM BIOS Huawei (华为) 定制版 ESXi 8.0U3c 标准版,Dell (戴尔)、HPE (慧与)、Lenovo (联想)、Inspur (浪潮)、Cisco (思科)、Hitachi (日立)、Fujitsu (富士…

【Go编程】流程控制

一、流程控制的作用: 流程控制语句是用来控制程序中各语句执行顺序的语句,可以把语句组合成能完成一定功能的小逻辑模块。 二、控制语句的分类: 控制语句分为三类:顺序、选择和循环。 “顺序结构”代表“先执行a,再执行b”的逻辑。 “条件判断结构”代表“如果…,则…”的…

在CDN上搭建支持反向代理的C2服务器(下)

免责声明: 本文技术只做研究之用,禁止用来从事非法用途,如有使用文章中的技术从事非法活动,一切后果由使用者自负,与作者无关。一、摘要 在上一篇文章中, 完成了Microsoft Azure 上的环境准备工作, 已经成功安装并配置了用于 Nginx 反向代理的虚拟机(VM)和用于 Cobalt Strike…

在CDN上搭建支持反向代理的C2服务器(上)

免责声明: 本文技术只做研究之用,禁止用来从事非法用途,如有使用文章中的技术从事非法活动,一切后果由使用者自负,与作者无关。一、摘要 在本文中,将探讨在微软Azure的CDN网络中使用C2域名和Nginx作为反向代理来构建一个红队基础设施。内容包括:C2域的选择和DNS配置、Cobalt St…

换个 AI 写

被遗忘,无法参与,无法长时间停课,对学校或教练组心存疑虑。每月的考试成绩。为了奥运会,并花费了无法停课的时间,以换取比赛课程的月度考试成绩。他们可以通过宣传来间接提高成绩。在未来的职业发展中,她取得了优异的成绩。缺乏时间,对江平在奥运会上取得优异成绩的怀疑…