目录
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