一个操作系统的实现学习笔记记录(1)
这本书本来应该在上学期看完的,但由于中途考试的耽误让我不得已只能暂时放下,现在赶紧抓紧时间学习一波;
环境为ununtu16.04,配置啥的我都是跟着书上一步一步走下来的。
就在刚刚我决定重新继续学这个一个操作系统的实现时,又发觉原来许多已经会了的知识点现在又变的模糊不堪,不得已对着书本不会的地方又重新查起,这让我又感到该写的博客还是要写,就算没人看,以后自己复习的时候也方便点。所以我觉得可能还是我对于博客的定义以及使用理解上还是有着偏差,知识就应该多吸取别人的经验,以自己最舒服的方法总结归纳下来,提高自己,毕竟搞技术最终还是要靠能力和成果说话。
前2章基本环境的配置跟着书上走就没啥问题,也可以参考下这位兄弟的步骤,很详细了,若遇到no bootable device的问题可以参考我另一篇博客的解决办法。
http://blog.csdn.net/friendley/article/details/51398336
第三章的环境配置,可以参考这篇博客的记录很详细了,他的方法和书上的基本一致
http://blog.csdn.net/baidu_33268787/article/details/51958856
下面是我自己配置完的截图,只要细心一点基本没啥问题
pmtest.asm修改后的代码
1 ; ========================================== 2 ; pmtest1.asm 3 ; 编译方法:nasm pmtest1.asm -o pmtest1.bin 4 ; ========================================== 5 6 DA_32 EQU 4000h ; 32 位段 7 DA_C EQU 98h ; 存在的只执行代码段属性值 8 DA_DRW EQU 92h ; 存在的可读写数据段属性值 9 ATCE32 EQU 4098h ;存在的只执行32代码段属性值 10 11 ;下面是存储段描述符的宏定义 12 ; 有三个参数:段界限,段基址,段属性其中宏定义中的%1代表参数1,%2代表参数2,%$ 13 %macro Descriptor 3 14 15 dw %2 & 0FFFFh ; 段界限1(参数2的低16位) 16 dw %1 & 0FFFFh ; 段基址1(参数1的低16位) 17 db (%1 >> 16) & 0FFh ; 段基址2(参数1的16-23位) 18 dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性1(高4位) + 段界限2( $ 19 db (%1 >> 24) & 0FFh ; 段基址3(参数1的24-31位) 20 %endmacro ; 共 8 字节 21 ;段界限共20位,段基地址30位,段属性共16位(含段界限高4位) 22 23 org 0100h 24 jmp LABEL_BEGIN 25 ;----------------------------------------------------------------------------------------------------------------------------------- 26 [SECTION .gdt] ;section是告诉编译器划分出一个叫gdt的段 27 ; GDT开始 28 ; 段基址, 段界限 , 属性 29 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 30 31 LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32; 非一致代码段 32 LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址 33 ; GDT 结束 34 35 GdtLen equ $ - LABEL_GDT ; GDT长度 36 GdtPtr dw GdtLen - 1 ; GDT界限 37 dd 0 ; GDT基地址 38 39 ; GDT 选择子 40 SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT 41 SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT 42 ; END of [SECTION .gdt]
;------------------------------------------------------------------------------------------------------------------------------------------ 43 44 [SECTION .s16] ;section是告诉编译器划分出一个叫s16的段 45 [BITS 16] ;指明它是一个16位代码段 46 LABEL_BEGIN: 47 mov ax, cs 48 mov ds, ax ;设置数据段 49 mov es, ax 50 mov ss, ax ;设置堆栈段 51 mov sp, 0100h ;设置栈底指针 52 53 ; 初始化 32 位代码段描述符 54 xor eax, eax 55 mov ax, cs 56 shl eax, 4 ;eax*16==段值*16(实模式下不用手工计算,但是这里需要自己计$ 57 add eax, LABEL_SEG_CODE32;加上LABEL_SEG_CODE32偏移,这样eax->LABEL_SEG_C$ 58 mov word [LABEL_DESC_CODE32 + 2], ax ;设置基地址1(0-15位) 59 shr eax, 16 60 mov byte [LABEL_DESC_CODE32 + 4], al ;设置基地址2(16-23位) 61 mov byte [LABEL_DESC_CODE32 + 7], ah ;设置基地址3(24-31位) 62 63 ; 为加载 GDTR 作准备 64 xor eax, eax 65 mov ax, ds 66 shl eax, 4 ;eax*16==段值*16 67 add eax, LABEL_GDT ; eax <- gdt 基地址 68 mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址 69 70 ; 加载 GDTR 71 lgdt [GdtPtr] ;将GdtPtr所指的内容送到GDT寄存器 72 73 ; 关中断 74 cli 75 76 ; 打开地址线A20 77 in al, 92h ;通过操作端口92h来打开A20 78 or al, 00000010b 79 out 92h, al 80 81 ; 准备切换到保护模式 ,此处为关键,cr0的第0位为PE位,此位为0时,CPU运行于实模式,为1时,CPU运行于保护模式 82 mov eax, cr0 83 or eax, 1 ;设置cr0的0位(PE位,PE=1准备进入保护模式) 84 mov cr0, eax ;更新cr0 85 86 ; 真正进入保护模式 87 jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0 处 88 ;因为此时系统已经运行于保护模式之下了,但此时cs的值仍是实模式下的值,故需要把代码段的选择子装入cs 89 ; END of [SECTION .s16] 90 [SECTION .s32]; 32 位代码段. 由实模式跳入. 91 [BITS 32] 92 93 LABEL_SEG_CODE32: 94 mov ax, SelectorVideo 95 mov gs, ax ; 视频段选择子(目的) 96 97 mov edi, (80 * 11 + 79) * 2 ; 屏幕第 11 行, 第 79 列。 98 mov ah, 0Ch ; 0000: 黑底 1100: 红字 99 mov al, 'P' 100 mov al, 'P' 101 mov [gs:edi], ax 102 103 ; 到此停止 104 jmp $ 105 106 SegCode32Len equ $ - LABEL_SEG_CODE32 107 ; END of [SECTION .s32]
现在我们来开始一一解释下
1.GDT是个啥?如在代码第25到42的内容
在IA32下,CPU有着两种工作模式:实模式和保护模式
intel8086是16位的CPU,它有着16位的寄存器,16位的数据总线以及20位的地址总线和1M的寻址能力
一个地址是由段和偏移两部分组成的,物理地址遵循这样的计算方法:
物理地址=段值×16+偏移
其中,段值和偏移都是16位的
而从80386开始,Intel的CPU进入了32位时代,80386有32位地址线,所以寻址空间可以达到4GB,此时,若再使用16位寄存器寻址的方法已经不够用了,所以我们需要新的方法来提供更大的寻址能力
在实模式下,16位的寄存器需要用:“段:偏移”的方法才能达到1MB的寻址能力
在保护模式下,段的概念发生了根本性变化,在实模式下,段值可以看做是地址的一部分,段值位xxxxh表示以xxxx0h开始的一段内存;而在保护模式下,虽然段值仍然由原来16位的cs,ds等寄存器表示,但此时它仅仅变成了一个索引,这个索引指向数据结构的一个表项,在表项中详细地定义了段的起始地址,界限,属性等内容,这个数据结构,就是GDT(实际上也有可能是LDT),GDT中的表项也有一个专业的名字,叫做描述符(Descriptor)。
也就是说,GDT的作用是用来提供段式存储机制,这种机制是通过段寄存器和GDT中的描述符共同提供的
这个我手绘的示意图表示的是代码段和数据段描述符
除了中间的属性稍显复杂,其他3部分分别定义了一个段的基址和界限
现在回到代码的第11到21行,
;下面是存储段描述符的宏定义 12 ; 有三个参数:段界限,段基址,段属性其中宏定义中的%1代表参数1,%2代表参数2,%$ 13 %macro Descriptor 3 14 15 dw %2 & 0FFFFh ; 段界限1(参数2的低16位) 16 dw %1 & 0FFFFh ; 段基址1(参数1的低16位) 17 db (%1 >> 16) & 0FFh ; 段基址2(参数1的16-23位) 18 dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性1(高4位) + 段界限2( $ 19 db (%1 >> 24) & 0FFh ; 段基址3(参数1的24-31位) 20 %endmacro ; 共 8 字节 21 ;段界限共20位,段基地址30位,段属性共16位(含段界限高4位)
Descriptor这个宏的作用就是把段基址,段界限和段属性安排在一个描述符中合适的位置
而在第27到33行的GDT中有3个描述符LABEL_GDT,LABEL_DESC_CODE32, LABEL_DESC_VIDEO
[SECTION .gdt] ;section是告诉编译器划分出一个叫gdt的段 27 ; GDT开始 28 ; 段基址, 段界限 , 属性 29 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 30 31 LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 ; 非一致代码段 32 LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址 33 ; GDT 结束
GDT中的每一个描述符定义一个段,在上面的代码中第93~95行的cs,ds通过选择子SelectorVideo与段联系起来
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
93 LABEL_SEG_CODE32: 94 mov ax, SelectorVideo 95 mov gs, ax ; 视频段选择子(目的)
直观看SelectorVideo
他像是描述符DESC_VIDEO相对于GDT基址的偏转,实际上它叫做选择子,他不是一个偏移,其结构如下
当T1和RPL都为0时,选择子就变成了对应描述符相对于GDT基址的偏移
整个的寻址方式如图
段:偏移 形式的逻辑地址经过段机制转化成 线性地址,而不是物理地址,只是在上面的程序中,线性地址就是物理地址
总的来说, 这篇文章前面代码所实现的就是从16位的实模式经过一个跳转进入32位的保护模式中 的过程
而进入保护模式的主要步骤为:
1.准备GDT(27行到33行)
2.用lgdt加载gdtr
3.打开A20(76行到79行,之所以要打开A20是为了关中断)
4.置cr0的PE(81行到86行)
5.跳转,进入保护模式 (第87行)
有关实模式与保护模式的区别与联系,这篇博客讲的很好,值得细读
http://www.cnblogs.com/chenwb89/p/operating_system_002.html
有关GDT与LDT的关系
https://www.cnblogs.com/chenwb89/p/operating_system_003.html
http://blog.csdn.net/zdwzzu2006/article/details/4030882