内容发布更新时间 : 2024/11/18 18:47:37星期一 下面是文章的全部内容请认真阅读。
seta20.1: seta20.2: movb $0xdf, %al outb %al, $0x60 # 打开A20 inb $0x64, %al testb $0x2, %al jnz seta20.2 # 等待8042键盘控制器不忙 movb $0xd1, %al outb %al, $0x64 # 发送写8042输出端口的指令 inb $0x64, %al testb $0x2, %al jnz seta20.1 # 等待8042键盘控制器不忙 1、为何开启A20,以及如何开启A20?
当 A20 地址线控制禁止时,则程序就像在 8086 中运行,1MB 以上的地是不可访问的。 在保护模式下 A20 地址线控制是要打开的。为了使能所有地址位的寻址能力,必须向键盘控 制器 8042 发送一个命令。键盘控制器 8042 将会将它的的某个输出引脚的输出置高电平,作 为 A20 地址线控制的输入。一旦设置成功之后,内存将不会再被绕回(memory wrapping),这 样我们就可以寻址 intel 80286 CPU 支持的 16M 内存空间,或者是寻址 intel 80386 以上级别 CPU 支持的所有 4G 内存空间了。 2、如何初始化GDT表?
lgdt gdtdesc #把gdt表的起始位置和界限装入GDTR寄存器 movl %cr0, êx orl $CR0_PE_ON, êx movl êx, %cr0 #把保护模式位开启 复习一下cr0寄存器,它的第0位为保护模式位PE:设置 PE 将让处理器工
gdt: SEG_NULLASM # 空段 # 代码段(起始地址,大小) # 数据段(起始地址,大小) SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) SEG_ASM(STA_W, 0x0, 0xffffffff) 作在保护模式下。复位PE将返回到实模式工作。此外,gdtdesc指出了全局描述符表在符号gdt处,如下
上面四句话实现了打开保护模式位。 3、如何使能进入保护模式? 通过长跳转指令
ljmp $PROT_MODE_CSEG, $protcseg 进入了保护模式。
进入保护模式之后还有一个步骤:把所有的数据段寄存器指向上面的GDT描述符表中的数据段(0x10)。
练习四、分析bootloader加载ELF格式的OS的过程。
在proj2中,增加主要增加了对磁盘简单的读取函数readsect() readseg()),
以及对ELF头的解析(ELF头结构在ELF.h文件中)。
static void readseg(uintptr_t va, uint32_t count, uint32_t offset) { } //实现了从kernel复制8个扇区(包含ELF头,共4KB)到0x10000 uintptr_t end_va = va + count; //指针移到边界 va -= offset % SECTSIZE; // 计算开始读的第一个扇区号 uint32_t secno = (offset / SECTSIZE) + 1; //逐个读取扇区 for (; va < end_va; va += SECTSIZE, secno ++) { } readsect((void *)va, secno); 疑问:为什么要把ELF头读到0X10000?从哪读?
以下为一些硬件端口上实现读取一个扇区到内存0x10000。
/* readsect - read a single sector at @secno into @dst */ static void readsect(void *dst, uint32_t secno) { } // wait for disk to be ready waitdisk(); outb(0x1F2, 1); // count = 1 outb(0x1F3, secno & 0xFF); outb(0x1F4, (secno >> 8) & 0xFF); outb(0x1F5, (secno >> 16) & 0xFF); outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0); outb(0x1F7, 0x20); waitdisk(); // read a sector insl(0x1F0, dst, SECTSIZE / 4); // cmd 0x20 - read sectors // wait for disk to be ready Readsect()函数的工作大致是:
1. 读 I/O 地址 0x1f7,等待磁盘准备好;
2. 写 I/O 地址 0x1f2~0x1f5,0x1f7,发出读取第 offseet 个扇区处的磁盘数据的命令;
3. 读 I/O 地址 0x1f7,等待磁盘准备好;
4. 连续读 I/O 地址 0x1f0,把磁盘扇区数据读到指定内存。
练习五、实现函数调用堆栈跟踪函数(需要编程)
可以获知栈底是在高地址,栈顶在低地址,压栈的次序为:参数(编程的时
uint32_t ebp = read_ebp(), eip = read_eip(); int i, j; for (i = 0; ebp != 0 && i < STACKFRAME_DEPTH; i ++) { cprintf(\, ebp, eip); uint32_t *args = (uint32_t *)ebp + 2; for (j = 0; j < 4; j ++) { cprintf(\, args[j]); } cprintf(\); print_debuginfo(eip - 1); eip = ((uint32_t *)ebp)[1]; ebp = ((uint32_t *)ebp)[0]; 候默认有四个参数)、返回地址、上一层EBP、局部变量。 注:read_ebp()和readeip()都是通过内联汇编实现的。
Eip-1是为了能找到上一条指令
结果图:
练习六、完善中断初始化和处理 (需要编程)
[练习6.1] 中断向量表中一个表项占多少字节?其中哪几位代表中断处理代码的入口?
中断向量表一个表项占用8字节,其中2-3字节是段选择子,0-1字节和6-7字节拼成位移,两者联合便是中断处理程序的入口地址。