# start address should be 0:7c00, in real mode, the beginning address of the running bootloader .globl start start: .code16 # Assemble for 16-bit mode 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
* waitdisk - wait for disk ready */ static void waitdisk(void) { while ((inb(0x1F7) & 0xC0) != 0x40) /* do nothing */; }
/* readsect - read a single sector at @secno into @dst */ static void readsect(void *dst, uint32_t secno) { // wait for disk to be ready waitdisk(); // 等待磁盘就绪
/* * * readseg - read @count bytes at @offset from kernel into virtual address @va, * might copy more than asked. * */ static void readseg(uintptr_t va, uint32_t count, uint32_t offset) { uintptr_t end_va = va + count;
// round down to sector boundary va -= offset % SECTSIZE;
// translate from bytes to sectors; kernel starts at sector 1 uint32_t secno = (offset / SECTSIZE) + 1;
// If this is too slow, we could read lots of sectors at a time. // We'd write more to memory than asked, but it doesn't matter -- // we load in increasing order. for (; va < end_va; va += SECTSIZE, secno ++) { readsect((void *)va, secno); } }
/* bootmain - the entry of bootloader */ void bootmain(void) { // 读取磁盘的第一页(大小为4K),这里一个sectsize为512字节 readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);
// 判断是否是合法的elf文件格式 if (ELFHDR->e_magic != ELF_MAGIC) { goto bad; }
// call the entry point from the ELF header // note: does not return ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();
bad: outw(0x8A00, 0x8A00); outw(0x8A00, 0x8E00);
/* do nothing */ while (1); }
从源码中可以看出程序控制流:
运行bootmain,调用readseg读取多个扇区
readseg循环执行readsect读取每个扇区
返回bootmain,判断elf格式
将ELFheader读入ph(pragram header,程序头表)
将ph中各个section读入内存
通过内核入口函数加载内核
6 实现函数调用堆栈跟踪函数
这里的代码根据注释不难写出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
void print_stackframe(void) { uint32_t ebp=read_ebp(); //(1) call read_ebp() to get the value of ebp. the type is (uint32_t) uint32_t eip=read_eip(); //(2) call read_eip() to get the value of eip. the type is (uint32_t) for(int i=0;i<STACKFRAME_DEPTH&&ebp!=0;i++){ //(3) from 0 .. STACKFRAME_DEPTH cprintf("ebp:0x%08x eip:0x%08x ",ebp,eip); //(3.1)printf value of ebp, eip uint32_t *tmp=(uint32_t *)ebp+2; cprintf("arg :0x%08x 0x%08x 0x%08x 0x%08x",*(tmp+0),*(tmp+1),*(tmp+2),*(tmp+3)); //(3.2)(uint32_t)calling
arguments [0..4] = the contents in address (unit32_t)ebp +2 [0..4] cprintf("\n"); //(3.3) cprintf("\n"); print_debuginfo(eip-1); //(3.4) call print_debuginfo(eip-1) to print the C calling function name and line
number, etc. eip=((uint32_t *)ebp)[1]; ebp=((uint32_t *)ebp)[0]; //(3.5) popup a calling stackframe } }
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! */ int i = 0; extern uintptr_t __vectors[]; for(i = 0; i < 255; ++i) { // vectors 中存储了中断处理程序的入口地址。vectors 定义在 vector.S 文件中,通过一个工具程序 vector.c 生成 SETGATE(idt[i], 0, GD_KTEXT, __vectors[i], 0); } // 切换用户模式到内核模式 SETGATE(idt[T_SWITCH_TOK], 1, GD_KTEXT, __vectors[T_SWITCH_TOK], 3);// 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;
思路是:每次 handles or dispatches an exception/interrupt都会调用trap,trap中通过累加tick这个时钟周期的全局变量(定义在clock.c)到100来触发时钟中断