我最近正在尝试自学操作系统并玩弄xv6操作系统进行教学。我使用的版本是来自 GitHub 的 x86 版本。 我一直在做的是在启动系统时尝试使用2级分页。为此,我在 main.c 中创建了页表和页目录,如下所示:
__attribute__((__aligned__(PGSIZE)))
pde_t entrypgdir[NPDENTRIES];
__attribute__((__aligned__(PGSIZE)))
pte_t entrypgtable[NPTENTRIES];
然后在entry.S中,我有一些汇编代码来初始化这两个数组,如下所示:
# The xv6 kernel starts executing in this file. This file is linked with
# the kernel C code, so it can refer to kernel symbols such as main().
# The boot block (bootasm.S and bootmain.c) jumps to entry below.
# Multiboot header, for multiboot boot loaders like GNU Grub.
# http://www.gnu.org/software/grub/manual/multiboot/multiboot.html
#
# Using GRUB 2, you can boot xv6 from a file stored in a
# Linux file system by copying kernel or kernelmemfs to /boot
# and then adding this menu entry:
#
# menuentry "xv6" {
# insmod ext2
# set root='(hd0,msdos1)'
# set kernel='/boot/kernel'
# echo "Loading ${kernel}..."
# multiboot ${kernel} ${kernel}
# boot
# }
#include "asm.h"
#include "memlayout.h"
#include "mmu.h"
#include "param.h"
# Multiboot header. Data to direct multiboot loader.
.p2align 2
.text
.globl multiboot_header
multiboot_header:
#define magic 0x1badb002
#define flags 0
.long magic
.long flags
.long (-magic-flags)
# By convention, the _start symbol specifies the ELF entry point.
# Since we haven't set up virtual memory yet, our entry point is
# the physical address of 'entry'.
.globl _start
_start = V2P_WO(entry)
# Entering xv6 on boot processor, with paging off.
.globl entry
entry:
# Turn on page size extension for 4Mbyte pages
// I comment the following to not use bigger pages
#movl %cr4, %eax
#orl $(CR4_PSE), %eax
#movl %eax, %cr4
# Set page directory
//My assembly code to initialize a page table starts
#set up the first page table page
xor %esi, %esi
1:
movl %esi, %eax
shll $12, %eax
orl $(PTE_P|PTE_W), %eax
movl $(V2P_WO(entrypgtable)), %edi
movl %esi, %ebx
shll $2, %ebx
addl %ebx, %edi
movl %eax, (%edi)
incl %esi
cmpl $1024, %esi
jb 1b
# Set page directory
movl $0, %esi
movl %esi, %ebx
shll $2, %ebx
movl $(V2P_WO(entrypgdir)), %edi
addl %ebx, %edi
movl $(V2P_WO(entrypgtable)), (%edi)
orl $(PTE_P | PTE_W), (%edi)
movl $512, %esi
movl %esi, %ebx
shll $2, %ebx
movl $(V2P_WO(entrypgdir)), %edi
addl %ebx, %edi
movl $(V2P_WO(entrypgtable)), (%edi)
orl $(PTE_P | PTE_W), (%edi)
//My assembly code to initialize a page table ends
movl $(V2P_WO(entrypgdir)), %eax
movl %eax, %cr3
# Turn on paging.
movl %cr0, %eax
orl $(CR0_PG|CR0_WP), %eax
movl %eax, %cr0
# Set up the stack pointer.
movl $(stack + KSTACKSIZE), %esp
# Jump to main(), and switch to executing at
# high addresses. The indirect call is needed because
# the assembler produces a PC-relative instruction
# for a direct jump.
mov $main, %eax
jmp *%eax
.comm stack, KSTACKSIZE
如你所见,基本思想很简单:原来的xv6系统在初始化系统时使用4mb大页面,这样他们只需要页目录就可以将内核代码映射到第一个物理页面。我禁用该选项并使用 4kb 页面,然后相应地创建一个新页表,然后将新页表放入页目录数组中。 如果我的理解正确的话,整个过程应该相当于下面的C代码:
__attribute__((__aligned__(PGSIZE)))
pde_t entrypgdir[NPDENTRIES];
__attribute__((__aligned__(PGSIZE)))
pte_t entrypgtable[NPTENTRIES];
void init_entrypgdir(void) {
for (int i = 0; i < NPTENTRIES; i++) {
entrypgtable[i] = (i << 12) | PTE_P | PTE_W;
}
entrypgdir[0] = (uint)(V2P_WO(entrypgtable)) | PTE_P | PTE_W;
entrypgdir[KERNBASE>>PDXSHIFT] = (uint)(V2P_WO(entrypgtable)) | PTE_P | PTE_W;
}
上面的代码位于main.c中,它与entry.S链接在一起。我已经确认entry.S可以访问我新创建的init_entrypgdir()函数。
现在问题来了:如果我想摆脱自己编写的汇编代码,而使用上面的C代码来初始化xv6怎么办?有没有办法用我的 C 代码替换我的汇编代码并仍然成功初始化操作系统?
我尝试将entry.S重写为:
#include "asm.h"
#include "memlayout.h"
#include "mmu.h"
#include "param.h"
# Multiboot header. Data to direct multiboot loader.
.p2align 2
.text
.globl multiboot_header
multiboot_header:
#define magic 0x1badb002
#define flags 0
.long magic
.long flags
.long (-magic-flags)
# By convention, the _start symbol specifies the ELF entry point.
# Since we haven't set up virtual memory yet, our entry point is
# the physical address of 'entry'.
.globl _start
_start = V2P_WO(entry)
# Entering xv6 on boot processor, with paging off.
.globl entry
entry:
# Turn on page size extension for 4Mbyte pages
// I comment the following to not use bigger pages
#movl %cr4, %eax
#orl $(CR4_PSE), %eax
#movl %eax, %cr4
// see if this works
call V2P_WO(init_entrypgdir)
# Set page directory
movl $(V2P_WO(entrypgdir)), %eax
movl %eax, %cr3
# Turn on paging.
movl %cr0, %eax
orl $(CR0_PG|CR0_WP), %eax
movl %eax, %cr0
# Set up the stack pointer.
movl $(stack + KSTACKSIZE), %esp
# Jump to main(), and switch to executing at
# high addresses. The indirect call is needed because
# the assembler produces a PC-relative instruction
# for a direct jump.
mov $main, %eax
jmp *%eax
.comm stack, KSTACKSIZE
然后,在 GDB 中,我在第 108、110 和 112 行创建了断点。
正如您在屏幕截图中看到的,断点 112 永远不会被命中,并且 qemu 监视器会像这样冻结
我想知道为什么112号线从来没有运行过?我的for循环有什么问题吗?
entry
开始时,电脑是0x10_000c
(地址是我编译的kernel
的elf入口,可能和其他的不一样)-fno-pic
中的CFLAGS
中有Makefile
,这意味着编译出来的C代码必须使用绝对地址。kernel.ld
指定所有部分都从 0x8010_0000
开始。entry
有两个重要的功能,初始化页表和从0x10_0000
跳转到0x8010_0000
。注意,elf文件kernel
被读取到物理地址0x10_0000
,在设置页表之前,0x8010_0000
是一个无效地址。我尝试过这样的可能方法:
void init_entrypgdir(void) {
register pde_t volatile *entrypgdir = (pde_t *) V2P_WO(entrypgdir);
register pte_t volatile *entrypgtable = (pte_t *) V2P_WO(entrypgtable);
for (int i = 0; i < NPTENTRIES; i++) {
entrypgtable[i] = (i << 12) | PTE_P | PTE_W;
}
entrypgdir[0] = (uint)(entrypgtable) | PTE_P | PTE_W;
entrypgdir[KERNBASE>>PDXSHIFT] = (uint)(entrypgtable) | PTE_P | PTE_W;
}
register
建议编译器将此变量放入寄存器中,volatile *
建议编译器它是内存访问,不要优化为空。你可以尝试去掉这两个关键字,你会发现该函数什么也没做。
而且不幸的是,通过这次更新,页表的初始化没有问题,但是调用时出现问题
userinit
:(