| Protected-mode kernel | 100000 +------------------------+ | I/O memory hole | 0A0000 +------------------------+ | Reserved for BIOS | Leave as much as possible unused ~ ~ | Command line | (Can also be below the X+10000 mark) X+10000 +------------------------+ | Stack/heap | For use by the kernel real-mode code. X+08000 +------------------------+ | Kernel setup | The kernel real-mode code. | Kernel boot sector | The kernel legacy boot sector. X +------------------------+ | Boot loader | <- Boot sector entry point 0x7C00 001000 +------------------------+ | Reserved for MBR/BIOS | 000800 +------------------------+ | Typically used by MBR | 000600 +------------------------+ | BIOS use only | 000000 +------------------------+
# Start the CPU: switch to 32-bit protected mode, jump into C. # The BIOS loads this code from the first sector of the hard disk into # memory at physical address 0x7c00 and starts executing in real mode # with %cs=0 %ip=7c00.
# Set up the important data segment registers (DS, ES, SS). #设置重要的数据段寄存器 #ax,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. #打开A20地址线 #为了兼容早期的PC机,第20根地址线在实模式下不能使用 #所以超过1MB的地址,默认就会返回到地址0,重新从0循环计数, #下面的代码打开A20地址线 seta20.1: #从0x64端口读入一个字节的数据到al中 inb $0x64, %al # Wait for not busy(8042 input buffer empty). testb $0x2, %al #如果上面的测试中发现al的第2位为0,就不执行该指令 #否则就循环检查 jnz seta20.1
#将0xd1写入到al中 movb $0xd1, %al # 0xd1 -> port 0x64 #将al中的数据写入到端口0x64中,也就是8042的P2端口 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). #测试al的第2位是否为0 testb $0x2, %al jnz seta20.2
#将0xdf写入到al中 movb $0xdf, %al # 0xdf -> port 0x60 #将al中的数据写入到0x60端口中,也就是让P2端口的A20位设置为1 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 #cr0中的第0位为1表示处于保护模式 #cr0中的第0位为0,表示处于实模式 #把控制寄存器cr0加载到eax中 movl %cr0, %eax #将eax中的第0位设置为1 orl $CR0_PE_ON, %eax #将eax中的值装入cr0中 movl %eax, %cr0
# Jump to next instruction, but in 32-bit code segment. # Switches processor into 32-bit mode. ljmp $PROT_MODE_CSEG, $protcseg #跳转到32位模式中的下一条指令 #将处理器切换为32位工作模式 #下面这条指令执行的结果会将$PROT_MODE_CSEG加载到cs中,cs对应的高速缓冲存储器会加载代码段描述符 #同样将$protcseg加载到ip中
.code32 # Assemble for 32-bit mode protcseg: # Set up the protected-mode data segment registers #设置保护模式下的数据寄存器 #将数据段选择子装入到ax中 movw $PROT_MODE_DSEG, %ax # Our data segment selector #将ax装入到其他数据段寄存器中,在装入的同时, #数据段描述符会自动的加入到这些段寄存器对应的高速缓冲寄存器中 movw %ax, %ds # -> DS: Data Segment movw %ax, %es # -> ES: Extra Segment movw %ax, %fs # -> FS movw %ax, %gs # -> GS movw %ax, %ss # -> SS: Stack Segment
# Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00) #设置栈指针,并且调用c函数 movl $0x0, %ebp #调用main.c中的bootmain函数 movl $start, %esp call bootmain
# Bootstrap GDT #强制4字节对齐 .p2align 2 # force 4 byte alignment #全局描述符表 gdt: SEG_NULLASM # null seg #代码段描述符 SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel #数据段描述符 SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel
/* * * print_stackframe - print a list of the saved eip values from the nested 'call' * instructions that led to the current point of execution * * The x86 stack pointer, namely esp, points to the lowest location on the stack * that is currently in use. Everything below that location in stack is free. Pushing * a value onto the stack will invole decreasing the stack pointer and then writing * the value to the place that stack pointer pointes to. And popping a value do the * opposite. * * The ebp (base pointer) register, in contrast, is associated with the stack * primarily by software convention. On entry to a C function, the function's * prologue code normally saves the previous function's base pointer by pushing * it onto the stack, and then copies the current esp value into ebp for the duration * of the function. If all the functions in a program obey this convention, * then at any given point during the program's execution, it is possible to trace * back through the stack by following the chain of saved ebp pointers and determining * exactly what nested sequence of function calls caused this particular point in the * program to be reached. This capability can be particularly useful, for example, * when a particular function causes an assert failure or panic because bad arguments * were passed to it, but you aren't sure who passed the bad arguments. A stack * backtrace lets you find the offending function. * * The inline function read_ebp() can tell us the value of current ebp. And the * non-inline function read_eip() is useful, it can read the value of current eip, * since while calling this function, read_eip() can read the caller's eip from * stack easily. * * In print_debuginfo(), the function debuginfo_eip() can get enough information about * calling-chain. Finally print_stackframe() will trace and print them for debugging. * * Note that, the length of ebp-chain is limited. In boot/bootasm.S, before jumping * to the kernel entry, the value of ebp has been set to zero, that's the boundary. * */ void print_stackframe(void) { /* LAB1 YOUR CODE : STEP 1 */ /* (1) call read_ebp() to get the value of ebp. the type is (uint32_t); * (2) call read_eip() to get the value of eip. the type is (uint32_t); * (3) from 0 .. STACKFRAME_DEPTH * (3.1) printf value of ebp, eip * (3.2) (uint32_t)calling arguments [0..4] = the contents in address (unit32_t)ebp +2 [0..4] * (3.3) cprintf("\n"); * (3.4) call print_debuginfo(eip-1) to print the C calling function name and line number, etc. * (3.5) popup a calling stackframe * NOTICE: the calling funciton's return addr eip = ss:[ebp+4] * the calling funciton's ebp = ss:[ebp] */ uint32_t ebp = read_ebp(); uint32_t eip = read_eip();
/* idt_init - initialize IDT to each of the entry points in kern/trap/vectors.S */ void idt_init(void) { /* LAB1 YOUR CODE : STEP 2 */ /* (1) Where are the entry addrs of each Interrupt Service Routine (ISR)? * All ISR's entry addrs are stored in __vectors. where is uintptr_t __vectors[] ? * __vectors[] is in kern/trap/vector.S which is produced by tools/vector.c * (try "make" command in lab1, then you will find vector.S in kern/trap DIR) * You can use "extern uintptr_t __vectors[];" to define this extern variable which will be used later. * (2) Now you should setup the entries of ISR in Interrupt Description Table (IDT). * Can you see idt[256] in this file? Yes, it's IDT! you can use SETGATE macro to setup each item of IDT * (3) After setup the contents of IDT, you will let CPU know where is the IDT by using 'lidt' instruction. * You don't know the meaning of this instruction? just google it! and check the libs/x86.h to know more. * Notice: the argument of lidt is idt_pd. try to find it! */ externuintptr_t __vectors[]; int i; for (i = 0; i < sizeof(idt) / sizeof(struct gatedesc); i ++) { SETGATE(idt[i], 0, GD_KTEXT, __vectors[i], DPL_KERNEL); } // set for switch from user to kernel SETGATE(idt[T_SWITCH_TOK], 0, GD_KTEXT, __vectors[T_SWITCH_TOK], DPL_USER); // load the IDT lidt(&idt_pd); }
case IRQ_OFFSET + IRQ_TIMER: /* LAB1 YOUR CODE : STEP 3 */ /* handle the timer interrupt */ /* (1) After a timer interrupt, you should record this event using a global variable (increase it), such as ticks in kern/driver/clock.c * (2) Every TICK_NUM cycle, you can print some info using a funciton, such as print_ticks(). * (3) Too Simple? Yes, I think so! */ ticks ++; if (ticks % TICK_NUM == 0) { print_ticks(); } break;