MIT的6.828課程是關於作業系統實作的有趣課程,我報名參加了相關的讀書會,並開始研讀課程內容,這篇文章主要紀錄Lab1的部份練習(以下描述問題時,我會用比較精簡的方式敘述,想知道完整說明的朋友請直接參考課程嘍~):
Ex1:練習x86 assembly。
Ans:由於該課程使用gas與gcc,所以我簡單練習一下AT&T的語法。
inline assembly的版本(由於在C function中嵌入asm,所以省卻prologue & epilogue):
Ex2:使用gdb觀察QEMU的BIOS在開機流程作了哪些事情。
Ans:BIOS基本上將PC的重要硬體週邊初始化並檢測是否有嚴重異常,若無,則讀進first bootable device的bootloader至physical address 0x7c00。課程中的boot device為IDE disk,所以會將sector 0讀進0x7c00,並將控制權移交給bootloader。
Ex3:研讀bootloader的行為。
Ans:主要有兩個檔案:boot/boot.S以及boot/main.c。在課程網站中所提供的文件其實說明的非常深入淺出,連我這個硬體白痴都讀的懂,極力推荐大家參考~
首先,從boot/Makefrag可以知道,bootloader的entry point設在start,並且從0x7c00處開始執行:
接著,由於歷史因素,需要將A20打開才能順利使用1MB以上的physical address。
從gdtdesc可以看到,loader所設定的protected mode非常單純,僅先引入32 bit addressing的能力:
OK,接著的bootmain()相對單純,基本上就是先讀取sector 1之後一個page,然後依據ELF header將kernel image的每個segment(ELF中的segment往往是多個sections的組合,想知道細節的話可以參考"程式設計師的自我修養")分別載入到指定的load address(program header中的LMA欄位,也就是下列程式的ph->p_pa):
Ex1:練習x86 assembly。
Ans:由於該課程使用gas與gcc,所以我簡單練習一下AT&T的語法。
- .global mmax
- mmax:
- pushl %ebp
- movl %esp, %ebp
- movl 8(%ebp), %eax
- cmpl 12(%ebp), %eax
- jle .L2
- movl 8(%ebp), %eax
- jmp .L3
- .L2:
- movl 12(%ebp), %eax
- .L3:
- popl %ebp
- ret
inline assembly的版本(由於在C function中嵌入asm,所以省卻prologue & epilogue):
- #include <stdio.h>
- static int mmax(int a, int b)
- {
- asm volatile (
- "movl %0, %%eax \n"
- "cmpl %1, %%eax \n"
- "jle .L2 \n"
- "movl %0, %%eax \n"
- "jmp .L3 \n"
- ".L2: \n"
- "movl %1, %%eax \n"
- ".L3: \n"
- ::"q"(a), "q"(b));
- }
- int main(void)
- {
- }
Ex2:使用gdb觀察QEMU的BIOS在開機流程作了哪些事情。
Ans:BIOS基本上將PC的重要硬體週邊初始化並檢測是否有嚴重異常,若無,則讀進first bootable device的bootloader至physical address 0x7c00。課程中的boot device為IDE disk,所以會將sector 0讀進0x7c00,並將控制權移交給bootloader。
Ex3:研讀bootloader的行為。
Ans:主要有兩個檔案:boot/boot.S以及boot/main.c。在課程網站中所提供的文件其實說明的非常深入淺出,連我這個硬體白痴都讀的懂,極力推荐大家參考~
首先,從boot/Makefrag可以知道,bootloader的entry point設在start,並且從0x7c00處開始執行:
- $(OBJDIR)/boot/boot: $(BOOT_OBJS)
- @echo + ld boot/boot
- $(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 -o $@.out $^
- $(V)$(OBJDUMP) -S $@.out >$@.asm
- $(V)$(OBJCOPY) -S -O binary -j .text $@.out $@
- $(V)perl boot/sign.pl $(OBJDIR)/boot/boot
- #include <inc/mmu.h>
- # 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 PROT_MODE_CSEG, 0x8 # kernel code segment selector
- .set PROT_MODE_DSEG, 0x10 # kernel data segment selector
- .set CR0_PE_ON, 0x1 # protected mode enable flag
- .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
接著,由於歷史因素,需要將A20打開才能順利使用1MB以上的physical address。
- # 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
- testb $0x2,%al
- jnz seta20.1
- movb $0xd1,%al # 0xd1 -> port 0x64
- outb %al,$0x64
- seta20.2:
- inb $0x64,%al # Wait for not busy
- testb $0x2,%al
- jnz seta20.2
- movb $0xdf,%al # 0xdf -> port 0x60
- outb %al,$0x60
- # Switch from real to protected mode, using a bootstrap GDT
- # and segment translation that makes virtual addresses
- # identical to their 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
從gdtdesc可以看到,loader所設定的protected mode非常單純,僅先引入32 bit addressing的能力:
- # Bootstrap GDT
- .p2align 2 # force 4 byte alignment
- gdt:
- SEG_NULL # null seg
- SEG(STA_X|STA_R, 0x0, 0xffffffff) # code seg
- SEG(STA_W, 0x0, 0xffffffff) # data seg
- gdtdesc:
- .word 0x17 # sizeof(gdt) - 1
- .long gdt # address gdt
- .code32 # Assemble for 32-bit mode
- protcseg:
- # Set up the protected-mode data segment registers
- movw $PROT_MODE_DSEG, %ax # Our data segment selector
- 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.
- movl $start, %esp
- call bootmain
OK,接著的bootmain()相對單純,基本上就是先讀取sector 1之後一個page,然後依據ELF header將kernel image的每個segment(ELF中的segment往往是多個sections的組合,想知道細節的話可以參考"程式設計師的自我修養")分別載入到指定的load address(program header中的LMA欄位,也就是下列程式的ph->p_pa):
- void
- bootmain(void)
- {
- struct Proghdr *ph, *eph;
- // read 1st page off disk
- readseg((uint32_t) ELFHDR, SECTSIZE*8, 0);
- // is this a valid ELF?
- if (ELFHDR->e_magic != ELF_MAGIC)
- goto bad;
- // load each program segment (ignores ph flags)
- ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
- eph = ph + ELFHDR->e_phnum;
- for (; ph < eph; ph++)
- // p_pa is the load address of this segment (as well
- // as the physical address)
- readseg(ph->p_pa, ph->p_memsz, ph->p_offset);
- // call the entry point from the ELF header
- // note: does not return!
- ((void (*)(void)) (ELFHDR->e_entry))();
- bad:
- outw(0x8A00, 0x8A00);
- outw(0x8A00, 0x8E00);
- while (1)
- /* do nothing */;
- }
留言
張貼留言