目錄
6.828
兩個神級教學項目: nand2tetris 與 MIT6.828
class : MIT 6.828
refers: inline assembly | I/O Ports | 8086寄存器略解 | x86-64架構寄存器詳解
notes: [MIT] 6.828 操作系統工程導讀 | MIT6.828-神級OS課程
Lab1
Exercise 2: BIOS
make gdb
[root@VM-0-8-centos lab]# make gdb
The target architecture is assumed to be i8086
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
0x0000fff0 in ?? ()
+ symbol-file obj/kern/kernel
(gdb) si
[f000:e05b] 0xfe05b: cmpl $0x0,%cs:0x6ac8
0x0000e05b in ?? ()
(gdb) si
[f000:e062] 0xfe062: jne 0xfd2e1
0x0000e062 in ?? ()
(gdb) si
[f000:e066] 0xfe066: xor %dx,%dx
0x0000e066 in ?? ()
(gdb) si
[f000:e068] 0xfe068: mov %dx,%ss
0x0000e068 in ?? ()
(gdb) si
[f000:e06a] 0xfe06a: mov $0x7000,%esp
0x0000e06a in ?? ()
(gdb) si
[f000:e070] 0xfe070: mov $0xf34c2,%edx
0x0000e070 in ?? ()
(gdb) si
[f000:e076] 0xfe076: jmp 0xfd15c
0x0000e076 in ?? ()
(gdb) si
[f000:d15c] 0xfd15c: mov %eax,%ecx
0x0000d15c in ?? ()
(gdb) si
[f000:d15f] 0xfd15f: cli
0x0000d15f in ?? ()
(gdb) si
[f000:d160] 0xfd160: cld
0x0000d160 in ?? ()
(gdb) si
[f000:d161] 0xfd161: mov $0x8f,%eax
0x0000d161 in ?? ()
(gdb) si
[f000:d167] 0xfd167: out %al,$0x70 //屏蔽NMI中斷信號
0x0000d167 in ?? ()
(gdb) si
[f000:d169] 0xfd169: in $0x71,%al
0x0000d169 in ?? ()
(gdb) si
[f000:d16b] 0xfd16b: in $0x92,%al
0x0000d16b in ?? ()
(gdb) si
[f000:d16d] 0xfd16d: or $0x2,%al
0x0000d16d in ?? ()
(gdb) si
[f000:d16f] 0xfd16f: out %al,$0x92 //開啟A20地址線
0x0000d16f in ?? ()
(gdb) si
[f000:d171] 0xfd171: lidtw %cs:0x6ab8 //設置中斷描述表
0x0000d171 in ?? ()
(gdb) si
[f000:d177] 0xfd177: lgdtw %cs:0x6a74 //設置全局描述表
0x0000d177 in ?? ()
(gdb) si
[f000:d17d] 0xfd17d: mov %cr0,%eax //CPU控制寄存器cr0
0x0000d17d in ?? ()
(gdb) si
[f000:d180] 0xfd180: or $0x1,%eax
0x0000d180 in ?? ()
(gdb) si
[f000:d184] 0xfd184: mov %eax,%cr0 //PE位置1,保護模式
0x0000d184 in ?? ()
(gdb) si
[f000:d187] 0xfd187: ljmpl $0x8,$0xfd18f
0x0000d187 in ?? ()
(gdb) si
The target architecture is assumed to be i386
=> 0xfd18f: mov $0x10,%eax
0x000fd18f in ?? ()
(gdb) si
=> 0xfd194: mov %eax,%ds
0x000fd194 in ?? ()
(gdb) si
=> 0xfd196: mov %eax,%es
0x000fd196 in ?? ()
(gdb) si
=> 0xfd198: mov %eax,%ss
0x000fd198 in ?? ()
(gdb) si
=> 0xfd19a: mov %eax,%fs
0x000fd19a in ?? ()
(gdb) si
=> 0xfd19c: mov %eax,%gs
0x000fd19c in ?? ()
(gdb) si
=> 0xfd19e: mov %ecx,%eax
0x000fd19e in ?? ()
(gdb) si
=> 0xfd1a0: jmp *%edx
0x000fd1a0 in ?? ()
(gdb) si
=> 0xf34c2: push %ebx
0x000f34c2 in ?? ()
(gdb) si
=> 0xf34c3: sub $0x2c,%esp
0x000f34c3 in ?? ()
(gdb) si
=> 0xf34c6: movl $0xf5b5c,0x4(%esp)
0x000f34c6 in ?? ()
(gdb) si
=> 0xf34ce: movl $0xf447b,(%esp)
0x000f34ce in ?? ()
(gdb) si
=> 0xf34d5: call 0xf099e
0x000f34d5 in ?? ()
(gdb) si
=> 0xf099e: lea 0x8(%esp),%ecx
0x000f099e in ?? ()
(gdb) si
=> 0xf09a2: mov 0x4(%esp),%edx
0x000f09a2 in ?? ()
(gdb) si
=> 0xf09a6: mov $0xf5b58,%eax
0x000f09a6 in ?? ()
(gdb) si
=> 0xf09ab: call 0xf0574
0x000f09ab in ?? ()
(gdb) si
=> 0xf0574: push %ebp
0x000f0574 in ?? ()
(gdb) si
=> 0xf0575: push %edi
0x000f0575 in ?? ()
(gdb) si
=> 0xf0576: push %esi
0x000f0576 in ?? ()
(gdb) si
=> 0xf0577: push %ebx
0x000f0577 in ?? ()
(gdb) si
=> 0xf0578: sub $0xc,%esp
0x000f0578 in ?? ()
(gdb) si
=> 0xf057b: mov %eax,0x4(%esp)
0x000f057b in ?? ()
(gdb) si
=> 0xf057f: mov %edx,%ebp
0x000f057f in ?? ()
(gdb) si
=> 0xf0581: mov %ecx,%esi
0x000f0581 in ?? ()
Ecercise 3: the Boot Loader
questions
- At what point does the processor start executing 32-bit code? What exactly causes the switch from 16- to 32-bit mode?
ljmp $PROT_MODE_CSEG, $protcseg
設置了A20地址線
- What is the last instruction of the boot loader executed, and what is the first instruction of the kernel it just loaded?
boot/main.c中
ELFHDR->e_entry
0x7d61
- Where is the first instruction of the kernel?
kernel: 0x10018
- How does the boot loader decide how many sectors it must read in order to fetch the entire kernel from disk? Where does it find this information?
Part 3: The Kernel
我們將會開始稍微細節地研究JOS的內核的最小部分,(而且我們終於即將開始寫一些代碼)。像bootloader一樣,Kernel也從一些匯編代碼開始,設置好環境以便C語言可以方便地運行。
使用虛擬內存來位置依賴工作
boot loader的鏈接地址和加載地址是完美一致的,但kernel的鏈接地址和加載地址就有分歧了。
操作系統內核通常被在高虛擬地址被鏈接和運行,例如0xf0100000,這是為了給用戶程序留下處理器的低虛擬地址空間來運行。下個lab會更加清晰。
很多機器沒有這么多的物理空間,所以不能指望把kernel放在那里。替代地,我們會使用處理器內存管理硬件來講虛擬地址映射到物理地址。這樣,kernel的虛擬地址就足夠高而留給用戶程序足夠地址空間了。kernel被加載到物理空間0x00100000,1MB位置,就在BIOS ROM之上。
現在我們只需要映射4MB的物理地址就足夠了。我們通過一個手寫的、靜態初始化的kern/entrypgdir.c里的頁目錄和頁表來實習。現在,我們不需要理解這些工資的細節,只需了解到這項可以達成的結果。指導kern/entry.S設置CR0_PG標志之后,內存引用才被當作物理地址,而且我們也不會改變這個。一旦CR0_PG被設定,內存引用就是被虛擬內存硬件從虛擬地址翻譯到的物理地址了。entrypgdir將0xf0000000到0xf04000000的虛擬地址翻譯成0x00000000到0x00400000的物理地址(反之亦然)。任何超出范圍的虛擬地址都會造成硬件異常,而且因為我們還沒有設置終端處理,進而會造成QEMU拋出機器狀態和退出。
Exercise 7:
- Use QEMU and GDB to trace into the JOS kernel and stop at the movl %eax, %cr0. Examine memory at 0x00100000 and at 0xf0100000. Now, single step over that instruction using the stepi GDB command. Again, examine memory at 0x00100000 and at 0xf0100000. Make sure you understand what just happened.
kernel
=> 0x10001d: mov %cr0,%eax
0x0010001d in ?? ()
(gdb) info registers
eax 0x110000 1114112
ecx 0x0 0
edx 0x9d 157
ebx 0x10094 65684
esp 0x7bec 0x7bec
ebp 0x7bf8 0x7bf8
esi 0x10094 65684
edi 0x0 0
eip 0x10001d 0x10001d
eflags 0x46 [ PF ZF ]
cs 0x8 8
ss 0x10 16
ds 0x10 16
es 0x10 16
fs 0x10 16
gs 0x10 16
(gdb) si
=> 0x100020: or $0x80010001,%eax
0x00100020 in ?? ()
(gdb) info registers
eax 0x11 17
ecx 0x0 0
edx 0x9d 157
ebx 0x10094 65684
esp 0x7bec 0x7bec
ebp 0x7bf8 0x7bf8
esi 0x10094 65684
edi 0x0 0
eip 0x100020 0x100020
eflags 0x46 [ PF ZF ]
cs 0x8 8
ss 0x10 16
ds 0x10 16
es 0x10 16
fs 0x10 16
gs 0x10 16
(gdb) si
=> 0x100025: mov %eax,%cr0
0x00100025 in ?? ()
(gdb) info registers
eax 0x80010011 -2147418095
ecx 0x0 0
edx 0x9d 157
ebx 0x10094 65684
esp 0x7bec 0x7bec
ebp 0x7bf8 0x7bf8
esi 0x10094 65684
edi 0x0 0
eip 0x100025 0x100025
eflags 0x86 [ PF SF ]
cs 0x8 8
ss 0x10 16
ds 0x10 16
es 0x10 16
fs 0x10 16
gs 0x10 16
(gdb) x/8x 0x00100000
0x100000: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766
0x100010: 0x34000004 0x0000b812 0x220f0011 0xc0200fd8
(gdb) x/8x 0xf0100000
0xf0100000 : 0x00000000 0x00000000 0x00000000 0x00000000
0xf0100010 : 0x00000000 0x00000000 0x00000000 0x00000000
(gdb) si
=> 0x100028: mov $0xf010002f,%eax
0x00100028 in ?? ()
(gdb) si
=> 0x10002d: jmp *%eax
0x0010002d in ?? ()
(gdb) si
=> 0xf010002f : mov $0x0,%ebp
relocated () at kern/entry.S:74
74 movl $0x0,%ebp # nuke frame pointer
(gdb) x/8x 0x00100000
0x100000: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766
0x100010: 0x34000004 0x0000b812 0x220f0011 0xc0200fd8
(gdb) x/8x 0xf0100000
0xf0100000 : 0x1badb002 0x00000000 0xe4524ffe 0x7205c766
0xf0100010 : 0x34000004 0x0000b812 0x220f0011 0xc0200fd8
- What is the first instruction after the new mapping is established that would fail to work properly if the mapping weren't in place? Comment out the movl %eax, %cr0 in kern/entry.S, trace into it, and see if you were right.
將CR0最高位置1,paging可以了,然后通過*%rax進入0xf010002f。
qemu fatal
[root@VM-0-8-centos lab]# make qemu-gdb
+ as kern/entry.S
+ ld obj/kern/kernel
ld: warning: section `.bss' type changed to PROGBITS
+ mk obj/kern/kernel.img
***
*** Now run 'make gdb'.
***
qemu-system-i386 -drive file=obj/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp:: 25000 -D qemu.log -S
VNC server running on `::1:5900'
qemu: fatal: Trying to execute code outside RAM or ROM at 0xf010002c
EAX=f010002c EBX=00010094 ECX=00000000 EDX=0000009d
ESI=00010094 EDI=00000000 EBP=00007bf8 ESP=00007bec
EIP=f010002c EFL=00000086 [--S--P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT= 00007c4c 00000017
IDT= 00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00110000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000084 CCD=80010011 CCO=EFLAGS
EFER=0000000000000000
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000
make: *** [qemu-gdb] Aborted
格式化輸出到控制台
在OS kernel中,我們需要自己完成所有類似 printf() 的I/O函數。
通讀kern/printf.c, lib/printfmt.c 和 kern/console.c,了解它們之間的關系。之后你會更清晰為什么printfmt.c在於單獨的lib目錄。
Exercise 8.
- We have omitted a small fragment of code - the code necessary to print octal numbers using patterns of the form "%o". Find and fill in this code fragment.
Process Management
內存中的進程|操作系統內的進程表示PCB
進程調度
graph LR; A(job queue)-->B(ready queue) A-->C(device queue)
IPC