lab1
启动顺序
1
相关寄存器设置初始值,cs寄存器初值为0xf000,ip寄存器初值为0xfff0,此时pc指针指向的内存地址为 base + offset = 0xfffffff0(0xffff0000 为cs寄存器隐含的值)
此地址为bios的eprom地址,通常为长跳转指令,跳转至1m的bios代码中执行
(注:视频中地址长度为20位,两个16位寄存器相加,即cs左移4位+ip,地址为0xffff0)
2
bios加载存储设备第一个扇区的512字节到内存的0x7c00处,pc跳转至0x7c00处执行代码(此512字节代码称之为bootloader)
(注:由于bios能力限制,不能一次加载完整个的os代码至内存中,故分多次加载)
bootloader的功能:
1.实模式切换到保护模式(从16位寻址空间切换至32位寻址空间)&段机制可以工作
2.读取kernel代码读入内存中固定区域内
3.将pc指针指向os的入口点,控制权交给操作系统
(注:cs等段寄存器在保护模式中(段机制)指向的是一个段描述符,段描述符中保存base等等信息)
保存段描述符的数组称之为全局描述符表(GDT)(段表),由bootloader建立,gdtr寄存器保存相应地址信息。(注:在μcore里面,所有段的基址为0,长度为4g)
段寄存器(cs等)高十三位存储gdt的index,最低两位(rp)表示优先级,第十四位为ldt标志位,表示是否使用ldt(本地描述符表)
3
将控制寄存器(CR0)第0号bit置为1,这样cpu就会进入保护模式。
4
加载elf格式的μcore os kernel,将相应的代码段数据段等信息读入内存。
c函数调用
内联汇编
c语言不能利用所有的汇编指令,故使用内联汇编
volatile:不需要优化
%0:第一个用到的寄存器
r:任意寄存器
a:eax
b:ebx
S:esi
D:edi
中断处理
中断或异常斗鱼一个中断服务例程关联,保存此关联信息的表称之为中断描述符表(IDT),其起始地址以及大小保存在中断描述符IDTR中。
中断处理时需要考虑特权级变化(通过段描述符的特权值位)
iret:弹出eflags和ss,esp
ret:eip(近跳转)
retf:弹出cs和eip(远)
通过中断处理实现系统调用
指定中断号,使用trap,特殊指令sysenter/sysexit)
练习
make V= 可以把编译时具体过程打印出来
特征标记:tools/sign.c
符合扇区规范的扇区标准:大小小于510,最后两个字节分别为0x55与0xAA(结束标志)
cli # Disable interrupts
cld # String operations increment
# Set up the important data segment registers (DS, ES, SS).
xorw %ax, %ax # Segment number zero
movw %ax, %ds # -> Data Segment
movw %ax, %es # -> Extra Segment
movw %ax, %ss # -> Stack Segment
# Enable A20:
# For backwards compatibility with the earliest PCs, physical
# address line 20 is tied low, so that addresses higher than
# 1MB wrap around to zero by default. This code undoes this.
seta20.1:
inb $0x64, %al # Wait for not busy(8042 input buffer empty).
testb $0x2, %al
jnz seta20.1
movb $0xd1, %al # 0xd1 -> port 0x64
outb %al, $0x64 # 0xd1 means: write data to 8042's P2 port
seta20.2:
inb $0x64, %al # Wait for not busy(8042 input buffer empty).
testb $0x2, %al
jnz seta20.2
movb $0xdf, %al # 0xdf -> port 0x60
outb %al, $0x60 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1
# Switch from real to protected mode, using a bootstrap GDT
# and segment translation that makes virtual addresses
# identical to physical addresses, so that the
# effective memory map does not change during the switch.
lgdt gdtdesc
movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0
# Jump to next instruction, but in 32-bit code segment.
# Switches processor into 32-bit mode.
ljmp $PROT_MODE_CSEG, $protcseg
inb指令:从端口向目标读入一个字节
首先不断地从0x64端口读入字节,直到为空
而后向0x64端口写0xdl(0xd1 means: write data to 8042’s P2 port)
不断地从0x64端口读入字节,直到为空
向0x60端口写入0xdf(0xdf = 11011111, means set P2’s A20 bit(the 1 bit) to 1)
lgdt:加载gdt
保存将cr0低字节置为pe-on状态,跳转。
加载内核文件
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); // cmd 0x20 - read sectors
// wait for disk to be ready
waitdisk();
// read a sector
insl(0x1F0, dst, SECTSIZE / 4); | 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端口读数据 |
就是传参以及设置为主盘,一个扇区,然后读取数据,大小为四分之一个扇区大小(128)