标签:
---恢复内容开始---
以Intel 80386为例,计算机加电后,CPU从物理地址0xFFFFFFF0(由初始化的CS:EIP确定,此时CS和IP的值分别是0xF000和0xFFF0))开始执行。在0xFFFFFFF0这里只是存放了一条跳转指令,通过跳转指令跳到BIOS例行程序起始点。BIOS做完计算机硬件自检和初始化后,会选择一个启动设备(例如软盘、硬盘、光盘等),并且读取该设备的第一扇区(即主引导扇区或启动扇区)到内存一个特定的地址0x7c00处,然后CPU控制权会转移到那个地址继续执行。至此BIOS的初始化工作做完了,进一步的工作交给了ucore的bootloader。
简单来说,就是
加电-->CPU执行内存地址为0xFFFFFFF0的指令(一条跳转指令)--->跳到BIOS程序起始点,执行BIOS程序(功能:计算机硬件自检和初始化),自检等完成后--->选择一启动设备,并读取第一扇区(主引导扇区)到内存的0x7c00处--->BIOS所有工作完成后, CPU执行内存的0x7c00中的程序,即bootloader。
BIOS将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader。bootloader完成的工作包括:
对应其工作的实现文件在lab1中的boot目录下的三个文件asm.h、bootasm.S和bootmain.c。
bootasm.S
1 .code16 # Assemble for 16-bit mode
2 cli # Disable interrupts
3 cld # String operations increment
4
5 # Set up the important data segment registers (DS, ES, SS).
6 xorw %ax, %ax # Segment number zero
7 movw %ax, %ds # -> Data Segment
8 movw %ax, %es # -> Extra Segment
9 movw %ax, %ss # -> Stack Segment
注释:.code16表示为16位的实模式,cli表示屏蔽系统中断 6-9为清空ds , es , ss 等段寄存器内容。
1 # Enable A20: 2 # For backwards compatibility with the earliest PCs, physical 3 # address line 20 is tied low, so that addresses higher than 4 # 1MB wrap around to zero by default. This code undoes this. 5 seta20.1: 6 inb $0x64, %al # Wait for not busy(8042 input buffer empty). 7 testb $0x2, %al 8 jnz seta20.1 9 10 movb $0xd1, %al # 0xd1 -> port 0x64 11 outb %al, $0x64 # 0xd1 means: write data to 8042‘s P2 port 12 13 seta20.2: 14 inb $0x64, %al # Wait for not busy(8042 input buffer empty). 15 testb $0x2, %al 16 jnz seta20.2 17 18 movb $0xdf, %al # 0xdf -> port 0x60 19 outb %al, $0x60 # 0xdf = 11011111, means set P2‘s A20 bit(the 1 bit) to 1
注释: 参考“百度文库 激活A20地址线详解”
1 # Switch from real to protected mode, using a bootstrap GDT
2 # and segment translation that makes virtual addresses
3 # identical to physical addresses, so that the
4 # effective memory map does not change during the switch.
5 lgdt gdtdesc
6 movl %cr0, %eax
7 orl $CR0_PE_ON, %eax
8 movl %eax, %cr0
注释:通过将CR0寄存器第一位置1,开启保护模式。
1 # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00) 2 movl $0x0, %ebp 3 movl $start, %esp 4 call bootmain
注释:跳转到bootmain.c
bootloader让CPU进入保护模式后,下一步的工作就是从硬盘上加载并运行OS。考虑到实现的简单性,bootloader的访问硬盘都是LBA模式的PIO(Program IO)方式,即所有的IO操作是通过CPU访问硬盘的IO地址寄存器完成。
一般主板有2个IDE通道,每个通道可以接2个IDE硬盘。访问第一个硬盘的扇区可设置IO地址寄存器0x1f0-0x1f7实现的,具体参数见下表。一般第一个IDE通道通过访问IO地址0x1f0-0x1f7来实现,第二个IDE通道通过访问0x170-0x17f实现。每个通道的主从盘的选择通过第6个IO偏移地址寄存器来设置。
表一 磁盘IO地址和对应功能
第6位:为1=LBA模式;0 = CHS模式 第7位和第5位必须为1
IO地址 | 功能 |
0x1f0 | 读数据,当0x1f7不为忙状态时,可以读。 |
0x1f2 | 要读写的扇区数,每次读写前,你需要表明你要读写几个扇区。最小是1个扇区 |
0x1f3 | 如果是LBA模式,就是LBA参数的0-7位 |
0x1f4 | 如果是LBA模式,就是LBA参数的8-15位 |
0x1f5 | 如果是LBA模式,就是LBA参数的16-23位 |
0x1f6 | 第0~3位:如果是LBA模式就是24-27位 第4位:为0主盘;为1从盘 |
0x1f7 | 状态和命令寄存器。操作时先给命令,再读取,如果不是忙状态就从0x1f0端口读数据 |
当前 硬盘数据是储存到硬盘扇区中,一个扇区大小为512字节。读一个扇区的流程(可参看boot/bootmain.c中的readsect函数实现)大致如下:
1 /* waitdisk - wait for disk ready */
2 static void
3 waitdisk(void) {
4 while ((inb(0x1F7) & 0xC0) != 0x40)
5 /* do nothing */;
6 }
7
8 /* readsect - read a single sector at @secno into @dst */
9 static void
10 readsect(void *dst, uint32_t secno) {
11 // wait for disk to be ready
12 waitdisk();
13
14 outb(0x1F2, 1); // count = 1
15 outb(0x1F3, secno & 0xFF);
16 outb(0x1F4, (secno >> 8) & 0xFF);
17 outb(0x1F5, (secno >> 16) & 0xFF);
18 outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0);
19 outb(0x1F7, 0x20); // cmd 0x20 - read sectors
20
21 // wait for disk to be ready
22 waitdisk();
23
24 // read a sector
25 insl(0x1F0, dst, SECTSIZE / 4);
26 }
---恢复内容结束---
标签:
原文地址:http://www.cnblogs.com/zslhg903/p/5781882.html