fezOS搭建
前言 2021年9月初
一直很好奇操作系统究竟是如何搭建起来的,现跟随这个项目 尝试写一个自己的操作系统(暂命名为fezOS)。我将从引导扇区的编写开始,逐步将其搭建,本博客记录搭建过程中的点点滴滴
2021-9-25
计划有变,原项目进行至将内核装载如内存时,不知道为啥,我的电脑上一直无法成功运行。尝试了很多方法仍不能解决此问题。加之最关键的这本书 没有写完,于是放弃原项目跟随学习,现在跟着《操作系统真相还原》这本书学习。之前的学习笔记保留,新项目的学习笔记接着旧的继续写
原项目 00-environment 本项目搭建于WSL的Ubuntu 20.04 LTS
,按照作者文档提示,下载了qemu
和nasm
下载qemu
的过程有些曲折,我直接使用命令
1 sudo apt-get install qemu
下载该软件,但在01-bootsector-barebones
小节中使用
1 qemu boot_sect_simple .bin
命令时,系统却报错找不到qemu
命令。后找到解决方案为
1 sudo apt-get install qemu-system -i386
命令下载qemu-system-i386
,再使用
1 qemu -system-i386 boot_sect_simple.bin
即可正常使用,若嫌名字太长,可以通过建立一个软链接
1 ln -s /usr/ bin/qemu-system-i386 /u sr/bin/ qemu
之后便可直接使用
1 qemu boot_sect_simple .bin
01-bootsector-barebones 这一节主要介绍了计算机在通电启动时究竟发生了些什么,关于这方面的知识请参见这篇文章
按文档中介绍,既可以蛮力地把这512个字节全列出来,也可以参考采用作者给出的汇编代码
1 2 3 4 5 6 7 8 ; Infinite loop (e9 fd ff) loop: jmp loop ; Fill with 510 zeros minus the size of the previous code times 510-($-$$) db 0 ; Magic number dw 0xaa55
其中($-$$)
虽然结合上下文能理解什么意思,但从没见过这种用法,查阅nasm
官方文档
1 2 3 4 5 NASM supports two special tokens in expressions, allowing calculations to involve the current assembly position: the $ and $$ tokens. $ evaluates to the assembly position at the beginning of the line containing the expression; so you can code an infinite loop using JMP $. $$ evaluates to the beginning of the current section; so you can tell how far into the section you are by using ($-$$).
才得以解惑,仅仅几行代码就让我觉得作者的水平那是相当的高啊~
接着开开心心
1 qemu boot_sect_simple .bin
得目标结果
02-bootsector-print 这一节的内容不多,就是测试下用中断在终端输出字符
结合上一小节的内容猜测,BIOS通过检测扇区最后两字节是否为aa55
来判断该扇区是否为其所寻找的引导扇区。当找到后,从该扇区头部开始执行指令
至于文档中提到的tty mode
, 我简单了解了一下,但仍然不太理解。但不影响打印字符功能的实现,这里我简单修改了打印内容
03-bootsector-memory 本节介绍系统启动后BOOT SECTOR进程内存分布布局情况,参考这本书 3.4.2节了解更多细节
04-bootsector-stack 这一节要求自行学习理解栈的概念,文档中提到sp
指向栈顶,bp
指向栈底,但就我自己学习王爽老师的汇编语言经历来看,bp
是完全不需要指向栈底的。只需初始化sp
即可,可以通过bp
指针查看栈中任意位置内容
经测验,栈中(至少本实验的栈中)采用的是小端存法。即若ax = 0x3938
,执行push ax
,0x39
存于高地址,0x38
存于低地址
05-bootsector-functions-strings 这一节介绍字符串和函数调用,都是很基础的东西,不必多说。
需要知道,汇编语言中一个程序要使用另一个文件的内容只需
文档中还提到了被调用的函数应使用pusha
和popa
指令确保消除函数调用的副作用。关于这两个函数我上网查了下,这样的函数实际上有两对,分别是pusha/popa
和pushd/popd
,两者的区别如下
The PUSHA (push all) and PUSHAD (push all double) mnemonics reference the same opcode. The PUSHA instruction is intended for use when the operand-size attribute is 16 and the PUSHAD instruction for when the operand-size attribute is 32. Some assemblers may force the operand size to 16 when PUSHA is used and to 32 when PUSHAD is used. Others may treat these mnemonics as synonyms (PUSHA/PUSHAD) and use the current setting of the operand-size attribute to determine the size of values to be pushed from the stack, regardless of the mnemonic used.
The POPA (pop all) and POPAD (pop all double) mnemonics reference the same opcode. The POPA instruction is intended for use when the operand-size attribute is 16 and the POPAD instruction for when the operand-size attribute is 32. Some assemblers may force the operand size to 16 when POPA is used and to 32 when POPAD is used (using the operand-size override prefix [66H] if necessary). Others may treat these mnemonics as synonyms (POPA/POPAD) and use the current setting of the operand-size attribute to determine the size of values to be popped from the stack, regardless of the mnemonic used. (The D flag in the current code segment’s segment descriptor determines the operand-size attribute.)
pusha/popa
用于操作数为16位的情况,pushd/popd
用于32位情形
测试结果如下
只换行不回车
06-bootsector-segmentation 介绍分段相关的问题,没什么新东西。不过倒是启发了我该如何讲清03
节问题。当我们使用如mov al,[the_secret]
这样的语句时,实际上是使用了mov al,[ds:the_secret]
,在03
小节中我们没初始化ds
寄存器,则其值默认为0
,然而Loaded Boot Sector
首地址实为0x7c00
,故而应设置ds = 0x 7c0
,采用org 0x7c00
等价于此
本节代码运行结果
07-bootsector-disk 介绍了如何从别的扇区读取数据。由文档里的这篇文章 可知,设置好几个通用寄存器的值,通过int 0x13
中断调用就可以从磁盘指定扇区读取数据到es:bx
所指定的位置处
两个程序的代码阅读起来没什么难度。boot_sect_disk.asm
这程序写得挺好的,把异常处理机制完善了,挺有参考价值的一个模板程序。
本节代码运行结果
08-32bit-print 这节让了解32位保护模式,VGA以及video memory
。当从16位系统变为32位系统时则不能再使用BIOS提供的中断在屏幕上输出字符,而采用在VGA memory从首地址为0xb8000
处开始打印,而这不正是王爽老师的《汇编语言》教材上所采用的字符串输出方式吗?
这一节所涉及32位模式屏幕输出相关知识参见王老师的汇编教材即可
09-32bit-gdt 没啥好说的,参考这本书 第4.2节了解关于GDT相关内容
10-32bit-enter 1.按需求定义好GDT表
2.使用cli
关闭中断
3.使用lgbt [gdt_descriptor]
加载GDT表
4.设置cr0
寄存器最低为1,至此正式从16位实模式进入32位保护模式
5.通过一个far jump
清空流水线中可能存在的尚未执行完的16位实模式下的指令
6.记得更新各段寄存器及堆栈
11-kernel-crosscompiler 略
12-kernel-c 假设我们有一个文件hello.c
1 2 3 4 5 6 7 8 9 10 11 Compile : gcc -ffreestanding -c hello .c -o hello .o Display info from object file : objdump -d hello .o Link : ld -o hello .bin -Ttext [offset] --oformat binary hello .o Decompile : ndisasm -b 32 hello .bin > hello .dis
13-kernel-barebones 就是在这里,我的实验环境始终无法运行书上例程,从此放弃原项目,转向《操作系统真相还原》的学习
操作系统真相还原 24号书一到手就迫不及待开始从头阅读,不得不吐槽口水话好多好多。哪怕预先知道这书比较啰嗦,但看下来真的有点打脑壳…明明几句话就能说清楚的东西却花几页的篇幅来介绍,看着看着人都给套进去了,知识屏蔽不如王爽老师的《汇编语言》(感觉知识屏蔽做得最好的是旧项目里那本教程,可惜没写完,不然绝对惊艳)
01-MBR 又从引导扇区开始学起,本书实验环境作者选择了bochs
,我尝试使用这个,但是遇到了好多麻烦,花了我很多时间与精力也一直没法解决这些问题,又用回了qemu
,不得不说qemu
对比起来是真的方便易用。但bochs
的尝试并非一无所获,bochs
带有的一个创建虚拟硬盘的工具bximage
,能非常方便的创造实验需要的一个虚拟硬盘
1 linux> bximage -hd -mode ="flat" -size =60 -q hd60M.img
以上是书上给出的示例命令,我在我的电视使用上述命令时报错不知道-hd
参数是啥。。。我也难得去找纠结到底是啥问题,而是直接
进入交互界面创造了自己需要的硬盘,然后就把bochs
扔到一边,回到qemu
的怀抱
在前几章的学习中写了几个/os/boot/mbr.asm
文件,这里就只贴最后一个版本的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 ;main boot record03 ;reads from disk and loads the loader %include "boot.inc" SECTION MBR vstart=0x7c00 mov ax, cs mov ds, ax mov es, ax mov ss, ax mov fs, ax mov sp, 0x7c00 mov ax, 0xb800 mov gs, ax ;clear screen ;int 0x10 function code: 0x06 ;description: roll up the screen ;input ;ah function code = 0x06 ;al = number of rows roll up (0 for all) ;bh = attributes of rows ;(cl, ch) = top left corner (x, y) of the window ;(dl, dh) = botton right corner (x, y) of the window ;no return value mov ax, 0x600 mov bx, 0x700 mov cx, 0 ;top left corner: (0, 0) mov dx, 0x184f ;bottom right corner: (24, 79) int 0x10 ;print on screen mov byte [gs: 0x00], '1' mov byte [gs: 0x01], 0b10000011; mov byte [gs: 0x02], ' ' mov byte [gs: 0x03], 0b10000011; mov byte [gs: 0x04], 'M' mov byte [gs: 0x05], 0b10000011; mov byte [gs: 0x06], 'B' mov byte [gs: 0x07], 0b10000011; mov byte [gs: 0x08], 'R' mov byte [gs: 0x09], 0b10000011; mov eax, LOADER_START_SECTOR; ;起始扇区LBA地址 mov bx, LOADER_BASE_ADDR; ;写入的地址 mov cx, 1 ;待读入的扇区数 call rd_disk_m_16 ;调用硬盘读取程序 jmp LOADER_BASE_ADDR rd_disk_m_16: ;功能:读取硬盘n个扇区 ;eax = LBA扇区号 ;bx = 数据写入内存的地址 ;cx = 读入的扇区数 mov esi, eax ;备份eax mov di, cx ;备份cx ;读写磁盘 ;第一步:设置要读取的扇区数 mov dx, 0x1f2 mov al, cl out dx, al ;读取的扇区数 mov eax, esi ;恢复ax ;第二步:将LBA地址存入0x1f3 ~ 0x1f6 ;LBA地址7 ~ 0位写入端口0x1f3 mov dx, 0x1f3 out dx, al ;LBA地址15 ~ 8位写入端口0x1f4 mov cl, 8 shr eax, cl mov dx, 0x1f4 out dx, al ;LBA地址23 ~ 16位写入端口0x1f5 shr eax, cl mov dx, 0x1f5 out dx, al shr eax, cl and al, 0xf ;LBA第27 ~ 24位 or al, 0xe0 ;设置7 ~ 4为1110,表示LBA模式 mov dx, 0x1f6 out dx, al ;第三步:向0x1f7端口写入读命令,0x20 mov dx, 0x1f7 mov al, 0x20 out dx, al ;第四步:检测硬件状态 .not_ready: ;同一端口,写入时表示写入命令字,读时表示读入硬件状态 nop in al, dx and al, 0x88 ;第3位为1表示硬件控制器已准备好数据传输 ;第7位为1表示硬件忙 cmp al, 0x08 jnz .not_ready ;第5步:从0x1f0端口读数据 mov ax, di mov dx, 256 mul dx mov cx, ax ;di为要读取的扇区数,一个扇区512字节,一次读取读一个字,因此一个扇区读256次 mov dx, 0x1f0 .go_on_read: in ax, dx mov [bx], ax add bx, 2 loop .go_on_read ret times 510 - ($ - $$) db 0 dw 0xaa55
MBR将位于磁盘第二个扇区的loader
加载到内存地址LOADER_BASE_ADDR
起始处的位置,然后跳转至此处执行loader
的代码。本书所采用的硬盘读取方法是通过硬盘控制器的几个端口寄存器一个字一个字读入内存实现的,涉及8个寄存器的使用,只是理论就够让人头疼了,我实在是把各个寄存器相应功能及参数等等一一贴出来了,感兴趣的话请参阅此书3.5.3节
/os/boot/include/boot.inc
文件内容
1 2 LOADER_BASE_ADDR equ 0 x900 LOADER_START_SECTOR equ 0 x2
/os/boot/loader.asm
文件内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 %include "boot.inc" section loader vstart=LOADER_BASE_ADDR ;输出背景色绿色,前景色红色,并跳动的字符串"2 LOADER" mov byte [gs: 0x00], '2' mov byte [gs: 0x01], 0xa4 mov byte [gs: 0x02], ' ' mov byte [gs: 0x03], 0xa4 mov byte [gs: 0x04], 'L' mov byte [gs: 0x05], 0xa4 mov byte [gs: 0x06], 'O' mov byte [gs: 0x07], 0xa4 mov byte [gs: 0x08], 'A' mov byte [gs: 0x09], 0xa4 mov byte [gs: 0x0a], 'D' mov byte [gs: 0x0b], 0xa4 mov byte [gs: 0x0c], 'E' mov byte [gs: 0x0d], 0xa4 mov byte [gs: 0x0e], 'R' mov byte [gs: 0x0f], 0xa4 jmp $
/os/boot/mbr.asm
与/os/boot/loader.asm
都使用了位于/include
目录下的/os/boot/include/boot.inc
,因此需按如下格式编译
1 2 linux> nasm -I include / -o mbr.bin mbr.asm linxu> nasm -I include / -o loader.bin loader.asm
再将两个文件分别写入我们虚拟磁盘的第一和第二个扇区
1 2 linux> dd if =./mbr.bin of =../hd60M.img bs =512 count =1 conv =notrunc linxu> dd if =./loader.bin of =../hd60M.img bs =512 count =1 seek =2 conv =notrunc
就可以运行查看结果啦~
02-实模式到保护模式 1.构建gdt表 2.打开A20 1 2 3 in al, 0x92 or al, 0b00000010 out 0x92, al
3.加载gdt
4.cr0第0位置1 1 2 3 mov eax, cr0 or eax, 0x1 mov cr0, eax
5.刷新流水线 1 jmp dword SELECTOR_CODE: p_mode_start
插曲 我已经要疯了,使用qemu
来进行第四章实验时又是出问题,一开始我在自己代码里找问题,但对比书上代码和别人的代码一直看不出问题,于是直接使用别人的能够得到正确显示的代码运行,但仍然没能得到预期的输出。于是我怀疑是qemu
自己的问题,于是又只好用回bochs
,接着,可预料到的,无穷无尽的报错,我的心里真的有点扛不住了,直到几分钟前(Sun Sep 26 16:20:34 CST 2021),终于,一切正常了!!!!!!!我找到了错误!!!!!!!!
写在这里:
下载bochs
,去官网,按照书上所言,下载bochs-2.6.2.tar.gz
,然后跟着书上步骤做,期间会遇见各种各样的问题,但在现在我看来都是小问题,网上各种论坛博客能找到解决方案,我就不细说了,唯一想提的,也就是我一直没解决掉的问题,在配置文件的romimage
和vgaromiamge
两者的路径需要写成这样
1 2 romimage: file =你的路径/BIOS-bochs-latest vgaromiamge: file =你的路径/BIOS-bochs-latest
在下载的bochs-2.6.2.tar.gz
解压出来的bochs-2.6.2
文件夹的bios/
子文件夹里正好这两个文件,于是我就把配置文件里的路径设置为这两个的路径,即
1 2 romimage: file =/home/ fez/Download/ bios-2.6 .2 /bios/ BIOS-bochs-latest vgaromiamge: file =/home/ fez/Download/ bios-2.6 .2 /bios/ BIOS-bochs-latest
于是乎,疯狂报错
1 ROM: couldn't open ROM image file ' /home/ fez/Download/ bios-2.6 .2 /bios/ BIOS-bochs-latest'
我就纳闷了,这不就是这个文件吗,为什么就是通过不了,看别人的这两个文件都在/usr
目录下,我猜是不是/usr
目录下也会有同名的这两个文件,于是搜索
1 2 3 fez@papyruszzz:$ find /usr -name 'BIOS-bochs-latest' /usr/ local/share/ bochs/BIOS-bochs-latest/usr/ local/bin/ bochs/share/ bochs/BIOS-bochs-latest
果然!!!使用/usr/local/share/bochs/BIOS-bochs-latest
这个,同理把vgaromiamge
的目录也换成/usr
目录下对应那个,完美解决
我靠。。。。还记得刚弹出正确运行界面时,压抑不住的怒吼。。。。。。。
至此,又换回bochs
来进行实验
6.代码及实验结果 /os/boot/mbr.asm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 ;main boot record03 ;reads from disk and loads the loader %include "boot.inc" SECTION MBR vstart=0x7c00 mov ax, cs mov ds, ax mov es, ax mov ss, ax mov fs, ax mov sp, 0x7c00 mov ax, 0xb800 mov gs, ax ;clear screen ;int 0x10 function code: 0x06 ;description: roll up the screen ;input ;ah function code = 0x06 ;al = number of rows roll up (0 for all) ;bh = attributes of rows ;(cl, ch) = top left corner (x, y) of the window ;(dl, dh) = botton right corner (x, y) of the window ;no return value mov ax, 0x600 mov bx, 0x700 mov cx, 0 ;top left corner: (0, 0) mov dx, 0x184f ;bottom right corner: (24, 79) int 0x10 ;print on screen mov byte [gs: 0x00], '1' mov byte [gs: 0x01], 0b10000011; mov byte [gs: 0x02], ' ' mov byte [gs: 0x03], 0b10000011; mov byte [gs: 0x04], 'M' mov byte [gs: 0x05], 0b10000011; mov byte [gs: 0x06], 'B' mov byte [gs: 0x07], 0b10000011; mov byte [gs: 0x08], 'R' mov byte [gs: 0x09], 0b10000011; mov eax, LOADER_START_SECTOR; ;起始扇区LBA地址 mov bx, LOADER_BASE_ADDR; ;写入的地址 mov cx, 4 ;待读入的扇区数 call rd_disk_m_16 ;调用硬盘读取程序 jmp LOADER_BASE_ADDR rd_disk_m_16: ;功能:读取硬盘n个扇区 ;eax = LBA扇区号 ;bx = 数据写入内存的地址 ;cx = 读入的扇区数 mov esi, eax ;备份eax mov di, cx ;备份cx ;读写磁盘 ;第一步:设置要读取的扇区数 mov dx, 0x1f2 mov al, cl out dx, al ;读取的扇区数 mov eax, esi ;恢复ax ;第二步:将LBA地址存入0x1f3 ~ 0x1f6 ;LBA地址7 ~ 0位写入端口0x1f3 mov dx, 0x1f3 out dx, al ;LBA地址15 ~ 8位写入端口0x1f4 mov cl, 8 shr eax, cl mov dx, 0x1f4 out dx, al ;LBA地址23 ~ 16位写入端口0x1f5 shr eax, cl mov dx, 0x1f5 out dx, al shr eax, cl and al, 0xf ;LBA第27 ~ 24位 or al, 0xe0 ;设置7 ~ 4为1110,表示LBA模式 mov dx, 0x1f6 out dx, al ;第三步:向0x1f7端口写入读命令,0x20 mov dx, 0x1f7 mov al, 0x20 out dx, al ;第四步:检测硬件状态 .not_ready: ;同一端口,写入时表示写入命令字,读时表示读入硬件状态 nop in al, dx and al, 0x88 ;第3位为1表示硬件控制器已准备好数据传输 ;第7位为1表示硬件忙 cmp al, 0x08 jnz .not_ready ;第5步:从0x1f0端口读数据 mov ax, di mov dx, 256 mul dx mov cx, ax ;di为要读取的扇区数,一个扇区512字节,一次读取读一个字,因此一个扇区读256次 mov dx, 0x1f0 .go_on_read: in ax, dx mov [bx], ax add bx, 2 loop .go_on_read ret times 510 - ($ - $$) db 0 dw 0xaa55
/os/boot/loader.asm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 %include "boot.inc" section loader vstart=LOADER_BASE_ADDR LOADER_STACK_TOP equ LOADER_BASE_ADDR jmp loader_start ;构建gdt GDT_BASE: dd 0x00000000 dd 0x00000000 CODE_DESC: dd 0x0000ffff dd DESC_CODE_HIGH4 DATA_STACK_DESC: dd 0x0000ffff dd DESC_DATA_HIGH4 VIDEO_DESC: dd 0x80000007 ;limit = (0xbffff - 0xb8000) / 4k = 7 dd DESC_VIDEO_HIGH4 ;此时DPL为0 GDT_SIZE equ $ - GDT_BASE GDT_LIMIT equ GDT_SIZE - 1 times 60 dq 0 ;此处预留60个描述符空位 SELECTOR_CODE equ (0x1 << 3) + TI_GDT + RPL0 SELECTOR_DATA equ (0x2 << 3) + TI_GDT + RPL0 SELECTOR_VIDEO equ (0x3 << 3) + TI_GDT + RPL0 ;以下是gdt指针,前两字节是gdt界限,后4字节是gdt起始地址 gdt_ptr dw GDT_LIMIT dd GDT_BASE LOADERMSG db '2 loader in real.' loader_start: ;int 0x10 功能号:0x13 功能:打印字符串 mov sp, LOADER_BASE_ADDR mov bp, LOADERMSG ;es:bp = 字符串首地址 mov cx, 17 ;cx = 字符串长度 mov ax, 0x1301 mov bx, 0x001f ;bh = 页号,bl = 属性(蓝底粉红字) mov dx, 0x1800 int 0x10 ;准备进入保护模式 ;1. 打开A20 in al, 0x92 or al, 0x2 out 0x92, al ;2. 加载GDT lgdt [gdt_ptr] ;3. cr0第0位置1 mov eax, cr0 or eax, 0x1 mov cr0, eax ;4. 刷新流水线 jmp dword SELECTOR_CODE: p_mode_start [bits 32] p_mode_start: mov ax, SELECTOR_DATA mov ds, ax mov es, ax mov ss, ax mov esp, LOADER_STACK_TOP mov ax, SELECTOR_VIDEO mov gs, ax mov byte [gs: 160], 'P' mov byte [gs: 161], 0x1f jmp $
/os/boot/include/boot.inc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 ;loader and kernel LOADER_BASE_ADDR equ 0x900 LOADER_START_SECTOR equ 0x2 ;gdt描述符属性 DESC_G_4K equ 0x800000 DESC_D_32 equ 0x400000 DESC_L equ 0x0 DESC_AVL equ 0x0 DESC_LIMIT_CODE2 equ 0xf0000 DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2 DESC_LIMIT_VIDEO2 equ 0xb ;书上这里有误,我的是正确的 DESC_P equ 0x8000 DESC_DPL_0 equ 0x0 DESC_DPL_1 equ 0x2000 DESC_DPL_2 equ 0x4000 DESC_DPL_3 equ 0x6000 DESC_S_CODE equ 0x1000 DESC_S_DATA equ DESC_S_CODE DESC_S_sys equ 0x0 DESC_TYPE_CODE equ 0x800 ;x=1, c=0, r=0, a=0 代码段可执行,非一致,不可读,已访问位清零 DESC_TYPE_DATA equ 0x200 ;x=0, e=0, w=1, a=0 数据段不可执行,向上扩展,可写,已访问位清零 DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + \ DESC_L + DESC_AVL +DESC_LIMIT_CODE2 + \ DESC_P +DESC_DPL_0 +DESC_S_CODE + \ DESC_TYPE_CODE + 0X00 DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + \ DESC_L + DESC_AVL +DESC_LIMIT_DATA2 + \ DESC_P +DESC_DPL_0 +DESC_S_DATA + \ DESC_TYPE_DATA + 0X00 DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + \ DESC_L + DESC_AVL +DESC_LIMIT_VIDEO2 + \ DESC_P +DESC_DPL_0 +DESC_S_DATA + \ DESC_TYPE_DATA + 0X00 ;选择子属性 RPL0 equ 00b RPL1 equ 01b RPL2 equ 10b RPL3 equ 11b TI_GDT equ 000b TI_LDT equ 100b
有个小坑,loader.bin
文件大小已经超过512字节了,一个扇区不够,应该用两个,指令为
1 linux> dd if =./loader.bin of =../hd60M.img bs =512 count =2 seek =2 conv =notrunc
运行结果
7.一点点不一样 相比原项目的从实模式进入保护模式程序,《操作系统真相还原》这本书里没提关中断的事情。不知道是暂时没讲到中断所以暂时不管还是单纯忘了或者觉得不需要?
反正就模拟实验而言确实不关中断也无伤大雅
03-保护模式进阶 1.物理地址容量 书上介绍了三种获取地址容量的方法,都是通过调用BIOS中断实现,个人认为这个知识点过于琐碎,对我的学习目标——认识操作系统——帮助有限,不想多说。具体细节见此参考文章
2.分页 介绍了分页机制原理及实现(采用二级页表)。代码和分析书上都很详细,我这里也懒得贴了。参考文章
再次从头开始 计算机启动过程 8086寻址范围为0x0 ~ 0xFFFFF
,计算机通电时,CS: IP
寄存器被设为0xFFFF:0000
,即从地址0xFFFF0
处起开始执行,只有十六字节就到头了,0xFFFF0
处的指令为jmp far F000:e05b
,于是又跳转至0xFE05B
处开始执行,这才是BIOS代码真正开始的地方。
接下来BIOS便检测内存、显卡等外设,在0x000 ~ 0x3FF
建立中断向量表IVT并填写中断例程。当BIOS完成其使命后,在磁盘0盘0道1扇区寻址MBR,MBR的末尾两字节为0x55AA
。若找到,BIOS将MBR加载至0x7C00
处,然后jmp 0: 0x7C00
跳转至MBR起始处,开始执行MBR代码