第四讲 lab1 BootLoader启动ucore os
4.1 启动顺序
理解x86-32的启动过程
x86启动顺序-寄存器初始值
CS和EIP的默认值如第三讲所述,只要一开电,就会把CS和EIP设成这两个值,然后决定在哪个地址取得相应的指令去执行。
实际地址为Base + EIP = FFFF0000H + 0000FFF0H = FFFFFFF0H
这是BIOS的EPROM所在地
通常第一条指令是一条长跳转指令
BootLoader做的事:
- 使能保护模式和段机制
- 从硬盘上读取kernel in ELF格式的ucore kernel(跟在MBR后面的扇区)并放到内存中固定的位置
- 跳转到ucore OS的入口点(entry point)执行,这时控制权到了ucore OS中
段机制
GDT(Global Discriptor Table)
理解实模式、保护模式
4.2 C函数调用的实现
套娃。
(1)ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
(2)EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。
4.3 GCC内联汇编(inline assembly)
什么是内联汇编?
- GCC对C语言的扩张(比如说4.1提到的LGDT加载全局描述符表)
- 可直接在C语句中插入汇编指令
- (我联想到了之前做嵌入式大作业时,要用到函数跳转指令,也是插入了一段汇编代码,这就是内联汇编吧)
有什么用?
- 调用C语言不支持的指令
- 用汇编在C语言中手动优化
如何工作? - 用给定的模板和约束来生成汇编指令
- 在C函数内形成汇编源码
示例1:
Assembly(.S):
movl $0xffff, %eax
—>
Inline assembly(.c):
asm("movl $0xffff, %%eax\n")
Syntax:
asm(assembler template
: output operands
(optional)
:input operands
(optional)
:clobbers
(optional)
);
示例2:
要看懂这段,需要这些先修知识:
先修知识
int main(void)
{
int x = 10, y;
asm ("movl %1, %%eax;"
"movl %%eax, %0;"
:"=r"(y) /* y is output operand */
:"r"(x) /* x is input operand */
:"%eax"); /* %eax is clobbered register */
}
okay,gcc 内联汇编很强大,却很难懂。
1、 首先,%是一个特殊的char,它用来定义寄存器和占位符,并且它还用来定义一些寄存器的名字(比如%EAX)双百分号用来表示真实的寄存器
2、 **%0,%1,…,%9是输入和输出操作数(operand),在这个例子中%0代表了y,%1代表了x,(0对应列表里的第一个数,1对应列表里里的第二个数)编译器在执行汇编代码之前会确定将变量值放进寄存器中了,并且编译器将确保输出操作数被写入到输出操作数列表中指定的变量。
3、 这个“=”只是表示这个个输出操作数,如果是输出的操作数,那就有等号,如果是输入操作数,那就没有等号
4、 r表示需要与某个通用寄存器相关联,先将操作数的值读入寄存器,再在指令中使用寄存器进行操作,最后将寄存器中的值给变量。
有了这一先验知识,就能读懂下面的代码了:
Inline assembly(*.c):
uint32_t cr0;
//首先把寄存器%cr0中的内容读到cr0这个变量中去
//这个过程并不是直接赋值的,而是先将通用寄存器%cr0中的值给了一个中间寄 存器,然后这个中间寄存器把值赋给变量cr0
asm volatile("movl %%cr0, %0\n":"=r"(cr0));
cr0 |= 0x80000000
//将变量cr0中的值给一个中间寄存器,中间寄存器再把值给了通用寄存器%cr0
asm volatile("movl %0, %%cr0\n"::"r"(cr0));
Generated assembly code(*.s):
movl %cr0, %ebx
movl %ebx, 12(%esp)
orl $-2147483648, 12(%esp)
movl 12(%esp),%eax
movl %eax,%cr0
示例3
先定义一系列局部变量,然后执行一个int80指令(一个特殊指令,产生软中断),与syntax一致,将立即数11给了寄存器0,arg1给了%ebx,arg2给了%ecx,arg3给了%edx,arg4给了%esi,然后调用int80指令,将返回的值赋给__res
p.s.这个地方老师讲的很模糊,不知道是不是int80指令是个黑箱,在此过程中产生了edi和ebp的操作。
4.4 x86中断处理过程
实现过程
中断 Interrupts
- 外部中断 External(hardware generated)interrupts串口、网卡、硬盘、时钟…
- 软件产生的中断Software generated interrupts The INT n指令,通常用于系统调用,应用程序可以通过软中断来获得操作系统提供的服务
异常 Exceptions
- 程序错误
- 软件产生的异常Software generated exceptions INTO,INT 3,BOUND
- 机器检查出的异常S
中断产生了,操作系统要做出响应;异常产生了,操作系统要根据异常的程度,可能会杀死出错的进程
每一个中断或异常与一个中断服务例程(Interrupt Service Routine,ISR)关联,其关联关系存储在中断描述符表中(Interrupt Description Table,IDT)
来了个中断,会从GDT(全局描述符表)中获取中断的基地址,然后加上中断号对应的偏移量,就能定位到IDT的位置,然后就能获取中断服务例程所在的位置。
从ISR(中断服务例程)返回
在内核态中产生中断,不会发生特权级的变化,栈不发生变化,;
在用户态中产生中断,也会跳到内核态,这时特权级发生变化,需要一个新的堆栈。
(这一点和第三讲提到的系统调用、程序调用很像)
先看左图,EIP和CS保存了中断之前程序的地址,EFLAGS保存了重要的标志位
再看右图,相比左图,多了个ESP和SS,它们保存了中断之前用户态的栈指针所处的位置
系统调用
也叫trap或软中断
如何实现
- 需要指定中断号
- 使用trap,也称Software generated interrupt
- 使用特殊指令(SYSENTER/SYSEXIT)
具体实验
练习一
操作系统镜像文件ucore.img是如何一步一步生成的?(需要比较详细地解释Makefile中每一条相关命令和命令参数的含义,以及说明命令导致的结果)
一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
补充材料:
如何调试Makefile
当执行make时,一般只会显示输出,不会显示make到底执行了哪些命令。
如想了解make执行了哪些命令,可以执行:
$ make “V=”
要获取更多有关make的信息,可上网查询,并请执行
$ man make
在此练习中,大家需要通过静态分析代码来了解:
操作系统镜像文件ucore.img是如何一步一步生成的?(需要比较详细地解释Makefile中每一条相关命令和命令参数的含义,以及说明命令导致的结果)
一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
补充材料:
如何调试Makefile
当执行make时,一般只会显示输出,不会显示make到底执行了哪些命令。
如想了解make执行了哪些命令,可以执行:
$ make “V=”
要获取更多有关make的信息,可上网查询,并请执行
$ man make
执行过程
这个练习需要先执行make clean操作,否则会出现这样的报错
执行make V=
这样做可以把详细的编译过程展现出来
从第二行开始到第33行都是将c文件转为.o文件的过程
+ cc kern/init/init.c
gcc -Ikern/init/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/init/init.c -o obj/kern/init/init.o
然后ld命令将这些目标文件转变成可执行文件
基本上是上面有哪些.o文件,这里就将其转变成可执行文件
ld -m elf_i386 -nostdlib -T tools/kernel.ld -o bin