《一个操作系统的实现》总结1——启动流程

来源:互联网 发布:android java 线程池 编辑:程序博客网 时间:2024/05/21 10:04

注:文中的代码是在ORANGE源码(最后一个版本chapter10\e\kernel的基础上所添加的注解,以 /*** ***/ 为标注,主要是一些重要文件和比较难理解的地方做了注解。

  由于回车或字符大小原因一些图在网页上的显示可能不是很准确,可以复制到本机看。

  另外,CSDN的编辑器有点shi,整篇发表有错误,因此拆成了三份,本意是一整篇文章。


  在操作系统正式开始之前我们先 make image ,这里做了一些重要的准备活动,在Makefile中可以看到:将boot.bin用dd写入软盘的第一个扇区,将loader.bin和kernel.bin(ELF格式,不能直接被执行)放入软盘根目录中,将shell命令程序打包并写入硬盘。




    一、启动流程  

S1、从软盘启动时,软盘的第一个扇区内容会被加载到内存0x7C00处,并从此处开始执行; 

S2、(boot.asm)在软盘的根目录寻找loader.bin,并读入内存,跳转到loader执行; 

S3、(loader.asm)在软盘的根目录寻找kernel.bin,并读入内存;  

S4、(loader.asm)跳入保护模式;  

S5、(loader.asm)建立页表机制;  

S6、(loader.asm)解析ELF格式的kernel.bin,根据其程序头信息和入口地址等将kernel载入到内存中的正确位置;  

S7、(loader.asm)跳到kernel的入口地址处,下面进入kernel执行;  

S8、(kernel.asm)初始化并使用新GDT、新IDT;  

S9、(kernel.asm)跳入kernel_main执行;

S10、最终,进入各进程交替运行、相互通信的状态,进程间的切换是时钟中断处理程序完成的。(中断处理程序、系统调用处理程序、系统任务、用户进程的初始化是在global.c和kernel/main.c中进行的,所有机制都初始化完成后,就进入了由时钟中断、键盘中断等中断驱动的如同一个生态系统的运行状态)



  这一部分涉及的文件有boot/boot.asm、boot/loader.asm、kernel/kernel.asm:

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++;                               boot.asm; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++;                                                     Forrest Yu, 2005; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++;%define_BOOT_DEBUG_; 做 Boot Sector 时一定将此行注释掉!将此行打开后用 nasm Boot.asm -o Boot.com 做成一个.COM文件易于调试%ifdef_BOOT_DEBUG_org  0100h; 调试状态, 做成 .COM 文件, 可调试%elseorg  07c00h; Boot 状态, Bios 将把 Boot Sector 加载到 0:7C00 处并开始执行;/***告诉程序被加载到cs实际值的偏移7c00h处,保证程序中跳转正确***/%endif;================================================================================================%ifdef_BOOT_DEBUG_BaseOfStackequ0100h; 调试状态下堆栈基地址(栈底, 从这个位置向低地址生长)%elseBaseOfStackequ07c00h; Boot状态下堆栈基地址(栈底, 从这个位置向低地址生长)%endif%include"load.inc";================================================================================================jmp short LABEL_START; Start to boot.nop; 这个 nop 不可少; 下面是 FAT12 磁盘的头, 之所以包含它是因为下面用到了磁盘的一些信息%include"fat12hdr.inc";/***前三句共同构成了FAT12文件系统引导扇区头,其中fat12hdr.inc中的db、dw等声明是实际占用内存空间的,属于引导扇区的组成部分,而equ声明只是宏定义,不占用实际内存。只有有这个头,该软盘才能被识别为FAT12文件系统,另外注意该格式是软盘对应的格式。(见P104)***/LABEL_START:movax, csmovds, axmoves, axmovss, axmovsp, BaseOfStack;/***cs初始时为0,为了能正确使用声明的标签(相对于cs)、数据(相对ds)、以及堆栈(相对ss)等,需要将各个段寄存器初始化为与cs相同的值,因为实模式下使用的是逻辑地址,在实际寻址时会加上对应的10*段寄存器值。另外注意该处最后将BaseOfStack初始化为了7c00h,即boot的起始处,这样做在使用栈时会覆盖7c00h前面的数据,由于500h-7c00h之间是未使用空间,因此可以随便覆盖(见P141图)***/; 清屏movax, 0600h; AH = 6,  AL = 0hmovbx, 0700h; 黑底白字(BL = 07h)movcx, 0; 左上角: (0, 0)movdx, 0184fh; 右下角: (80, 50)int10h; int 10hmovdh, 0; "Booting  "callDispStr; 显示字符串xorah, ah; ┓xordl, dl; ┣ 软驱复位int13h; ┛; 下面在 A 盘的根目录寻找 LOADER.BIN;/***我们在bochs的bochsrc配置文件中配置了floppya: 1_44=a.img, status=inserted 和 boot: a 两句,即我们的虚拟机是使用软盘启动的,该软盘即是a.img,我们首先是将boot.asm编译的boot.bin用dd写入到a.img,注意是写入,不是复制粘贴,即boot.bin已经成了该盘开始的一部分,前面已经说过,最开始的三句话确定了该盘的文件系统是FAT12。后来我们才把把loader.bin和kernel.bin放到了这个软盘的根目录下,所以才会有下面的寻找loader.bin***/movword [wSectorNo], SectorNoOfRootDirectoryLABEL_SEARCH_IN_ROOT_DIR_BEGIN:cmpword [wRootDirSizeForLoop], 0; ┓jzLABEL_NO_LOADERBIN; ┣ 判断根目录区是不是已经读完decword [wRootDirSizeForLoop]; ┛ 如果读完表示没有找到 LOADER.BINmovax, LOADER_SEGmoves, ax; es <- LOADER_SEGmovbx, LOADER_OFF; bx <- LOADER_OFF于是, es:bx = LOADER_SEG:LOADER_OFFmovax, [wSectorNo]; ax <- Root Directory 中的某 Sector 号movcl, 1callReadSectormovsi, LoaderFileName; ds:si -> "LOADER  BIN"movdi, LOADER_OFF; es:di -> LOADER_SEG:0100 = LOADER_SEG*10h+100cldmovdx, 10hLABEL_SEARCH_FOR_LOADERBIN:cmpdx, 0; ┓循环次数控制,jzLABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR; ┣如果已经读完了一个 Sector,decdx; ┛就跳到下一个 Sectormovcx, 11LABEL_CMP_FILENAME:cmpcx, 0jzLABEL_FILENAME_FOUND; 如果比较了 11 个字符都相等, 表示找到deccxlodsb; ds:si -> alcmpal, byte [es:di]jzLABEL_GO_ONjmpLABEL_DIFFERENT; 只要发现不一样的字符就表明本 DirectoryEntry 不是; 我们要找的 LOADER.BINLABEL_GO_ON:incdijmpLABEL_CMP_FILENAME;继续循环LABEL_DIFFERENT:anddi, 0FFE0h; else ┓di &= E0 为了让它指向本条目开头adddi, 20h;      ┃movsi, LoaderFileName;      ┣ di += 20h  下一个目录条目jmpLABEL_SEARCH_FOR_LOADERBIN;    ┛LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:addword [wSectorNo], 1jmpLABEL_SEARCH_IN_ROOT_DIR_BEGINLABEL_NO_LOADERBIN:movdh, 2; "No LOADER."callDispStr; 显示字符串%ifdef_BOOT_DEBUG_movax, 4c00h; ┓int21h; ┛没有找到 LOADER.BIN, 回到 DOS%elsejmp$; 没有找到 LOADER.BIN, 死循环在这里%endifLABEL_FILENAME_FOUND:; 找到 LOADER.BIN 后便来到这里继续movax, RootDirSectorsanddi, 0FFE0h; di -> 当前条目的开始adddi, 01Ah; di -> 首 Sectormovcx, word [es:di]pushcx; 保存此 Sector 在 FAT 中的序号addcx, axaddcx, DeltaSectorNo; 这句完成时 cl 里面变成 LOADER.BIN 的起始扇区号 (从 0 开始数的序号)movax, LOADER_SEGmoves, ax; es <- LOADER_SEGmovbx, LOADER_OFF; bx <- LOADER_OFF于是, es:bx = LOADER_SEG:LOADER_OFF = LOADER_SEG * 10h + LOADER_OFFmovax, cx; ax <- Sector 号LABEL_GOON_LOADING_FILE:pushax; ┓pushbx; ┃movah, 0Eh; ┃ 每读一个扇区就在 "Booting  " 后面打一个点, 形成这样的效果:moval, '.'; ┃movbl, 0Fh; ┃ Booting ......int10h; ┃popbx; ┃popax; ┛movcl, 1callReadSectorpopax; 取出此 Sector 在 FAT 中的序号callGetFATEntrycmpax, 0FFFhjzLABEL_FILE_LOADEDpushax; 保存 Sector 在 FAT 中的序号movdx, RootDirSectorsaddax, dxaddax, DeltaSectorNoaddbx, [BPB_BytsPerSec]jmpLABEL_GOON_LOADING_FILELABEL_FILE_LOADED:movdh, 1; "Ready."callDispStr; 显示字符串;/***执行下面一句,cs会被赋值为LOADER_SEG即90000h,而实际loader.bin被加载的开始位置是90100h处,因此跳到了90100h处执行,loader.asm中最开始的org 0100h就是由于这个原因。***/; *****************************************************************************************************jmpLOADER_SEG:LOADER_OFF; 这一句正式跳转到已加载到内存中的 LOADER.BIN 的开始处; 开始执行 LOADER.BIN 的代码; Boot Sector 的使命到此结束; *****************************************************************************************************;============================================================================;变量;----------------------------------------------------------------------------wRootDirSizeForLoopdwRootDirSectors; Root Directory 占用的扇区数, 在循环中会递减至零.wSectorNodw0; 要读取的扇区号bOdddb0; 奇数还是偶数;============================================================================;字符串;----------------------------------------------------------------------------LoaderFileNamedb"LOADER  BIN", 0; LOADER.BIN 之文件名; 为简化代码, 下面每个字符串的长度均为 MessageLengthMessageLengthequ9BootMessage:db"Booting  "; 9字节, 不够则用空格补齐. 序号 0Message1db"Ready.   "; 9字节, 不够则用空格补齐. 序号 1Message2db"No LOADER"; 9字节, 不够则用空格补齐. 序号 2;============================================================================;----------------------------------------------------------------------------; 函数名: DispStr;----------------------------------------------------------------------------; 作用:;显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based)DispStr:movax, MessageLengthmuldhaddax, BootMessagemovbp, ax; ┓movax, ds; ┣ ES:BP = 串地址moves, ax; ┛movcx, MessageLength; CX = 串长度movax, 01301h; AH = 13,  AL = 01hmovbx, 0007h; 页号为0(BH = 0) 黑底白字(BL = 07h)movdl, 0int10h; int 10hret;----------------------------------------------------------------------------; 函数名: ReadSector;----------------------------------------------------------------------------; 作用:;从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中ReadSector:; -----------------------------------------------------------------------; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号); -----------------------------------------------------------------------; 设扇区号为 x;                           ┌ 柱面号 = y >> 1;       x           ┌ 商 y ┤; -------------- => ┤      └ 磁头号 = y & 1;  每磁道扇区数     │;                   └ 余 z => 起始扇区号 = z + 1pushbpmovbp, spsubesp, 2; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2]movbyte [bp-2], clpushbx; 保存 bxmovbl, [BPB_SecPerTrk]; bl: 除数divbl; y 在 al 中, z 在 ah 中incah; z ++movcl, ah; cl <- 起始扇区号movdh, al; dh <- yshral, 1; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2)movch, al; ch <- 柱面号anddh, 1; dh & 1 = 磁头号popbx; 恢复 bx; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^movdl, [BS_DrvNum]; 驱动器号 (0 表示 A 盘).GoOnReading:movah, 2; 读moval, byte [bp-2]; 读 al 个扇区int13hjc.GoOnReading; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止addesp, 2popbpret;----------------------------------------------------------------------------; 函数名: GetFATEntry;----------------------------------------------------------------------------; 作用:;找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中;需要注意的是, 中间需要读 FAT 的扇区到 es:bx 处, 所以函数一开始保存了 es 和 bxGetFATEntry:pushespushbxpushaxmovax, LOADER_SEG; ┓subax, 0100h; ┣ 在 LOADER_SEG 后面留出 4K 空间用于存放 FATmoves, ax; ┛popaxmovbyte [bOdd], 0movbx, 3mulbx; dx:ax = ax * 3movbx, 2divbx; dx:ax / 2  ==>  ax <- 商, dx <- 余数cmpdx, 0jzLABEL_EVENmovbyte [bOdd], 1LABEL_EVEN:;偶数xordx, dx; 现在 ax 中是 FATEntry 在 FAT 中的偏移量. 下面来计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区)movbx, [BPB_BytsPerSec]divbx; dx:ax / BPB_BytsPerSec  ==>ax <- 商   (FATEntry 所在的扇区相对于 FAT 来说的扇区号);dx <- 余数 (FATEntry 在扇区内的偏移)。pushdxmovbx, 0; bx <- 0于是, es:bx = (LOADER_SEG - 100):00 = (LOADER_SEG - 100) * 10haddax, SectorNoOfFAT1; 此句执行之后的 ax 就是 FATEntry 所在的扇区号movcl, 2callReadSector; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界发生错误, 因为一个 FATEntry 可能跨越两个扇区popdxaddbx, dxmovax, [es:bx]cmpbyte [bOdd], 1jnzLABEL_EVEN_2shrax, 4LABEL_EVEN_2:andax, 0FFFhLABEL_GET_FAT_ENRY_OK:popbxpopesret;----------------------------------------------------------------------------times 510-($-$$)db0; 填充剩下的空间,使生成的二进制代码恰好为512字节dw 0xaa55; 结束标志

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++;                               loader.asm; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++;                                                     Forrest Yu, 2005; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++org  0100h;/***参见boot.asm中跳转的解释***/jmpLABEL_START; Start; 下面是 FAT12 磁盘的头, 之所以包含它是因为下面用到了磁盘的一些信息%include"fat12hdr.inc"%include"load.inc"%include"pm.inc"; GDT ------------------------------------------------------------------------------------------------------------------------------------------------------------;                                                段基址            段界限     , 属性LABEL_GDT:Descriptor             0,                    0, 0; 空描述符;/***这是代码段***/LABEL_DESC_FLAT_C:Descriptor             0,              0fffffh, DA_CR  | DA_32 | DA_LIMIT_4K; 0 ~ 4G;/***这是数据段***/LABEL_DESC_FLAT_RW:Descriptor             0,              0fffffh, DA_DRW | DA_32 | DA_LIMIT_4K; 0 ~ 4G;/***这是显存***/LABEL_DESC_VIDEO:Descriptor 0B8000h,               0ffffh, DA_DRW                         | DA_DPL3; 显存首地址; GDT ------------------------------------------------------------------------------------------------------------------------------------------------------------GdtLenequ$ - LABEL_GDTGdtPtrdwGdtLen - 1; 段界限ddLOADER_PHY_ADDR + LABEL_GDT; 基地址 (让基地址八字节对齐将起到优化速度之效果,目前懒得改);/***LOADER_PHY_ADDR是90000h,由于受到org 0100h的影响,LABEL_GDT是相对于程序开始的偏移量加org的值,即0100h***/; The GDT is not a segment itself; instead, it is a data structure in linear address space.; The base linear address and limit of the GDT must be loaded into the GDTR register. -- IA-32 Software Developer’s Manual, Vol.3A; GDT 选择子 ----------------------------------------------------------------------------------SelectorFlatCequLABEL_DESC_FLAT_C- LABEL_GDTSelectorFlatRWequLABEL_DESC_FLAT_RW- LABEL_GDTSelectorVideoequLABEL_DESC_VIDEO- LABEL_GDT + SA_RPL3; GDT 选择子 ----------------------------------------------------------------------------------BaseOfStackequ0100hLABEL_START:; <--- 从这里开始 *************movax, csmovds, axmoves, axmovss, axmovsp, BaseOfStackmovdh, 0; "Loading  "callDispStrRealMode; 显示字符串; 得到内存数movebx, 0; ebx = 后续值, 开始时需为 0movdi, _MemChkBuf; es:di 指向一个地址范围描述符结构(Address Range Descriptor Structure).MemChkLoop:moveax, 0E820h; eax = 0000E820hmovecx, 20; ecx = 地址范围描述符结构的大小movedx, 0534D4150h; edx = 'SMAP'int15h; int 15hjc.MemChkFailadddi, 20incdword [_dwMCRNumber]; dwMCRNumber = ARDS 的个数cmpebx, 0jne.MemChkLoopjmp.MemChkOK.MemChkFail:movdword [_dwMCRNumber], 0.MemChkOK:; 下面在 A 盘的根目录寻找 KERNEL.BINmovword [wSectorNo], SectorNoOfRootDirectoryxorah, ah; ┓xordl, dl; ┣ 软驱复位int13h; ┛LABEL_SEARCH_IN_ROOT_DIR_BEGIN:cmpword [wRootDirSizeForLoop], 0; ┓jzLABEL_NO_KERNELBIN; ┣ 判断根目录区是不是已经读完, 如果读完表示没有找到 KERNEL.BINdecword [wRootDirSizeForLoop]; ┛movax, KERNEL_FILE_SEGmoves, ax; es <- KERNEL_FILE_SEGmovbx, KERNEL_FILE_OFF; bx <- KERNEL_FILE_OFF于是, es:bx = KERNEL_FILE_SEG:KERNEL_FILE_OFF = KERNEL_FILE_SEG * 10h + KERNEL_FILE_OFFmovax, [wSectorNo]; ax <- Root Directory 中的某 Sector 号movcl, 1callReadSectormovsi, KernelFileName; ds:si -> "KERNEL  BIN"movdi, KERNEL_FILE_OFF; es:di -> KERNEL_FILE_SEG:???? = KERNEL_FILE_SEG*10h+????cldmovdx, 10hLABEL_SEARCH_FOR_KERNELBIN:cmpdx, 0; ┓jzLABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR; ┣ 循环次数控制, 如果已经读完了一个 Sector, 就跳到下一个 Sectordecdx; ┛movcx, 11LABEL_CMP_FILENAME:cmpcx, 0; ┓jzLABEL_FILENAME_FOUND; ┣ 循环次数控制, 如果比较了 11 个字符都相等, 表示找到deccx; ┛lodsb; ds:si -> alcmpal, byte [es:di]; if al == es:dijzLABEL_GO_ONjmpLABEL_DIFFERENTLABEL_GO_ON:incdijmpLABEL_CMP_FILENAME;继续循环LABEL_DIFFERENT:anddi, 0FFE0h; else┓这时di的值不知道是什么, di &= e0 为了让它是 20h 的倍数adddi, 20h;     ┃movsi, KernelFileName;     ┣ di += 20h  下一个目录条目jmpLABEL_SEARCH_FOR_KERNELBIN;   ┛LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:addword [wSectorNo], 1jmpLABEL_SEARCH_IN_ROOT_DIR_BEGINLABEL_NO_KERNELBIN:movdh, 3; "No KERNEL."callDispStrRealMode; 显示字符串jmp$; 没有找到 KERNEL.BIN, 死循环在这里LABEL_FILENAME_FOUND:; 找到 KERNEL.BIN 后便来到这里继续movax, RootDirSectorsanddi, 0FFF0h; di -> 当前条目的开始pusheaxmoveax, [es : di + 01Ch]; ┓movdword [dwKernelSize], eax; ┛保存 KERNEL.BIN 文件大小cmpeax, KERNEL_VALID_SPACEja.1popeaxjmp.2.1:movdh, 4; "Too Large"callDispStrRealMode; 显示字符串jmp$; KERNEL.BIN 太大,死循环在这里.2:adddi, 01Ah; di -> 首 Sectormovcx, word [es:di]pushcx; 保存此 Sector 在 FAT 中的序号addcx, axaddcx, DeltaSectorNo; 这时 cl 里面是 LOADER.BIN 的起始扇区号 (从 0 开始数的序号)movax, KERNEL_FILE_SEGmoves, ax; es <- KERNEL_FILE_SEGmovbx, KERNEL_FILE_OFF; bx <- KERNEL_FILE_OFF于是, es:bx = KERNEL_FILE_SEG:KERNEL_FILE_OFF = KERNEL_FILE_SEG * 10h + KERNEL_FILE_OFFmovax, cx; ax <- Sector 号LABEL_GOON_LOADING_FILE:pushax; ┓pushbx; ┃movah, 0Eh; ┃ 每读一个扇区就在 "Loading  " 后面moval, '.'; ┃ 打一个点, 形成这样的效果:movbl, 0Fh; ┃ Loading ......int10h; ┃popbx; ┃popax; ┛movcl, 1callReadSectorpopax; 取出此 Sector 在 FAT 中的序号callGetFATEntrycmpax, 0FFFhjzLABEL_FILE_LOADEDpushax; 保存 Sector 在 FAT 中的序号movdx, RootDirSectorsaddax, dxaddax, DeltaSectorNoaddbx, [BPB_BytsPerSec]jc.1; 如果 bx 重新变成 0,说明内核大于 64Kjmp.2;/***现在的kernel.bin可存放的位置是70000h-90000h,实模式下一段为64K,如果超过了该大小需要从70000h开始的段转为80000h开始的段,即段寄存器加1000h***/.1:pushax; es += 0x1000  ← es 指向下一个段movax, esaddax, 1000hmoves, axpopax.2:jmpLABEL_GOON_LOADING_FILELABEL_FILE_LOADED:callKillMotor; 关闭软驱马达;;; ;; 取硬盘信息;;; xoreax, eax;;; movah, 08h; Code for drive parameters;;; movdx, 80h; hard drive;;; int0x13;;; jb.hderr; No such drive?;;; ;; cylinder number;;; xorax, ax; ax <- 0;;; movah, cl; ax <- cl;;; shrah, 6;;; andah, 3   ; cl bits 7-6: high two bits of maximum cylinder number;;; moval, ch; CH = low eight bits of maximum cylinder number;;; ;; sector number;;; andcl, 3Fh; cl bits 5-0: max sector number (1-origin);;; ;; head number;;; incdh; dh = 1 + max head number (0-origin);;; mov[_dwNrHead], dh;;; mov[_dwNrSector], cl;;; mov[_dwNrCylinder], ax;;; jmp.hdok;;; .hderr:;;; movdword [_dwNrHead], 0FFFFh;;; .hdok:;; 将硬盘引导扇区内容读入内存 0500h 处xor     ax, axmov     es, axmov     ax, 0201h       ; AH = 02                        ; AL = number of sectors to read (must be nonzero) mov     cx, 1           ; CH = low eight bits of cylinder number                        ; CL = sector number 1-63 (bits 0-5)                        ;      high two bits of cylinder (bits 6-7, hard disk only)mov     dx, 80h         ; DH = head number                        ; DL = drive number (bit 7 set for hard disk)mov     bx, 500h        ; ES:BX -> data bufferint     13h;; 硬盘操作完毕movdh, 2; "Ready."callDispStrRealMode; 显示字符串; 下面准备跳入保护模式 -------------------------------------------; 加载 GDTRlgdt[GdtPtr]; 关中断cli; 打开地址线A20inal, 92horal, 00000010bout92h, al; 准备切换到保护模式moveax, cr0oreax, 1movcr0, eax; 真正进入保护模式jmpdword SelectorFlatC:(LOADER_PHY_ADDR+LABEL_PM_START);/***SelectorFlatC选择子对应的段,基址为0,因此冒号后面应该是实际的线性地址,由于还没有开启页表机制,此处就是物理地址了。LOADER的实际的物理地址即是LOADER_PHY_ADDR,而再加上LABEL_PM_START即是下面的保护模式代码入口***/;============================================================================;变量;----------------------------------------------------------------------------wRootDirSizeForLoopdwRootDirSectors; Root Directory 占用的扇区数wSectorNodw0; 要读取的扇区号bOdddb0; 奇数还是偶数dwKernelSizedd0; KERNEL.BIN 文件大小;============================================================================;字符串;----------------------------------------------------------------------------KernelFileNamedb"KERNEL  BIN", 0; KERNEL.BIN 之文件名; 为简化代码, 下面每个字符串的长度均为 MessageLengthMessageLengthequ9LoadMessage:db"Loading  "Message1db"         "Message2db"Ready.   "Message3db"No KERNEL"Message4db"Too Large";============================================================================;----------------------------------------------------------------------------; 函数名: DispStrRealMode;----------------------------------------------------------------------------; 运行环境:;实模式(保护模式下显示字符串由函数 DispStr 完成); 作用:;显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based)DispStrRealMode:movax, MessageLengthmuldhaddax, LoadMessagemovbp, ax; ┓movax, ds; ┣ ES:BP = 串地址moves, ax; ┛movcx, MessageLength; CX = 串长度movax, 01301h; AH = 13,  AL = 01hmovbx, 0007h; 页号为0(BH = 0) 黑底白字(BL = 07h)movdl, 0adddh, 3; 从第 3 行往下显示int10h; int 10hret;----------------------------------------------------------------------------; 函数名: ReadSector;----------------------------------------------------------------------------; 作用:;从序号(Directory Entry 中的 Sector 号)为 ax 的的 Sector 开始, 将 cl 个 Sector 读入 es:bx 中ReadSector:; -----------------------------------------------------------------------; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号); -----------------------------------------------------------------------; 设扇区号为 x;                           ┌ 柱面号 = y >> 1;       x           ┌ 商 y ┤; -------------- => ┤      └ 磁头号 = y & 1;  每磁道扇区数     │;                   └ 余 z => 起始扇区号 = z + 1pushbpmovbp, spsubesp, 2; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2]movbyte [bp-2], clpushbx; 保存 bxmovbl, [BPB_SecPerTrk]; bl: 除数divbl; y 在 al 中, z 在 ah 中incah; z ++movcl, ah; cl <- 起始扇区号movdh, al; dh <- yshral, 1; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2)movch, al; ch <- 柱面号anddh, 1; dh & 1 = 磁头号popbx; 恢复 bx; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^movdl, [BS_DrvNum]; 驱动器号 (0 表示 A 盘).GoOnReading:movah, 2; 读moval, byte [bp-2]; 读 al 个扇区int13hjc.GoOnReading; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止addesp, 2popbpret;----------------------------------------------------------------------------; 函数名: GetFATEntry;----------------------------------------------------------------------------; 作用:;找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中;需要注意的是, 中间需要读 FAT 的扇区到 es:bx 处, 所以函数一开始保存了 es 和 bxGetFATEntry:pushespushbxpushaxmovax, KERNEL_FILE_SEG; ┓subax, 0100h; ┣ 在 KERNEL_FILE_SEG 后面留出 4K 空间用于存放 FATmoves, ax; ┛popaxmovbyte [bOdd], 0movbx, 3mulbx; dx:ax = ax * 3movbx, 2divbx; dx:ax / 2  ==>  ax <- 商, dx <- 余数cmpdx, 0jzLABEL_EVENmovbyte [bOdd], 1LABEL_EVEN:;偶数xordx, dx; 现在 ax 中是 FATEntry 在 FAT 中的偏移量. 下面来计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区)movbx, [BPB_BytsPerSec]divbx; dx:ax / BPB_BytsPerSec  ==>ax <- 商   (FATEntry 所在的扇区相对于 FAT 来说的扇区号);dx <- 余数 (FATEntry 在扇区内的偏移)。pushdxmovbx, 0; bx <- 0于是, es:bx = (KERNEL_FILE_SEG - 100):00 = (KERNEL_FILE_SEG - 100) * 10haddax, SectorNoOfFAT1; 此句执行之后的 ax 就是 FATEntry 所在的扇区号movcl, 2callReadSector; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界发生错误, 因为一个 FATEntry 可能跨越两个扇区popdxaddbx, dxmovax, [es:bx]cmpbyte [bOdd], 1jnzLABEL_EVEN_2shrax, 4LABEL_EVEN_2:andax, 0FFFhLABEL_GET_FAT_ENRY_OK:popbxpopesret;----------------------------------------------------------------------------;----------------------------------------------------------------------------; 函数名: KillMotor;----------------------------------------------------------------------------; 作用:;关闭软驱马达KillMotor:pushdxmovdx, 03F2hmoval, 0outdx, alpopdxret;----------------------------------------------------------------------------; 从此以后的代码在保护模式下执行 ----------------------------------------------------; 32 位代码段. 由实模式跳入 ---------------------------------------------------------[SECTION .s32]ALIGN32[BITS32]LABEL_PM_START:movax, SelectorVideomovgs, axmovax, SelectorFlatRWmovds, axmoves, axmovfs, axmovss, axmovesp, TopOfStack;/***cs在前面跳到这边时已被赋值为SelectorFlatC,即代码段的选择子;另外这里,gs被赋值为显存数据段的选择子;ds、es、fs、ss被赋值为数据段的选择子***/callDispMemInfo;;; callDispReturn;;; callDispHDInfo; int 13h 读出的硬盘 geometry 好像有点不对头,不知道为什么,干脆不管它了callSetupPaging;movah, 0Fh; 0000: 黑底    1111: 白字;moval, 'P';mov[gs:((80 * 0 + 39) * 2)], ax; 屏幕第 0 行, 第 39 列。;/***kernel.bin是elf格式的文件,真正载入内存运行需要根据其Program Header的内容将每个段的内容载入到指定的线性地址中(见P123)***/callInitKernel;jmp$;; fill in BootParam[]movdword [BOOT_PARAM_ADDR], BOOT_PARAM_MAGIC ; Magic Numbermoveax, [dwMemSize]mov[BOOT_PARAM_ADDR + 4], eax ; memory sizemoveax, KERNEL_FILE_SEGshleax, 4addeax, KERNEL_FILE_OFFmov[BOOT_PARAM_ADDR + 8], eax ; phy-addr of kernel.bin;/***Kernel被加载到了600h处,程序的入口地址是1000h***/;***************************************************************jmpSelectorFlatC:KRNL_ENT_PT_PHY_ADDR; 正式进入内核 *;***************************************************************; 内存看上去是这样的:;              ┃                                    ┃;              ┃                 .                  ┃;              ┃                 .                  ┃;              ┃                 .                  ┃;              ┣━━━━━━━━━━━━━━━━━━┫;              ┃■■■■■■■■■■■■■■■■■■┃;              ┃■■■■■■Page  Tables■■■■■■┃;              ┃■■■■■(大小由LOADER决定)■■■■┃;    00101000h ┃■■■■■■■■■■■■■■■■■■┃ PAGE_TBL_BASE;              ┣━━━━━━━━━━━━━━━━━━┫;              ┃■■■■■■■■■■■■■■■■■■┃;    00100000h ┃■■■■Page Directory Table■■■■┃ PAGE_DIR_BASE  <- 1M;              ┣━━━━━━━━━━━━━━━━━━┫;              ┃□□□□□□□□□□□□□□□□□□┃;       F0000h ┃□□□□□□□System ROM□□□□□□┃;              ┣━━━━━━━━━━━━━━━━━━┫;              ┃□□□□□□□□□□□□□□□□□□┃;       E0000h ┃□□□□Expansion of system ROM □□┃;              ┣━━━━━━━━━━━━━━━━━━┫;              ┃□□□□□□□□□□□□□□□□□□┃;       C0000h ┃□□□Reserved for ROM expansion□□┃;              ┣━━━━━━━━━━━━━━━━━━┫;              ┃□□□□□□□□□□□□□□□□□□┃ B8000h ← gs;       A0000h ┃□□□Display adapter reserved□□□┃;              ┣━━━━━━━━━━━━━━━━━━┫;              ┃□□□□□□□□□□□□□□□□□□┃;       9FC00h ┃□□extended BIOS data area (EBDA)□┃;              ┣━━━━━━━━━━━━━━━━━━┫;              ┃■■■■■■■■■■■■■■■■■■┃;       90000h ┃■■■■■■■LOADER.BIN■■■■■■┃ somewhere in LOADER ← esp;              ┣━━━━━━━━━━━━━━━━━━┫;              ┃■■■■■■■■■■■■■■■■■■┃;              ┃■■■■■■■■■■■■■■■■■■┃;       70000h ┃■■■■■■■KERNEL.BIN■■■■■■┃;              ┣━━━━━━━━━━━━━━━━━━┫;              ┃■■■■■■■■■■■■■■■■■■┃;              ┃■■■■■■■■■■■■■■■■■■┃;              ┃■■■■■■■■■■■■■■■■■■┃;              ┃■■■■■■■■■■■■■■■■■■┃;              ┃■■■■■■■■■■■■■■■■■■┃;              ┃■■■■■■■■■■■■■■■■■■┃;              ┃■■■■■■■■■■■■■■■■■■┃ 7C00h~7DFFh : BOOT SECTOR, overwritten by the kernel;              ┃■■■■■■■■■■■■■■■■■■┃;              ┃■■■■■■■■■■■■■■■■■■┃;              ┃■■■■■■■■■■■■■■■■■■┃;        1000h ┃■■■■■■■■KERNEL■■■■■■■┃ 1000h ← KERNEL 入口 (KRNL_ENT_PT_PHY_ADDR);              ┣━━━━━━━━━━━━━━━━━━┫;              ┃                                    ┃;         500h ┃              F  R  E  E            ┃;              ┣━━━━━━━━━━━━━━━━━━┫;              ┃□□□□□□□□□□□□□□□□□□┃;         400h ┃□□□□ROM BIOS parameter area □□┃;              ┣━━━━━━━━━━━━━━━━━━┫;              ┃◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇┃;           0h ┃◇◇◇◇◇◇Int  Vectors◇◇◇◇◇◇┃;              ┗━━━━━━━━━━━━━━━━━━┛ ← cs, ds, es, fs, ss;;;┏━━━┓┏━━━┓;┃■■■┃ 我们使用 ┃□□□┃ 不能使用的内存;┗━━━┛┗━━━┛;┏━━━┓┏━━━┓;┃      ┃ 未使用空间┃◇◇◇┃ 可以覆盖的内存;┗━━━┛┗━━━┛;; 注:KERNEL 的位置实际上是很灵活的,可以通过同时改变 LOAD.INC 中的 KRNL_ENT_PT_PHY_ADDR 和 MAKEFILE 中参数 -Ttext 的值来改变。;     比如,如果把 KRNL_ENT_PT_PHY_ADDR 和 -Ttext 的值都改为 0x400400,则 KERNEL 就会被加载到内存 0x400000(4M) 处,入口在 0x400400。;; ------------------------------------------------------------------------; 显示 AL 中的数字; ------------------------------------------------------------------------DispAL:pushecxpushedxpushedimovedi, [dwDispPos]movah, 0Fh; 0000b: 黑底    1111b: 白字movdl, alshral, 4movecx, 2.begin:andal, 01111bcmpal, 9ja.1addal, '0'jmp.2.1:subal, 0Ahaddal, 'A'.2:mov[gs:edi], axaddedi, 2moval, dlloop.begin;addedi, 2mov[dwDispPos], edipopedipopedxpopecxret; DispAL 结束-------------------------------------------------------------; ------------------------------------------------------------------------; 显示一个整形数; ------------------------------------------------------------------------DispInt:moveax, [esp + 4]shreax, 24callDispALmoveax, [esp + 4]shreax, 16callDispALmoveax, [esp + 4]shreax, 8callDispALmoveax, [esp + 4]callDispALmovah, 07h; 0000b: 黑底    0111b: 灰字moval, 'h'pushedimovedi, [dwDispPos]mov[gs:edi], axaddedi, 4mov[dwDispPos], edipopediret; DispInt 结束------------------------------------------------------------; ------------------------------------------------------------------------; 显示一个字符串; ------------------------------------------------------------------------DispStr:pushebpmovebp, esppushebxpushesipushedimovesi, [ebp + 8]; pszInfomovedi, [dwDispPos]movah, 0Fh.1:lodsbtestal, aljz.2cmpal, 0Ah; 是回车吗?jnz.3pusheaxmoveax, edimovbl, 160divblandeax, 0FFhinceaxmovbl, 160mulblmovedi, eaxpopeaxjmp.1.3:mov[gs:edi], axaddedi, 2jmp.1.2:mov[dwDispPos], edipopedipopesipopebxpopebpret; DispStr 结束------------------------------------------------------------; ------------------------------------------------------------------------; 换行; ------------------------------------------------------------------------DispReturn:pushszReturncallDispStr;printf("\n");addesp, 4ret; DispReturn 结束---------------------------------------------------------; ------------------------------------------------------------------------; 内存拷贝,仿 memcpy; ------------------------------------------------------------------------; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize);; ------------------------------------------------------------------------MemCpy:pushebpmovebp, esppushesipushedipushecxmovedi, [ebp + 8]; Destinationmovesi, [ebp + 12]; Sourcemovecx, [ebp + 16]; Counter.1:cmpecx, 0; 判断计数器jz.2; 计数器为零时跳出moval, [ds:esi]; ┓incesi; ┃; ┣ 逐字节移动movbyte [es:edi], al; ┃incedi; ┛dececx; 计数器减一jmp.1; 循环.2:moveax, [ebp + 8]; 返回值popecxpopedipopesimovesp, ebppopebpret; 函数结束,返回; MemCpy 结束-------------------------------------------------------------; 显示内存信息 --------------------------------------------------------------DispMemInfo:pushesipushedipushecxpushszMemChkTitlecallDispStraddesp, 4movesi, MemChkBufmovecx, [dwMCRNumber];for(int i=0;i<[MCRNumber];i++) // 每次得到一个ARDS(Address Range Descriptor Structure)结构.loop:;{movedx, 5;for(int j=0;j<5;j++)// 每次得到一个ARDS中的成员,共5个成员movedi, ARDStruct;{// 依次显示:BaseAddrLow,BaseAddrHigh,LengthLow,LengthHigh,Type.1:;pushdword [esi];callDispInt;DispInt(MemChkBuf[j*4]); // 显示一个成员popeax;stosd;ARDStruct[j*4] = MemChkBuf[j*4];addesi, 4;decedx;cmpedx, 0;jnz.1;}callDispReturn;printf("\n");cmpdword [dwType], 1;if(Type == AddressRangeMemory) // AddressRangeMemory : 1, AddressRangeReserved : 2jne.2;{moveax, [dwBaseAddrLow];addeax, [dwLengthLow];cmpeax, [dwMemSize];if(BaseAddrLow + LengthLow > MemSize)jb.2;mov[dwMemSize], eax;MemSize = BaseAddrLow + LengthLow;.2:;}loop.loop;};callDispReturn;printf("\n");pushszRAMSize;callDispStr;printf("RAM size:");addesp, 4;;pushdword [dwMemSize];callDispInt;DispInt(MemSize);addesp, 4;popecxpopedipopesiret; ---------------------------------------------------------------------------;;; ; 显示内存信息 --------------------------------------------------------------;;; DispHDInfo:;;; pusheax;;; cmpdword [dwNrHead], 0FFFFh;;; je.nohd;;; pushszCylinder;;; callDispStr; printf("C:");;;; addesp, 4;;; pushdword [dwNrCylinder] ; NR Cylinder;;; callDispInt;;; popeax;;; pushszHead;;; callDispStr; printf(" H:");;;; addesp, 4;;; pushdword [dwNrHead] ; NR Head;;; callDispInt;;; popeax;;; pushszSector;;; callDispStr; printf(" S:");;;; addesp, 4;;; pushdword [dwNrSector] ; NR Sector;;; callDispInt;;; popeax;;; jmp.hdinfo_finish;;; .nohd:;;; pushszNOHD;;; callDispStr; printf("No hard drive. System halt.");;;; addesp, 4;;; jmp$; 没有硬盘,死在这里;;; .hdinfo_finish:;;; callDispReturn;;; popeax;;; ret;;; ; ---------------------------------------------------------------------------; 启动分页机制 --------------------------------------------------------------SetupPaging:; 根据内存大小计算应初始化多少PDE以及多少页表xoredx, edxmoveax, [dwMemSize]movebx, 400000h; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小divebxmovecx, eax; 此时 ecx 为页表的个数,也即 PDE 应该的个数testedx, edxjz.no_remainderincecx; 如果余数不为 0 就需增加一个页表.no_remainder:pushecx; 暂存页表个数; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.; 首先初始化页目录movax, SelectorFlatRWmoves, axmovedi, PAGE_DIR_BASE; 此段首地址为 PAGE_DIR_BASExoreax, eaxmoveax, PAGE_TBL_BASE | PG_P  | PG_USU | PG_RWW.1:stosdaddeax, 4096; 为了简化, 所有页表在内存中是连续的.loop.1; 再初始化所有页表popeax; 页表个数movebx, 1024; 每个页表 1024 个 PTEmulebxmovecx, eax; PTE个数 = 页表个数 * 1024movedi, PAGE_TBL_BASE; 此段首地址为 PAGE_TBL_BASExoreax, eaxmoveax, PG_P  | PG_USU | PG_RWW.2:stosdaddeax, 4096; 每一页指向 4K 的空间loop.2moveax, PAGE_DIR_BASEmovcr3, eaxmoveax, cr0oreax, 80000000hmovcr0, eaxjmpshort .3.3:nopret; 分页机制启动完毕 ----------------------------------------------------------; InitKernel ---------------------------------------------------------------------------------; 将 KERNEL.BIN 的内容经过整理对齐后放到新的位置; --------------------------------------------------------------------------------------------InitKernel:; 遍历每一个 Program Header,根据 Program Header 中的信息来确定把什么放进内存,放到什么位置,以及放多少。xoresi, esimovcx, word [KERNEL_FILE_PHY_ADDR + 2Ch]; ┓ ecx <- pELFHdr->e_phnummovzxecx, cx; ┛movesi, [KERNEL_FILE_PHY_ADDR + 1Ch]; esi <- pELFHdr->e_phoffaddesi, KERNEL_FILE_PHY_ADDR; esi <- OffsetOfKernel + pELFHdr->e_phoff.Begin:moveax, [esi + 0]cmpeax, 0; PT_NULLjz.NoActionpushdword [esi + 010h]; size┓moveax, [esi + 04h];┃addeax, KERNEL_FILE_PHY_ADDR;┣ ::memcpy((void*)(pPHdr->p_vaddr),pusheax; src┃uchCode + pPHdr->p_offset,pushdword [esi + 08h]; dst┃pPHdr->p_filesz;callMemCpy;┃addesp, 12;┛.NoAction:addesi, 020h; esi += pELFHdr->e_phentsizedececxjnz.Beginret; InitKernel ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^; SECTION .data1 之开始 ---------------------------------------------------------------------------------------------[SECTION .data1]ALIGN32LABEL_DATA:; 实模式下使用这些符号; 字符串_szMemChkTitle:db"BaseAddrL BaseAddrH LengthLow LengthHigh   Type", 0Ah, 0_szRAMSize:db"RAM size: ", 0;;; _szCylinderdb"HD Info : C=", 0;;; _szHeaddb" H=", 0;;; _szSectordb" S=", 0;;; _szNOHDdb"No hard drive. System halt.", 0_szReturn:db0Ah, 0;; 变量;;; _dwNrCylinderdd0;;; _dwNrHeaddd0;;; _dwNrSectordd0_dwMCRNumber:dd0; Memory Check Result_dwDispPos:dd(80 * 7 + 0) * 2; 屏幕第 7 行, 第 0 列。_dwMemSize:dd0_ARDStruct:; Address Range Descriptor Structure_dwBaseAddrLow:dd0_dwBaseAddrHigh:dd0_dwLengthLow:dd0_dwLengthHigh:dd0_dwType:dd0_MemChkBuf:times256db0;;; 保护模式下使用这些符号szMemChkTitleequLOADER_PHY_ADDR + _szMemChkTitleszRAMSizeequLOADER_PHY_ADDR + _szRAMSize;;; szCylinderequLOADER_PHY_ADDR + _szCylinder;;; szHeadequLOADER_PHY_ADDR + _szHead;;; szSectorequLOADER_PHY_ADDR + _szSector;;; szNOHDequLOADER_PHY_ADDR + _szNOHDszReturnequLOADER_PHY_ADDR + _szReturn;;; dwNrCylinderequLOADER_PHY_ADDR + _dwNrCylinder;;; dwNrHeadequLOADER_PHY_ADDR + _dwNrHead;;; dwNrSectorequLOADER_PHY_ADDR + _dwNrSectordwDispPosequLOADER_PHY_ADDR + _dwDispPosdwMemSizeequLOADER_PHY_ADDR + _dwMemSizedwMCRNumberequLOADER_PHY_ADDR + _dwMCRNumberARDStructequLOADER_PHY_ADDR + _ARDStructdwBaseAddrLowequLOADER_PHY_ADDR + _dwBaseAddrLowdwBaseAddrHighequLOADER_PHY_ADDR + _dwBaseAddrHighdwLengthLowequLOADER_PHY_ADDR + _dwLengthLowdwLengthHighequLOADER_PHY_ADDR + _dwLengthHighdwTypeequLOADER_PHY_ADDR + _dwTypeMemChkBufequLOADER_PHY_ADDR + _MemChkBuf; 堆栈就在数据段的末尾StackSpace:times1000hdb0TopOfStackequLOADER_PHY_ADDR + $; 栈顶; SECTION .data1 之结束 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^



; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++;                               kernel.asm; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++;                                                     Forrest Yu, 2005; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++%include "sconst.inc"; 导入函数externcstartexternkernel_mainexternexception_handlerexternspurious_irqexternclock_handlerexterndisp_strexterndelayexternirq_table; 导入全局变量externgdt_ptrexternidt_ptrexternp_proc_readyexterntssexterndisp_posexternk_reenterexternsys_call_tablebits 32[SECTION .data]clock_int_msgdb"^", 0[SECTION .bss]StackSpaceresb2 * 1024StackTop:; 栈顶[section .text]; 代码在此global _start; 导出 _startglobal restartglobal sys_callglobaldivide_errorglobalsingle_step_exceptionglobalnmiglobalbreakpoint_exceptionglobaloverflowglobalbounds_checkglobalinval_opcodeglobalcopr_not_availableglobaldouble_faultglobalcopr_seg_overrunglobalinval_tssglobalsegment_not_presentglobalstack_exceptionglobalgeneral_protectionglobalpage_faultglobalcopr_errorglobalhwint00globalhwint01globalhwint02globalhwint03globalhwint04globalhwint05globalhwint06globalhwint07globalhwint08globalhwint09globalhwint10globalhwint11globalhwint12globalhwint13globalhwint14globalhwint15_start:; 此时内存看上去是这样的(更详细的内存情况在 LOADER.ASM 中有说明):;              ┃                                    ┃;              ┃                 ...                ┃;              ┣━━━━━━━━━━━━━━━━━━┫;              ┃■■■■■■Page  Tables■■■■■■┃;              ┃■■■■■(大小由LOADER决定)■■■■┃ PageTblBase;    00101000h ┣━━━━━━━━━━━━━━━━━━┫;              ┃■■■■Page Directory Table■■■■┃ PageDirBase = 1M;    00100000h ┣━━━━━━━━━━━━━━━━━━┫;              ┃□□□□ Hardware  Reserved □□□□┃ B8000h ← gs;       9FC00h ┣━━━━━━━━━━━━━━━━━━┫;              ┃■■■■■■■LOADER.BIN■■■■■■┃ somewhere in LOADER ← esp;       90000h ┣━━━━━━━━━━━━━━━━━━┫;              ┃■■■■■■■KERNEL.BIN■■■■■■┃;       80000h ┣━━━━━━━━━━━━━━━━━━┫;              ┃■■■■■■■■KERNEL■■■■■■■┃ 30400h ← KERNEL 入口 (KernelEntryPointPhyAddr);       30000h ┣━━━━━━━━━━━━━━━━━━┫;              ┋                 ...                ┋;              ┋                                    ┋;           0h ┗━━━━━━━━━━━━━━━━━━┛ ← cs, ds, es, fs, ss;;; GDT 以及相应的描述符是这样的:;;              Descriptors               Selectors;              ┏━━━━━━━━━━━━━━━━━━┓;              ┃         Dummy Descriptor           ┃;              ┣━━━━━━━━━━━━━━━━━━┫;              ┃         DESC_FLAT_C    (0~4G)     ┃   8h = cs;              ┣━━━━━━━━━━━━━━━━━━┫;              ┃         DESC_FLAT_RW   (0~4G)     ┃  10h = ds, es, fs, ss;              ┣━━━━━━━━━━━━━━━━━━┫;              ┃         DESC_VIDEO                 ┃  1Bh = gs;              ┗━━━━━━━━━━━━━━━━━━┛;; 注意! 在使用 C 代码的时候一定要保证 ds, es, ss 这几个段寄存器的值是一样的; 因为编译器有可能编译出使用它们的代码, 而编译器默认它们是一样的. 比如串拷贝操作会用到 ds 和 es.;;; 把 esp 从 LOADER 挪到 KERNELmovesp, StackTop; 堆栈在 bss 段中movdword [disp_pos], 0sgdt[gdt_ptr]; cstart() 中将会用到 gdt_ptrcallcstart; 在此函数中改变了gdt_ptr,让它指向新的GDT;/***;该函数做的事情有:;(1)将老GDT复制到新GDT;(2)初始化gdt_ptr;(3)初始化idt_ptr;(4)调用init_prot() -------------(1)调用init_8259A()-------------------------------初始化主从8259A,关掉所有中断,初始化中断响应为默认的spurious_irq;                          |-------(2)初始化内部中断如除零等,以及外部中断,处理程序即是该文件后面所列的中断异常处理;                          |-------(3)填充 GDT 中 tss 的描述符;                          |-------(4)填充 GDT 中进程的 LDT 的描述符;***/lgdt[gdt_ptr]; 使用新的GDT;/***原先使用的是BIOS初始化的中断向量表,即内存的0-400h处,在这里换成了新的含有256中断向量的中断向量表。这也意味着现在0-400h处的内存可以被覆盖掉了。***/lidt[idt_ptr];/***Intel不提供直接给cs赋值的指令,只能用这种跨段跳转的方式隐式对cs进行赋值***/jmpSELECTOR_KERNEL_CS:csinitcsinit:; “这个跳转指令强制使用刚刚初始化的结构”——<<OS:D&I 2nd>> P90.;jmp 0x40:0;ud2xoreax, eaxmovax, SELECTOR_TSSltrax;stijmpkernel_main;/***;该函数在kerne_main中,做的事情主要有:;(1)根据task_table和user_proc_table初始化proc_table;(2)调用initial_clock()--------------------------------打开中断,设定中断处理程序为clock_handler (clock.c);(3)调用initial_keyboard()-----------------------------打开中断,设定中断处理程序为keyboard_handler  (keyboard.c);//硬盘中断的初始化init_hd()在task_hd()的开始调用,于渊的代码组织真心是硬伤;(4)调用restart()--------------------------------------在该文件末尾,跳入p_proc_ready指向的进程,之前被赋值为proc_table中的第一个进程执行,即task_tty (tty.c);***/;hlt; 中断和异常 -- 硬件中断; ---------------------------------%macrohwint_master1;/***结合代码6.46,进入中断时ss、esp、eflags、cs、eip已被依次压栈,此时esp指向的是retaddr+4***/;/***中断处理过程默认不响应中断,所以此时不响应中断,IF为0***/callsaveinal, INT_M_CTLMASK; `.oral, (1 << %1);  | 屏蔽当前中断outINT_M_CTLMASK, al; /;/***屏蔽掉相同的中断,不会出现相同中断的重入情况***/moval, EOI; `. 置EOI位outINT_M_CTL, al; /sti; CPU在响应中断的过程中会自动关中断,这句之后就允许响应新的中断push%1; `.;/***中断处理程序,在该过程中,可以响应所有其他中断,因为打开了eoi和sti***/call[irq_table + 4 * %1];  | 中断处理程序popecx; /cliinal, INT_M_CTLMASK; `.andal, ~(1 << %1);  | 恢复接受当前中断outINT_M_CTLMASK, al; /ret;/***ret是只弹出一个eip,然后执行,这里是弹出的由save中压入的参数,即跳到_restart或restart1执行,真正从中断返回是在这两个处理的最后的iretd***/%endmacroALIGN16hwint00:; Interrupt routine for irq 0 (the clock).hwint_master0ALIGN16hwint01:; Interrupt routine for irq 1 (keyboard)hwint_master1ALIGN16hwint02:; Interrupt routine for irq 2 (cascade!)hwint_master2ALIGN16hwint03:; Interrupt routine for irq 3 (second serial)hwint_master3ALIGN16hwint04:; Interrupt routine for irq 4 (first serial)hwint_master4ALIGN16hwint05:; Interrupt routine for irq 5 (XT winchester)hwint_master5ALIGN16hwint06:; Interrupt routine for irq 6 (floppy)hwint_master6ALIGN16hwint07:; Interrupt routine for irq 7 (printer)hwint_master7; ---------------------------------%macrohwint_slave1;/***对8258A从片设置EOI位必须保证主片也同样被设置***/callsaveinal, INT_S_CTLMASK; `.oral, (1 << (%1 - 8));  | 屏蔽当前中断outINT_S_CTLMASK, al; /moval, EOI; `. 置EOI位(master)outINT_M_CTL, al; /nop; `. 置EOI位(slave)outINT_S_CTL, al; /  一定注意:slave和master都要置EOIsti; CPU在响应中断的过程中会自动关中断,这句之后就允许响应新的中断push%1; `.call[irq_table + 4 * %1];  | 中断处理程序popecx; /cliinal, INT_S_CTLMASK; `.andal, ~(1 << (%1 - 8));  | 恢复接受当前中断outINT_S_CTLMASK, al; /ret%endmacro; ---------------------------------ALIGN16hwint08:; Interrupt routine for irq 8 (realtime clock).hwint_slave8ALIGN16hwint09:; Interrupt routine for irq 9 (irq 2 redirected)hwint_slave9ALIGN16hwint10:; Interrupt routine for irq 10hwint_slave10ALIGN16hwint11:; Interrupt routine for irq 11hwint_slave11ALIGN16hwint12:; Interrupt routine for irq 12hwint_slave12ALIGN16hwint13:; Interrupt routine for irq 13 (FPU exception)hwint_slave13ALIGN16hwint14:; Interrupt routine for irq 14 (AT winchester)hwint_slave14ALIGN16hwint15:; Interrupt routine for irq 15hwint_slave15; 中断和异常 -- 异常divide_error:push0xFFFFFFFF; no err codepush0; vector_no= 0jmpexceptionsingle_step_exception:push0xFFFFFFFF; no err codepush1; vector_no= 1jmpexceptionnmi:push0xFFFFFFFF; no err codepush2; vector_no= 2jmpexceptionbreakpoint_exception:push0xFFFFFFFF; no err codepush3; vector_no= 3jmpexceptionoverflow:push0xFFFFFFFF; no err codepush4; vector_no= 4jmpexceptionbounds_check:push0xFFFFFFFF; no err codepush5; vector_no= 5jmpexceptioninval_opcode:push0xFFFFFFFF; no err codepush6; vector_no= 6jmpexceptioncopr_not_available:push0xFFFFFFFF; no err codepush7; vector_no= 7jmpexceptiondouble_fault:push8; vector_no= 8jmpexceptioncopr_seg_overrun:push0xFFFFFFFF; no err codepush9; vector_no= 9jmpexceptioninval_tss:push10; vector_no= Ajmpexceptionsegment_not_present:push11; vector_no= Bjmpexceptionstack_exception:push12; vector_no= Cjmpexceptiongeneral_protection:push13; vector_no= Djmpexceptionpage_fault:push14; vector_no= Ejmpexceptioncopr_error:push0xFFFFFFFF; no err codepush16; vector_no= 10hjmpexceptionexception:callexception_handleraddesp, 4*2; 让栈顶指向 EIP,堆栈中从顶向下依次是:EIP、CS、EFLAGShlt; =============================================================================;                                   save; =============================================================================save:;/***此函数是经过call进来的,因此进入之前eip已被压入retaddr处,此时esp指向retaddr***/        pushad          ; `.        push    ds      ;  |        push    es      ;  | 保存原寄存器值        push    fs      ;  |        push    gs      ; /;; 注意,从这里开始,一直到 `mov esp, StackTop',中间坚决不能用 push/pop 指令,;; 因为当前 esp 指向 proc_table 里的某个位置,push 会破坏掉进程表,导致灾难性后果!movesi, edx; 保存 edx,因为 edx 里保存了系统调用的参数;(没用栈,而是用了另一个寄存器 esi)movdx, ssmovds, dxmoves, dxmovfs, dxmovedx, esi; 恢复 edx        mov     esi, esp                    ;esi = 进程表起始地址        inc     dword [k_reenter]           ;k_reenter++;        cmp     dword [k_reenter], 0        ;if(k_reenter ==0)        jne     .1                          ;{        mov     esp, StackTop               ;  mov esp, StackTop <--切换到内核栈;/***如果不是中断重入,就压入_restart,然后返回save的下一句继续执行***/        push    restart                     ;  push restart        jmp     [esi + RETADR - P_STACKBASE];  return;.1:                                         ;} else { 已经在内核栈,不需要再切换;/***如果是中断重入,压入restart1,然后返回save下一句继续执行***/        push    restart_reenter             ;  push restart_reenter        jmp     [esi + RETADR - P_STACKBASE];  return;                                            ;}; =============================================================================;                                 sys_call; =============================================================================;/***系统调用***/sys_call:        call    save        stipushesipushdword [p_proc_ready]pushedxpushecxpushebx        call    [sys_call_table + eax * 4]addesp, 4 * 4popesi        mov     [esi + EAXREG - P_STACKBASE], eax        cli        ret; ====================================================================================;                                   restart; ====================================================================================;/***重入的情况由于是要返回到上一层的中断处理程序,这里不涉及堆栈的转换,因此直接pop出压入的寄存器值即可;而非重入的情况是返回进程,涉及到堆栈的转换,因此会有over_call_unhold的堆栈转换的处理***/restart:;/***p_proc_ready这个值会在进程调度的时候改变成进程表中的不同值以实现进程的切换***/movesp, [p_proc_ready]lldt[esp + P_LDT_SEL] leaeax, [esp + P_STACKTOP]movdword [tss + TSS3_S_SP0], eaxrestart_reenter:decdword [k_reenter]popgspopfspopespopdspopadaddesp, 4iretd



    可以看出,kernel之所以称之为kernel是因为它是整个系统的核心。
    从用户的角度来看,整个系统就是很多的用户进程在交替运行和通信。
    用户进程的切换是时钟中断触发时钟中断处理程序,在时钟中断处理程序的schedule()中进行进程调度,而由中断发生转到中断处理程序是在陷入内核后进行的;
    用户进程间的通信,包括和系统任务的通信是通过调用send_recv()进行通信,而该函数实际上一个包装的系统调用,系统调用也是通过陷入内核进行处理的,它将发送者的消息缓冲区内容复制到接收者的消息缓冲区,从而实现消息的传递。
    用户的输入是通过键盘中断转到键盘中断处理程序将用户输入存放到键盘缓冲区,系统任务task_tty会不停地取出缓冲区中内容并送到用户进程中和显示到屏幕上,这就实现了用户与计算机的交互。这里面涉及到了中断处理和进程间通信(task_tty与用户进程),因此也是需要陷入内核处理。
    因此,内核是核心、是桥梁,是维持这个生态系统运行的重中之重。



	
				
		
原创粉丝点击