《Linux内核设计的艺术》学习笔记(一)从开机加电到加载三个汇编源码
实验内核版本:0.11
◆ 从开机到main函数的三步:
① 启动BIOS,准备实模式下的中断向量表和中断服务程序;
② 从启动盘加载OS程序到内存中,加载OS程序的工作就是利用第一步中的中断服务程序实现的;
③ 为执行保护模式下32位的main函数做过渡工作。
➩ Intel将所有80x86系列的CPU硬件都设计为加电即进入16位实模式状态运行;
➩ 将CPU硬件逻辑设计为在加电瞬间强行将CS置为0xFFFF,IP置为0x0000,即是CS:IP指向了0xFFFF0这个地址;
整个过程是一个纯硬件完成给你的,恰好ROM-BIOS的入口地址就是0xFFFF0,即是BIOS程序的第一条指令就设计在这个位置上。
➤ BIOS加载中断向量和终端服务程序到内存
BIOS通常被固化在ROM中,它通常会检查显卡、内存等自检操作,当然,在这里最值得一提的是BOIS在内存中建立中断向量表和中断服务程序。书中选择的BIOS的大小为8KB,地址为:0xFE000~0xFFFFF。
① BIOS在内存最开始的位置(0x00000)用1KB的内存空间(0x00000~0x003FFF)构建中断向量表。中断向量表由256个中断向量,每个中断向量占4 Byte,其中两个Byte为CS的值,两个Byte为IP的值。1K = 256 × 4B。
② 在紧接着中断向量表的256 Byte的内存空间构建BIOS数据区(0x00400~0x004FF)。
③ 在大约56KB以后的位置(0xE2CE)加载8KB左右的与中断向量相应的若干中断服务程序。0xE2CE = 14 + 12 × 16 + 2 × 162 + 14 × 163 = 14 + 12 × 24 + 2 × 28 + 14 × 212(14 × 4 × 210 = 56KB) = 56.52734375KB。
◆ 加载OS内核程序:
现在将开始执行boot操作了。此时,计算机将分三次将OS逐次加载到内存中。
① 由BIOS中断int0x19 把第1扇区bootsect的内容加载到内存中。
② 由bootsect将第1个扇区之后的4个扇区加载至内存。
③ 由bootsect将第5个扇区之后的240个扇区加载至内存。
图1 boot下的三个文件
➤ 加载引导程序bootsect(第一个扇区)
1. 计算机硬件与BIOS联手,通过CPU执行int 0x19中断将引导程序所在的第一个扇区加载至内存。int 0x19中断向量指向的中断服务程序的入口地址是0x0E6F2,该中断服务程序的功能是固定的,它会将软驱的0号磁头对应盘面的0磁道1扇区的内存拷贝至0x07C00处。引导程序代码在boot/bootsect.s文件中。
2. 当它被执行时,首先会将自己移动到0x90000(代码段最开始的地方就做了这个操作line 46)。
3. 然后,它设置堆栈,栈顶指定为0x9000:0xFF00。
4. 读第2~5共4个扇区的代码(setup.s)到内存0x90200处。
5. 将第5个扇区之后的240个扇区的代码(system模块)读入到以0x10000开始的内存。
6. 之后,bootsect.s将控制权交给setup.s。
图2 执行过程图
bootsect.s代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 ! 2 ! SYS_SIZE is the number of clicks (16 bytes) to be loaded. 3 ! 0x3000 is 0x30000 bytes = 196kB, more than enough for current 4 ! versions of linux 5 ! 6 SYSSIZE = 0x3000 7 ! 8 ! bootsect.s (C) 1991 Linus Torvalds 9 ! 10 ! bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves 11 ! iself out of the way to address 0x90000, and jumps there. 12 ! 13 ! It then loads 'setup' directly after itself (0x90200), and the system 14 ! at 0x10000, using BIOS interrupts. 15 ! 16 ! NOTE! currently system is at most 8*65536 bytes long. This should be no 17 ! problem, even in the future. I want to keep it simple. This 512 kB 18 ! kernel size should be enough, especially as this doesn't contain the 19 ! buffer cache as in minix 20 ! 21 ! The loader has been made as simple as possible, and continuos 22 ! read errors will result in a unbreakable loop. Reboot by hand. It 23 ! loads pretty fast by getting whole sectors at a time whenever possible. 24 25 .globl begtext, begdata, begbss, endtext, enddata, endbss 26 .text 27 begtext: 28 .data 29 begdata: 30 .bss 31 begbss: 32 .text 33 34 SETUPLEN = 4 ! nr of setup-sectors 35 BOOTSEG = 0x07c0 ! original address of boot-sector 36 INITSEG = 0x9000 ! we move boot here - out of the way 37 SETUPSEG = 0x9020 ! setup starts here 38 SYSSEG = 0x1000 ! system loaded at 0x10000 (65536). 39 ENDSEG = SYSSEG + SYSSIZE ! where to stop loading 40 41 ! ROOT_DEV: 0x000 - same type of floppy as boot. 42 ! 0x301 - first partition on first drive etc 43 ROOT_DEV = 0x306 44 45 entry start 46 start: 47 mov ax,#BOOTSEG 48 mov ds,ax 49 mov ax,#INITSEG 50 mov es,ax 51 mov cx,#256 52 sub si,si 53 sub di,di 54 rep 55 movw 56 jmpi go,INITSEG 57 go: mov ax,cs 58 mov ds,ax 59 mov es,ax 60 ! put stack at 0x9ff00. 61 mov ss,ax 62 mov sp,#0xFF00 ! arbitrary value >>512 63 64 ! load the setup-sectors directly after the bootblock. 65 ! Note that 'es' is already set up. 66 67 load_setup: 68 mov dx,#0x0000 ! drive 0, head 0 69 mov cx,#0x0002 ! sector 2, track 0 70 mov bx,#0x0200 ! address = 512, in INITSEG 71 mov ax,#0x0200+SETUPLEN ! service 2, nr of sectors 72 int 0x13 ! read it 73 jnc ok_load_setup ! ok - continue 74 mov dx,#0x0000 75 mov ax,#0x0000 ! reset the diskette 76 int 0x13 77 j load_setup 78 79 ok_load_setup: 80 81 ! Get disk drive parameters, specifically nr of sectors/track 82 83 mov dl,#0x00 84 mov ax,#0x0800 ! AH=8 is get drive parameters 85 int 0x13 86 mov ch,#0x00 87 seg cs 88 mov sectors,cx 89 mov ax,#INITSEG 90 mov es,ax 91 92 ! Print some inane message 93 94 mov ah,#0x03 ! read cursor pos 95 xor bh,bh 96 int 0x10 97 98 mov cx,#24 99 mov bx,#0x0007 ! page 0, attribute 7 (normal) 100 mov bp,#msg1 101 mov ax,#0x1301 ! write string, move cursor 102 int 0x10 103 104 ! ok, we've written the message, now 105 ! we want to load the system (at 0x10000) 106 107 mov ax,#SYSSEG 108 mov es,ax ! segment of 0x010000 109 call read_it 110 call kill_motor 111 112 ! After that we check which root-device to use. If the device is 113 ! defined (!= 0), nothing is done and the given device is used. 114 ! Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending 115 ! on the number of sectors that the BIOS reports currently. 116 117 seg cs 118 mov ax,root_dev 119 cmp ax,#0 120 jne root_defined 121 seg cs 122 mov bx,sectors 123 mov ax,#0x0208 ! /dev/ps0 - 1.2Mb 124 cmp bx,#15 125 je root_defined 126 mov ax,#0x021c ! /dev/PS0 - 1.44Mb 127 cmp bx,#18 128 je root_defined 129 undef_root: 130 jmp undef_root 131 root_defined: 132 seg cs 133 mov root_dev,ax 134 135 ! after that (everyting loaded), we jump to 136 ! the setup-routine loaded directly after 137 ! the bootblock: 138 139 jmpi 0,SETUPSEG 140 141 ! This routine loads the system at address 0x10000, making sure 142 ! no 64kB boundaries are crossed. We try to load it as fast as 143 ! possible, loading whole tracks whenever we can. 144 ! 145 ! in: es - starting address segment (normally 0x1000) 146 ! 147 sread: .word 1+SETUPLEN ! sectors read of current track 148 head: .word 0 ! current head 149 track: .word 0 ! current track 150 151 read_it: 152 mov ax,es 153 test ax,#0x0fff 154 die: jne die ! es must be at 64kB boundary 155 xor bx,bx ! bx is starting address within segment 156 rp_read: 157 mov ax,es 158 cmp ax,#ENDSEG ! have we loaded all yet? 159 jb ok1_read 160 ret 161 ok1_read: 162 seg cs 163 mov ax,sectors 164 sub ax,sread 165 mov cx,ax 166 shl cx,#9 167 add cx,bx 168 jnc ok2_read 169 je ok2_read 170 xor ax,ax 171 sub ax,bx 172 shr ax,#9 173 ok2_read: 174 call read_track 175 mov cx,ax 176 add ax,sread 177 seg cs 178 cmp ax,sectors 179 jne ok3_read 180 mov ax,#1 181 sub ax,head 182 jne ok4_read 183 inc track 184 ok4_read: 185 mov head,ax 186 xor ax,ax 187 ok3_read: 188 mov sread,ax 189 shl cx,#9 190 add bx,cx 191 jnc rp_read 192 mov ax,es 193 add ax,#0x1000 194 mov es,ax 195 xor bx,bx 196 jmp rp_read 197 198 read_track: 199 push ax 200 push bx 201 push cx 202 push dx 203 mov dx,track 204 mov cx,sread 205 inc cx 206 mov ch,dl 207 mov dx,head 208 mov dh,dl 209 mov dl,#0 210 and dx,#0x0100 211 mov ah,#2 212 int 0x13 213 jc bad_rt 214 pop dx 215 pop cx 216 pop bx 217 pop ax 218 ret 219 bad_rt: mov ax,#0 220 mov dx,#0 221 int 0x13 222 pop dx 223 pop cx 224 pop bx 225 pop ax 226 jmp read_track 227 228 /* 229 * This procedure turns off the floppy drive motor, so 230 * that we enter the kernel in a known state, and 231 * don't have to worry about it later. 232 */ 233 kill_motor: 234 push dx 235 mov dx,#0x3f2 236 mov al,#0 237 outb 238 pop dx 239 ret 240 241 sectors: 242 .word 0 243 244 msg1: 245 .byte 13,10 246 .ascii "Loading system ..." 247 .byte 13,10,13,10 248 249 .org 508 250 root_dev: 251 .word ROOT_DEV 252 boot_flag: 253 .word 0xAA55 254 255 .text 256 endtext: 257 .data 258 enddata: 259 .bss 260 endbss: