★PART1:32位保护模式下内核简易模型
1. 内核的结构,功能和加载
每个内核的主引导程序都会有所不同,因为内核都会有不同的结构。有时候主引导程序的一些段和内核段是可以共用的(事实上加载完内核以后就不需要主引导程序了),和加载一般的用户程序一样,主引导程序也是需要从硬盘中读取程序到指定的内存空间中。
同时,作为一个内核,也是一个程序,而且是一个具有管理全局的能力的程序,应该有固定的段,一般来说,内核应该包括以下几个部分:
1. 公用例程段(实现API功能)
2. 内核数据区(用于预读一些数据和一些内核内置的保留的内容)
3. 内核代码区(用于执行内核自己的代码…)
PS:上述段都应该具有特权0级,特权级在教材的14章讲述
在主引导程序中,为了加载内核,首先应该在GDT中加载内核的所有的描述符。但是内核的描述符是不确定的(因为段界限和段的线性基地址不知道),所以要根据内核头部的信息来确定,内核的头部和普通的程序是一样的,也是应该把各个段的信息和入口点的相对地址列出来,同时还需要具有段的选择子(因为内核是固定的,选择子也应该固定)。然后主引导程序构建这些段的描述符(需要重定位,不过不需要再把段信息写入内核头部了,因为选择子有了),并且把描述符写入GDT并且重新加载GDT即可(主要是刷新GDT的大小)。
那怎么根据内核所给出的信息构建内核的描述符呢?教材给出了一种方法。(edi的内容是内核头部的地址)
其实原理很简单,就是给出需要加载的段的属性(因为加载什么段我们是知道的),然后只要填充好描述符的高32位和低32位就完了,教材这里用到了一个bswap(bswap r32)命令,简单来说这个命令就是将以某个寄存器中8位为一个段的首尾交换,如图:
2. 保护模式下的用户程序的重定位和加载
首先要明白一个事情,在保护模式下(特别是在有权限管理的情况下),程序是很难进行自我加载的,必须通过内核来加载。当然了,用户程序必须符合固定的格式,才能被内核识别和加载。一般来说,内核需要给用户程序提供3个段,这三个段一般是:
1. 代码段,
2. 数据段(这里应该有两个区域:用户程序头部和文字常量区),
3. 栈段,其中栈段要根据用户程序的建议的栈段大小来定(一般不建议用户自己创建栈段)。
(正常来讲还要有堆和BSS段,但是先不搞那么复杂)
内核从硬盘读取了用户程序并加载到相应位置的时候,就读取用户的程序头。我们知道,内核有公用程序段,是专门用来给用户程序提供公用例程的,这就是现代操作系统的API(Application Programming Interface),更直观来说就是就是一堆库函数名(比如C中的scanf,printf…)。早期的系统,API是通过中断号的方式公布的(也就是要通过软中断进入),现在常用方法是使用符号名。正常来讲,一个现代的操作系统应该是具有一个专门的链接器(Linker)来构建文件头的。教材用一个比较原始的方式来构建文件头了,其实这只是一个简单的字符串的匹配的过程而已。
(内核数据区的符号表的样貌)
在我们的规定中,用户程序的符号表固定是256个字符,同时每个符号还需要预留一个区域来保存符号对应过程的选择子(本章过程的调用是通过GDT进行的,下一章将会介绍调用门)。两个字符串的比较可以使用cmpsb(字节比较),cmpsw(字比较),cmpsd(双字比较),在16为模式下,源字符串的首地址由DS:SI指定,目的字符串的首地址由ES:DI指定,在32位模式下,则分别是DS:ESI和ES:EDI,在处理器内部,cmps指令的操作是把两个操作数相减,然后根据结果设置标志寄存器中的标志位。
单纯的cmps(指令族)只比较一次,需要通过rep前缀来不断驱动(直到ecx为0),但是这里不能单纯地使用rep指令,否则就无法比较了
重定位符号表后,我们就要对用户程序的段进行重定位和内存的分配,当然了,理论上在读取用户头部的时候就应该开辟一个内存放置用户程序,但是内核的做法是,先预读头部到内核数据区,然后再给用户程序分配内存,在现代操作系统中,内存分配是一个很重要的工作。内存管理程序不仅要把内存分块管理以便给应用程序分配内存,还要负责回收,还要进行虚拟内存管理的工作,非常复杂。这里教材用的是一个非常简单的分配demo,没有检测内存是否越界的功能,而且也没有内存回收。我往上加了一点东西。
接下来就是段的重定位和描述符的创建了,当然这一章没有讲特权级,所以教材呢就直接把所有的程序都按特权0级的权限加载了。当然这是一种很不好的做法,教材在14章会介绍如何用特权3级来加载程序。其实也不难,懂怎么加载内核就知道怎么怎么加载用户程序了,主要是栈段的分配要用到内存分配函数而已。
这里要注意的地方是Set_New_GDT这个过程,要在GDT中安装描述符,必须知道他的物理地址和大小,要知道这些消息,可以用sdgt指令(Store Global Descriptor Table Regiser)它用于将GDT军训器的基地址和边界信息保存到指定的内存位置,sgdt的指令格式是sgdt m/48,这个指令不会影响任何标志位。另外还有一个新的指令movzx
movzx是一个带零拓展的传送(Move With Zero-Extend)指令格式为(其实就是相当于先xor一下16/32位寄存器再写入低位的寄存器)
movzx r16,r/m8
movzx r32,r/m8
movzx r32,r/m16
注意movzx的指令目的操作数只能是16位或者是32位的通用寄存器,源操作数只能是8位或者16位的寄存器或者内存地址,而且目的操作数和源操作数的大小是不一样的。和movzx指令差不多的指令是movsx指令,他是带符号的传送,这个指令就不像movzx一样只会拓展0了,而是根据数的最高位来拓展。
GDTR的界限部分在初始化的时候会初始化为0xFFFF,当+1时,如果看成16位数,则会是0x0000,如果看成32位数,则会变成0x00010000,为了避免不必要的麻烦,直接写成inc bx,那这样的话开始填充描述符的时候就不是从0x00010000这个偏移地址开始了,而是0x00000000开始了。
因为应用程序给出的都是栈的建议大小,所以一般都是直接给出的是建议大小,所以我们直接按建议大小来乘以4KB的大小来分配内存,要注意的是栈段是向下拓展的,高地址才是栈段的线性基地址。
2. 用户程序的执行
一旦加载了用户程序,那么我们就可以直接直接一个跳转指令跳转到用户程序了
此时ds应该指向用户程序头部。0x10刚好是偏移地址(32位)+选择子(16位),接下来就是在用户程序执行一系列操作了,因为我们已经在用户程序填入了公用例程段的选择子,所以我们直接用fs指向用户头部,然后执行这些公用例程就可以了。
TerminateProgram是返回内核的公用例程,返回内核后就可以回收用户程序用的内存和创建的GDT了。
方法简单粗暴,当然学了14,15章以后我们就能学习到正确的任务切换方法了。
最后,教材还给出一种调试程序的方法,其实个人感觉没有什么用,还不如直接在bochs看呢。应该就是为了讲xlat这个查表指令而写的,该指令要求事先在DS:(E)BX出定义一个用于转换编码的表格,在16位下使用bx,在32位下使用ebx。指令执行的时候,处理器访问该表格,用AL寄存器作为偏移量,从表格中取出一个字节,传回AL寄存器。教材上写了一个调试函数。
每次将edx移动4次,总共需要移动8次,而且每次只取4位,Core_Data_Segement段的bin_hex中有16进制的对照表。Xlat不影响任何标志位。
★PART2:13章的代码
1. 源代码:
1 ;========================保护模式主引导扇区代码======================== 2 core_phy_base: equ 0x00040000 ;内核加载地址 3 core_sector_address: equ 0x00000001 ;内核所在扇区 4 5 mov ax,cs 6 mov ss,ax 7 mov sp,0x7c00 8 9 mov eax,[cs:pgdt_base+0x7c00+0x02] 10 xor edx,edx 11 mov ebx,0x10 12 div ebx 13 14 mov ds,eax ;让ds指向gdt位置进行操作 15 mov ebx,edx ;别忘了还有可能出现偏移地址 16 17 ;---------------------描述符#0--------------------- 18 mov dword [ebx+0x00],0x00000000 ;空描述符 19 mov dword [ebx+0x04],0x00000000 20 ;---------------------描述符#1--------------------- 21 mov dword [ebx+0x08],0x0000ffff ;4GB向上拓展数据段 22 mov dword [ebx+0x0c],0x00cf9200 23 ;---------------------描述符#2--------------------- 24 mov dword [ebx+0x10],0x7c0001ff ;代码段 25 mov dword [ebx+0x14],0x00409800 26 ;---------------------描述符#3--------------------- 27 mov dword [ebx+0x18],0x7c00fffe ;栈段 28 mov dword [ebx+0x1c],0x00cf9600 29 ;---------------------描述符#4--------------------- 30 mov dword [ebx+0x20],0x80007fff ;屏幕显示段 31 mov dword [ebx+0x24],0x0040920b 32 33 mov word[cs:pgdt_base+0x7c00],39 ;加载gdt 34 lgdt [cs:pgdt_base+0x7c00] 35 36 in al,0x92 ;快速开启A20 37 or al,0x02 ;是写入2,不要搞错了,写入1就是重启了 38 out 0x92,al 39 cli ;关掉BIOS中断 40 41 mov eax,cr0 42 or eax,0x01 ;设置PE位 43 mov cr0,eax 44 45 jmp dword 0x0010:flush ;进入保护模式 46 47 [bits 32] 48 flush: 49 mov eax,0x0008 ;选择4GB的代码段直接给ds 50 mov ds,eax 51 mov eax,0x0018 52 mov ss,eax ;设置栈段 53 xor esp,esp 54 55 ;接下来开始读取内核头部 56 mov esi,core_sector_address 57 mov edi,core_phy_base 58 call read_harddisk_0 59 60 mov eax,[core_phy_base] ;读取用户总长度 61 xor edx,edx 62 mov ebx,512 63 div ebx 64 65 cmp edx,0 66 jne @read_last_sector 67 dec eax 68 @read_last_sector: 69 cmp eax,0 70 je @setup 71 mov ecx,eax 72 .read_last: 73 inc esi 74 call read_harddisk_0 75 loop .read_last 76 @setup: 77 mov edi,core_phy_base 78 mov esi,[pgdt_base+0x7c00+0x02] 79 80 ;重新构建描述符#1 81 mov eax,[edi+0x04] ;eax线性地址,ebx是下一个线性地址 82 mov ebx,[edi+0x08] 83 sub ebx,eax ;得到段的总长度 84 dec ebx ;段长度-1就是段界限 85 add eax,edi ;得到真正的段的加载地址 86 mov ecx,0x00409800 ;公用例程段 87 call make_gdt_descriptor 88 mov [esi+0x28],eax ;gdt低32位 89 mov [esi+0x2c],edx ;gdt高32位 90 91 ;重新构建描述符#2 92 mov eax,[edi+0x08] ;eax线性地址,ebx是下一个线性地址 93 mov ebx,[edi+0x0c] 94 sub ebx,eax ;得到段的总长度 95 dec ebx ;段长度-1就是段界限 96 add eax,edi ;得到真正的段的加载地址 97 mov ecx,0x00409200 ;内核数据段 98 call make_gdt_descriptor 99 mov [esi+0x30],eax ;gdt低32位 100 mov [esi+0x34],edx ;gdt高32位 101 102 ;重新构建描述符#3 103 mov eax,[edi+0x0c] ;eax线性地址,ebx是下一个线性地址 104 mov ebx,[edi+0x00] ;注意这里是程序的总长度(很容易搞错变成0x10) 105 sub ebx,eax ;得到段的总长度 106 dec ebx ;段长度-1就是段界限 107 add eax,edi ;得到真正的段的加载地址 108 mov ecx,0x00409800 ;内核代码段 109 call make_gdt_descriptor 110 mov [esi+0x38],eax ;gdt低32位 111 mov [esi+0x3c],edx ;gdt高32位 112 113 mov word[pgdt_base+0x7c00],63 ;现在ds指向的就是4GB的内存,不可以用cs(会引发中断的) 114 lgdt [pgdt_base+0x7c00] 115 116 jmp far [edi+0x10] ;可以这样跳的原因是因为在内核中,选择子都是固定的,所以不用自己操心 117 118 ;=============================函数部分================================= 119 read_harddisk_0: ;esi存了28位的硬盘号 120 push ecx 121 122 mov edx,0x1f2 ;读取一个扇区 123 mov al,0x01 124 out dx,al 125 126 mov eax,esi ;0~7位,0x1f3端口 127 inc edx 128 out dx,al 129 130 mov al,ah ;8~15位,0x1f4端口 131 inc edx 132 out dx,al 133 134 shr eax,16 ;16-23位,0x1f5端口 135 inc edx 136 out dx,al 137 138 mov al,ah ;24-28位,LBA模式主硬盘 139 inc edx 140 and al,0x0f 141 or al,0xe0 142 out dx,al 143 144 inc edx ;读命令,0x1f7端口 145 mov al,0x20 146 out dx,al 147 148 .wait: 149 in al,dx 150 and al,0x88 151 cmp al,0x08 152 jne .wait 153 154 mov dx,0x1f0 155 mov ecx,256 156 .read: 157 in ax,dx 158 mov [edi],ax 159 add edi,2 160 loop .read 161 162 pop ecx 163 164 ret 165 ;---------------------------------------------------------------------- 166 make_gdt_descriptor: 167 ;eax:线性基地址 168 ;ebx:段界限 169 ;ecx:属性 170 mov edx,eax 171 and edx,0xffff0000 ;得到线性基地址的16-31位 172 rol edx,8 173 bswap edx ;强行把0-7位和16-23位互换 174 or edx,ecx ;装载属性 175 176 shl eax,16 177 or ax,bx ;配好段界限低16位 178 179 and ebx,0x000f0000 180 or edx,ebx ;装载段界限的16-19位 181 182 ret 183 ;====================================================================== 184 pgdt_base dw 0 185 dd 0x00007e00 ;GDT的物理地址 186 ;====================================================================== 187 times 510-($-$$) db 0 188 dw 0xaa55
1 ;===============================内核程序================================= 2 ;定义内核所要用到的选择子 3 All_4GB_Segment equ 0x0008 ;4GB的全内存区域 4 Stack_Segement equ 0x0018 ;内核栈区 5 Print_Segement equ 0x0020 ;显存映射区 6 Sys_Routine_Segement equ 0x0028 ;公用例程段 7 Core_Data_Segement equ 0x0030 ;内核数据区 8 Core_Code_Segement equ 0x0038 ;内核代码段 9 ;---------------------------------------------------------------- 10 User_Program_Address equ 50 ;用户程序所在逻辑扇区 11 ;=============================内核程序头部=============================== 12 SECTION header vstart=0 13 Program_Length dd Program_end ;内核总长度 14 Sys_Routine_Seg dd section.Sys_Routine.start ;公用例程段线性地址 15 Core_Data_Seg dd section.Core_Data.start ;内核数据区线性地址 16 Core_Code_Seg dd section.Core_Code.start ;内核代码区线性地址 17 Code_Entry dd start ;注意偏移地址一定是32位的 18 dw Core_Code_Segement 19 ;---------------------------------------------------------------- 20 [bits 32] 21 ;========================================================================= 22 ;============================公用例程区=================================== 23 ;========================================================================= 24 SECTION Sys_Routine align=16 vstart=0 25 ReadHarddisk: ;esi:28位磁盘号 26 ;ebx:偏移地址 27 pushad 28 29 mov dx,0x1f2 30 mov al,0x01 ;读一个扇区 31 out dx,al 32 33 inc edx ;0-7位 34 mov eax,esi 35 out dx,al 36 37 inc edx ;8-15位 38 mov al,ah 39 out dx,al 40 41 inc edx ;16-23位 42 shr eax,16 43 out dx,al 44 45 inc edx ;24-28位,主硬盘,LBA模式 46 mov al,ah 47 and al,0x0f 48 or al,0xe0 49 out dx,al 50 51 inc edx 52 mov al,0x20 53 out dx,al 54 55 _wait: 56 in al,dx 57 and al,0x88 58 cmp al,0x08 59 jne _wait 60 61 mov dx,0x1f0 62 mov ecx,256 63 64 _read: 65 in ax,dx 66 mov [ebx],ax 67 add ebx,2 68 loop _read 69 70 popad 71 retf 72 ;---------------------------------------------------------------- 73 put_string: ;ebx:偏移地址 74 pushad 75 76 _print: 77 mov cl,[ebx] 78 cmp cl,0 79 je _exit 80 call put_char 81 inc ebx 82 jmp _print 83 _exit: 84 popad 85 retf ;段间返回 86 ;-------------------------------------------------------------- 87 put_char: ;cl就是要显示的字符 88 push ebx 89 push es 90 push ds 91 92 mov dx,0x3d4 93 mov al,0x0e ;高8位 94 out dx,al 95 mov dx,0x3d5 96 in al,dx 97 mov ah,al ;先把高8位存起来 98 mov dx,0x3d4 99 mov al,0x0f ;低8位 100 out dx,al 101 mov dx,0x3d5 102 in al,dx ;现在ax就是当前光标的位置 103 104 _judge: 105 cmp cl,0x0a 106 je _set_0x0a 107 cmp cl,0x0d 108 je _set_0x0d 109 _print_visible: 110 mov bx,ax 111 mov eax,Print_Segement 112 mov es,eax 113 shl bx,1 ;注意这里一定要把ebx变成原来的两倍,实际位置是光标位置的两倍 114 mov [es:bx],cl ;注意这里是屏幕! 115 mov byte[es:bx+1],0x07 116 add bx,2 117 shr bx,1 118 jmp _roll_screen 119 _set_0x0d: ;回车 120 mov bl,80 121 div bl 122 mul bl 123 mov bx,ax 124 jmp _set_cursor 125 _set_0x0a: ;换行 126 mov bx,ax 127 add bx,80 128 jmp _roll_screen 129 _roll_screen: 130 cmp bx,2000 131 jl _set_cursor 132 mov eax,Print_Segement 133 mov ds,eax 134 mov es,eax 135 136 cld 137 mov edi,0x00 138 mov esi,0xa0 139 mov ecx,1920 140 rep movsw 141 _cls: 142 mov bx,3840 143 mov ecx,80 144 _print_blank: 145 mov word[es:bx],0x0720 146 add bx,2 147 loop _print_blank 148 mov bx,1920 ;别总是忘了光标的位置! 149 _set_cursor: ;改变后的光标位置在bx上 150 mov dx,0x3d4 151 mov al,0x0f ;低8位 152 out dx,al 153 154 mov al,bl 155 mov dx,0x3d5 156 out dx,al 157 158 mov dx,0x3d4 159 mov al,0x0e ;高8位 160 out dx,al 161 162 mov al,bh 163 mov dx,0x3d5 164 out dx,al 165 166 pop ds 167 pop es 168 pop ebx 169 ret 170 ;---------------------------------------------------------------- 171 allocate_memory: ;简易内存分配策略 172 ;输入ecx:想要分配的总字节数 173 ;输出ecx:分配的线性基地址 174 push ds 175 push eax 176 push ebx 177 178 mov eax,Core_Data_Segement 179 mov ds,eax 180 mov eax,[ram_alloc] 181 mov edx,eax ;edx暂存一下eax 182 add eax,ecx 183 184 cmp eax,edx ;发现新分配的现地址比原来的还小,说明已经溢出 185 jge _alloc 186 mov ebx,mem_alloc_fail 187 call Sys_Routine_Segement:put_string 188 mov ecx,0 ;分配为0说明已经分配失败 189 jmp _exit1 190 _alloc: 191 192 mov ebx,eax 193 and ebx,0xfffffffc 194 add ebx,4 ;强行向上取整 195 test eax,0x00000003 196 cmovnz eax,ebx 197 mov ecx,[ram_alloc] ;要返回要分配的初始地址 198 mov [ram_alloc],eax ;下一次分配的线性基地址 199 add [ram_recycled],eax 200 sub [ram_recycled],ecx ;记录大小 201 202 _exit1: 203 pop ebx 204 pop eax 205 pop ds 206 207 retf 208 ;---------------------------------------------------------------- 209 recycled_memory_and_gdt: 210 mov eax,[ram_recycled] 211 sub [ram_alloc],eax 212 mov dword[ram_recycled],0 ;因为我们还没学到多任务,先这样简单地清零 213 214 sgdt [pgdt_base_tmp] 215 sub word[pgdt_base_tmp],32 ;应用程序的4个段全部减掉 216 lgdt [pgdt_base_tmp] ;重新加载内核 217 retf 218 ;---------------------------------------------------------------- 219 PrintDword: ;显示edx内容的一个调试函数 220 pushad 221 push ds 222 223 mov eax,Core_Data_Segement 224 mov ds,eax 225 226 mov ebx,bin_hex 227 mov ecx,8 228 229 _query: 230 rol edx,4 231 mov eax,edx 232 and eax,0x0000000f 233 xlat 234 235 push ecx 236 mov cl,al 237 call put_char 238 pop ecx 239 240 loop _query 241 242 pop ds 243 popad 244 245 retf 246 ;---------------------------------------------------------------- 247 Make_Descriptor: ;构造描述符 248 ;输入: 249 ;eax:线性基地址 250 ;ebx:段界限 251 ;ecx:属性 252 ;输出: 253 ;eax:描述符低32位 254 ;edx:描述符高32位 255 mov edx,eax 256 and edx,0xffff0000 257 rol edx,8 258 bswap edx 259 or edx,ecx 260 261 shl eax,16 262 or ax,bx 263 and ebx,0x000f0000 264 or edx,ebx 265 retf 266 ;---------------------------------------------------------------- 267 Set_New_GDT: ;装载新的描述符 268 ;输入:edx:eax描述符 269 ;输出:cx选择子 270 push ds 271 push es 272 273 mov ebx,Core_Data_Segement 274 mov ds,ebx 275 276 mov ebx,All_4GB_Segment 277 mov es,ebx 278 279 sgdt [pgdt_base_tmp] 280 281 movzx ebx,word[pgdt_base_tmp] 282 inc bx ;注意这里要一定是inc bx而不是inc ebx,因为gdt段地址初始化是0xffff的 283 ;要用到回绕特性 284 add ebx,[pgdt_base_tmp+0x02] ;得到pgdt的线性基地址 285 286 mov [es:ebx],eax 287 mov [es:ebx+0x04],edx ;装载新的gdt符 288 ;装载描述符要装载到实际位置上 289 290 add word[pgdt_base_tmp],8 ;给gdt的段界限加上8(字节) 291 292 lgdt [pgdt_base_tmp] ;加载gdt到gdtr的位置和实际表的位置无关 293 294 mov ax,[pgdt_base_tmp] ;得到段界限 295 xor dx,dx 296 mov bx,8 ;得到gdt大小 297 div bx 298 mov cx,ax 299 shl cx,3 ;得到选择子,ti=0(全局描述符),rpl=0(申请特权0级) 300 301 pop es 302 pop ds 303 retf 304 ;========================================================================= 305 ;===========================内核数据区==================================== 306 ;========================================================================= 307 SECTION Core_Data align=16 vstart=0 308 ;------------------------------------------------------------------------------- 309 pgdt_base_tmp: dw 0 ;这一章的用户程序都是从GDT中加载的 310 dd 0 311 312 ram_alloc: dd 0x00100000 ;下次分配内存时的起始地址(直接暴力从0x00100000开始分配了) 313 ram_recycled dd 0 ;这里储存程序实际用的大小 314 salt: 315 salt_1: db '@Printf' ;@Printf函数(公用例程) 316 times 256-($-salt_1) db 0 317 dd put_string 318 dw Sys_Routine_Segement 319 320 salt_2: db '@ReadHarddisk' ;@ReadHarddisk函数(公用例程) 321 times 256-($-salt_2) db 0 322 dd ReadHarddisk 323 dw Sys_Routine_Segement 324 325 salt_3: db '@PrintDwordAsHexString' ;@PrintDwordAsHexString函数(公用例程) 326 times 256-($-salt_3) db 0 327 dd PrintDword 328 dw Sys_Routine_Segement 329 330 salt_4: db '@TerminateProgram' ;@TerminateProgram函数(内核例程) 331 times 256-($-salt_4) db 0 332 dd _return_point 333 dw Core_Code_Segement 334 335 salt_length: equ $-salt_4 336 salt_items_sum equ ($-salt)/salt_length ;得到项目总数 337 338 message_1 db ' If you seen this message,that means we ' 339 db 'are now in protect mode,and the system ' 340 db 'core is loaded,and the video display ' 341 db 'routine works perfectly.',0x0d,0x0a,0 342 343 message_5 db ' Loading user program...',0 344 345 do_status db 'Done.',0x0d,0x0a,0 346 347 message_6 db 0x0d,0x0a,0x0d,0x0a,0x0d,0x0a 348 db ' User program terminated,control returned.',0 349 message_7 db 0x0d,0x0a,0x0d,0x0a 350 db ' We have been backed to kernel.',0 351 message_8 db 0x0d,0x0a 352 db ' The GDT and memory have benn recycled.',0 353 354 bin_hex db '0123456789ABCDEF' 355 ;put_hex_dword子过程用的查找表 356 core_buf times 2048 db 0 ;内核用的缓冲区(2049个字节(2MB)) 357 358 esp_pointer dd 0 ;内核用来临时保存自己的栈指针 359 360 cpu_brnd0 db 0x0d,0x0a,' ',0 361 cpu_brand times 52 db 0 362 cpu_brnd1 db 0x0d,0x0a,0x0d,0x0a,0 363 mem_alloc_fail db 'The Program is too large to load' 364 ;========================================================================= 365 ;===========================内核代码区==================================== 366 ;========================================================================= 367 SECTION Core_Code align=16 vstart=0 368 load_program: ;输入esi:磁盘号 369 ;输出ax: 用户程序头部选择子 370 push esi 371 push ds 372 push es 373 374 mov eax,Core_Data_Segement 375 mov ds,eax ;切换到内核数据段 376 377 mov ebx,core_buf ;ebx要在内核数据缓冲区(先读取头部在缓冲区,esi已经是有扇区号了) 378 call Sys_Routine_Segement:ReadHarddisk 379 380 mov eax,[core_buf] ;读取用户程序长度 381 382 mov ebx,eax ;给ebx一个副本 383 and ebx,0xfffffe00 ;清空低9位(强制对齐512) 384 add ebx,512 ;加上512 385 test eax,0x000001ff 386 cmovnz eax,ebx ;低9位不为0则使用向上取整的结果 387 388 mov ecx,eax ;eax是整个程序的向上取整的大小 389 call Sys_Routine_Segement:allocate_memory ;先分配内存给整个程序,再分配内存给栈区 390 mov ebx,ecx ;这个才是真正的程要用到的线性基地址 391 392 push ebx ;ebx就是用户程序加载到内存的地址 393 xor edx,edx 394 mov ecx,512 ;千万不要改掉ebx 395 div ecx 396 mov ecx,eax 397 398 mov eax,All_4GB_Segment ;切换到4GB段区域(平坦模式) 399 mov ds,eax 400 401 _loop_read: 402 call Sys_Routine_Segement:ReadHarddisk ;esi还是User_Program_Address 403 inc esi 404 add ebx,512 405 loop _loop_read 406 407 ;加载用户程序头部段(下面的所有段的地址都要转化为选择子) 408 pop edi ;程序被装载的基地址 409 mov eax,edi 410 mov ebx,[edi+0x04] ;用户程序头部的长度 411 dec ebx ;段界限 412 mov ecx,0x00409200 413 call Sys_Routine_Segement:Make_Descriptor 414 call Sys_Routine_Segement:Set_New_GDT 415 mov [edi+0x04],cx ;放入选择子 416 417 ;加载用户程序代码段 418 mov eax,edi 419 add eax,[edi+0x14] ;别忘记重定位了 420 mov ebx,[edi+0x18] ;用户程序代码段的长度 421 dec ebx ;段界限 422 mov ecx,0x00409800 423 call Sys_Routine_Segement:Make_Descriptor 424 call Sys_Routine_Segement:Set_New_GDT 425 mov [edi+0x14],cx ;放入选择子 426 427 ;加载用户程序数据段 428 mov eax,edi 429 add eax,[edi+0x1c] ;别忘记重定位了 430 mov ebx,[edi+0x20] ;用户程序数据段的长度 431 dec ebx ;段界限 432 mov ecx,0x00409200 433 call Sys_Routine_Segement:Make_Descriptor 434 call Sys_Routine_Segement:Set_New_GDT 435 mov [edi+0x1c],cx ;放入选择子 436 437 ;加载用户程序栈段(用户程序给出的建议大小) 438 mov ecx,[edi+0x0c] ;确定栈段的建议大小 439 mov ebx,0x000fffff 440 sub ebx,ecx ;这就是段界限了 441 mov eax,4096 ;4KB 442 mul ecx 443 add [ram_recycled],eax ;加上分配的内存 444 445 mov ecx,eax ;这个时候eax是大小 446 call Sys_Routine_Segement:allocate_memory ;分到内存 447 add eax,ecx ;eax线性基地址,ebx段界限 448 mov ecx,0x00c09600 ;4KB粒度向下拓展数据段 449 call Sys_Routine_Segement:Make_Descriptor 450 call Sys_Routine_Segement:Set_New_GDT 451 mov [edi+0x08],cx 452 453 ;现在开始重定位API符号表 454 ;--------------------------------------------------------------------- 455 mov eax,[edi+0x04] ;先设es再ds,不要搞反了,现在es的0x04这个地方是头部的选择子 456 mov es,eax 457 mov eax,Core_Data_Segement 458 mov ds,eax 459 460 cld 461 mov ecx,[es:0x24] ;得到用户程序符号表的条数 462 mov edi,0x28 ;用户符号表的偏移地址是0x28 463 464 _loop_U_SALT: 465 push edi 466 push ecx 467 468 mov ecx,salt_items_sum 469 mov esi,salt 470 471 _loop_C_SALT: 472 push edi 473 push esi 474 push ecx 475 476 mov ecx,64 ;比较256个字节 477 repe cmpsd 478 jne _re_match ;如果成功匹配,那么esi和edi刚好会在数据区之后的 479 480 mov eax,[esi] ;偏移地址 481 mov [es:edi-256],eax ;把偏移地址填入用户程序的符号区 482 mov ax,[esi+0x04] ;段的选择子 483 mov [es:edi-252],ax ;把段的选择子填入用户程序的段选择区 484 485 _re_match: 486 pop ecx 487 pop esi 488 add esi,salt_length 489 pop edi 490 loop _loop_C_SALT 491 492 pop ecx 493 pop edi 494 add edi,256 495 loop _loop_U_SALT 496 ;--------------------------------------------------------------------- 497 mov ax,[es:0x04] ;把头部的段选择子给ax 498 499 pop es 500 pop ds 501 pop esi 502 ret 503 start: 504 mov eax,Core_Data_Segement 505 mov ds,eax 506 507 mov ebx,message_1 508 call Sys_Routine_Segement:put_string 509 510 mov eax,0 511 cpuid 512 cmp eax,0x80000004 ;判断是否有0x80000002-0x80000004功能 513 jl _@load 514 515 ;显示处理器品牌信息,从80486的后期版本开始引入 516 mov eax,0x80000002 517 cpuid 518 mov [cpu_brand+0x00],eax 519 mov [cpu_brand+0x04],ebx 520 mov [cpu_brand+0x08],ecx 521 mov [cpu_brand+0x0c],edx 522 523 mov eax,0x80000003 524 cpuid 525 mov [cpu_brand+0x10],eax 526 mov [cpu_brand+0x14],ebx 527 mov [cpu_brand+0x18],ecx 528 mov [cpu_brand+0x1c],edx 529 530 mov eax,0x80000004 531 cpuid 532 mov [cpu_brand+0x20],eax 533 mov [cpu_brand+0x24],ebx 534 mov [cpu_brand+0x28],ecx 535 mov [cpu_brand+0x2c],edx 536 537 mov ebx,cpu_brnd0 538 call Sys_Routine_Segement:put_string 539 mov ebx,cpu_brand 540 call Sys_Routine_Segement:put_string 541 mov ebx,cpu_brnd1 542 call Sys_Routine_Segement:put_string 543 544 _@load: 545 mov ebx,message_5 546 call Sys_Routine_Segement:put_string 547 mov esi,User_Program_Address 548 call load_program 549 550 mov ebx,do_status 551 call Sys_Routine_Segement:put_string 552 553 mov [esp_pointer],esp ;临时保存一下栈指针 554 mov ds,ax ;使ds指向用户程序头部 555 556 jmp far [0x10] 557 558 _return_point: 559 560 mov eax,Core_Data_Segement 561 mov ds,eax 562 mov eax,Stack_Segement 563 mov ss,eax ;重新设置数据段和栈段 564 mov esp,[esp_pointer] 565 mov ebx,message_7 566 call Sys_Routine_Segement:put_string 567 568 call Sys_Routine_Segement:recycled_memory_and_gdt 569 mov ecx,[ram_alloc] 570 571 mov ebx,message_8 572 call Sys_Routine_Segement:put_string 573 cli 574 hlt 575 ;========================================================================= 576 SECTION core_trail 577 ;---------------------------------------------------------------- 578 Program_end:
1 ;==============================用户程序======================================= 2 SECTION header vstart=0 3 4 program_length dd program_end ;程序总长度#0x00 5 6 head_len dd header_end ;程序头部的长度#0x04 7 8 stack_seg dd 0 ;用于接收堆栈段选择子#0x08 9 stack_len dd 1 ;程序建议的堆栈大小#0x0c 10 ;以4KB为单位 11 12 prgentry dd start ;程序入口#0x10 13 code_seg dd section.code.start ;代码段位置#0x14 14 code_len dd code_end ;代码段长度#0x18 15 16 data_seg dd section.data.start ;数据段位置#0x1c 17 data_len dd data_end ;数据段长度#0x20 18 ;------------------------------------------------------------------------------- 19 ;符号地址检索表 20 salt_items dd (header_end-salt)/256 ;#0x24 21 22 salt: ;#0x28 23 Printf: db '@Printf' 24 times 256-($-Printf) db 0 25 26 TerminateProgram:db '@TerminateProgram' 27 times 256-($-TerminateProgram) db 0 28 29 ReadHarddisk: db '@ReadHarddisk' 30 times 256-($-ReadHarddisk) db 0 31 32 header_end: 33 ;=============================================================================== 34 SECTION data align=16 vstart=0 35 36 buffer times 1024 db 0 ;缓冲区 37 38 message_1 db 0x0d,0x0a,0x0d,0x0a 39 db '**********User program is runing**********' 40 db 0x0d,0x0a,0 41 message_2 db ' Disk data:',0x0d,0x0a,0 42 43 data_end: 44 45 ;=============================================================================== 46 [bits 32] 47 ;=============================================================================== 48 SECTION code align=16 vstart=0 49 start: 50 User_Data_File equ 100 ;数据文件存放地点 51 mov eax,ds 52 mov fs,eax 53 54 mov eax,[stack_seg] 55 mov ss,eax 56 mov esp,0 57 58 mov eax,[data_seg] 59 mov ds,eax 60 61 mov ebx,message_1 62 call far [fs:Printf] 63 64 mov esi,User_Data_File 65 mov ebx,buffer ;缓冲区偏移地址 66 call far [fs:ReadHarddisk] ;相当于调用函数 67 68 mov ebx,message_2 69 call far [fs:Printf] 70 71 mov ebx,buffer 72 call far [fs:Printf] 73 74 jmp far [fs:TerminateProgram] ;将控制权返回到系统 75 76 code_end: 77 78 ;=============================================================================== 79 SECTION trail 80 ;------------------------------------------------------------------------------- 81 program_end:
2. 课后习题
其实这一章的课后习题很无聊的,就是把栈段那里改一下就好了,用户程序按照第八章那样自己填一个区域。
1 ;加载用户程序栈段 2 mov eax,edi 3 add eax,[edi+0x08] 4 mov ebx,[edi+0x0c] ;用户程序栈段的长度 5 add eax,ebx ;得到栈段的线性基地址 6 mov edx,0xffffffff 7 sub edx,ebx 8 mov ebx,edx ;得到段界限 9 10 mov ecx,0x00409600 11 call Sys_Routine_Segement:Make_Descriptor 12 call Sys_Routine_Segement:Set_New_GDT 13 mov [edi+0x08],cx ;放入选择子
1 ;==============================用户程序======================================= 2 SECTION header vstart=0 3 4 program_length dd program_end ;程序总长度#0x00 5 6 head_len dd header_end ;程序头部的长度#0x04 7 8 stack_seg dd section.stack.start ;栈段位置#0x08 9 stack_len dd stack_end ;栈段长度#0x0c 10 11 12 prgentry dd start ;程序入口#0x10 13 code_seg dd section.code.start ;代码段位置#0x14 14 code_len dd code_end ;代码段长度#0x18 15 16 data_seg dd section.data.start ;数据段位置#0x1c 17 data_len dd data_end ;数据段长度#0x20 18 ;------------------------------------------------------------------------------- 19 ;符号地址检索表 20 salt_items dd (header_end-salt)/256 ;#0x24 21 22 salt: ;#0x28 23 Printf: db '@Printf' 24 times 256-($-Printf) db 0 25 26 TerminateProgram:db '@TerminateProgram' 27 times 256-($-TerminateProgram) db 0 28 29 ReadHarddisk: db '@ReadHarddisk' 30 times 256-($-ReadHarddisk) db 0 31 32 header_end: 33 ;=============================================================================== 34 SECTION data align=16 vstart=0 35 36 buffer times 1024 db 0 ;缓冲区 37 38 message_1 db 0x0d,0x0a,0x0d,0x0a 39 db '**********User program is runing**********' 40 db 0x0d,0x0a,0 41 message_2 db ' Disk data:',0x0d,0x0a,0 42 43 data_end: 44 45 ;=============================================================================== 46 [bits 32] 47 ;=============================================================================== 48 SECTION code align=16 vstart=0 49 start: 50 User_Data_File equ 100 ;数据文件存放地点 51 mov eax,ds 52 mov fs,eax 53 54 mov eax,[stack_seg] 55 mov ss,eax 56 mov esp,0 57 58 mov eax,[data_seg] 59 mov ds,eax 60 61 mov ebx,message_1 62 call far [fs:Printf] 63 64 mov esi,User_Data_File 65 mov ebx,buffer ;缓冲区偏移地址 66 call far [fs:ReadHarddisk] ;相当于调用函数 67 68 mov ebx,message_2 69 call far [fs:Printf] 70 71 mov ebx,buffer 72 call far [fs:Printf] 73 74 jmp far [fs:TerminateProgram] ;将控制权返回到系统 75 76 code_end: 77 ;=============================================================================== 78 SECTION stack align=16 vstart=0 79 times 4096 db 0 80 stack_end: 81 ;=============================================================================== 82 SECTION trail 83 ;------------------------------------------------------------------------------- 84 program_end: