从零实现一个操作系统-day14

虚拟内存管理

虚拟内存就是对每一个进程而言,对它来说它认为它独占所有4G内存,进程内的地址就是以这4G的虚拟内存来表示的,当要执行时,cpu通过分段机制和分页机制将虚拟地址转换成物理内存地址进行访问。同时一个进程也不是所有的页都在内存中,只有部分在内存中,当需要的页不在内存时产生一个缺页中断,然后进行调度,将需要的页调入内存

仿照linux的设计,对于一个进程的4G虚拟空间3G-4G的空间给系统内核,0-3G给用户程序,现在要将内核映射到虚拟地址空间的3G-4G,但是映射完加载内核就需要页表来指示正式的物理内存地址,但是内核不加载就没有页表,所有需要一个临时的页表

内核的映射

修改链接器的脚本script/kernel.ld

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/*
* * kernel.ld -- 针对 kernel 格式所写的链接脚本
* */

ENTRY(start)
SECTIONS
{
PROVIDE( kern_start = 0xC0100000);
/* 段起始位置 */
. = 0x100000;
.init.text :
{
*(.init.text)
. = ALIGN(4096);
}
.init.data :
{
*(.init.data)
. = ALIGN(4096);
}
. += 0xC0000000;
.text : AT(ADDR(.text) - 0xC0000000)
{
*(.text)
. = ALIGN(4096);
}

.data : AT(ADDR(.data) - 0xC0000000)
{
*(.data)
*(.rodata)
. = ALIGN(4096);
}

.bss : AT(ADDR(.bss) - 0xC0000000)
{
*(.bss)
. = ALIGN(4096);
}

.stab : AT(ADDR(.stab) - 0xC0000000)
{
*(.stab)
. = ALIGN(4096);
}

.stabstr : AT(ADDR(.stabstr) - 0xC0000000)
{
*(.stabstr)
. = ALIGN(4096);
}
PROVIDE( kern_end = . );
/DISCARD/ : { *(.comment) *(.eh_frame) }
}

第8行修改了内核的加载地址为3G,然后新增的两个.init段放临时页表和函数,这两个段放在0x100000处给grub加载,然后将当前地址加上0xC0000000的偏移量
后面的部分和原来的区别就是加了AT(ADDR(.xxxx) - 0xC0000000)这些,这些是指明区段所载入内存的实际地址,所以将当前偏移量减去0xC0000000就是实际加载地址
链接器修改了,相应的其他代码也要修改
boot/boot.s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
......

[BITS 32] ; 所有代码以 32-bit 的方式编译
section .init.text ; 临时代码段从这里开始

; 在代码段的起始位置设置符合 Multiboot 规范的标记

dd MBOOT_HEADER_MAGIC ; GRUB 会通过这个魔数判断该映像是否支持
dd MBOOT_HEADER_FLAGS ; GRUB 的一些加载时选项,其详细注释在定义处
dd MBOOT_CHECKSUM ; 检测数值,其含义在定义处

[GLOBAL start] ; 向外部声明内核代码入口,此处提供该声明给链接器
[GLOBAL mboot_ptr_tmp] ; 向外部声明 struct multiboot * 变量
[EXTERN kern_entry] ; 声明内核 C 代码的入口函数

start:
cli ; 此时还没有设置好保护模式的中断处理,要关闭中断
; 所以必须关闭中断
mov [mboot_ptr_tmp], ebx ; 将 ebx 中存储的指针存入全局变量
mov esp, STACK_TOP ; 设置内核栈地址
and esp, 0FFFFFFF0H ; 栈地址按照字节对齐16
mov ebp, 0 ; 帧指针修改为 0
call kern_entry ; 调用内核入口函数
stop:
hlt ; 停机指令,可以降低 CPU 功耗
jmp stop ; 到这里结束,关机什么的后面再说

;-----------------------------------------------------------------------------
section .init.data ; 开启分页前临时数据段
stack: times 1024 db 0 ; 临时内核栈
STACK_TOP equ $-stack-1 ; 内核栈顶,$ 符指代是当前地址
mboot_ptr_tmp: dd 0 ;临时的全局multiboot结构体指针

第五行修改代码段从.init.text开始,同时指定kern_entry()函数在代码段.init.text处,并且在该函数中定义临时页表,切换到高虚拟地址的kern_init()执行,并且切换内核栈和multiboot结构体指针
修改include/pmm.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef INCLUDE_PMM_H
#define INCLUDE_PMM_H

#include "multiboot.h"

// 内核文件在内存中的起始和结束位置
// 在链接器脚本中要求链接器定义
extern uint8_t kern_start[];
extern uint8_t kern_end[];
extern uint32_t phy_mem_count;//动态分配的物理内存总数

#define PMM_MAX_SIZE 0x20000000//规定最大的物理内存为512MB
#define PMM_PAGE_SIZE 0x1000 //一页的大小为4KB
#define PAGE_MAX_SIZE (PMM_MAX_SIZE/PMM_PAGE_SIZE)//最多的物理页面的数量
#define STACK_SIZE 8192//线程栈的大小
#define PHY_PAGE_MASK 0xFFFFF000//页掩码按照 4096 对齐地址

//打印物理内存布局
void show_memory_map();

void init_pmm();//初始化内存布局
uint32_t pmm_alloc_page();//申请一页物理页,返回该页的地址
void pmm_free_page(uint32_t p);//释放申请的内存
#endif// INCLUDE_PMM_H

修改init/entry.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include "console.h" 
#include "timer.h"
#include "debug.h"
#include "gdt.h"
#include "idt.h"
#include "pmm.h"
#include "vmm.h"

//内核初始化函数
void kern_init();
// 开启分页机制之后的 Multiboot 数据指针
multiboot_t *glb_mboot_ptr;
// 开启分页机制之后的内核栈
char kern_stack[STACK_SIZE];

// 内核使用的临时页表和页目录
// 该地址必须是页对齐的地址,内存 0-640KB 肯定是空闲的
__attribute__((section(".init.data"))) pgd_t *pgd_tmp = (pgd_t *)0x1000;
__attribute__((section(".init.data"))) pgd_t *pte_low = (pgd_t *)0x2000;
__attribute__((section(".init.data"))) pgd_t *pte_hign = (pgd_t *)0x3000;
// 内核入口函数
__attribute__((section(".init.text"))) void kern_entry()
{
pgd_tmp[0] = (uint32_t)pte_low | PAGE_PRESENT | PAGE_WRITE;
pgd_tmp[PGD_INDEX(PAGE_OFFSET)] = (uint32_t)pte_hign | PAGE_PRESENT |PAGE_WRITE;
// 映射内核虚拟地址 4MB 到物理地址的前 4MB
int i;
for (i = 0; i < 1024; i++) {
pte_low[i] = (i << 12) | PAGE_PRESENT | PAGE_WRITE;
}
// 映射 0x00000000-0x00400000 的物理地址到虚拟地址 0xC0000000-0xC0400000
for (i = 0; i < 1024; i++) {
pte_hign[i] = (i << 12) | PAGE_PRESENT | PAGE_WRITE;
}
// 设置临时页表
asm volatile ("mov %0, %%cr3" : : "r" (pgd_tmp));
uint32_t cr0;
// 启用分页,将 cr0 寄存器的分页位置为 1 就好
asm volatile ("mov %%cr0, %0" : "=r" (cr0));
cr0 |= 0x80000000;
asm volatile ("mov %0, %%cr0" : : "r" (cr0));
// 切换内核栈
uint32_t kern_stack_top = ((uint32_t)kern_stack + STACK_SIZE) & 0xFFFFFFF0;
asm volatile ("mov %0, %%esp\n\t" "xor %%ebp, %%ebp" : : "r" (kern_stack_top));
// 更新全局 multiboot_t 指针
glb_mboot_ptr = mboot_ptr_tmp + PAGE_OFFSET;
// 调用内核初始化函数
kern_init();
}
void kern_init()
{
init_debug();
init_gdt();
init_idt();
console_clear();

printk_color(rc_black, rc_green, "Hello, OS kernel!\n");
init_timer(100);
//开启中断
//asm volatile("sti");
printk("kernel in memory start: 0x%08X\n", kern_start);
printk("kernel in memory end: 0x%08X\n", kern_end);
printk("kernel in memory used: %d KB\n\n", (kern_end - kern_start +1023) / 1024);
show_memory_map();
init_pmm();

printk_color(rc_black, rc_red, "\nThe Count of Physical Memory Page is: %u\n\n", phy_mem_count);

uint32_t allc_addr = NULL;
printk_color(rc_black, rc_light_brown , "Test Physical Memory Alloc :\n");
allc_addr = pmm_alloc_page();
printk_color(rc_black, rc_light_brown , "Alloc Physical Addr: 0x%08X\n",allc_addr);
allc_addr = pmm_alloc_page();
printk_color(rc_black, rc_light_brown , "Alloc Physical Addr: 0x%08X\n",allc_addr);
allc_addr = pmm_alloc_page();
printk_color(rc_black, rc_light_brown , "Alloc Physical Addr: 0x%08X\n",allc_addr);
allc_addr = pmm_alloc_page();
printk_color(rc_black, rc_light_brown , "Alloc Physical Addr: 0x%08X\n",allc_addr);
while (1)
{
asm volatile ("hlt");
}
}

把原来的内核入口函数改成了kern_init(),kern_entry()函数里定义了临时页表并且开启分页机制,然后修改了内核栈到虚拟地址,然后调用kern_init();attribute((section(“.init.text”)))是gcc提供的指定函数或数据的存储区段
一点点来看:
首先定义的pgt_temp是临时页目录(临时二级页表),pgd_t这个数据类型在vmm.h内定义是uint32_t,它放在.init.data段,起始地址是0x1000
pte_low是在低端地址的页表,pte_high是在3g以上高端地址的页表,起始地址分别为0x2000,和0x3000,所以临时页目录的大小就是0x2000-0x1000=0x1000是4KB,低端页表因为只要映射内核的4MB地址所以一页页表就够了。这些都在临时数据段(.init.data)
kern_entry()函数放在临时代码段(.init.text),该函数的加载地址就是0x100000(在链接器脚本中定义的),该函数第一行代码就是将页目录的第一项进行设置
页目录和页表项的格式如下

将页目录的第一项映射到低端页表,PAGE_PRESENT为0x1代表存在,PAGE_WRITE为0x2,这样构造的页目录第一项就为0x1003
然后函数第二行就是将第一张高端页表映射到页目录中,PGD_INDEX(PAGE_OFFSET)是获取地址的页目录号,因为一个32位虚拟地址的高10位是页目录中偏移,所以#define PGD_INDEX(x) (((x) >> 22) & 0x3FF)获取虚拟地址的高10位就是页目录中的偏移,这里就是将高端地址的第一位也就是3G获取它的页目录号,然后继续构造页目录项
然后是映射内核虚拟地址 4MB 到物理地址的前 4MB,4MB也就是1024页,页号左移12位刚好就是每一页的起始地址
映射 0x00000000-0x00400000 的物理地址到虚拟地址 0xC0000000-0xC0400000,也是4MB,对于二级页表来说更上面是一样的,它映射0xC0000000是通过页目录的偏移实现的
然后往CR3寄存器写入页目录的基址,将CR0寄存器的第31位置为1代表开启分页模式
在kern_entry()定义的页表将0x00000000-0x00400000 的物理地址到虚拟地址 0xC0000000-0xC0400000,同时还将映射内核虚拟地址 4MB 到物理地址的前 4MB,这是因为在进入kern_entry()时还没有开启分页机制,开启分页机制映射0x00000000-0x00400000 的物理地址到虚拟地址 0xC0000000-0xC0400000后在1MB处的kern_entry()函数会出错,所以映射一下低位4MB
更新一下include/multiboot.h

1
2
3
4
5
6
......

// 未开启分页前的multbbot_t 指针
extern multiboot_t *mboot_ptr_tmp;
// 声明全局的 multiboot_t * 指针
extern multiboot_t *glb_mboot_ptr;

修改显存地址 drivers/console.c

1
2
3
4
......
#include "vmm.h"
static uint16_t *video_memory= (uint16_t *)(0xB8000+PAGE_OFFSET);//显存的起始地址,每两个字节表>
......

之前的elf_t结构体存储的是低端内存的地址,现在也必须加上页偏移:
kernel/debug/elf.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
......
// 从 multiboot_t 结构获取信息ELF
elf_t elf_from_multiboot(multiboot_t *mb)
{
int i;
elf_t elf;
elf_section_header_t *sh = (elf_section_header_t *)mb->addr;

uint32_t shstrtab = sh[mb->shndx].addr;
for (i = 0; i < mb->num; i++) {
const char *name = (const char *)(shstrtab + sh[i].name)+ PAGE_OFFSET;
// 在 GRUB 提供的 multiboot 信息中寻找
// 内核 ELF 格式所提取的字符串表和符号表
if (strcmp(name, ".strtab") == 0) {
elf.strtab = (const char *)sh[i].addr+PAGE_OFFSET;
elf.strtabsz = sh[i].size;
}
if (strcmp(name, ".symtab") == 0) {
elf.symtab = (elf_symbol_t*)(sh[i].addr+PAGE_OFFSET);
elf.symtabsz = sh[i].size;
}
}
return elf;
}
......

mm/vmm.c实现内核页表和映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include "idt.h"
#include "string.h"
#include "debug.h"
#include "vmm.h"
#include "pmm.h"
//内核页目录
pgd_t pgd_kern[PGD_SIZE] __attribute__ ((aligned(PAGE_SIZE)));
//内核页表
static pte_t pte_kern[PTE_COUNT][PTE_SIZE] __attribute__ ((aligned(PAGE_SIZE)));

void init_vmm ()
{
//0xC0000000在页目录的偏移值
uint32_t kern_pte_first_idx = PGD_INDEX(PAGE_OFFSET);
uint32_t i,j;
for (i=kern_pte_first_idx,j=0;i<kern_pte_first_idx+PTE_COUNT;++i,++j)
{
//将页表映射到页目录,程序内的地址是虚拟地址,所以要减去偏移
pgd_kern[i]=(uint32_t)pte_kern[j]-PAGE_OFFSET|PAGE_PRESENT | PAGE_WRITE;
uint32_t * pte= (uint32_t *)pte_kern;
//映射所有的物理页
for (i=1;i<PTE_SIZE*PTE_COUNT;++i)
{
pte[i]=(i<<12)|PAGE_WRITE|PAGE_PRESENT;
}
//页目录的物理地址
uint32_t pgd_kern_phy_addr = (uint32_t)pgd_kern - PAGE_OFFSET;
// 注册页错误中断的处理函数 ( 14 是页故障的中断号 )
register_interrupt_handler(14, &page_fault);
//切换页表
switch_pgd(pgd_kern_phy_addr);
}
}
void switch_pgd (uint32_t pgd_kern_phy_addr)
{
asm volatile ("mov %0, %%cr3" : : "r" (pgd_kern_phy_addr));
}
//使用 flags 指出的页权限,把物理地址 pa 映射到虚拟地址 va
void map (pgd_t *pgd_now, uint32_t va, uint32_t pa, uint32_t flags)
{
uint32_t pgd_idx=PGD_INDEX (va);
uint32_t pte_idx=PTE_INDEX (va);
pte_t *pte = (pte_t *)(pgd_now[pgd_idx] & PAGE_MASK);
if (!pte)
{
pte=(pte_t*)pmm_alloc_page ();
// 转换到内核线性地址并清 0
pgd_now [pgd_idx]= (uint32_t)pte|PAGE_PRESENT | PAGE_WRITE;
pte = (pte_t *)((uint32_t)pte + PAGE_OFFSET);
bzero(pte, PAGE_SIZE);
}else
{
//转换到内核线性地址
pte = (pte_t *)((uint32_t)pte + PAGE_OFFSET);
}
pte [pte_idx]= (pa&PAGE_MASK)|flags;
// 通知 CPU 更新页表缓存
asm volatile ("invlpg (%0)" : : "a" (va));
}
//取消va虚拟地址的映射
void unmap(pgd_t *pgd_now, uint32_t va)
{
uint32_t pgd_idx=PGD_INDEX (va);
uint32_t pte_idx=PTE_INDEX (va);
pte_t *pte = (pte_t *)(pgd_now[pgd_idx] & PAGE_MASK);
//该页表不在页目录中
if (!pte)
return;
//转换到内核线性地址
pte = (pte_t*)((uint32_t)pte + PAGE_OFFSET);
pte [pte_idx]=0;
// 通知 CPU 更新页表缓存
asm volatile ("invlpg (%0)" : : "a" (va));
}
//获取虚拟地址对应的物理地址,成功获取返回1并将物理地址写入pa,否则返回0
uint32_t get_mapping(pgd_t *pgd_now, uint32_t va, uint32_t *pa)
{
uint32_t pgd_idx=PGD_INDEX (va);
uint32_t pte_idx=PTE_INDEX (va);
pte_t *pte = (pte_t *)(pgd_now[pgd_idx] & PAGE_MASK);
//该页表不在页目录中
if (!pte)
return 0;
//转换到内核线性地址
pte = (pte_t*)((uint32_t)pte + PAGE_OFFSET);
if(pte [pte_idx]!=0&&pa)
{
*pa=pte[pte_idx]&PAGE_MASK;
return 1;
}
return 0;
}

init_vmm()函数跟之前的临时页表的部分差不多,同时注册了一个14号中断函数处理页面出错


map函数是将虚拟地址映射到物理地址,函数前两行是获取虚拟地址对应的页目录偏移和页表偏移,分别是高10位和低10位
然后通过页目录取得二级页表的物理地址,若该页表不存在则申请一页内存,然后映射入页目录,获取到二级页表之后要在函数中访问它需要取得它的虚拟地址,所以加上偏移,然后将对应物理页的地址构造成页表项映射到该二级页表中


unmap函数用于取消映射,直接将对应二级页表的页表项设置为0,因为在构建内核页表的时候没有映射第0页就是方便这时候当NULL


get_mapping函数是获取虚拟地址对应的物理地址成功获取返回1并将物理地址写入pa,否则返回0


一些东西的定义mm/vmm.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#ifndef INCLUDE_VMM_H
#define INCLUDE_VMM_H

#include "types.h"
#include "idt.h"
// 内核的偏移地址
#define PAGE_OFFSET 0xC0000000
/**
* 12 * P−− 位 0 是存在 (Present) 标志,用于指明表项对地址转换是否有效。
* 13 * P = 1 表示有效; P = 0 表示无效。
* 14 * 在页转换过程中,如果说涉及的页目录或页表的表项无效,则会导致一个异常。
* 15 * 如果 P = 0 ,那么除表示表项无效外,其余位可供程序自由使用。
* 16 * 例如,操作系统可以使用这些位来保存已存储在磁盘上的页面的序号。
* 17 */
#define PAGE_PRESENT 0x1

/**
* R/W −− 位 1 是读 / 写 (Read/Write) 标志。如果等于 1 ,表示页面可以被读、写或执行。
* 如果为 0 ,表示页面只读或可执行。
* 当处理器运行在超级用户特权级(级别 0,1 或) 2 时,则 R/W 位不起作用。
* 页目录项中的 R/W 位对其所映射的所有页面起作用。
*/
#define PAGE_WRITE 0x2

/**
* U/S −− 位 2 是用户 / 超级用户 (User/Supervisor) 标志。
* 如果为 1 ,那么运行在任何特权级上的程序都可以访问该页面。
* 如果为 0 ,那么页面只能被运行在超级用户特权级 (0,1 或 2) 上的程序访问。
* 页目录项中的 U/S 位对其所映射的所有页面起作用。
*/
#define PAGE_USER 0x4

// 虚拟分页大小
#define PAGE_SIZE 4096

// 页掩码,用于 4KB 对齐
#define PAGE_MASK 0xFFFFF000

// 获取一个地址的页目录项
#define PGD_INDEX(x) (((x) >> 22) & 0x3FF)

// 获取一个地址的页表项
#define PTE_INDEX(x) (((x) >> 12) & 0x3FF)

// 获取一个地址的页內偏移
#define OFFSET_INDEX(x) ((x) & 0xFFF)

// 页目录数据类型
typedef uint32_t pgd_t;

// 页表数据类型
typedef uint32_t pte_t;

// 页表成员数
#define PGD_SIZE (PAGE_SIZE/sizeof(pte_t))

// 页表成员数
#define PTE_SIZE (PAGE_SIZE/sizeof(uint32_t))

// 映射 512MB 内存所需要的页表数
#define PTE_COUNT 128

// 内核页目录区域
extern pgd_t pgd_kern[PGD_SIZE];

// 初始化虚拟内存管理
void init_vmm();

// 更换当前的页目录
void switch_pgd(uint32_t pd);

// 使用 flags 指出的页权限,把物理地址 pa 映射到虚拟地址 va
void map(pgd_t *pgd_now, uint32_t va, uint32_t pa, uint32_t flags);

// 取消虚拟地址 va 的物理映射
void unmap(pgd_t *pgd_now, uint32_t va);

// 如果虚拟地址 va 映射到物理地址则返回 1
// 同时如果 pa 不是空指针则把物理地址写入 pa 参数
uint32_t get_mapping(pgd_t *pgd_now, uint32_t va, uint32_t *pa);

// 页错误中断的函数处理
void page_fault(pt_regs *regs);

#endif // INCLUDE_VMM_H

实现页错误中断处理函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include "vmm.h"
#include "debug.h"
void page_fault(pt_regs *regs)
{
uint32_t cr2;
asm volatile ("mov %%cr2, %0" : "=r" (cr2));
printk("Page fault at 0x%x, virtual faulting address 0x%x\n", regs->eip,cr2);
printk("Error code: %x\n", regs->err_code);

// bit 0 为 0 指页面不存在内存里
if ( !(regs->err_code & 0x1)) {
printk_color(rc_black, rc_red, "Because the page wasn't present.\n");
}
// bit 1 为 0 表示读错误,为 1 为写错误
if (regs->err_code & 0x2) {
printk_color(rc_black, rc_red, "Write error.\n");
} else {
printk_color(rc_black, rc_red, "Read error.\n");
}
// bit 2 为 1 表示在用户模式打断的,为 0 是在内核模式打断的
if (regs->err_code & 0x4) {
printk_color(rc_black, rc_red, "In user mode.\n");
} else {
printk_color(rc_black, rc_red, "In kernel mode.\n");
}
// bit 3 为 1 表示错误是由保留位覆盖造成的
if (regs->err_code & 0x8) {
printk_color(rc_black, rc_red, "Reserved bits being overwritten.\n");
}
// bit 4 为 1 表示错误发生在取指令的时候
if (regs->err_code & 0x10) {
printk_color(rc_black, rc_red, "The fault occurred during an instruction fetch.\n");
}

while (1);
}

debug

测试一下发现无法运行报错了,然后开始寻找问题,先排查了一遍发现代码没有问题
看报错信息显示如下

发现是在0xc0105000这里出错了
然后我用objdump -h time_kernel得到如下结果

发现多了两个段.text.__x86.get_pc_thunk.ax.text.__x86.get_pc_thunk.bx他们的VMA地址是加了偏移量之后的
然后使用objdump -d time_kernel发现这两个段的函数在分页开启之前就被调用了,查资料知道这两个函数是传递寄存器的值,所以我们要把它们放在分页之前
修改scripts/kernel.ld

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
......
.init.data :
{
*(.init.data)
. = ALIGN(4096);
}
.text.__x86.get_pc_thunk.ax :
{
*(.__x86.get_pc_thunk.ax)
. = ALIGN(4096);
}
.text.__x86.get_pc_thunk.bx :
{
*(.__x86.get_pc_thunk.bx)
. = ALIGN(4096);
}
. = ALIGN(4096);
. += 0xC0000000;
.text : AT(ADDR(.text) - 0xC0000000)
......

在.init.data之后.text之前加入这两个段
现在测试就成功了

本文标题:从零实现一个操作系统-day14

文章作者:

发布时间:2020年05月19日 - 19:05

最后更新:2020年05月28日 - 19:05

原始链接:http://startcraft.cn/post/d8753fac.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------The End-------------