【发布时间】:2020-08-04 05:21:16
【问题描述】:
我尝试实现自己的操作系统,现在我尝试实现分页机制。
我创建了一个页面目录,并创建了内核代码的标识映射。
但是,在存储第一个页表的物理地址并启用分页后,我的代码似乎丢失了 - 意思是,虚拟地址没有映射到物理地址,或者映射到错误的地址。
我可以在 GDB 中看到这种行为:我的代码操作码变为 add [eax], al。
我的结构是:
#define MAX_PAGES_PER_TABLE 1024
#define MAX_TABLES_PER_DIR 1024
typedef struct {
uint32_t present : 1; // Page present in memory
uint32_t rw : 1; // Read-only if clear, readwrite if set
uint32_t user : 1; // Supervisor level only if clear
uint32_t accessed : 1; // Has the page been accessed since last refresh?
uint32_t dirty : 1; // Has the page been written to since last refresh?
uint32_t unused : 7; // Amalgamation of unused and reserved bits
uint32_t frame : 20; // Frame address (shifted right 12 bits)
} page_t;
typedef struct {
page_t pages[MAX_PAGES_PER_TABLE];
} page_table_t;
typedef struct {
page_table_t* page_tables[MAX_TABLES_PER_DIR]; // Array of pointers to pagetables.
/*
Array of pointers to the pagetables above, but gives their *physical*
location, for loading into the CR3 register.
*/
uint32_t page_tables_physical[MAX_TABLES_PER_DIR];
/*
The physical address of page_tables_physical. This comes into play
when we get our kernel heap allocated and the directory
may be in a different location in virtual memory.
*/
uint32_t physical_address;
} page_directory_t;
这是我的启用分页代码:
page_directory_t* current_page_dir;
void switch_page_directory(page_directory_t* new_page_directory)
{
current_page_dir = new_page_directory;
// let the cpu know the physical address of the page tables
asm volatile("mov %0, %%cr3" : : "r"(&new_page_directory->page_tables_physical));
uint32_t cr0;
asm volatile("mov %%cr0, %0" : "=r"(cr0));
cr0 |= 0x80000000; // set the PG flag of cr0 - enable paging
asm volatile("mov %0, %%cr0" : : "r" (cr0));
}
我仔细检查了页面目录看起来没问题,但也许我错了,所以这里是身份映射代码:
void alloc_frame(page_t *page, int is_kernel, int is_writeable)
{
if (page->frame != 0) {
// there is already a frame associated with the page
return;
}
uint32_t first_free_frame_address = first_not_set(frames);
if (first_free_frame_address == frames.len + 1) {
panic("no free pages");
}
set_bit(first_free_frame_address, frames);
page->frame = first_free_frame_address * ALIGNMENT;
page->present = 1;
page->rw = is_writeable ? 1: 0;
page->user = is_kernel ? 0 : 1;
}
page_t* get_page(uint32_t address, int create, page_directory_t* dir)
{
// when dividing the address by alignment, the index of the page is received
uint32_t address_index = address / ALIGNMENT;
// according to the index of the address,
// the index of the table, and the index of the page in the table are calculated
uint32_t table_index = address_index / MAX_PAGES_PER_TABLE;
uint32_t page_index = address_index % MAX_PAGES_PER_TABLE;
if (dir->page_tables[table_index]) {
// page table exists
return &dir->page_tables[table_index]->pages[page_index];
} else if (create) {
// page table doesn't exist - creating it
uint32_t physical_address;
dir->page_tables[table_index] = (page_table_t*) kmalloc_internal(sizeof(page_table_t), 1, &physical_address);
// set a first entry in the table
memory_set((uint8_t*)dir->page_tables[table_index], 0, ALIGNMENT);
// give the first page, the attributes: Present, Read/Write, Kernel page
physical_address |= 0x3;
// save the physical address of the table
dir->page_tables_physical[table_index] = physical_address;
return &dir->page_tables[table_index]->pages[page_index];
} else {
//page table doesn't exist - page cannot be retrieved
return 0;
}
}
void initialize_paging()
{
initialize_frames();
page_directory_t* page_dir = (page_directory_t*) kmalloc(sizeof(page_directory_t));
memory_set((uint8_t*) page_dir, 0, sizeof(page_directory_t));
// set the physical addresses of the page tables to 0 with attributes:
// kernel tables, rw, not present
int j;
uint8_t* tmp = (uint8_t*) page_dir->page_tables_physical;
for (j = 0; j < 1024; j++) {
memory_set(tmp, 2, 1);
tmp++;
memory_set(tmp, 0, 3);
tmp += 3;
}
current_page_dir = page_dir;
// We need to identity map (phys addr = virt addr) from
// 0x0 to the end of used memory, so we can access this
// transparently, as if paging wasn't enabled.
// NOTE that we use a while loop here deliberately.
// inside the loop body we actually change free_physical_address
// by calling kmalloc(). A while loop causes this to be
// computed on-the-fly rather than once at the start.
uint32_t free_physical_address = get_current_physicall_address();
uint32_t i = 0;
while (i <= free_physical_address)
{
// Kernel code is readable but not writeable from userspace.
alloc_frame(get_page(i, 1, page_dir), 1, 1);
i += ALIGNMENT;
// get_page() may allocate more space for new tables
free_physical_address = get_current_physicall_address();
}
代码很多,所以我总结一下:alloc_frame() 找到下一个空闲帧 - 使用 bitset(我没有将 bitset 代码放在这里,因为它可以正常工作。get_page() ,返回与给定地址对应的页条目,如果不存在则创建页表。initialize_paging() 创建页目录并进行身份映射。
以下函数不在此处,但这是它们的摘要:kmalloc() 分配与 4096 对齐的内存。get_current_physicall_address() 返回 kmalloc 将在下次运行时分配的地址。memory_set()是我对memset() 的实现。
任何帮助将不胜感激!
提前致谢!
【问题讨论】:
-
您没有显示足够的代码,但是由于您声称您开始执行看起来像
add [eax], al的指令,我假设您将带有代码的页面映射到错误的物理地址并开始执行内存用零字节填充。当执行转换为 32 位指令add [eax], al时,它恰好是零字节。至于为什么你有错误的映射,我无法从你的代码中看出它不是minimal reproducible example -
@MichaelPetch 我用更多代码示例更新了我的问题。不幸的是,它并不是最小的,因为我不知道我的代码中是什么导致了问题。无论如何,如果您能看一下,我将不胜感激,也许您会发现我做错了什么。在此先感谢:)
-
您是否在 BOCHS 中单步执行了您的代码?它有一个内置的调试器,可以帮助你解码一些 GDT 或其他结构,以确保你设置了你的意思。
-
@PeterCordes 我使用 QEMU,但我会尝试使用 BOCHS,如果你说它有帮助的话
-
Bochs 可以根据当前页表轻松告诉您哪些物理地址映射到给定的线性地址(命令
page)。它还有一个方便的页面映射转储(info tab命令)
标签: gcc x86 paging inline-assembly osdev