第16章讲的是分页机制和动态页面分配的问题,说实话这个一开始接触是会把人绕晕的,但是这个的确太重要了,有了分页机制内存管理就变得很简单,而且能直接实现平坦模式。
★PART1:Intel X86基础分页机制
1. 页目录、页表和页
首先先要明白分页是怎么来的,简单来讲,分页其实就是内存块的映射管理。在我们之前的章节中,我们都是使用的分段管理模式,处理器中负责分段的部件是段部件,段管理机制是Intel处理器最基本的处理机制,在任何时候都是无法关闭的。而当开启了分页管理之后,处理器会把4GB的内存分成长度相同的段,也就是说用长度固定的页来代替长度不一的段。页的分配由处理器固件来进行,可以实现非常高效的操作。页的最小单位是4KB,也就是说4GB的内存可以分成1048576个页(页的物理地址的低12位全是0)。
在分页模式下,操作系统可以创建一个为所有任务共用的4GB虚拟内存空间。也可以为每一个任务创建一个独立的4GB虚拟内存空间,当一个程序加载的时候,操作系统既要在虚拟内存空间中分配空间,而且也要在物理内存中分配相应的也页面。另外,如果允许页共享,多个段或者多个程序可以用同一个页来存放各自的数据。4GB虚拟内存空间只是一个用来指示内存使用状况的一个机制,当操作系统加载一个程序并且创建为任务时,操作系统在虚拟内存空间中寻找空闲的段,并映射到空闲的页中取去。然后,到真正加载程序的时候,再把原本属于段的数据按页的尺寸拆分,分开写入相应的页中。页最大的好处就是方便操作系统进行内存管理,特别是虚拟内存管理,每个任务都可以有4GB虚拟内存,但是假如机器没有那么大的内存,操作系统也可以认为确实存在那么大的内存,当一个程序使用的内存超过了实际的物理内存,那么操作系统就会搜索那些暂时用不到的页,并且把他们转移到磁盘中,并且调入马上要使用的页。(当然这种操作非常地花费时间,这就是为什么小内存的电脑会有很严重的卡顿现象)。
Intel处理器的最基础的分页管理机制就是二级管理机制。(当然现在更新的处理器支持更复杂的分页操作,但是教材也没有提及。)如果每个操使用直接分页管理(也就是把4GB的内存直接分成1048576个页),这需要1048576个表项,每个表项是4字节,所以映射表的总大小是4MB。事实上程序往往用不到4GB的内存,所以1级映射是一个很严重的浪费。但是如果采用层次化分页管理,那么就会产生巨大的内存节约。所谓Intel的分页管理,其实就是把页拆成3个部分(页目录,页表和页)。
页目录(Page Directory Table,PDT)的物理地址由PDBR(Page Directory Base Register,也就是CR3寄存器)指定,每个任务都可以有自己的页目录。每个任务的TSS段就有自己的CR3的物理地址。每次进行任务切换的时候,CR3的内容都会被替换改为新任务的CR3域中的物理地址。页目录也是一个的页。但是他里面存放的是页表的地址,所以页目录可以指向1024个页表。
页表也可以指向1024个页,每个项也是4个字节,同样页表的大小也是页的大小。
页目录,页表和页的关系:(图片出处看水印)
处理器有页部件,专门负责线性地址到物理地址的转换工作。它首先将段部件送来的32位线性地址截成3段,分别是高10位(页目录的索引),中间的10位(页表的索引)和低12位(页内偏移)。操作系统要负责填写页目录和页表地址,然后程序的内存访问就可以像上图那样进行转换了。
2. 页目录、页表和CR3的填写
P(Present)是存在位,为“1”时,表示页表或者页位于内存中,否则,表示页表或者页不在内存中,必须先予以创建,或者从磁盘调入内存后方可使用。
RW(Read/Write)是读/写位,为“0”时表示这样的页只能读取,为“1”时,可读可写。
US(User/Supervisor)是用户/管理位。为“1”时,允许所有特权级别的程序访问;为“0”时,只允许特权级为0,1,2的程序访问,特权级为3的程序不能访问。
PWT(Page-Level Write-Through)是页级通写位,和高速缓存有关,“通写”是处理器高速缓存的一种工作方式,这意味用来间接决定是否采用此种方式来改善页面的访问效率。
A(Accessed)是访问位。该位由处理器固件来设置,用来指示此表项所指向的页是否被访问过。(这个坑爹的属性将会在练习题中体现)。
D(Dirty)是脏位。该位由处理器固件来设置,用来指示此表项所指向的页是否被写入数据。(和A位一样,练习题会有体现)。
PAT(Page Attribute Table)页属性表支持位。此位涉及更为复杂的分页系统。和页高速缓存有关。
G(Global)是全局位,用来只是该表项指向的页是否为全局属性。如果页是全局属性的,那么,他将一直在高速缓存中一直保存(地址转换速度会加快)。因为页高速缓存的缓存容量有限,只能存放频繁使用的那些表项。而且,当因为任务切换等原因改变CR3寄存器的内容时,整个页高速缓存的内容都会被刷新。
AVL位被处理器忽略,软件可以使用。
进入分页模式之后,所有东西的地址都变成了虚拟线性地址了,包括GDT,LDT和TSS的地址等等。需要注意的是,页目录和页表在内存中的位置必须是在有效的可用内存范围(比如2GB内存只能在2GB内存中设置页目录和页表)。注意页目录必须进行初始化,主要是对P位置零,如果P位等于0,而处理器又执行了一个对这个页表的访问,那么就会引发异常中断。
清空页表以后就是要对页目录进行初始化了,在教材的“系统”中,把页目录设置在0x00020000这个物理地址中。然后在页目录中的最后一项,指向页目录自己。然后只进行最底下1MB内存的页面设置(所以只需要一个页表),然后就可以进行页表的初始化了,初始化过后。就可以直接进行CR3的设置,CR3的设置如下图:
其实CR3的设置和页表和页的填写差不多,只是除了PCD位和PWT位外,其他都不需要填写。
最后一步就是打开分页了,这个开关也是在CR0那里,称为PG位,在CR0的最高位31位上,注意这个功能只有在保护模式下才能开启(也就是CR0的PE位(0位)要是1),当PG位为1的时候,段部件传来的内存地址必须要经过页部件的转换才能得到真实的内存地址。
有关CR0-3这4个寄存器的控制可以看:http://www.cnblogs.com/Philip-Tell-Truth/articles/5341686.html
以上代码就就是开启页功能的一个演示。
3. 任务全局空间和局部空间的页面演示,页面查找的例子
在15章我们说过,任务的4GB地址空间包括两个部分:局部空间和全局空间,全局空间是所有任务共用的,内核就是所有任务共用的,它属于每个任务的全局空间。在教材的系统中,规定4GB的虚拟高2GB空间就是全局空间,地址范围是0x80000000-0xFFFFFFFF,局部空间使用低2GB空间,地址范围是0x00000000-0x7FFFFFFF,在任何时候,如果段部件发出的线性地址高于等于0x80000000,指向和访问的就是全局地址空间,或者说是内核。另外在上面我说过,我们把页目录的最后一项指向自己页目录的页,把页目录看成页表,再把页目录看成页表,刚好这个页表的最后一项也是指向页目录,那样我们就可以实现对页目录的内容进行访问,否则我们将无法访问页目录,因为处理器不允许访问一个没有登记的页(访问了会引发异常中断)。
但是为了保证内核的全局部分可以被其他程序使用,要创建内核的映射,内核应该具有两个部分,一个是和他的物理地址一样的对应(低地址)部分,另一个是映射到高地址的对应部分。这样说可能会比较抽象,我们直接看代码就好了。然后接下来就是把GDT的内容全部重定位了(GDT页必须使用虚拟线性地址,已经在前面把内核映射到高地址空间了),当然我们这里的全局空间映射位置取的比较好,是0x80000000,所以只要往GDT的最高4位or一个8就可以了。
注意 mov dword[es:ebx+esi],PDT_Mem_Address,add dword[es:ebx+esi],0x00001003这两句话,ebx的内容是0XFFFFF000,ESI的内容是0x00000200,因此段部件发出的线性地址是0XFFFFF200,现在举这个访问页目录的例子,以更加清楚了解如何访问页:
所以现在位于物理地址0x00021000这个地方的页被页目录中两个项所指向,但是这两个项所映射的物理地址时不一样的!!!!,页目录的第0项对应的映射是0x00000000-0x000FFFFF,0x800项对应的是0x80000000-0x800FFFFF。这也是一个分级管理有效地缩减表的占用的一个证据:因为全局空间总是映射到高区间,所以如果采用单映射的话,必须先准备2MB的表项先把局部空间描述完了才能到全局空间。
注意因为段部件的内容不会因为你做好了映射而自动把自己的内容改变,所以需要显式切换,那就是上面代码的后面的部分。
★PART2:保护模式页管理模式下的内核任务的创建
1. 内核的虚拟内存的分配
说实话教材说了那么多,其实就是最重要的是讲明白一个东西就行了,那就是页的分配。把这个搞明白了其他东西都是一个套路。
现代操作系统可以跟踪所有页的分配状况。内存空间来自于插在主板上的内存条,按照新的工业标准,每个内存条上焊有一个很小的只读存储器,用于标明该内存条的容量和工作参数,作为一个PCI(E)设备,软件可以读取它,以获得计算机上的物理内存容量。然后简历上述的页分配表。但是由固件创建的表是每个表项占1个字节的,如果有4GB内存,则最多分220个页,要占用1MB的内存。但是如果以比特来指示页的使用状况,那么最多使用1048576个比特(128KB),将会产生巨大的内存节约。我们的系统为了简单,假定我们的系统只有2MB的内存,2MB的内存,可以分512个页,需要512个比特,我们可以在内核区定义比特串:
我们可以看到底下的1MB已经被内核和ROM-BIOS分配的差不多了。但是页的分配可以不连续的,接下来我们就可以在代码中看到这个问题。
注意代码中我们使用了bts指令,这个指令用于测试串中的某个比特位。用该比特的值设置EFLAGS寄存器的CF标志位,然后将这个标志置“1”,他的最基本两种形式为:
bts r/m16,r16
bts r/m32,r32
目的操作数可以是16/32位的通用寄存器,或者指向一个包含了16/32位实际操作数的内存党员,用于指定位串;源操作数可以是16/32位的通用寄存器,用于指定待测试的比特在位串的索引 (位置)。其他类似的指令还有:btr,btc和bt,他们的区别如下:
例程首先做的事情就是在页目录看一下相应的页表是否已经在页目录中登记了,如果没有登记,则分配一个新的页作为页表然后写入页目录相应位置,否则就直接使用这个页表(当然这个管理程序非常粗糙,会有非常严重的问题,但是现在我们要把事情简单化)。
2. 任务程序的加载
其实也是一堆套路,只不过我们需要注意的是,因为我们现在用的是页管理模式,所以要使用平坦模式。平坦模式是现代操作系统流行的管理模式,抛弃段管理模式冗杂的段的管理,可以大大简化代码的编写难度(注意的是页也有一点特权控制)。
教材上加载程序的思路其实和前面几章是一样的,但是使用的是平坦模式来加载,并且对TCB进行了一些改变,而且使用了向上拓展的栈段(其实向上拓展的栈段只是段界限的检查会不一样,那是处理器的事情,push和pop指令的操作是一样的)。TCB变成了下图这个样子:
最后要说明的是,分页机制下,和分段机制是一样的,内存也是先登记后使用的。程序在编写和编译之后,都是连续的,在加载后不能保证这一点。页的分配是随机的,尽管页不是连续的,但是线性地址是连续的就足够了。处理器访问数据,取指令,用的是线性地址。教材把所有的用户程序都从0x00000000开始加载,其实是不合理的,多段模型之下段内元素的偏移量都是相对于段而言的,在程序加载之后,段的描述符的基地址,就是段实际加载的位置。也就是这样,多段模型下,不管段加载到哪里,都不会影响段内元素的访问,这就是多段模型下程序可以重定位和浮动的根本原因。
而在平坦模式下,程序的重定位和浮动实现比较复杂,在现代流行的操作系统中,编写的程序必须符合一定的规范才能重定位,比如很多系统要求用户提供一个标准的重定位表,列出所有需要动态加载的元素,程序加载后,操作系统会找到这个表,用实际的加载地址修正每一个表项。(非常复杂的一个过程)。
★PART3:本章的课后练习题
1. 显示当前任务的当前任务的虚拟地址的前55个双字,前50个页面的物理地址
显示虚拟地址前55个双字书上是有例程的,就是物理地址这个比较麻烦一点,其实也不麻烦,主要是要把页目录指向也目录自己和指向内核的那两个页表项修改为特权级3的程序也能访问。说下几个坑:
1. 之前几章的PrintDword过程都是有问题的,因为我忽略了我的put_char过程和书上的不一样,要把put_char压栈的那几个操作改成pushad,出栈改为popad
2. 页目录/页表内登记的物理地址的低12位都是有含义的!页表/页的物理地址的31-12位才会出现在项中,低12位是属性!特别是A和D位,处理器会自动将他们设置。显示物理内存的时候低12位都是0(and一下,页都是4KB对齐的)。
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_AddressA equ 50 ;用户程序所在逻辑扇区 11 User_Program_AddressB equ 80 ;用户程序所在逻辑扇区 12 Switch_Stack_Size equ 4096 ;切换栈段的大小 13 PDT_Mem_Address equ 0x00020000 ;PDT在内存加载的地址 14 Global_Page_Directory equ 0x80000000 ;给全局空间的映射地址 15 ;=============================内核程序头部=============================== 16 SECTION header vstart=0 17 Program_Length dd Program_end ;内核总长度 18 Sys_Routine_Seg dd section.Sys_Routine.start ;公用例程段线性地址 19 Core_Data_Seg dd section.Core_Data.start ;内核数据区线性地址 20 Core_Code_Seg dd section.Core_Code.start ;内核代码区线性地址 21 Code_Entry dd start ;注意偏移地址一定是32位的 22 dw Core_Code_Segement 23 ;---------------------------------------------------------------- 24 [bits 32] 25 ;========================================================================= 26 ;============================公用例程区=================================== 27 ;========================================================================= 28 SECTION Sys_Routine align=16 vstart=0 29 ReadHarddisk: ;push1:28位磁盘号(esi) 30 ;push2:应用程序数据段选择子(ax->ds) 31 ;push3: 偏移地址(ebx) 32 ;push4: 应用程序代码段选择子(dx) 33 pushad 34 push ds 35 push es 36 37 mov ebp,esp 38 39 mov esi,[ebp+15*4] 40 movzx eax,word[ebp+14*4] 41 mov ebx,[ebp+13*4] 42 movzx edx,word[ebp+12*4] 43 44 arpl ax,dx 45 mov ds,ax 46 47 mov dx,0x1f2 48 mov al,0x01 ;读一个扇区 49 out dx,al 50 51 inc edx ;0-7位 52 mov eax,esi 53 out dx,al 54 55 inc edx ;8-15位 56 mov al,ah 57 out dx,al 58 59 inc edx ;16-23位 60 shr eax,16 61 out dx,al 62 63 inc edx ;24-28位,主硬盘,LBA模式 64 mov al,ah 65 and al,0x0f 66 or al,0xe0 67 out dx,al 68 69 inc edx 70 mov al,0x20 71 out dx,al 72 73 _wait: 74 in al,dx 75 and al,0x88 76 cmp al,0x08 77 jne _wait 78 79 mov dx,0x1f0 80 mov ecx,256 81 _read: 82 in ax,dx 83 mov [ebx],ax 84 add ebx,2 85 loop _read 86 87 pop es 88 pop ds 89 popad 90 retf 16 ;4个数据 91 ;---------------------------------------------------------------- 92 put_string: ;ebx:偏移地址 93 pushad 94 push ds 95 push es 96 97 _print: 98 mov cl,[ebx] 99 cmp cl,0 100 je _exit 101 call put_char 102 inc ebx 103 jmp _print 104 _exit: 105 pop es 106 pop ds 107 popad 108 retf ;段间返回 109 ;-------------------------------------------------------------- 110 put_char: ;cl就是要显示的字符 111 pushad 112 push es 113 push ds 114 115 mov dx,0x3d4 116 mov al,0x0e ;高8位 117 out dx,al 118 mov dx,0x3d5 119 in al,dx 120 mov ah,al ;先把高8位存起来 121 mov dx,0x3d4 122 mov al,0x0f ;低8位 123 out dx,al 124 mov dx,0x3d5 125 in al,dx ;现在ax就是当前光标的位置 126 127 _judge: 128 cmp cl,0x0a 129 je _set_0x0a 130 cmp cl,0x0d 131 je _set_0x0d 132 _print_visible: 133 mov bx,ax 134 mov eax,Print_Segement 135 mov es,eax 136 shl bx,1 ;注意这里一定要把ebx变成原来的两倍,实际位置是光标位置的两倍 137 mov [es:bx],cl ;注意这里是屏幕! 138 mov byte[es:bx+1],0x07 139 add bx,2 140 shr bx,1 141 jmp _roll_screen 142 _set_0x0d: ;回车 143 mov bl,80 144 div bl 145 mul bl 146 mov bx,ax 147 jmp _set_cursor 148 _set_0x0a: ;换行 149 mov bx,ax 150 add bx,80 151 jmp _roll_screen 152 _roll_screen: 153 cmp bx,2000 154 jl _set_cursor 155 mov eax,Print_Segement 156 mov ds,eax 157 mov es,eax 158 159 cld 160 mov edi,0x00 161 mov esi,0xa0 162 mov ecx,1920 163 rep movsw 164 _cls: 165 mov bx,3840 166 mov ecx,80 167 _print_blank: 168 mov word[es:bx],0x0720 169 add bx,2 170 loop _print_blank 171 mov bx,1920 ;别总是忘了光标的位置! 172 _set_cursor: ;改变后的光标位置在bx上 173 mov dx,0x3d4 174 mov al,0x0f ;低8位 175 out dx,al 176 177 mov al,bl 178 mov dx,0x3d5 179 out dx,al 180 181 mov dx,0x3d4 182 mov al,0x0e ;高8位 183 out dx,al 184 185 mov al,bh 186 mov dx,0x3d5 187 out dx,al 188 189 pop ds 190 pop es 191 popad 192 ret 193 ;---------------------------------------------------------------- 194 Make_Seg_Descriptor: ;构造段描述符 195 ;输入: 196 ;eax:线性基地址 197 ;ebx:段界限 198 ;ecx:属性 199 ;输出: 200 ;eax:段描述符低32位 201 ;edx:段描述符高32位 202 mov edx,eax 203 and edx,0xffff0000 204 rol edx,8 205 bswap edx 206 or edx,ecx 207 208 shl eax,16 209 or ax,bx 210 and ebx,0x000f0000 211 or edx,ebx 212 retf 213 ;---------------------------------------------------------------- 214 Make_Gate_Descriptor: ;构造门描述符 215 ;输入: 216 ;eax:段内偏移地址 217 ;bx: 段的选择子 218 ;cx: 段的属性 219 ;输出: 220 ;eax:门描述符低32位 221 ;edx:门描述符高32位 222 push ebx 223 push ecx 224 225 mov edx,eax 226 and edx,0xffff0000 ;要高16位 227 or dx,cx 228 229 shl ebx,16 230 and eax,0x0000ffff 231 or eax,ebx 232 233 pop ecx 234 pop ebx 235 236 retf 237 ;---------------------------------------------------------------- 238 Set_New_GDT: ;装载新的全局描述符 239 ;输入:edx:eax描述符 240 ;输出:cx选择子 241 push ds 242 push es 243 244 mov ebx,Core_Data_Segement 245 mov ds,ebx 246 247 mov ebx,All_4GB_Segment 248 mov es,ebx 249 250 sgdt [pgdt_base_tmp] 251 252 movzx ebx,word[pgdt_base_tmp] 253 inc bx ;注意这里要一定是inc bx而不是inc ebx,因为gdt段界限初始化是0xffff的 254 ;要用到回绕特性 255 add ebx,[pgdt_base_tmp+0x02] ;得到pgdt的线性基地址 256 257 mov [es:ebx],eax 258 mov [es:ebx+0x04],edx ;装载新的gdt符 259 ;装载描述符要装载到实际位置上 260 261 add word[pgdt_base_tmp],8 ;给gdt的段界限加上8(字节) 262 263 lgdt [pgdt_base_tmp] ;加载gdt到gdtr的位置和实际表的位置无关 264 265 mov ax,[pgdt_base_tmp] ;得到段界限 266 xor dx,dx 267 mov bx,8 ;得到gdt大小 268 div bx 269 mov cx,ax 270 shl cx,3 ;得到选择子,ti=0(全局描述符),rpl=0(申请特权0级) 271 272 pop es 273 pop ds 274 retf 275 ;---------------------------------------------------------------- 276 Set_New_LDT_To_TCB: ;装载新的局部描述符 277 ;输入:edx:eax描述符 278 ; : ebx:TCB线性基地址 279 ;输出:cx选择子 280 281 push edi 282 push eax 283 push ebx 284 push edx 285 push ds 286 287 mov ecx,All_4GB_Segment 288 mov ds,ecx 289 290 mov edi,[ebx+0x0c] ;LDT的线性基地址 291 movzx ecx,word[ebx+0x0a] 292 inc cx ;得到实际的LDT的大小(界限还要-1) 293 294 mov [edi+ecx+0x00],eax 295 mov [edi+ecx+0x04],edx 296 297 add cx,8 298 dec cx 299 300 mov [ebx+0x0a],cx 301 302 mov ax,cx 303 xor dx,dx 304 mov cx,8 305 div cx 306 307 shl ax,3 308 mov cx,ax 309 or cx,0x0004 ;LDT,第三位TI位一定是1 310 311 pop ds 312 pop edx 313 pop ebx 314 pop eax 315 pop edi 316 retf 317 ;---------------------------------------------------------------- 318 PrintDword: ;显示edx内容的一个调试函数 319 pushad 320 push ds 321 322 mov eax,Core_Data_Segement 323 mov ds,eax 324 325 mov ebx,bin_hex 326 mov ecx,8 327 328 _query: 329 rol edx,4 330 mov eax,edx 331 and eax,0x0000000f 332 xlat 333 334 push ecx 335 mov cl,al 336 call put_char 337 pop ecx 338 339 loop _query 340 341 pop ds 342 popad 343 344 retf 345 ;---------------------------------------------------------------- 346 allocate_4KB_page: ;输入:无 347 ;输出eax:页的物理地址 348 ;注意这个是近调用 349 push ebx 350 push ecx 351 push edx 352 push ds 353 354 mov eax,Core_Data_Segement 355 mov ds,eax 356 357 xor eax,eax 358 359 _search_pages: 360 bts [page_bit_map],eax 361 jnc _found_not_uesd 362 inc eax 363 cmp eax,page_map_len*8 364 jl _search_pages 365 366 mov ebx,No_More_Page 367 call Sys_Routine_Segement:put_string 368 hlt ;无可用页,直接停机 369 370 _found_not_uesd: 371 shl eax,12 ;eax相当于是选择子,乘以一个4KB得到物理地址 372 373 pop ds 374 pop edx 375 pop ecx 376 pop ebx 377 ret 378 ;---------------------------------------------------------------- 379 alloc_inst_a_page: ;分配一个页,并安装在当前活动的层级分页结构中 380 ;输入:EBX=页的线性地址 381 ;输出:无 382 push eax 383 push ebx 384 push edi 385 push esi 386 push ds 387 388 mov eax,All_4GB_Segment ;转平坦模式 389 mov ds,eax 390 391 _test_P: ;在页目录中看是否存在这个页表 392 mov esi,ebx 393 and esi,0xffc00000 394 shr esi,20 395 or esi,0xfffff000 ;指向页目录本身 396 test dword[esi],0x00000001 397 jnz _get_page_and_create_new_page 398 _create_new_page_directory: 399 call allocate_4KB_page 400 or eax,0x00000007 ;存在于主存,可读可写,允许特权级3程序访问 401 mov [esi],eax 402 _get_page_and_create_new_page: 403 mov esi,ebx 404 shr esi,10 ;页表在页目录的偏移项 405 and esi,0x003ff000 ;得到页表的偏移地址 406 or esi,0xffc00000 ;指向页目录 407 408 and ebx,0x003ff000 409 shr ebx,10 ;中间10位是页目录-页表-表内偏移量(注意这里的层次理解) 410 or esi,ebx ;esi就是页的对应线性地址 411 call allocate_4KB_page 412 or eax,0x00000007 ;存在于主存,可读可写,允许特权级3程序访问 413 mov [esi],eax 414 415 pop ds 416 pop esi 417 pop edi 418 pop ebx 419 pop eax 420 retf 421 ;---------------------------------------------------------------- 422 Copy_Page: ;把在创建的包含全局和私有部分的页表复制一份给用户程序用 423 ;输入:无 424 ;输出eax:页的物理地址 425 push ds 426 push es 427 push edi 428 push esi 429 push ebx 430 push ecx 431 push edx 432 433 mov eax,Core_Data_Segement 434 mov ds,eax 435 mov edx,[task_pos] 436 sub edx,4 437 add edx,[page_header] 438 mov edi,[page_soft_header] 439 sub edi,0x1000 440 mov esi,[page_header] ;指向全局页目录 441 mov [es:0x16],edi 442 443 mov eax,All_4GB_Segment 444 mov ds,eax 445 mov es,eax 446 447 call allocate_4KB_page 448 mov ebx,eax 449 or ebx,0x00000007 450 mov [edx],ebx 451 452 mov ecx,1024 453 cld 454 repe movsd 455 456 pop edx 457 pop ecx 458 pop ebx 459 pop esi 460 pop edi 461 pop es 462 pop ds 463 464 retf 465 ;---------------------------------------------------------------- 466 ;========================================================================= 467 ;===========================内核数据区==================================== 468 ;========================================================================= 469 SECTION Core_Data align=16 vstart=0 470 ;------------------------------------------------------------------------------- 471 pgdt_base_tmp: dw 0 472 dd 0 473 474 salt: 475 salt_1: db '@Printf' ;@Printf函数(公用例程) 476 times 256-($-salt_1) db 0 477 dd put_string 478 dw Sys_Routine_Segement 479 dw 0 ;参数个数 480 481 salt_2: db '@ReadHarddisk' ;@ReadHarddisk函数(公用例程) 482 times 256-($-salt_2) db 0 483 dd ReadHarddisk 484 dw Sys_Routine_Segement 485 dw 4 ;参数个数 486 487 salt_3: db '@PrintDwordAsHexString' ;@PrintDwordAsHexString函数(公用例程) 488 times 256-($-salt_3) db 0 489 dd PrintDword 490 dw Sys_Routine_Segement 491 dw 0 ;参数个数 492 493 salt_length: equ $-salt_3 494 salt_items_sum equ ($-salt)/salt_length ;得到项目总数 495 496 salt_tp: dw 0 ;任务门,专门拿来给程序切换到全局空间的 497 498 message_1 db ' If you seen this message,that means we ' 499 db 'are now in protect mode,and the system ' 500 db 'core is loaded,and the video display ' 501 db 'routine works perfectly.',0x0d,0x0a,0 502 503 message_2 db ' Loading user program...',0 504 505 do_status db 'Done.',0x0d,0x0a,0 506 507 message_3 db 0x0d,0x0a,0x0d,0x0a,0x0d,0x0a 508 db ' User program terminated,control returned.' 509 db 0x0d,0x0a,0x0d,0x0a,0 510 message_4 db ' We have been backed to kernel.',0x0d,0x0a,0 511 message_5 db ' The GDT and memory have benn recycled.',0 512 message_6 db ' From the system wide gate:',0x0d,0x0a,0 513 message_7 db ' Setting the gate discriptor...',0 514 message_In_Gate db ' Hi!My name is Philip:',0x0d,0x0a,0 515 message_page db ' Paging is enabled.System core is mapped to' 516 db ' address 0x80000000.',0x0d,0x0a,0 517 core_stop db 0x0d,0x0a,' Processor HALT.',0 518 No_More_Page db '********No more pages********',0 519 task_switch db 0x0d,0x0a,' Task switching...@_@',0x0d,0x0a,0 520 521 bin_hex db '0123456789ABCDEF' 522 ;put_hex_dword子过程用的查找表 523 core_buf times 2048 db 0 ;内核用的缓冲区(2049个字节(2MB)) 524 525 esp_pointer dd 0 ;内核用来临时保存自己的栈指针 526 527 cpu_brnd0 db 0x0d,0x0a,' ',0 528 cpu_brand times 52 db 0 529 cpu_brnd1 db 0x0d,0x0a,0x0d,0x0a,0 530 core_ss dw 0 531 core_sp dd 0 532 ;程序管理器的任务信息 533 prgman_tss dd 0 ;程序管理器的TSS基地址 534 dw 0 ;程序管理器的TSS描述符选择子 535 ;假设只有2MB内存可以用的意思,正确的做法应该先读PCI(E),然后再分配! 536 page_bit_map db 0xff,0xff,0xff,0xff,0xff,0x55,0x55,0xff 537 db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff 538 db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff 539 db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff 540 db 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55 541 db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 542 db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 543 db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 544 page_map_len equ $-page_bit_map 545 core_next_laddr dd 0x80100000 ;内核空间中下一个可分配的线性地址 546 task_pos dd 0x00000ffc ;任务程序的页表在全局页目录的偏移 547 page_header dd 0xfffff000 ;全局页目录 548 page_soft_header dd 0xfffff000 ;加载页目录地址 549 550 tcb_chain dd 0 ;任务控制块链头指针 551 ;========================================================================= 552 ;===========================内核代码区==================================== 553 ;========================================================================= 554 SECTION Core_Code align=16 vstart=0 555 ;--------------------------------------------------------------------- 556 append_to_tcb: ;写入新的TCB链 557 ;输入:ecx新的TCB线性基地址 558 pushad 559 560 push ds 561 push es 562 563 mov eax,All_4GB_Segment 564 mov es,eax 565 566 mov eax,Core_Data_Segement 567 mov ds,eax 568 569 mov dword[es:ecx+0x00],0 570 mov eax,[tcb_chain] 571 cmp eax,0x00 572 je _notcb 573 574 _search_tcb: 575 mov edx,[tcb_chain+0x00] 576 mov eax,[es:edx] 577 cmp eax,0x00 578 jne _search_tcb 579 580 mov [es:edx+0x00],ecx 581 jmp _out_tcb_search 582 583 _notcb: 584 mov [tcb_chain],ecx 585 586 _out_tcb_search: 587 pop es 588 pop ds 589 590 popad 591 ret 592 ;--------------------------------------------------------------------- 593 load_program: ;输入push1:逻辑扇区号 594 ; push2: 线性基地址 595 pushad 596 push ds 597 push es 598 599 mov ebp,esp ;别忘了把参数传给ebp 600 601 mov eax,Core_Data_Segement 602 mov ds,eax ;切换到内核数据段 603 604 mov eax,All_4GB_Segment 605 mov es,eax 606 607 mov ebx,0xfffff000 608 xor esi,esi 609 _flush_private: 610 mov dword[es:ebx+esi*4],0x00000000 611 inc esi 612 cmp esi,512 613 jl _flush_private 614 615 mov edi,[ebp+11*4] ;获取tcb的线性基地址,别忘了调用相对近调用还要有1个push 616 mov esi,[ebp+12*4] ;esi必须是逻辑扇区号 617 mov ebx,core_buf ;ebx要在内核数据缓冲区(先读取头部在缓冲区,esi已经是有扇区号了) 618 619 push esi 620 push ds 621 push ebx 622 push cs 623 call Sys_Routine_Segement:ReadHarddisk 624 625 mov eax,[core_buf] ;读取用户程序长度 626 mov ebx,eax 627 and ebx,0xfffff000 ;清空低12位(强制对齐4096:4KB) 628 add ebx,4096 629 test eax,0x00000fff 630 cmovnz eax,ebx ;低12位不为0则使用向上取整的结果 631 632 mov ecx,eax 633 shr ecx,12 ;看占了几页 634 mov eax,All_4GB_Segment ;切换到4GB段区域(平坦模式) 635 mov ds,eax 636 mov edi,[ebp+11*4] ;获取tcb的线性基地址 637 mov esi,[ebp+12*4] ;esi必须是逻辑扇区号 638 639 _loop_read_@1: 640 mov ebx,[es:edi+0x06] ;从TCB取得下一个可用的虚拟线性地址 641 add dword[es:edi+0x06],0x1000 642 call Sys_Routine_Segement:alloc_inst_a_page 643 644 push ecx 645 mov ecx,8 ;512*8==4096 646 _loop_read_@2: 647 push esi 648 push ds 649 push ebx 650 push cs 651 call Sys_Routine_Segement:ReadHarddisk ;esi还是User_Program_Address 652 653 mov eax,[es:0xffc00000] 654 mov eax,[es:0xffc00004] 655 mov eax,[es:0xffc00008] 656 657 inc esi 658 add ebx,512 659 loop _loop_read_@2 660 pop ecx 661 loop _loop_read_@1 662 663 mov eax,Core_Data_Segement ;把数据段切回来 664 mov ds,eax 665 mov esi,edi ;esi: TCB的线性基地址 666 667 mov ebx,[core_next_laddr] ;TSS必须在全局空间中进行 668 call Sys_Routine_Segement:alloc_inst_a_page 669 add dword[core_next_laddr],4096 670 671 mov [es:esi+0x14],ebx ;填写TSS的线性基地址 672 mov word[es:esi+0x12],103 ;无I/O映射 673 674 mov ebx,[es:esi+0x06] ;从用户私有空间建立LDT 675 add dword[es:esi+0x06],0x1000 676 call Sys_Routine_Segement:alloc_inst_a_page 677 mov [es:esi+0x0c],ebx ;填写LDT的线地址 678 679 mov edi,[es:esi+0x14] ;edi就是TSS的线性地址 680 ;接下来就是一堆套路了,这里和15章14章的程序不一样,这里直接填TSS,比较直观 681 ;不用给用户程序头回填段的选择子了,现在是平坦模式的演示 682 ;代码段 683 mov eax,0x00000000 684 mov ebx,0x000fffff 685 mov ecx,0x00c0f800 686 call Sys_Routine_Segement:Make_Seg_Descriptor 687 mov ebx,esi 688 call Sys_Routine_Segement:Set_New_LDT_To_TCB 689 or cx,0x0003 ;特权级为3 690 mov [es:edi+76],cx ;CS域 691 692 ;数据段 693 mov eax,0x00000000 694 mov ebx,0x000fffff 695 mov ecx,0x00c0f200 696 call Sys_Routine_Segement:Make_Seg_Descriptor 697 mov ebx,esi 698 call Sys_Routine_Segement:Set_New_LDT_To_TCB 699 or cx,0x0003 ;特权级为3 700 mov [es:edi+72],cx ;ES域,已经映射到全局空间了 701 mov [es:edi+84],cx ;DS域,已经映射到全局空间了 702 mov [es:edi+88],cx ;FS域,已经映射到全局空间了 703 mov [es:edi+92],cx ;GS域,已经映射到全局空间了 704 705 ;创建一系列栈 706 ;创建自身特权级为3的栈 707 mov ebx,[es:esi+0x06] 708 add dword[es:esi+0x06],0x1000 709 call Sys_Routine_Segement:alloc_inst_a_page ;自动在用户私有空间的页表登记了 710 mov [es:edi+80],cx ;cx是数据段的选择子 711 mov edx,[es:esi+0x06] ;向上拓展的ESP的初始值 712 mov [es:edi+56],edx 713 714 ;创建特权级0的栈 715 mov ebx,[es:esi+0x06] 716 add dword[es:esi+0x06],0x1000 717 call Sys_Routine_Segement:alloc_inst_a_page ;自动在用户私有空间的页表登记了 718 719 mov eax,0x00000000 720 mov ebx,0x000fffff 721 mov ecx,0x00c09200 ;4KB粒度的堆栈段描述符,特权级0 722 call Sys_Routine_Segement:Make_Seg_Descriptor 723 mov ebx,esi 724 call Sys_Routine_Segement:Set_New_LDT_To_TCB 725 or cx,0x0000 ;选择子特权级为0 726 727 mov [es:edi+8],cx ;cx是数据段的选择子 728 mov edx,[es:esi+0x06] ;向上拓展的ESP0的初始值 729 mov [es:edi+4],edx 730 731 ;创建特权级1的栈 732 mov ebx,[es:esi+0x06] 733 add dword[es:esi+0x06],0x1000 734 call Sys_Routine_Segement:alloc_inst_a_page ;自动在用户私有空间的页表登记了 735 736 mov eax,0x00000000 737 mov ebx,0x000fffff 738 mov ecx,0x00c0b200 ;4KB粒度的堆栈段描述符,特权级1 739 call Sys_Routine_Segement:Make_Seg_Descriptor 740 mov ebx,esi 741 call Sys_Routine_Segement:Set_New_LDT_To_TCB 742 or cx,0x0001 ;选择子特权级为1 743 744 mov [es:edi+16],cx ;cx是数据段的选择子 745 mov edx,[es:esi+0x06] ;向上拓展的ESP1的初始值 746 mov [es:edi+12],edx 747 748 ;创建特权级2的栈 749 mov ebx,[es:esi+0x06] 750 add dword[es:esi+0x06],0x1000 751 call Sys_Routine_Segement:alloc_inst_a_page ;自动在用户私有空间的页表登记了 752 753 mov eax,0x00000000 754 mov ebx,0x000fffff 755 mov ecx,0x00c0d200 ;4KB粒度的堆栈段描述符,特权级2 756 call Sys_Routine_Segement:Make_Seg_Descriptor 757 mov ebx,esi 758 call Sys_Routine_Segement:Set_New_LDT_To_TCB 759 or cx,0x0002 ;选择子特权级为2 760 761 mov [es:edi+24],cx ;cx是数据段的选择子 762 mov edx,[es:esi+0x06] ;向上拓展的ESP2的初始值 763 mov [es:edi+20],edx 764 765 ;现在开始重定位API符号表 766 ;--------------------------------------------------------------------- 767 mov eax,All_4GB_Segment ;因为这个时候用户头部在LDT,而LDT还没有被加载,只能通过4GB空间访问 768 mov es,eax 769 mov eax,Core_Data_Segement 770 mov ds,eax 771 772 cld 773 mov ecx,[es:0x0c] 774 mov edi,[es:0x08] 775 776 _loop_U_SALT: 777 push edi 778 push ecx 779 780 mov ecx,salt_items_sum 781 mov esi,salt 782 783 _loop_C_SALT: 784 push edi 785 push esi 786 push ecx 787 788 mov ecx,64 ;比较256个字节 789 repe cmpsd 790 jne _re_match ;如果成功匹配,那么esi和edi刚好会在数据区之后的 791 792 mov eax,[esi] ;偏移地址 793 mov [es:edi-256],eax ;把偏移地址填入用户程序的符号区 794 mov ax,[esi+0x04] ;段的选择子 795 796 or ax,0x0002 ;把RPL改为3,代表(内核)赋予应用程序以特权级3 797 mov [es:edi-252],ax ;把段的选择子填入用户程序的段选择区 798 799 _re_match: 800 pop ecx 801 pop esi 802 add esi,salt_length 803 pop edi 804 loop _loop_C_SALT 805 806 pop ecx 807 pop edi 808 add edi,256 809 loop _loop_U_SALT 810 ;--------------------------------------------------------------------- 811 ;----------------------填入临时中转任务门选择子----------------------- 812 mov ax,[salt_tp] 813 mov [es:0x14],ax ;填充任务门选择子 814 ;--------------------------------------------------------------------- 815 mov esi,[ebp+11*4] ;重新获得TCB的线性基地址 816 817 ;在GDT中存入LDT信息 818 mov eax,[es:esi+0x0c] 819 movzx ebx,word[es:esi+0x0a] 820 mov ecx,0x00408200 ;LDT描述符,特权级0级 821 call Sys_Routine_Segement:Make_Seg_Descriptor 822 call Sys_Routine_Segement:Set_New_GDT 823 mov [es:esi+0x10],cx ;在TCB放入LDT选择子 824 825 ;构建TSS剩下的信息表 826 mov ebx,[es:esi+0x14] 827 mov [es:ebx+96],cx ;TSS中LDT选择子 828 829 mov word[es:ebx+0],0 ;填充反向链(任务切换的时候处理器会帮着填的,不用操心) 830 mov dx,[es:esi+0x12] ;TSS段界限 831 mov [es:ebx+102],dx 832 mov word[es:ebx+100],0 ;T=0 833 834 mov eax,[es:0x04] ;从任务的4GB地址空间获取入口点 835 mov [es:ebx+32],eax ;填写TSS的EIP域 836 837 pushfd 838 pop edx 839 mov [es:ebx+36],edx ;EFLAGS 840 841 ;在GDT中存入TSS信息 842 mov eax,[es:esi+0x14] 843 movzx ebx,word[es:esi+0x12] 844 mov ecx,0x00408900 845 call Sys_Routine_Segement:Make_Seg_Descriptor 846 call Sys_Routine_Segement:Set_New_GDT 847 mov [es:esi+0x18],cx 848 849 ;复制一份页表 850 call Sys_Routine_Segement:Copy_Page 851 mov ebx,[es:esi+0x14] 852 mov [es:ebx+28],eax ;填写PDBR(CR3) 853 854 pop es 855 pop ds 856 popad 857 ret 8 ;相当于是stdcall,过程清栈 858 ;--------------------------------------------------------------------- 859 start: 860 mov eax,Core_Data_Segement 861 mov ds,eax 862 mov eax,All_4GB_Segment 863 mov es,eax 864 865 mov ebx,message_1 866 call Sys_Routine_Segement:put_string 867 868 ;下面准备开启页管理 869 mov ecx,1024 870 mov ebx,PDT_Mem_Address 871 xor esi,esi 872 873 _flush_PDT: ;清空页表 874 mov dword[es:ebx+esi*4],0x00000000 875 inc esi 876 loop _flush_PDT 877 878 ;下面的代码会非常绕,注意观察 879 ;页目录的最后一个32字节是指向自己的页表(这个页表就是页目录) 880 mov dword[es:ebx+4092],PDT_Mem_Address 881 or dword[es:ebx+4092],0x00000007 ;属性:存在于物理内存,可写可读,啥程序都能访问 882 883 ;页目录的第一个页表指示最底下1MB内存的4KB页(内核代码,必须虚拟地址和物理地址一致) 884 mov dword[es:ebx+0],PDT_Mem_Address 885 or dword[es:ebx+0],0x00000007 ;属性:存在于物理内存,可写可读,啥程序都能访问 886 or dword[es:ebx+0],0x00001000 ;注意是这个页表是放在目录的后一个页! 887 888 ;现在0x00020000的页是第0个页表(指示页目录),0x00021000是第一个页表(指示底下1MB的东西) 889 mov ebx,PDT_Mem_Address 890 or ebx,0x00001000 891 xor eax,eax 892 xor esi,esi 893 894 _make_page: 895 mov edx,eax 896 or edx,0x00000007 ;属性:存在于物理内存,可写可读,啥程序都能访问 897 mov [es:ebx+esi*4],edx ;物理位置 898 add eax,0x1000 899 inc esi 900 cmp esi,256 901 jl _make_page 902 903 _make_page_last: 904 mov dword[es:ebx+esi*4],0x00000000 ;标记页为无效 905 inc esi 906 cmp esi,1024 907 jl _make_page_last 908 909 mov eax,PDT_Mem_Address 910 mov cr3,eax ;把页目录基地址放在cr3,准备开启页功能 911 912 mov eax,cr0 913 or eax,0x80000000 914 mov cr0,eax ;置PG位,开启页功能 915 916 ;--------------------------已开启页功能-------------------------------- 917 ;------------------开始映射高端内存区到页目录表中---------------------- 918 mov ebx,0xfffff000 ;表示页表指向页目录自己 919 mov esi,Global_Page_Directory ;映射的起始地址 920 shr esi,22 921 shl esi,2 922 mov dword[es:ebx+esi],PDT_Mem_Address 923 add dword[es:ebx+esi],0x00001003 924 925 sgdt [pgdt_base_tmp] 926 mov ebx,[pgdt_base_tmp+2] ;GDT线性基地址 927 928 or dword[es:ebx+0x10+4],Global_Page_Directory ;0x10是刚好是64个字节,忽略0字串 929 or dword[es:ebx+0x18+4],Global_Page_Directory 930 or dword[es:ebx+0x20+4],Global_Page_Directory 931 or dword[es:ebx+0x28+4],Global_Page_Directory 932 or dword[es:ebx+0x30+4],Global_Page_Directory 933 or dword[es:ebx+0x38+4],Global_Page_Directory 934 935 add dword[pgdt_base_tmp+2],Global_Page_Directory ;线性基地址也要变 936 937 lgdt [pgdt_base_tmp] 938 jmp Core_Code_Segement:_flush ;强制刷新代码段,映射到高端内存区 939 940 _flush: 941 mov eax,Stack_Segement 942 mov ss,eax 943 944 mov eax,Core_Data_Segement 945 mov ds,eax 946 947 mov ebx,message_page 948 call Sys_Routine_Segement:put_string 949 ;---------------------------------------------------------------------- 950 951 _@load: 952 ;----------------------------安装门------------------------------------ 953 mov edi,salt 954 mov ecx,salt_items_sum 955 _set_gate: 956 push ecx 957 mov eax,[edi+256] 958 mov bx,[edi+260] ;选择子 959 mov cx,0xec00 ;门是特权级是3的门,那么任何程序都能调用 960 or cx,[edi+262] ;加上参数个数 961 962 call Sys_Routine_Segement:Make_Gate_Descriptor 963 call Sys_Routine_Segement:Set_New_GDT 964 mov [edi+260],cx ;回填选择子 965 add edi,salt_length 966 pop ecx 967 loop _set_gate 968 ;---------------------------------------------------------------------- 969 mov ebx,message_In_Gate 970 call far [salt_1+256] ;调用门显示字符信息(忽略偏移地址(前4字节)) 971 972 mov ebx,[core_next_laddr] 973 call Sys_Routine_Segement:alloc_inst_a_page 974 add dword[core_next_laddr],4096 ;指向下一个页 975 976 mov word[es:ebx+100],0 ;TI=0 977 mov word[es:ebx+102],103 ;任务管理器不需要I/O映射,要大于等于界限 978 mov word[es:ebx+96],0 ;任务允许没有自己的LDT 979 mov eax,cr3 980 mov dword[es:ebx+28],eax ;设置CR3,注意不是0了! 981 mov word[es:ebx+0],0 ;没有前一个任务 982 983 mov eax,ebx 984 mov ebx,103 ;TSS段界限 985 mov ecx,0x00408900 986 call Sys_Routine_Segement:Make_Seg_Descriptor 987 call Sys_Routine_Segement:Set_New_GDT 988 mov [prgman_tss+0x04],cx 989 990 ltr cx ;启动任务 991 ;------------------安装用户管理程序的临时返回任务门-------------------- 992 mov eax,0x0000 ;TSS不需要偏移地址 993 mov bx,[prgman_tss+0x04] ;TSS的选择子 994 mov cx,0xe500 995 996 call Sys_Routine_Segement:Make_Gate_Descriptor 997 call Sys_Routine_Segement:Set_New_GDT 998 mov [salt_tp],cx ;填入临时中转任务门选择子,注意不需要加260了 999 ;---------------------------------------------------------------------- 1000 mov ebx,[core_next_laddr] 1001 call Sys_Routine_Segement:alloc_inst_a_page 1002 add dword[core_next_laddr],4096 ;指向下一个页 1003 1004 mov dword[es:ebx+0x06],0 ;用户程序从0位置开始分配 1005 mov word[es:ebx+0x0a],0xffff ;LDT初始界限 1006 mov ecx,ebx ;添加到TCB链中 1007 call append_to_tcb 1008 1009 push dword User_Program_AddressA 1010 push ecx 1011 1012 call load_program 1013 1014 mov ebx,task_switch 1015 call Sys_Routine_Segement:put_string 1016 1017 jmp far [es:ecx+0x14] 1018 ;call far [es:ecx+0x14] 1019 1020 mov ebx,core_stop 1021 call Sys_Routine_Segement:put_string 1022 1023 hlt 1024 ;---------------------------------------------------------------------- 1025 ;========================================================================= 1026 SECTION core_trail 1027 ;---------------------------------------------------------------- 1028 Program_end:
1 ;================================用户程序======================================= 2 program_length dd program_end ;程序总长度#0x00 3 entry_point dd start ;程序入口点#0x04 4 salt_position dd salt_begin ;SALT表起始偏移量#0x08 5 salt_items dd (salt_end-salt_begin)/256 6 ;SALT条目数#0x0C 7 TpBack: dd 0 ;任务门的偏移地址没用,直接填充就可以了 8 dw 0 ;任务门的选择子#0x14 9 Own_Page dd 0 ;自己页面的物理地址#0x16 10 ;------------------------------------------------------------------------------- 11 ;符号地址检索表 12 salt_begin: 13 PrintString db '@Printf' 14 times 256-($-PrintString) db 0 15 TerminateProgram: db '@TerminateProgram' 16 times 256-($-TerminateProgram) db 0 17 ;------------------------------------------------------------------------------- 18 reserved times 256*500 db 0 ;保留一个空白区,以演示分页 19 ;------------------------------------------------------------------------------- 20 ReadDiskData db '@ReadHarddisk' 21 times 256-($-ReadDiskData) db 0 22 PrintDwordAsHex db '@PrintDwordAsHexString' 23 times 256-($-PrintDwordAsHex) db 0 24 salt_end: 25 message_0 db 0x0d,0x0a, 26 db ' ............User task is running with ' 27 db 'paging enabled!............',0x0d,0x0a,0 28 message_1 db 0x0d,0x0a, 29 db ' ..........,,,..The address of the user' 30 db ' task(first 50)!...........',0x0d,0x0a,0 31 space db 0x20,0x20,0 32 next_line: db 0x0d,0x0a,0 33 Own_Page_Message db 0x0d,0x0a, 34 db 0x20,0x20,'Task Page: ',0 35 ;------------------------------------------------------------------------------- 36 [bits 32] 37 ;------------------------------------------------------------------------------- 38 start: 39 ;--------------------------显示50个页内容------------------------ 40 mov ebx,message_0 41 call far[PrintString] 42 43 xor esi,esi 44 mov ecx,50 45 .b1: 46 mov ebx,space 47 call far[PrintString] 48 49 mov edx,[esi*4] 50 call far[PrintDwordAsHex] 51 inc esi 52 loop .b1 53 54 ;--------------------显示新任务页目录地址-------------------------- 55 mov ebx,Own_Page_Message 56 call far[PrintString] 57 mov ebx,space 58 call far [PrintString] 59 mov ebx,[Own_Page] 60 mov edx,[es:ebx] 61 and edx,0xfffff000 ;处理器会设置A和D位,直接忽略得到物理地址了 62 call far[PrintDwordAsHex] 63 64 mov ebx,next_line 65 call far [PrintString] 66 ;--------------------显示任务前50个页物理地址---------------------- 67 mov ebx,message_1 68 call far[PrintString] 69 mov edi,0xffc00000 70 mov ecx,50 71 xor esi,esi 72 73 _show: 74 mov ebx,space 75 call far [PrintString] 76 77 mov edx,[es:edi+esi*4] 78 and edx,0xfffff000 ;处理器会设置A和D位,直接忽略得到物理地址了 79 call far[PrintDwordAsHex] 80 inc esi 81 loop _show 82 jmp far [fs:TpBack] 83 ;iretd 84 ;------------------------------------------------------------------------------- 85 program_end: