ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配

时间:2021-07-01 01:24:17

  第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个字节,同样页表的大小也是页的大小。

  页目录,页表和页的关系:(图片出处看水印)

  ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配

  处理器有页部件,专门负责线性地址到物理地址的转换工作。它首先将段部件送来的32位线性地址截成3段,分别是高10位(页目录的索引),中间的10位(页表的索引)和低12位(页内偏移)。操作系统要负责填写页目录和页表地址,然后程序的内存访问就可以像上图那样进行转换了。

2. 页目录、页表和CR3的填写

  ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配

  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的设置如下图:

  ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配

  其实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

  ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配

  ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配

  以上代码就就是开启页功能的一个演示。

3. 任务全局空间和局部空间的页面演示,页面查找的例子

       在15章我们说过,任务的4GB地址空间包括两个部分:局部空间和全局空间,全局空间是所有任务共用的,内核就是所有任务共用的,它属于每个任务的全局空间。在教材的系统中,规定4GB的虚拟高2GB空间就是全局空间,地址范围是0x80000000-0xFFFFFFFF,局部空间使用低2GB空间,地址范围是0x00000000-0x7FFFFFFF,在任何时候,如果段部件发出的线性地址高于等于0x80000000,指向和访问的就是全局地址空间,或者说是内核。另外在上面我说过,我们把页目录的最后一项指向自己页目录的页,把页目录看成页表,再把页目录看成页表,刚好这个页表的最后一项也是指向页目录,那样我们就可以实现对页目录的内容进行访问,否则我们将无法访问页目录,因为处理器不允许访问一个没有登记的页(访问了会引发异常中断)。

       但是为了保证内核的全局部分可以被其他程序使用,要创建内核的映射,内核应该具有两个部分,一个是和他的物理地址一样的对应(低地址)部分,另一个是映射到高地址的对应部分。这样说可能会比较抽象,我们直接看代码就好了。然后接下来就是把GDT的内容全部重定位了(GDT页必须使用虚拟线性地址,已经在前面把内核映射到高地址空间了),当然我们这里的全局空间映射位置取的比较好,是0x80000000,所以只要往GDT的最高4位or一个8就可以了。

ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配   

  注意 mov dword[es:ebx+esi],PDT_Mem_Address,add dword[es:ebx+esi],0x00001003这两句话,ebx的内容是0XFFFFF000,ESI的内容是0x00000200,因此段部件发出的线性地址是0XFFFFF200,现在举这个访问页目录的例子,以更加清楚了解如何访问页:

   ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配

  ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配

ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配

  所以现在位于物理地址0x00021000这个地方的页被页目录中两个项所指向,但是这两个项所映射的物理地址时不一样的!!!!,页目录的第0项对应的映射是0x00000000-0x000FFFFF,0x800项对应的是0x80000000-0x800FFFFF。这也是一个分级管理有效地缩减表的占用的一个证据:因为全局空间总是映射到高区间,所以如果采用单映射的话,必须先准备2MB的表项先把局部空间描述完了才能到全局空间。

  注意因为段部件的内容不会因为你做好了映射而自动把自己的内容改变,所以需要显式切换,那就是上面代码的后面的部分。

★PART2:保护模式页管理模式下的内核任务的创建

1. 内核的虚拟内存的分配

  说实话教材说了那么多,其实就是最重要的是讲明白一个东西就行了,那就是页的分配。把这个搞明白了其他东西都是一个套路。

  现代操作系统可以跟踪所有页的分配状况。内存空间来自于插在主板上的内存条,按照新的工业标准,每个内存条上焊有一个很小的只读存储器,用于标明该内存条的容量和工作参数,作为一个PCI(E)设备,软件可以读取它,以获得计算机上的物理内存容量。然后简历上述的页分配表。但是由固件创建的表是每个表项占1个字节的,如果有4GB内存,则最多分220个页,要占用1MB的内存。但是如果以比特来指示页的使用状况,那么最多使用1048576个比特(128KB),将会产生巨大的内存节约。我们的系统为了简单,假定我们的系统只有2MB的内存,2MB的内存,可以分512个页,需要512个比特,我们可以在内核区定义比特串:

   ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配

  我们可以看到底下的1MB已经被内核和ROM-BIOS分配的差不多了。但是页的分配可以不连续的,接下来我们就可以在代码中看到这个问题。

ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配

 

ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配

 

注意代码中我们使用了bts指令,这个指令用于测试串中的某个比特位。用该比特的值设置EFLAGS寄存器的CF标志位,然后将这个标志置“1”,他的最基本两种形式为:

                     bts r/m16,r16

                     bts r/m32,r32

  目的操作数可以是16/32位的通用寄存器,或者指向一个包含了16/32位实际操作数的内存党员,用于指定位串;源操作数可以是16/32位的通用寄存器,用于指定待测试的比特在位串的索引 (位置)。其他类似的指令还有:btr,btc和bt,他们的区别如下:

ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配

  例程首先做的事情就是在页目录看一下相应的页表是否已经在页目录中登记了,如果没有登记,则分配一个新的页作为页表然后写入页目录相应位置,否则就直接使用这个页表(当然这个管理程序非常粗糙,会有非常严重的问题,但是现在我们要把事情简单化)。

2. 任务程序的加载

  其实也是一堆套路,只不过我们需要注意的是,因为我们现在用的是页管理模式,所以要使用平坦模式。平坦模式是现代操作系统流行的管理模式,抛弃段管理模式冗杂的段的管理,可以大大简化代码的编写难度(注意的是页也有一点特权控制)。

  教材上加载程序的思路其实和前面几章是一样的,但是使用的是平坦模式来加载,并且对TCB进行了一些改变,而且使用了向上拓展的栈段(其实向上拓展的栈段只是段界限的检查会不一样,那是处理器的事情,push和pop指令的操作是一样的)。TCB变成了下图这个样子:

ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配

ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配

  最后要说明的是,分页机制下,和分段机制是一样的,内存也是先登记后使用的。程序在编写和编译之后,都是连续的,在加载后不能保证这一点。页的分配是随机的,尽管页不是连续的,但是线性地址是连续的就足够了。处理器访问数据,取指令,用的是线性地址。教材把所有的用户程序都从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: