实验完善代码 LAB2-4下载链接 提取码:79t8
1、JOS内核
回到前面内核可执行文件是如何被boot loader 加载到内存中的,在 boot loader完成了模式转换和将ELF 文件加载到内存之后,就会执行跳转指令。
((void (*)(void)) (ELFHDR->e_entry))(); //boot/main.c
由于内核可执行文件被加载到内存的0x0010000,也就是e_entry 所表示的物理地址。
- 在内核代码中,如何实现从链接地址到加载地址之间的转换的?
保护模式下,有 物理地址 = GDT(全局描述符表) 表项中段基址 + 偏移地址,而实际上指针的值通常就是偏移地址,那么段基址是多少呢?
通过命令在进入保护模式之前,可以查看GDT 表的内容,GDT表中一个标识代码段的,另一个标识数据段。可以看到两个段的线性地址都是0,即代表段的基址是0,这个时候也就是说偏移地址就是物理地址。
进入内核之后,GDT表中线性地址的值变为0x10000000,由于在内核执行过程中,偏移地址都是大于0xf0100000, 于是加上0x10000000 后便会造成高位的进位,但是由于地址最多就只有32位,于是实际上高位的进位就被舍去了,这样一来,最后的物理地址便会是内存的低位,在0x100000附近,而不会寻址到0xf0100000这样的不存在的高位物理地址空间。
部分内核代码解析
entry:
movw $0x1234,0x472 # warm boot
movl $(RELOC(entry_pgdir)),
movl %eax, %cr3
movl %cr0, %eax
orl $(CR0_PE|CR0_PG|CR0_WP), %eax
movl %eax, %cr0
mov $relocated, %eax
jmp *%eax
relocated:
movl $0x0,%ebp
movl $(bootstacktop),%esp
call i386_init
spin: jmp spin
.data
###################################################################
# boot stack
###################################################################
.p2align PGSHIFT # force page alignment //强制4K对齐
.globl bootstack
bootstack:
.space KSTKSIZE //在inc/memlayout 中定义为32k。
.globl bootstacktop
bootstacktop:
这是boot loader 将ELF 可执行文件加载到内核,这段可执行文件的第一段代码。这段代码首先加载了新的GDT表,紧接着初始化了堆栈相关的寄存器。
两个全局变量bootstack表示内存中的第一个临时堆栈的位置,尺寸大小为KSTKSIZE; bootstacktop指向这段区域后的第一个字节,刚开始堆栈是空的,因此栈顶就是bootstacktop所指向的位置,于是将bootstacktop的值赋给esp寄存器。
设置完之后程序跳转到i386_init。
void i386_init(void)
{
extern char edata[], end[];
memset(edata, 0, end - edata);
cons_init();
cprintf("6828 decimal is %o octal!\n", 6828);
test_backtrace(5);
while (1)
monitor(NULL);
}
堆栈解析
堆栈具有先进后出的特点,在实际的内存中,栈低是固定的,栈顶可以变化,向低地址方向生长。
需要出栈一个字的数据,即4个字节,这时候需要当前esp指向位置开始的4个字节,并且在出栈之后将esp +4 。
需要进栈一个字的数据,首先将esp - 4,并把这个字存在esp指向位置开始的4个字节处。
分析了解调用函数时,堆栈的变化,我们需要了解这个过程中几个关键的寄存器。
- eip 存储当前执行指令的下一条指令在内存中的偏移地址;
- esp 存储指向栈顶的指针
- ebp存储指向当前函数需要使用的参数的指针
在程序调用函数中,首先会将函数需要的参数进栈,然后将eip 中的一个字进栈(也就是下一条指令在内存中的位置),这样在函数调用结束之后可以通过eip 返回调用函数的程序。一进入调用函数,第一件事就是将ebp 进栈,然后将esp 的值赋给ebp , 这样此时ebp 变指向堆栈中存储ebp,eip和函数参数的地方,所以ebp 通常指向当前函数所需要的参数。所以当一个函数调用另一个函数的时候,被调用的函数执行时的ebp 指向调用她的函数的ebp 值存放的位置。
2、Exercise 12
lab/kern/init.c 中调用了test_backtrace(), 其中,mon_backtrace是exercise 12 的内容,这个函数的声明以及定义在lab/kern/monitor.c 文件中
int mon_backtrace(int argc, char **argv,struct Trapframe *tf)
{
uint32_t *ebp,*eip;
uint32_t arg0,arg1,arg2,arg3,arg4;
ebp = (uint32_t *)read_ebp();
eip = (uint32_t *)ebp[1];
arg0 = ebp[2];
arg1 = ebp[3];
arg2 = ebp[4];
arg3 = ebp[5];
arg4 = ebp[6];
cprintf("stack backtrace\n");
cprintf("ebp \t eip \t arg0\targ1\targ2\targ3\targ4\t \n");
while(ebp != 0)
{
cprintf("ebp = %x , eip = %x, %x , %x , %x , %x , %x \n",ebp,eip,arg0,arg1,arg2,arg3,arg4);
ebp = (uint32_t *)ebp[0];//读取调用函数的ebp
eip = (uint32_t *)ebp[1];
arg0 = ebp[2];
arg1 = ebp[3];
arg2 = ebp[4];
arg3 = ebp[5];
arg4 = ebp[6];
}
return 0;
}
3、Chanllenge exercise (未完待续)
LAB1上中介绍了显示输出函数的具体内容,根据字符属性修改程序,实现不同颜色的输出。
追溯一个字符打印的过程,cprintf -> vcprintf -> vprintfmt -> cputchar -> cons_putc -> cga_putc ,关于颜色的控制在函数cga_putc中。
static void cga_putc(int c)
{
// if no attribute given, then use black on white
if (!(c & ~0xFF))
c |= 0x0700;
如果没有赋予颜色属性,默认的属性为白底黑字。
本文参考文章 http://grid.hust.edu.cn/zyshao/OSEngineering.htm
推荐这位博主系列的文章