分页是现在CPU核心的管理内存方式,网上介绍材料很多,这里不赘述,简单介绍一下分页的背景和原理

  1、先说说为什么要分段

  •  实模式下程序之间不隔离,互相能直接读写对方内存,或跳转到其他进程的代码运行,导致泄密、出错,通过分段隔离不同程序代码对不同内存单元的读写权限
  • 用户程序在内存种加载的地址不确定,通过分段对程序的数据、代码重定位,才能在运行时正确寻址(如果没有特殊声明,编译器编译后生成文件的代码和数据都是相对文件头开始计算偏移的)

  2、再说说为什么要分页?

     物理内存是有限的,主流普通PC机内存也就8G~16G,除了运行os,还要尽可能多地运行用户程序。但现代大型的用户程序动则大几百M,甚至几个G,要想“同时”把这么多的用户程序加载到内存运行该怎么办了?

  • CPU的分页机制把物理内存分割成4K大小的空间,称为“页”。
  • 32位的windows操作系统针对每个进程,虚拟出了4GB的进程空间。对于进程来说,低2G的空间随便用,无需任何顾忌。那么问题又来了,不同进程很有可能用到了同样的地址,怎么防止冲突?
  • os会根据实际情况,把虚拟地址”挂载“到适合的物理页。对于不同的进程,代码种即使用了同样的地址,os也会挂载到不同的物理内存,这些对于进程来说都是透明不可见的,也不需要关心;
  • 内存的空间是有限的,为了尽量多”并发“运行进程,os会酌情把物理页的数据存储到磁盘的pagefile.sys文件。当进程执行需要用到时,发现物理内存没有,此时产生缺页异常,os负责从磁盘取回这些数据放回内存,让进程继续执行;
  •  分页可以让段基址和limit变平坦(64位已经这样了),段仅用来鉴权,或在32位和64位之间来回切换(利用这个特性可以让64位的os兼容32位的应用程序,也可以将32位程序的某些重要数据,比如key、密钥、密码之类的放在64位模式下,达到在3环下反调试、反逆向的目的,详细的过程见这里:https://www.bilibili.com/video/BV1SJ411K7LR
  • windwos会对页赋予各种属性,比如可执行,可读写。可人为将页属性更改,比如代码所在的页改为不可执行、不可读,进程运行到这种页时产生缺页异常。此时如果hook pagefault函数,根据异常原因分别处理:如果是执行,那么把页属性改成可执行,替换成自己想要执行的代码;如果是读取异常,那么给该线性地址挂载原物理页。这种hook能达到隐藏钩子的目的,能在VT下过PG保护,这就是著名的shaodw walker,详细过程可以参考这里:https://www.bilibili.com/video/BV1Hb411n7Mw

  3、核心代码解读

  (1)准备PDT

  •  页目录物理地址0x20000开始,后续会把这个地址赋值给CR3;
  • PDE也是32位=4字节,那么PDE大小=1024*4=4096字节,刚好是一个页,那么PDT结尾就是0x20000+0x1000=0x21000;
  1.       ;创建系统内核的页目录表PDT
  2. ;页目录表清零
  3. mov ecx, ;1024个目录项PDE
  4. mov ebx,0x00020000 ;页目录的物理地址
  5. xor esi,esi
  6. .b1:
  7. mov dword [es:ebx+esi],0x00000000 ;页目录表项清零
  8. add esi,
  9. loop .b1
  10.  
  11. ;在页目录内创建指向页目录自己的目录项,最后一项指向自己,那么线性地址高20位是0xFFFFF的时候,转成物理地址就是页目录自己
  12. mov dword [es:ebx+],0x00020003
  13.  
  14. ;在页目录内创建与线性地址0x00000000对应的目录项
  15. mov dword [es:ebx+],0x00021003 ;写入目录项(页表的物理地址和属性)
  •  以上代码执行完毕后,内存图如下:分别在PDT的首位写入两个地址,其他的都清零,那么问题来了,为啥要分别写这两个数,而不是其他的数?

  

  • 先解释一下PDT的第一项为什么会是0x00021003

    一旦开启分页,所有地址都会被认为是线性地址,都会经过转换才能获取物理地址,这是CPU的硬件机制决定的,操作系统都要遵守,无法例外。既然0x20000~0x21000这段地址已经被用于存放PDT,那么就不应该再被写入,避免PDT被破坏,导致线性地址映射到物理地址出错,所以物理地址必须从0x21000开始;这里把0x21000开始的地方用来存放页表;

  • 再解释一下最后一个PDE为什么是0x20003

   由于业务变化多端,无法在开启分页前全部确定最终地址,导致很多PDT要开启分页后再填;那么问题又来了,一旦开启分页,任何线性地址都要转换才能得到物理地址,PDT也不例外,怎么让线性地址转换后落入0x20000~0x21000这个物理区间了?

      来分析一种特殊的地址,前20位都是1,比如0xFFFFF200. 按照10-10-12拆分,3个偏移分别0x3ff, 0x3ff乘以4后分别是 0xffc,0xffc;

      第一次转换:0x20000+0xffc=0x20ffc,得到0x20003;后3byte是属性,基址就是0x20000;

      第二次转换:0x20000+0xffc=0x20ffc,得到0x20003;后3byte是属性,基址还是0x20000;

   最后一次转换:0x20000 + 0x200= 0x20200,地址还是落在0x20000~0x21000区间;所以结论就是:线性地址前20位都是1,转成物理地址会落在PDT内部,线性地址最后12位就是PDT内的偏移;通过一些巧妙的数字设置,这里把页目录当成页表在用了;

   最后12位是属性位:

  (2)正式开始分页前最后的准备工作:初始化PET页表,让其映射最低端0~1MB的物理地址;实模式下低端1MB物理地址都有用了,所以必须先把这部分地址映射,防止分页开启后找不到;下面有第(3)点有PDT和PET的内存表,方便理解

  1. ;创建与上面那个目录项相对应的页表,初始化页表项
  2. mov ebx,0x00021000 ;页表的物理地址
  3. xor eax,eax ;起始页的物理地址
  4. xor esi,esi ;esi=0
  5. .b2:
  6. mov edx,eax ;edx=eax; eax = 0x1000*n
  7. or edx,0x00000003 ;edx=0x1000*n+3;u/s=1,不允许3环程序访问;P=1,页在内存种;RW=1,页可读可写;
  8. mov [es:ebx+esi*],edx ;登记页的物理地址; 0x21000~0x21400都是PTE,隐射从0~1MB(256*4096=1Mb)的物理地址;
  9. add eax,0x1000 ;下一个相邻页的物理地址
  10. inc esi
  11. cmp esi, ;仅低端1MB内存对应的页才是有效的
  12. jl .b2
  13.  
  14. .b3: ;其余的页表项置为无效
  15. mov dword [es:ebx+esi*],0x00000000 ;0x21400~(0x21400+(1024-256)*4=0x22000)清零;
  16. inc esi
  17. cmp esi,
  18. jl .b3

  (3)这里 es:ebx+esi = 0xFFFFF800, 开启分页机制后,会映射到0x20800,同样也赋值0x21003,指向页表第一个位置;

  1.      ;在页目录内创建与线性地址0x80000000对应的目录项
  2. mov ebx,0xfffff000 ;页目录自己的线性地址;高5字节都是F,低3字节就是PDT内的偏移
  3. mov esi,0x80000000 ;映射的起始地址
  4. shr esi, ;取线性地址高10位(目录索引),esi=0x200
  5. shl esi, ;索引乘以4得到偏移
  6. mov dword [es:ebx+esi],0x00021003 ;写入目录项(页表的物理地址和属性)es:ebx+esi = 0xFFFFF800

    虽说这两个PDE都指向同一个页表,但各自的线性地址确不同:第一个线性地址范围0x00000000~0x000FFFFF(PDT的索引是0), 第二个线性地址的范围是0x80000000~0x800FFFFF(PDT的索引是800);为什么要让两个不同的线性地址段指向同一个PTE,进而共享同一块物理内存了? 站在应用开发角度,已经习惯了将0x80000000作为内核地址,并且各个用户程序共享。但此时GDT已加载到0x0~0xFFFFF的低1MB空间,后续内核代码、内核数据段、API也会加载到这1MB空间,为了兼容现有的用户习惯,需要将0x80000000也映射到这里的物理地址;所以这里的结论:线性地址0x80000000~0x800FFFFF映射的物理地址:0x00000~0xFFFFF

              

    物理地址内容如下,这里设计就很巧妙了

  •    比如未分页的时候物理地址0x00007e10, or 0x80000000后变成0x80007e10,经过下面PDE和PTE的转换,线性地址0x80007e10又变回了物理地址0x00007e10,分页开启在在物理地址保存的各个GDT or 0x80000000 就行,其他没任何影响,照常使用;
  • 原0x00000000~0x000FFFFF 低1MB的物理空间,分页开启后转成的物理地址没变。比如0x00007e10,当成线性地址转换成物理地址后还是0x00007e10;
  • 巧妙之处:(1)高10位是0x000或0x800的线性地址,在PDT表中查找到0x00021003,这是PTE的起始地址;  (2)中间10位是PTD的偏移,每个偏移都乘以0x1000,比如上面的0x007,得到0x7000;(3)最后3字节是页内偏移,所以得到的结果还是以前的物理地址0x00007e10;

  (4)此时已开启了分页模式,所有地址都会被认为是线性地址,为了正常找到在实模式下已经存好的描述符,这里对每个描述符最高位置1,原因上面已经解释过:这么做能让新的线性地址经过PDE和PTE的转换后还能变回以前的物理地址,比如线性地址0x80007e10又变回了物理地址0x00007e10

       这里把内核各个核心段的描述符最高位都置1,构建内核区域的线性地址:

  1.      ;将GDT中的段描述符映射到线性地址0x80000000
  2. sgdt [pgdt]
  3. mov ebx,[pgdt+] ;ebx存放GDTbase
  4. or dword [es:ebx+0x10+],0x80000000 ;
  5. or dword [es:ebx+0x18+],0x80000000 ;内核堆栈段
  6. or dword [es:ebx+0x20+],0x80000000 ;视频显示缓冲区
  7. or dword [es:ebx+0x28+],0x80000000 ;API
  8. or dword [es:ebx+0x30+],0x80000000 ;内核数据段
  9. or dword [es:ebx+0x38+],0x80000000 ;内核代码段
  10. add dword [pgdt+],0x80000000 ;GDTR也用的是线性地址
  11. lgdt [pgdt]

    此刻问题又来了:这个时候不是已经开启分页了么?es:ebx+0x18+4 = 0x7e00+0x18+0x4= 0x7e1c,这个地址会被当成线性地址看待;如果按照10-10-12分页,0x7e1c转成物理地址后还是0x7e1c,描述符的最高位成功置1; 更改后的描述符 0x80cf9600`0x7c00fffe ,段基址0x80007c00,转成物理地址后还是0x7c00;

       

  (5)API段一共提供了4个函数,在内核数据段对这4个函数都有登记,每个函数的格式:函数名(不超过256字节,不够的填0补充)、API段内偏移、API段选择子,这个类似于导出表;这里构造每个API函数调用们(权限控制在3环的程序访问),然后将selector写回原选择子处;

      其实在API(原作者称为sys_routine段),出了这4个,还有其他函数,比如make_gate_descriptor、set_up_gdt_descriptor、alloc_inst_a_page等,只不过这两个函数并未在导出表列举,一般情况下用户程序是不知道其地址的;同时也是内核0环权限,普通3环程序也无权访问,但还是有办法调用,比如在windows下,做逆向时需要调用很多内核未导出函数,在驱动中完全可以根据特征码查找这些函数的偏移地址,然后call调用,详细可参考之前的文章:https://www.cnblogs.com/theseventhson/p/13024325.html

  1.     ;以下开始安装为整个系统服务的调用门。特权级之间的控制转移必须使用门
  2. mov edi,salt ;C-SALT表的起始位置,内核API函数导出表,有函数名称、函数在API段内的偏移、API段的选择子
  3. mov ecx,salt_items ;C-SALT表的条目数量,ecx=4
  4. .b4:
  5. push ecx
  6. mov eax,[edi+] ;该条目入口点的32位偏移地址;API函数的段内偏移地址
  7. mov bx,[edi+] ;该条目入口点的段选择子 API函数所在段的选择子
  8. mov cx,1_11_0_1100_000_00000B ;特权级3的调用门(3以上的特权级才
  9. ;允许访问),0个参数(因为用寄存器
  10. ;传递参数,而没有用栈)
  11. call sys_routine_seg_sel:make_gate_descriptor
  12. call sys_routine_seg_sel:set_up_gdt_descriptor
  13. mov [edi+],cx ;将返回的门描述符选择子回填
  14. add edi,salt_item_len ;指向下一个C-SALT条目
  15. pop ecx
  16. loop .b4

   (6)分配物理页:为了简单,这里只使用2M内存,可用512个页;512个页用512位保存状态,0表示空闲,1表示使用,这512位存放在page_bit_map中;分配内存时先逐个遍历,是0的话就占用;同时把索引号乘以0x1000就是物理地址了;

  1. allocate_a_4k_page: ;分配一个4KB的页
  2. ;输入:无
  3. ;输出:EAX=页的物理地址
  4. push ebx
  5. push ecx
  6. push edx
  7. push ds
  8.  
  9. mov eax,core_data_seg_sel
  10. mov ds,eax
  11.  
  12. xor eax,eax
  13. .b1: ;遍历page_bit_map,找到第一个标识是0的位,说明该页还未使用
  14. bts [page_bit_map],eax ;[page_bit_map]第eax的位复制给CF,同时置1
  15. jnc .b2 ;CF=0,说明找到了空闲的物理页;物理页索引存放在eax
  16. inc eax ;没有找到,eax+1继续找
  17. cmp eax,page_map_len* ;遍历到page_bit_map末尾了吗?
  18. jl .b1 ;没有就从头继续找
  19.  
  20. mov ebx,message_3
  21. call sys_routine_seg_sel:put_string
  22. hlt ;没有可以分配的页,停机
  23.  
  24. .b2:
  25. shl eax, ;eax存放了空闲的物理页索引,乘以40960x1000)就是地址
  26.  
  27. pop ds
  28. pop edx
  29. pop ecx
  30. pop ebx
  31.  
  32. ret

  (7)给指定的线性地址挂载物理页

  • 线性地址也要求0x1000对齐
  • 这里构造新的线性地址:(1)原线性地址高10位放在新地址中间13~22位;原线性地址中间10位(13~22)放新地址低3~12位;新地址高10位置1,这样一来,原地址高10位会作为页目录表的偏移,原地址中间10位作为页表内偏移mov [esi],eax 会把找好的物理页地址放入合适的页表项,最终完成线性地址到物理地址的映射
  1. alloc_inst_a_page: ;给指定的线性地址挂载物理页
  2. ;层级分页结构中
  3. ;输入:EBX=页的线性地址,比如0x80104000
  4. push eax
  5. push ebx
  6. push esi
  7. push ds
  8.  
  9. mov eax,mem_0_4_gb_seg_sel
  10. mov ds,eax
  11.  
  12. ;检查该线性地址所对应的页表是否存在;把ebx10位作为PDT的索引查找PTE
  13. mov esi,ebx ;esi=0x80104000
  14. and esi,0xffc00000 ;只保留最高的10位,低22位清零,得到PDT的索引,esi=0x80000000
  15. shr esi, ;高12位移到低12位:得到页目录索引,并乘以4,得到PTEPDE内的偏移地址;esi=0x00000800
  16. or esi,0xfffff000 ;页目录自身的线性地址+表内偏移;最高20位置1的线性地址,转换成物理地址=PDT基址(这里是0x20000)+esi,相当于最低3字节就是PDT内的偏移,高20位置1确保物理地址还是落在PDT内;esi=0xfffff800
  17.  
  18. test dword [esi],0x00000001 ;P位是否为“1”.如果PDT某项有PTE,结尾不会是0;如果是0,说明还未挂载物理页;[esi]=0x00000003,最后4位是0011
  19. jnz .b1 ;否已经有对应的页表
  20.  
  21. ;创建该线性地址所对应的页表
  22. call allocate_a_4k_page ;分配一个页做为页表
  23. or eax,0x00000007 ;该页的属性:U/S=1,允许3环访问;RW=1,可读可写;P=1,表明有物理页了
  24. mov [esi],eax ;在页目录中登记该物理地址
  25.  
  26. .b1: ;不论是否执行JNZ .b1,代码最终会走到这里来
  27. ;开始访问该线性地址所对应的页表
  28. mov esi,ebx ;esi=0x80104000
  29. shr esi, ;高22位移到低22位,esi=0x00200410
  30. and esi,0x003ff000 ;只保留原线性地址高10位,也就是PDT的偏移;esi=0x00200000
  31. or esi,0xffc00000 ;原线性地址最高10位保存在esi的中间10位,即11-20位;高10位置1,这样在PDT内查的时候能得到0x21003,也就是页表的基址;
  32.  
  33. ;得到该线性地址在页表内的对应条目(页表项)
  34. and ebx,0x003ff000 ;ebx=0x00104000,保留原线性地址中间10
  35. shr ebx, ;相当于右移12位,再乘以4;原线性地址中间10位右移到低2~11位,得到页表内的偏移;ebx=0x410
  36. or esi,ebx ;页表项的线性地址;原线性地址的高10位、中间10位依次右移,现在是从2~20位,高11位置1;原线性地址高10位用来作为页表的偏移,中间10位用来做页表的偏移; esi=0xFFF00410
  37. call allocate_a_4k_page ;分配一个页,这才是要安装的页
  38. or eax,0x00000007
  39. mov [esi],eax
  40.  
  41. pop ds
  42. pop esi
  43. pop ebx
  44. pop eax
  45.  
  46. retf

  第一次传入的线性地址是0x80101000,还查不到对应的物理页:

 

 执行完mov [esi],eax后,0x8010100的线性地址被映射到了0x2b000的物理地址:

 

    (8) 在当前PDT,ebx低3字节就是页目录内的偏移;把底2G的页目录清空,根据实际情况填上用户程序的页目录,再复制到其他地方,这样不用切换CR3(一旦切换,需要新的页目录和页表,但还未建设好了,CPU会抛异常的),可以利用现有的地址转换体系;后续每创建新任务,这部分的页目录表都要清零;从0x20800开始的页目录都是映射0x80000000的线性地址,这部分属于各个任务共享的内核

  1. ;清空当前页目录的前半部分(对应低2GB的局部地址空间)
  2. mov ebx,0xfffff000
  3. xor esi,esi
  4. .b1:
  5. mov dword [es:ebx+esi*],0x00000000
  6. inc esi
  7. cmp esi,
  8. jl .b1

    运行完后,内存变成这样:

  (9)所谓 “每个用户程序都拥有4GB的虚拟空间” ,核心原理体现在这里了: 每个用户程序都单独定制一个页目录表和页表。每个用户程序页目录表的第1项到512项都映射自己的物理地址,尽管不同用户程序同样用低2G的线性地址,但映射的物理地址却可以不同

     mov [0xfffffff8],ebx: 这里把存放用户程序页目录表的物理地址放在内核地址页目录表的倒数第二项;如果有第二个用户程序,可以放在倒数第三项,即mov [0xfffffff4],ebx   以此类推;

  1. create_copy_cur_pdir: ;创建新页目录,并复制当前页目录内容
  2. ;输入:无
  3. ;输出:EAX=新页目录的物理地址
  4. push ds
  5. push es
  6. push esi
  7. push edi
  8. push ebx
  9. push ecx
  10.  
  11. mov ebx,mem_0_4_gb_seg_sel
  12. mov ds,ebx
  13. mov es,ebx
  14.  
  15. call allocate_a_4k_page
  16. mov ebx,eax
  17. or ebx,0x00000007 ;用户程序的页目录和页表,当然是3环能访问的,所以U/S=1RW=1可读可写;P=1表明已经有物理页
  18. mov [0xfffffff8],ebx ;页目录表倒数第二项(最后一项已经是0x20003了)
  19.  
  20. mov esi,0xfffff000 ;ESI->当前页目录的线性地址
  21. mov edi,0xffffe000 ;EDI->新页目录的线性地址,刚好指向页目录表的倒数第二项,存放了刚才申请的物理地址
  22. mov ecx, ;ECX=要复制的目录项数
  23. cld
  24. repe movsd
  25.  
  26. pop ecx
  27. pop ebx
  28. pop edi
  29. pop esi
  30. pop es
  31. pop ds
  32.  
  33. retf

  (10) API段的描述符和选择子都重置并写回,3环的用户程序才能调用

  1. push edi
  2. push esi
  3. push ecx
  4.  
  5. mov ecx, ;检索表中,每条目的比较次数
  6. repe cmpsd ;每次比较4字节
  7. jnz .b6
  8. mov eax,[esi] ;若匹配,则esi恰好指向其后的地址
  9. mov [es:edi-],eax ;将字符串改写成偏移地址
  10. mov ax,[esi+]
  11. or ax,0000000000000011B ;以用户程序自己的特权级使用调用门
  12. ;故RPL=3
  13. mov [es:edi-],ax ;回填调用门选择子

  (11)把用户程序导入表需要的函数和内核API段的函数根据名称一一对比,发现名称一样的说明匹配上了,把这些内核API的物理地址、选择子等回填到用户程序的导入表,当用户程序调用API时,才能跳转到正确的地方执行

  1. ;重定位SALT
  2. mov eax,mem_0_4_gb_seg_sel ;访问任务的4GB虚拟地址空间时用
  3. mov es,eax
  4.  
  5. mov eax,core_data_seg_sel
  6. mov ds,eax
  7.  
  8. cld
  9.  
  10. mov ecx,[es:0x0c] ;U-SALT条目数;位于用户程序程序0x0C
  11. mov edi,[es:0x08] ;U-SALT4GB空间内的偏移;位于用户程序0x08偏移处
  12. .b4:
  13. push ecx
  14. push edi
  15.  
  16. mov ecx,salt_items
  17. mov esi,salt
  18. .b5:
  19. push edi
  20. push esi
  21. push ecx
  22.  
  23. mov ecx, ;检索表中,每条目的比较次数
  24. repe cmpsd ;每次比较4字节
  25. jnz .b6
  26. mov eax,[esi] ;esi是内核API地址
  27. mov [es:edi-],eax ;edi是用户程序导入表的API地址,这里把内核API地址写入用户程序导入表,用户程序调用时直接跳转到内核API处执行
  28. mov ax,[esi+] ;
  29. or ax,0000000000000011B ;以用户程序自己的特权级使用调用门
  30. ;故RPL=3
  31. mov [es:edi-],ax ;回填调用门选择子到用户程序的导入表

   把内核API的偏移和选择子回填到用户程序导入表关键代码:

    其他代码都是利用TSS、TR、任务门切换任务相关的。在32位下,利用TSS切换任务效率较低,需要数百个时钟周期,所以windwos和linux并未采用该方式;64位下连intel自己都废弃这种方式,感兴趣的读者可自行分析剩余代码;

  4、分页机制要点

  •  为了最大程度利用内存,物理页都是挨着连续分配的,第一个页0x00001000,第二个页0x00002000,直到最后一个页0xFFFFF000;不难发现物理页地址必须以000结尾(或则说除以0x1000余数为0)
  • 一旦分页开启,所有地址都会被CPU当成线性地址处理,需要先转成物理地址,这是硬件机制决定的,os也不例外,所以最初构造页目录表的时候有一定的技巧,比如页目录表最后一项指向开始,中间0x20800也指向页表第一基址、低512个页目录给用户程序使用、每个用户程序各自赋值一份页目录表和页表;
  • 有了分页,分段就不再那么重要了(64位windows段都平坦了)。通过对页目录表和页表的控制,同样可以达到控制程序对物理内存的使用;

  5、为方便理解,这里梳理了一下核心的步骤和流程:

  

MBR引导代码

  1. core_base_address equ 0x00040000 ;常数,内核加载的起始内存地址
  2. core_start_sector equ 0x00000001 ;常数,内核的起始逻辑扇区号
  3.  
  4. mov ax,cs
  5. mov ss,ax
  6. mov sp,0x7c00
  7.  
  8. ;计算GDT所在的逻辑段地址
  9. mov eax,[cs:pgdt+0x7c00+0x02] ;GDT32位物理地址
  10. xor edx,edx
  11. mov ebx,
  12. div ebx ;分解成16位逻辑地址
  13.  
  14. mov ds,eax ;令DS指向该段以进行操作;ds=0x7e0
  15. mov ebx,edx ;段内起始偏移地址,ebx =0x00
  16.  
  17. ;跳过0#号描述符的槽位
  18. ;创建1#描述符,这是一个数据段,对应0~4GB的线性地址空间
  19. mov dword [ebx+0x08],0x0000ffff ;基地址为0,段界限为0xFFFFF
  20. mov dword [ebx+0x0c],0x00cf9200 ;粒度为4KB,存储器段描述符
  21.  
  22. ;创建保护模式下初始代码段描述符
  23. mov dword [ebx+0x10],0x7c0001ff ;基地址为0x00007c00,界限0x1FF
  24. mov dword [ebx+0x14],0x00409800 ;粒度为1个字节,代码段描述符
  25.  
  26. ;建立保护模式下的堆栈段描述符 ;基地址为0x00007C00,界限0xFFFFE
  27. mov dword [ebx+0x18],0x7c00fffe ;粒度为4KB
  28. mov dword [ebx+0x1c],0x00cf9600
  29.  
  30. ;建立保护模式下的显示缓冲区描述符
  31. mov dword [ebx+0x20],0x80007fff ;基地址为0x000B8000,界限0x07FFF
  32. mov dword [ebx+0x24],0x0040920b ;粒度为字节
  33.  
  34. ;初始化描述符表寄存器GDTR
  35. mov word [cs: pgdt+0x7c00], ;描述符表的界限
  36.  
  37. lgdt [cs: pgdt+0x7c00]
  38.  
  39. in al,0x92 ;南桥芯片内的端口
  40. or al,0000_0010B
  41. out 0x92,al ;打开A20
  42.  
  43. cli ;中断机制尚未工作
  44.  
  45. mov eax,cr0
  46. or eax,
  47. mov cr0,eax ;设置PE
  48.  
  49. ;以下进入保护模式... ...
  50. jmp dword 0x0010:flush ;16位的描述符选择子:32位偏移
  51. ;清流水线并串行化处理器
  52. [bits ]
  53. flush:
  54. mov eax,0x0008 ;以前是实模式的段基址,现在重新加载保护模式的数据段(0..4GB)选择子
  55. mov ds,eax
  56.  
  57. mov eax,0x0018 ;加载堆栈段选择子
  58. mov ss,eax
  59. xor esp,esp ;堆栈指针 <- 0
  60.  
  61. ;以下加载系统核心程序
  62. mov edi,core_base_address
  63.  
  64. mov eax,core_start_sector
  65. mov ebx,edi ;起始地址
  66. call read_hard_disk_0 ;以下读取程序的起始部分(一个扇区)
  67.  
  68. ;以下判断整个程序有多大
  69. mov eax,[edi] ;核心程序尺寸
  70. xor edx,edx
  71. mov ecx, ;512字节每扇区
  72. div ecx
  73.  
  74. or edx,edx
  75. jnz @1 ;未除尽,因此结果比实际扇区数少1
  76. dec eax ;已经读了一个扇区,扇区总数减1
  77. @1:
  78. or eax,eax ;考虑实际长度≤512个字节的情况
  79. jz setup ;EAX=0 ?
  80.  
  81. ;读取剩余的扇区
  82. mov ecx,eax ;32位模式下的LOOP使用ECX
  83. mov eax,core_start_sector
  84. inc eax ;从下一个逻辑扇区接着读
  85. @2:
  86. call read_hard_disk_0
  87. inc eax
  88. loop @2 ;循环读,直到读完整个内核
  89.  
  90. setup: ;系统各个段在0x00040000内存中重定位
  91. mov esi,[0x7c00+pgdt+0x02] ;不可以在代码段内寻址pgdt,但可以
  92. ;通过4GB的段来访问, esi=0x7e00
  93. ;建立公用例程段描述符
  94. mov eax,[edi+0x04] ;公用例程sys_routine代码段起始汇编地址=0x18edi=0x00040000
  95. mov ebx,[edi+0x08] ;核心数据段core_data汇编地址=0x01e4
  96. sub ebx,eax ;core_data紧跟着sys_routinecore_data-sys_routine得到sys_routine长度
  97. dec ebx ;core_data的前面,也就是公用例程段sys_routine界限
  98. add eax,edi ;公用例程段基地址:sys_routine=0x18,加上0x00040000得到sys_routine在内存的地址;
  99. mov ecx,0x00409800 ;字节粒度的代码段描述符
  100. call make_gdt_descriptor
  101. mov [esi+0x28],eax ;描述符低32eax=0x001801cb,存入0x7e00+0x28
  102. mov [esi+0x2c],edx ;描述符高32edx=0x00409804,存入0x7e00+0x2c
  103. ;00409804`001801cb: 段基址00040018,limit=0x01cb;4:G=0,D/B=1,L=0,AVL=0;9:p=1,DPL=00,s=1;TYPE=8是代码段;
  104. ;在0x7e00处原描述符的末尾追加新描述符,原有描述符不变
  105.  
  106. ;建立核心数据段描述符
  107. mov eax,[edi+0x08] ;核心数据段起始汇编地址
  108. mov ebx,[edi+0x0c] ;核心代码段汇编地址
  109. sub ebx,eax
  110. dec ebx ;核心数据段界限
  111. add eax,edi ;核心数据段基地址
  112. mov ecx,0x00409200 ;字节粒度的数据段描述符
  113. call make_gdt_descriptor
  114. mov [esi+0x30],eax
  115. mov [esi+0x34],edx
  116.  
  117. ;建立核心代码段描述符
  118. mov eax,[edi+0x0c] ;核心代码段core_code起始汇编地址
  119. mov ebx,[edi+0x00] ;程序总长度
  120. sub ebx,eax
  121. dec ebx ;核心代码段界限
  122. add eax,edi ;核心代码段基地址
  123. mov ecx,0x00409800 ;字节粒度的代码段描述符
  124. call make_gdt_descriptor
  125. mov [esi+0x38],eax
  126. mov [esi+0x3c],edx
  127.  
  128. mov word [0x7c00+pgdt], ;描述符表的界限; 0x3f,0x7e00:高4byte是GDT基址,低2byte是limit
  129.  
  130. lgdt [0x7c00+pgdt] ;保护模式新增3个段,分别对应内核3个段
  131.  
  132. jmp far [edi+0x10] ;edi=0x00040000,edi+0x10=core_code
  133.  
  134. ;-------------------------------------------------------------------------------
  135. read_hard_disk_0: ;从硬盘读取一个逻辑扇区
  136. ;EAX=逻辑扇区号
  137. ;DS:EBX=目标缓冲区地址
  138. ;返回:EBX=EBX+512
  139. push eax
  140. push ecx
  141. push edx
  142.  
  143. push eax
  144.  
  145. mov dx,0x1f2
  146. mov al,
  147. out dx,al ;读取的扇区数
  148.  
  149. inc dx ;0x1f3
  150. pop eax
  151. out dx,al ;LBA地址7~0
  152.  
  153. inc dx ;0x1f4
  154. mov cl,
  155. shr eax,cl
  156. out dx,al ;LBA地址15~8
  157.  
  158. inc dx ;0x1f5
  159. shr eax,cl
  160. out dx,al ;LBA地址23~16
  161.  
  162. inc dx ;0x1f6
  163. shr eax,cl
  164. or al,0xe0 ;第一硬盘 LBA地址27~24
  165. out dx,al
  166.  
  167. inc dx ;0x1f7
  168. mov al,0x20 ;读命令
  169. out dx,al
  170.  
  171. .waits:
  172. in al,dx
  173. and al,0x88
  174. cmp al,0x08
  175. jnz .waits ;不忙,且硬盘已准备好数据传输
  176.  
  177. mov ecx, ;总共要读取的字数
  178. mov dx,0x1f0
  179. .readw:
  180. in ax,dx
  181. mov [ebx],ax
  182. add ebx,
  183. loop .readw
  184.  
  185. pop edx
  186. pop ecx
  187. pop eax
  188.  
  189. ret
  190.  
  191. ;-------------------------------------------------------------------------------
  192. make_gdt_descriptor: ;构造描述符
  193. ;输入:EAX=线性基地址,比如sys_routine=0x00040018;
  194. ; EBX=段界限,比如sys_routine=0x1e4-0x18-1=0x1cb
  195. ; ECX=属性(各属性位都在原始 比如sys_routine=0x00409800
  196. ; 位置,其它没用到的位置0)
  197. ;返回:EDX:EAX=完整的描述符
  198. mov edx,eax
  199. shl eax, ;eax从0x00040018变为0x00180000;
  200. or ax,bx ;描述符前32位(EAX)构造完毕,eax=0x001801cb;
  201.  
  202. and edx,0xffff0000 ;清除基地址中无关的位 edx=0x00040000
  203. rol edx, ;edx = 0x04000000
  204. bswap edx ;装配基址的31~24和23~16 (80486+); edx = 0x00000004; 31-24于0-7交换,23-16与8-15交换
  205.  
  206. xor bx,bx ;ebx=0x00000000
  207. or edx,ebx ;装配段界限的高4位,edx=0x00000004
  208.  
  209. or edx,ecx ;装配属性 edx=0x00409804
  210.  
  211. ret
  212.  
  213. ;-------------------------------------------------------------------------------
  214. pgdt dw
  215. dd 0x00007e00 ;GDT的物理地址
  216. ;-------------------------------------------------------------------------------
  217. times -($-$$) db
  218. db 0x55,0xaa

内核代码:

  1. ;以下常量定义部分。内核的大部分内容都应当固定
  2. core_code_seg_sel equ 0x38 ;内核代码段选择子
  3. core_data_seg_sel equ 0x30 ;内核数据段选择子
  4. sys_routine_seg_sel equ 0x28 ;系统公共例程代码段的选择子
  5. video_ram_seg_sel equ 0x20 ;视频显示缓冲区的段选择子
  6. core_stack_seg_sel equ 0x18 ;内核堆栈段选择子
  7. mem_0_4_gb_seg_sel equ 0x08 ;整个0-4GB内存的段的选择子
  8.  
  9. ;-------------------------------------------------------------------------------
  10. ;以下是系统核心的头部,用于加载核心程序
  11. core_length dd core_end ;核心程序总长度#00
  12.  
  13. sys_routine_seg dd section.sys_routine.start
  14. ;系统公用例程段位置#04
  15.  
  16. core_data_seg dd section.core_data.start
  17. ;核心数据段位置#08
  18.  
  19. core_code_seg dd section.core_code.start
  20. ;核心代码段位置#0c
  21.  
  22. core_entry dd start ;核心代码段入口点#10
  23. dw core_code_seg_sel
  24.  
  25. ;===============================================================================
  26. [bits ]
  27. ;===============================================================================
  28. SECTION sys_routine vstart= ;系统公共例程代码段
  29. ;-------------------------------------------------------------------------------
  30. ;字符串显示例程
  31. put_string: ;显示0终止的字符串并移动光标
  32. ;输入:DS:EBX=串地址
  33. push ecx
  34. .getc:
  35. mov cl,[ebx]
  36. or cl,cl
  37. jz .exit
  38. call put_char
  39. inc ebx
  40. jmp .getc
  41.  
  42. .exit:
  43. pop ecx
  44. retf ;段间返回
  45.  
  46. ;-------------------------------------------------------------------------------
  47. put_char: ;在当前光标处显示一个字符,并推进
  48. ;光标。仅用于段内调用
  49. ;输入:CL=字符ASCII
  50. pushad
  51.  
  52. ;以下取当前光标位置
  53. mov dx,0x3d4
  54. mov al,0x0e
  55. out dx,al
  56. inc dx ;0x3d5
  57. in al,dx ;高字
  58. mov ah,al
  59.  
  60. dec dx ;0x3d4
  61. mov al,0x0f
  62. out dx,al
  63. inc dx ;0x3d5
  64. in al,dx ;低字
  65. mov bx,ax ;BX=代表光标位置的16位数
  66.  
  67. cmp cl,0x0d ;回车符?
  68. jnz .put_0a
  69. mov ax,bx
  70. mov bl,
  71. div bl
  72. mul bl
  73. mov bx,ax
  74. jmp .set_cursor
  75.  
  76. .put_0a:
  77. cmp cl,0x0a ;换行符?
  78. jnz .put_other
  79. add bx,
  80. jmp .roll_screen
  81.  
  82. .put_other: ;正常显示字符
  83. push es
  84. mov eax,video_ram_seg_sel ;0x800b8000段的选择子
  85. mov es,eax
  86. shl bx,
  87. mov [es:bx],cl
  88. pop es
  89.  
  90. ;以下将光标位置推进一个字符
  91. shr bx,
  92. inc bx
  93.  
  94. .roll_screen:
  95. cmp bx, ;光标超出屏幕?滚屏
  96. jl .set_cursor
  97.  
  98. push ds
  99. push es
  100. mov eax,video_ram_seg_sel
  101. mov ds,eax
  102. mov es,eax
  103. cld
  104. mov esi,0xa0 ;小心!32位模式下movsb/w/d
  105. mov edi,0x00 ;使用的是esi/edi/ecx
  106. mov ecx,
  107. rep movsd
  108. mov bx, ;清除屏幕最底一行
  109. mov ecx, ;32位程序应该使用ECX
  110. .cls:
  111. mov word[es:bx],0x0720
  112. add bx,
  113. loop .cls
  114.  
  115. pop es
  116. pop ds
  117.  
  118. mov bx,
  119.  
  120. .set_cursor:
  121. mov dx,0x3d4
  122. mov al,0x0e
  123. out dx,al
  124. inc dx ;0x3d5
  125. mov al,bh
  126. out dx,al
  127. dec dx ;0x3d4
  128. mov al,0x0f
  129. out dx,al
  130. inc dx ;0x3d5
  131. mov al,bl
  132. out dx,al
  133.  
  134. popad
  135.  
  136. ret
  137.  
  138. ;-------------------------------------------------------------------------------
  139. read_hard_disk_0: ;从硬盘读取一个逻辑扇区,也就是每次读512字节;1个页需要读8
  140. ;EAX=逻辑扇区号
  141. ;DS:EBX=目标缓冲区地址
  142. ;返回:EBX=EBX+512
  143. push eax
  144. push ecx
  145. push edx
  146.  
  147. push eax
  148.  
  149. mov dx,0x1f2
  150. mov al,
  151. out dx,al ;读取的扇区数
  152.  
  153. inc dx ;0x1f3
  154. pop eax
  155. out dx,al ;LBA地址7~0
  156.  
  157. inc dx ;0x1f4
  158. mov cl,
  159. shr eax,cl
  160. out dx,al ;LBA地址15~8
  161.  
  162. inc dx ;0x1f5
  163. shr eax,cl
  164. out dx,al ;LBA地址23~16
  165.  
  166. inc dx ;0x1f6
  167. shr eax,cl
  168. or al,0xe0 ;第一硬盘 LBA地址27~24
  169. out dx,al
  170.  
  171. inc dx ;0x1f7
  172. mov al,0x20 ;读命令
  173. out dx,al
  174.  
  175. .waits:
  176. in al,dx
  177. and al,0x88
  178. cmp al,0x08
  179. jnz .waits ;不忙,且硬盘已准备好数据传输
  180.  
  181. mov ecx, ;总共要读取的字数
  182. mov dx,0x1f0
  183. .readw:
  184. in ax,dx
  185. mov [ebx],ax
  186. add ebx,
  187. loop .readw
  188.  
  189. pop edx
  190. pop ecx
  191. pop eax
  192.  
  193. retf ;段间返回
  194.  
  195. ;-------------------------------------------------------------------------------
  196. put_hex_dword: ;在当前光标处以十六进制形式显示
  197. ;一个双字并推进光标
  198. ;输入:EDX=要转换并显示的数字
  199. ;输出:无
  200. pushad
  201. push ds
  202.  
  203. mov ax,core_data_seg_sel ;切换到核心数据段
  204. mov ds,ax
  205.  
  206. mov ebx,bin_hex ;指向核心数据段内的转换表
  207. mov ecx,
  208. .xlt:
  209. rol edx,
  210. mov eax,edx
  211. and eax,0x0000000f
  212. xlat
  213.  
  214. push ecx
  215. mov cl,al
  216. call put_char
  217. pop ecx
  218.  
  219. loop .xlt
  220.  
  221. pop ds
  222. popad
  223.  
  224. retf
  225.  
  226. ;-------------------------------------------------------------------------------
  227. set_up_gdt_descriptor: ;在GDT内安装一个新的描述符,还是在0x7e00的地方
  228. ;输入:EDX:EAX=描述符
  229. ;输出:CX=描述符的选择子
  230. push eax
  231. push ebx
  232. push edx
  233.  
  234. push ds
  235. push es
  236.  
  237. mov ebx,core_data_seg_sel ;切换到核心数据段
  238. mov ds,ebx
  239.  
  240. sgdt [pgdt] ;以便开始处理GDT
  241.  
  242. mov ebx,mem_0_4_gb_seg_sel
  243. mov es,ebx
  244.  
  245. movzx ebx,word [pgdt] ;GDT界限
  246. inc bx ;GDT总字节数,也是下一个描述符偏移
  247. add ebx,[pgdt+] ;下一个描述符的线性地址
  248.  
  249. mov [es:ebx],eax ;
  250. mov [es:ebx+],edx ;
  251.  
  252. add word [pgdt], ;增加一个描述符的大小
  253.  
  254. lgdt [pgdt] ;对GDT的更改生效
  255.  
  256. mov ax,[pgdt] ;得到GDT界限值
  257. xor dx,dx
  258. mov bx,
  259. div bx ;除以8,去掉余数
  260. mov cx,ax
  261. shl cx, ;将索引号移到正确位置
  262.  
  263. pop es
  264. pop ds
  265.  
  266. pop edx
  267. pop ebx
  268. pop eax
  269.  
  270. retf
  271. ;-------------------------------------------------------------------------------
  272. make_seg_descriptor: ;构造存储器和系统的段描述符
  273. ;输入:EAX=线性基地址
  274. ; EBX=段界限
  275. ; ECX=属性。各属性位都在原始
  276. ; 位置,无关的位清零
  277. ;返回:EDX:EAX=描述符
  278. mov edx,eax
  279. shl eax,
  280. or ax,bx ;描述符前32位(EAX)构造完毕
  281.  
  282. and edx,0xffff0000 ;清除基地址中无关的位
  283. rol edx,
  284. bswap edx ;装配基址的31~2423~16 (80486+)
  285.  
  286. xor bx,bx
  287. or edx,ebx ;装配段界限的高4
  288.  
  289. or edx,ecx ;装配属性
  290.  
  291. retf
  292.  
  293. ;-------------------------------------------------------------------------------
  294. make_gate_descriptor: ;构造门的描述符(调用门等)
  295. ;输入:EAX=门代码在段内偏移地址
  296. ; BX=门代码所在段的选择子
  297. ; CX=段类型及属性等(各属
  298. ; 性位都在原始位置)
  299. ;返回:EDX:EAX=完整的描述符
  300. push ebx
  301. push ecx
  302.  
  303. mov edx,eax
  304. and edx,0xffff0000 ;得到偏移地址高16
  305. or dx,cx ;组装属性部分到EDX
  306.  
  307. and eax,0x0000ffff ;得到偏移地址低16
  308. shl ebx,
  309. or eax,ebx ;组装段选择子部分
  310.  
  311. pop ecx
  312. pop ebx
  313.  
  314. retf
  315.  
  316. ;-------------------------------------------------------------------------------
  317. allocate_a_4k_page: ;分配一个4KB的页
  318. ;输入:无
  319. ;输出:EAX=页的物理地址
  320. push ebx
  321. push ecx
  322. push edx
  323. push ds
  324.  
  325. mov eax,core_data_seg_sel
  326. mov ds,eax
  327.  
  328. xor eax,eax
  329. .b1: ;遍历page_bit_map,找到第一个标识是0的位,说明该页还未使用
  330. bts [page_bit_map],eax ;[page_bit_map]第eax的位复制给CF,同时置1
  331. jnc .b2 ;CF=0,说明找到了空闲的物理页;物理页索引存放在eax
  332. inc eax ;没有找到,eax+1继续找
  333. cmp eax,page_map_len* ;遍历到page_bit_map末尾了吗?
  334. jl .b1 ;没有就从头继续找
  335.  
  336. mov ebx,message_3
  337. call sys_routine_seg_sel:put_string
  338. hlt ;没有可以分配的页,停机
  339.  
  340. .b2:
  341. shl eax, ;eax存放了空闲的物理页索引,乘以40960x1000)就是地址
  342.  
  343. pop ds
  344. pop edx
  345. pop ecx
  346. pop ebx
  347.  
  348. ret
  349.  
  350. ;-------------------------------------------------------------------------------
  351. alloc_inst_a_page: ;给指定的线性地址挂载物理页
  352. ;层级分页结构中
  353. ;输入:EBX=页的线性地址,比如0x80104000
  354. push eax
  355. push ebx
  356. push esi
  357. push ds
  358.  
  359. mov eax,mem_0_4_gb_seg_sel
  360. mov ds,eax
  361.  
  362. ;检查该线性地址所对应的页表是否存在;把ebx10位作为PDT的索引查找PTE
  363. mov esi,ebx ;esi=0x80104000
  364. and esi,0xffc00000 ;只保留最高的10位,低22位清零,得到PDT的索引,esi=0x80000000
  365. shr esi, ;高12位移到低12位:得到页目录索引,并乘以4,得到PTEPDE内的偏移地址;esi=0x00000800
  366. or esi,0xfffff000 ;页目录自身的线性地址+表内偏移;最高20位置1的线性地址,转换成物理地址=PDT基址(这里是0x20000)+esi,相当于最低3字节就是PDT内的偏移,高20位置1确保物理地址还是落在PDT内;esi=0xfffff800
  367.  
  368. test dword [esi],0x00000001 ;P位是否为“1”.如果PDT某项有PTE,结尾不会是0;如果是0,说明还未挂载物理页;[esi]=0x00000003,最后4位是0011
  369. jnz .b1 ;否已经有对应的页表
  370.  
  371. ;创建该线性地址所对应的页表
  372. call allocate_a_4k_page ;分配一个页做为页表
  373. or eax,0x00000007 ;该页的属性:U/S=1,允许3环访问;RW=1,可读可写;P=1,表明有物理页了
  374. mov [esi],eax ;在页目录中登记该物理地址
  375.  
  376. .b1: ;不论是否执行JNZ .b1,代码最终会走到这里来
  377. ;开始访问该线性地址所对应的页表
  378. mov esi,ebx ;esi=0x80104000
  379. shr esi, ;高22位移到低22位,esi=0x00200410
  380. and esi,0x003ff000 ;只保留原线性地址高10位,也就是PDT的偏移;esi=0x00200000
  381. or esi,0xffc00000 ;原线性地址最高10位保存在esi的中间10位,即11-20位;高10位置1,这样在PDT内查的时候能得到0x21003,也就是页表的基址;
  382.  
  383. ;得到该线性地址在页表内的对应条目(页表项)
  384. and ebx,0x003ff000 ;ebx=0x00104000,保留原线性地址中间10
  385. shr ebx, ;相当于右移12位,再乘以4;原线性地址中间10位右移到低2~11位,得到页表内的偏移;ebx=0x410
  386. or esi,ebx ;页表项的线性地址;原线性地址的高10位、中间10位依次右移,现在是从2~20位,高11位置1;原线性地址高10位用来作为页表的偏移,中间10位用来做页表的偏移; esi=0xFFF00410
  387. call allocate_a_4k_page ;分配一个页,这才是要安装的页
  388. or eax,0x00000007
  389. mov [esi],eax
  390.  
  391. pop ds
  392. pop esi
  393. pop ebx
  394. pop eax
  395.  
  396. retf
  397.  
  398. ;-------------------------------------------------------------------------------
  399. create_copy_cur_pdir: ;创建新页目录,并复制当前页目录内容
  400. ;输入:无
  401. ;输出:EAX=新页目录的物理地址
  402. push ds
  403. push es
  404. push esi
  405. push edi
  406. push ebx
  407. push ecx
  408.  
  409. mov ebx,mem_0_4_gb_seg_sel
  410. mov ds,ebx
  411. mov es,ebx
  412.  
  413. call allocate_a_4k_page
  414. mov ebx,eax
  415. or ebx,0x00000007 ;用户程序的页目录和页表,当然是3环能访问的,所以U/S=1RW=1可读可写;P=1表明已经有物理页
  416. mov [0xfffffff8],ebx ;页目录表倒数第二项(最后一项已经是0x20003了)
  417.  
  418. mov esi,0xfffff000 ;ESI->当前页目录的线性地址
  419. mov edi,0xffffe000 ;EDI->新页目录的线性地址,刚好指向页目录表的倒数第二项,存放了刚才申请的物理地址
  420. mov ecx, ;ECX=要复制的目录项数
  421. cld
  422. repe movsd
  423.  
  424. pop ecx
  425. pop ebx
  426. pop edi
  427. pop esi
  428. pop es
  429. pop ds
  430.  
  431. retf
  432.  
  433. ;-------------------------------------------------------------------------------
  434. terminate_current_task: ;终止当前任务
  435. ;注意,执行此例程时,当前任务仍在
  436. ;运行中。此例程其实也是当前任务的
  437. ;一部分
  438. mov eax,core_data_seg_sel
  439. mov ds,eax
  440.  
  441. pushfd
  442. pop edx
  443.  
  444. test dx,0100_0000_0000_0000B ;测试NT
  445. jnz .b1 ;当前任务是嵌套的,到.b1执行iretd
  446. jmp far [program_man_tss] ;程序管理器任务
  447. .b1:
  448. iretd
  449.  
  450. sys_routine_end:
  451.  
  452. ;===============================================================================
  453. SECTION core_data vstart= ;系统核心的数据段
  454. ;-------------------------------------------------------------------------------
  455. pgdt dw ;用于设置和修改GDT
  456. dd
  457. ;为了简化,这里只用2M内存,有512个物理页;已经占用的置1,没用的置0
  458. page_bit_map db 0xff,0xff,0xff,0xff,0xff,0x55,0x55,0xff ;低地址基本都用光了,高地址还空着
  459. db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
  460. db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
  461. db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
  462. db 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
  463. db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
  464. db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
  465. db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
  466. page_map_len equ $-page_bit_map
  467.  
  468. ;符号地址检索表,类似于导出表,详细记录了可供第三方调用的函数名、函数地址
  469. salt:
  470. salt_1 db '@PrintString' ;从@PrintString开始,长度是12字节
  471. times -($-salt_1) db ;剩余256-12=244字节填0API函数名最长不超过256字节
  472. dd put_string ;函数在API段内的偏移
  473. dw sys_routine_seg_sel ;API段的选择子,根据后面这6字节可以直接调用API函数
  474.  
  475. salt_2 db '@ReadDiskData'
  476. times -($-salt_2) db
  477. dd read_hard_disk_0
  478. dw sys_routine_seg_sel
  479.  
  480. salt_3 db '@PrintDwordAsHexString'
  481. times -($-salt_3) db
  482. dd put_hex_dword
  483. dw sys_routine_seg_sel
  484.  
  485. salt_4 db '@TerminateProgram'
  486. times -($-salt_4) db
  487. dd terminate_current_task
  488. dw sys_routine_seg_sel
  489.  
  490. salt_item_len equ $-salt_4
  491. salt_items equ ($-salt)/salt_item_len
  492.  
  493. message_0 db ' Working in system core,protect mode.'
  494. db 0x0d,0x0a,
  495.  
  496. message_1 db ' Paging is enabled.System core is mapped to'
  497. db ' address 0x80000000.',0x0d,0x0a,
  498.  
  499. message_2 db 0x0d,0x0a
  500. db ' System wide CALL-GATE mounted.',0x0d,0x0a,
  501.  
  502. message_3 db '********No more pages********',
  503.  
  504. message_4 db 0x0d,0x0a,' Task switching...@_@',0x0d,0x0a,
  505.  
  506. message_5 db 0x0d,0x0a,' Processor HALT.',
  507.  
  508. bin_hex db '0123456789ABCDEF'
  509. ;put_hex_dword子过程用的查找表
  510.  
  511. core_buf times db ;内核用的缓冲区
  512.  
  513. cpu_brnd0 db 0x0d,0x0a,' ',
  514. cpu_brand times db
  515. cpu_brnd1 db 0x0d,0x0a,0x0d,0x0a,
  516.  
  517. ;任务控制块链
  518. tcb_chain dd
  519.  
  520. ;内核信息
  521. core_next_laddr dd 0x80100000 ;内核空间中下一个可分配的线性地址;每次在线性地址分配一块内存,该值就会增加;
  522. program_man_tss dd ;程序管理器的TSS描述符选择子
  523. dw
  524.  
  525. core_data_end:
  526.  
  527. ;===============================================================================
  528. SECTION core_code vstart=
  529. ;-------------------------------------------------------------------------------
  530. fill_descriptor_in_ldt: ;在LDT内安装一个新的描述符
  531. ;输入:EDX:EAX=描述符
  532. ; EBX=TCB基地址
  533. ;输出:CX=描述符的选择子
  534. push eax
  535. push edx
  536. push edi
  537. push ds
  538.  
  539. mov ecx,mem_0_4_gb_seg_sel
  540. mov ds,ecx
  541.  
  542. mov edi,[ebx+0x0c] ;获得LDT基地址
  543.  
  544. xor ecx,ecx
  545. mov cx,[ebx+0x0a] ;获得LDT界限
  546. inc cx ;LDT的总字节数,即新描述符偏移地址
  547.  
  548. mov [edi+ecx+0x00],eax
  549. mov [edi+ecx+0x04],edx ;安装描述符
  550.  
  551. add cx,
  552. dec cx ;得到新的LDT界限值
  553.  
  554. mov [ebx+0x0a],cx ;更新LDT界限值到TCB
  555.  
  556. mov ax,cx
  557. xor dx,dx
  558. mov cx,
  559. div cx
  560.  
  561. mov cx,ax
  562. shl cx, ;左移3位,并且
  563. or cx,0000_0000_0000_0100B ;使TI位=1,指向LDT,最后使RPL=00
  564.  
  565. pop ds
  566. pop edi
  567. pop edx
  568. pop eax
  569.  
  570. ret
  571.  
  572. ;-------------------------------------------------------------------------------
  573. load_relocate_program: ;加载并重定位用户程序
  574. ;输入: PUSH 逻辑扇区号
  575. ; PUSH 任务控制块基地址
  576. ;输出:无
  577. pushad
  578.  
  579. push ds
  580. push es
  581.  
  582. mov ebp,esp ;为访问通过堆栈传递的参数做准备
  583.  
  584. mov ecx,mem_0_4_gb_seg_sel
  585. mov es,ecx
  586.  
  587. ;清空当前页目录的前半部分(对应低2GB的局部地址空间)
  588. mov ebx,0xfffff000
  589. xor esi,esi
  590. .b1:
  591. mov dword [es:ebx+esi*],0x00000000
  592. inc esi
  593. cmp esi,
  594. jl .b1
  595.  
  596. ;以下开始分配内存并加载用户程序
  597. mov eax,core_data_seg_sel
  598. mov ds,eax ;切换DS到内核数据段
  599.  
  600. mov eax,[ebp+*] ;从堆栈中取出用户程序起始扇区号
  601. mov ebx,core_buf ;读取程序头部数据
  602. call sys_routine_seg_sel:read_hard_disk_0
  603.  
  604. ;以下判断整个程序有多大
  605. mov eax,[core_buf] ;程序尺寸
  606. mov ebx,eax
  607. and ebx,0xfffff000 ;使之4KB对齐
  608. add ebx,0x1000
  609. test eax,0x00000fff ;程序的大小正好是4KB的倍数吗?
  610. cmovnz eax,ebx ;不是。使用凑整的结果
  611.  
  612. mov ecx,eax
  613. shr ecx, ;程序占用的总4KB页数,即用户程序需要几个页加载
  614.  
  615. mov eax,mem_0_4_gb_seg_sel ;切换DS0-4GB的段
  616. mov ds,eax
  617.  
  618. mov eax,[ebp+*] ;起始扇区号
  619. mov esi,[ebp+*] ;从堆栈中取得TCB的基地址
  620. .b2:
  621. mov ebx,[es:esi+0x06] ;取得可用的线性地址
  622. add dword [es:esi+0x06],0x1000 ;线性地址分配后加0x1000,下次从这里继续申请新内存
  623. call sys_routine_seg_sel:alloc_inst_a_page
  624.  
  625. push ecx
  626. mov ecx,
  627. .b3:
  628. call sys_routine_seg_sel:read_hard_disk_0
  629. inc eax
  630. loop .b3
  631.  
  632. pop ecx
  633. loop .b2
  634.  
  635. ;在内核地址空间内创建用户任务的TSS
  636. mov eax,core_data_seg_sel ;切换DS到内核数据段
  637. mov ds,eax
  638.  
  639. mov ebx,[core_next_laddr] ;用户任务的TSS必须在全局空间上分配
  640. call sys_routine_seg_sel:alloc_inst_a_page
  641. add dword [core_next_laddr],
  642.  
  643. mov [es:esi+0x14],ebx ;在TCB中填写TSS的线性地址
  644. mov word [es:esi+0x12], ;在TCB中填写TSS的界限值
  645.  
  646. ;在用户任务的局部地址空间内创建LDT
  647. mov ebx,[es:esi+0x06] ;从TCB中取得可用的线性地址
  648. add dword [es:esi+0x06],0x1000
  649. call sys_routine_seg_sel:alloc_inst_a_page
  650. mov [es:esi+0x0c],ebx ;填写LDT线性地址到TCB
  651.  
  652. ;建立程序代码段描述符
  653. mov eax,0x00000000
  654. mov ebx,0x000fffff
  655. mov ecx,0x00c0f800 ;4KB粒度的代码段描述符,特权级3
  656. call sys_routine_seg_sel:make_seg_descriptor
  657. mov ebx,esi ;TCB的基地址
  658. call fill_descriptor_in_ldt
  659. or cx,0000_0000_0000_0011B ;设置选择子的特权级为3
  660.  
  661. mov ebx,[es:esi+0x14] ;从TCB中获取TSS的线性地址
  662. mov [es:ebx+],cx ;填写TSSCS
  663.  
  664. ;建立程序数据段描述符
  665. mov eax,0x00000000
  666. mov ebx,0x000fffff
  667. mov ecx,0x00c0f200 ;4KB粒度的数据段描述符,特权级3
  668. call sys_routine_seg_sel:make_seg_descriptor
  669. mov ebx,esi ;TCB的基地址
  670. call fill_descriptor_in_ldt
  671. or cx,0000_0000_0000_0011B ;设置选择子的特权级为3
  672.  
  673. mov ebx,[es:esi+0x14] ;从TCB中获取TSS的线性地址
  674. mov [es:ebx+],cx ;填写TSSDS
  675. mov [es:ebx+],cx ;填写TSSES
  676. mov [es:ebx+],cx ;填写TSSFS
  677. mov [es:ebx+],cx ;填写TSSGS
  678.  
  679. ;将数据段作为用户任务的3特权级固有堆栈
  680. mov ebx,[es:esi+0x06] ;从TCB中取得可用的线性地址
  681. add dword [es:esi+0x06],0x1000
  682. call sys_routine_seg_sel:alloc_inst_a_page
  683.  
  684. mov ebx,[es:esi+0x14] ;从TCB中获取TSS的线性地址
  685. mov [es:ebx+],cx ;填写TSSSS
  686. mov edx,[es:esi+0x06] ;堆栈的高端线性地址
  687. mov [es:ebx+],edx ;填写TSSESP
  688.  
  689. ;在用户任务的局部地址空间内创建0特权级堆栈
  690. mov ebx,[es:esi+0x06] ;从TCB中取得可用的线性地址
  691. add dword [es:esi+0x06],0x1000
  692. call sys_routine_seg_sel:alloc_inst_a_page
  693.  
  694. mov eax,0x00000000
  695. mov ebx,0x000fffff
  696. mov ecx,0x00c09200 ;4KB粒度的堆栈段描述符,特权级0
  697. call sys_routine_seg_sel:make_seg_descriptor
  698. mov ebx,esi ;TCB的基地址
  699. call fill_descriptor_in_ldt
  700. or cx,0000_0000_0000_0000B ;设置选择子的特权级为0
  701.  
  702. mov ebx,[es:esi+0x14] ;从TCB中获取TSS的线性地址
  703. mov [es:ebx+],cx ;填写TSSSS0
  704. mov edx,[es:esi+0x06] ;堆栈的高端线性地址
  705. mov [es:ebx+],edx ;填写TSSESP0
  706.  
  707. ;在用户任务的局部地址空间内创建1特权级堆栈
  708. mov ebx,[es:esi+0x06] ;从TCB中取得可用的线性地址
  709. add dword [es:esi+0x06],0x1000
  710. call sys_routine_seg_sel:alloc_inst_a_page
  711.  
  712. mov eax,0x00000000
  713. mov ebx,0x000fffff
  714. mov ecx,0x00c0b200 ;4KB粒度的堆栈段描述符,特权级1
  715. call sys_routine_seg_sel:make_seg_descriptor
  716. mov ebx,esi ;TCB的基地址
  717. call fill_descriptor_in_ldt
  718. or cx,0000_0000_0000_0001B ;设置选择子的特权级为1
  719.  
  720. mov ebx,[es:esi+0x14] ;从TCB中获取TSS的线性地址
  721. mov [es:ebx+],cx ;填写TSSSS1
  722. mov edx,[es:esi+0x06] ;堆栈的高端线性地址
  723. mov [es:ebx+],edx ;填写TSSESP1
  724.  
  725. ;在用户任务的局部地址空间内创建2特权级堆栈
  726. mov ebx,[es:esi+0x06] ;从TCB中取得可用的线性地址
  727. add dword [es:esi+0x06],0x1000
  728. call sys_routine_seg_sel:alloc_inst_a_page
  729.  
  730. mov eax,0x00000000
  731. mov ebx,0x000fffff
  732. mov ecx,0x00c0d200 ;4KB粒度的堆栈段描述符,特权级2
  733. call sys_routine_seg_sel:make_seg_descriptor
  734. mov ebx,esi ;TCB的基地址
  735. call fill_descriptor_in_ldt
  736. or cx,0000_0000_0000_0010B ;设置选择子的特权级为2
  737.  
  738. mov ebx,[es:esi+0x14] ;从TCB中获取TSS的线性地址
  739. mov [es:ebx+],cx ;填写TSSSS2
  740. mov edx,[es:esi+0x06] ;堆栈的高端线性地址
  741. mov [es:ebx+],edx ;填写TSSESP2
  742.  
  743. ;重定位SALT
  744. mov eax,mem_0_4_gb_seg_sel ;访问任务的4GB虚拟地址空间时用
  745. mov es,eax
  746.  
  747. mov eax,core_data_seg_sel
  748. mov ds,eax
  749.  
  750. cld
  751.  
  752. mov ecx,[es:0x0c] ;U-SALT条目数
  753. mov edi,[es:0x08] ;U-SALT4GB空间内的偏移
  754. .b4:
  755. push ecx
  756. push edi
  757.  
  758. mov ecx,salt_items
  759. mov esi,salt
  760. .b5:
  761. push edi
  762. push esi
  763. push ecx
  764.  
  765. mov ecx, ;检索表中,每条目的比较次数
  766. repe cmpsd ;每次比较4字节
  767. jnz .b6
  768. mov eax,[esi] ;若匹配,则esi恰好指向其后的地址
  769. mov [es:edi-],eax ;将字符串改写成偏移地址
  770. mov ax,[esi+]
  771. or ax,0000000000000011B ;以用户程序自己的特权级使用调用门
  772. ;故RPL=3
  773. mov [es:edi-],ax ;回填调用门选择子
  774. .b6:
  775.  
  776. pop ecx
  777. pop esi
  778. add esi,salt_item_len
  779. pop edi ;从头比较
  780. loop .b5
  781.  
  782. pop edi
  783. add edi,
  784. pop ecx
  785. loop .b4
  786.  
  787. ;在GDT中登记LDT描述符
  788. mov esi,[ebp+*] ;从堆栈中取得TCB的基地址
  789. mov eax,[es:esi+0x0c] ;LDT的起始线性地址
  790. movzx ebx,word [es:esi+0x0a] ;LDT段界限
  791. mov ecx,0x00408200 ;LDT描述符,特权级0
  792. call sys_routine_seg_sel:make_seg_descriptor
  793. call sys_routine_seg_sel:set_up_gdt_descriptor
  794. mov [es:esi+0x10],cx ;登记LDT选择子到TCB
  795.  
  796. mov ebx,[es:esi+0x14] ;从TCB中获取TSS的线性地址
  797. mov [es:ebx+],cx ;填写TSSLDT
  798.  
  799. mov word [es:ebx+], ;反向链=0
  800.  
  801. mov dx,[es:esi+0x12] ;段长度(界限)
  802. mov [es:ebx+],dx ;填写TSSI/O位图偏移域
  803.  
  804. mov word [es:ebx+], ;T=0
  805.  
  806. mov eax,[es:0x04] ;从任务的4GB地址空间获取入口点
  807. mov [es:ebx+],eax ;填写TSSEIP
  808.  
  809. pushfd
  810. pop edx
  811. mov [es:ebx+],edx ;填写TSSEFLAGS
  812.  
  813. ;在GDT中登记TSS描述符
  814. mov eax,[es:esi+0x14] ;从TCB中获取TSS的起始线性地址
  815. movzx ebx,word [es:esi+0x12] ;段长度(界限)
  816. mov ecx,0x00408900 ;TSS描述符,特权级0
  817. call sys_routine_seg_sel:make_seg_descriptor
  818. call sys_routine_seg_sel:set_up_gdt_descriptor
  819. mov [es:esi+0x18],cx ;登记TSS选择子到TCB
  820.  
  821. ;创建用户任务的页目录
  822. ;注意!页的分配和使用是由页位图决定的,可以不占用线性地址空间
  823. call sys_routine_seg_sel:create_copy_cur_pdir
  824. mov ebx,[es:esi+0x14] ;从TCB中获取TSS的线性地址
  825. mov dword [es:ebx+],eax ;填写TSSCR3(PDBR)域
  826.  
  827. pop es ;恢复到调用此过程前的es
  828. pop ds ;恢复到调用此过程前的ds
  829.  
  830. popad
  831.  
  832. ret ;丢弃调用本过程前压入的参数
  833.  
  834. ;-------------------------------------------------------------------------------
  835. append_to_tcb_link: ;在TCB链上追加任务控制块
  836. ;输入:ECX=TCB线性基地址
  837. push eax
  838. push edx
  839. push ds
  840. push es
  841.  
  842. mov eax,core_data_seg_sel ;令DS指向内核数据段
  843. mov ds,eax
  844. mov eax,mem_0_4_gb_seg_sel ;令ES指向0..4GB
  845. mov es,eax
  846.  
  847. mov dword [es: ecx+0x00], ;当前TCB指针域清零,以指示这是最
  848. ;后一个TCB
  849.  
  850. mov eax,[tcb_chain] ;TCB表头指针
  851. or eax,eax ;链表为空?
  852. jz .notcb
  853.  
  854. .searc:
  855. mov edx,eax
  856. mov eax,[es: edx+0x00]
  857. or eax,eax
  858. jnz .searc
  859.  
  860. mov [es: edx+0x00],ecx
  861. jmp .retpc
  862.  
  863. .notcb:
  864. mov [tcb_chain],ecx ;若为空表,直接令表头指针指向TCB
  865.  
  866. .retpc:
  867. pop es
  868. pop ds
  869. pop edx
  870. pop eax
  871.  
  872. ret
  873.  
  874. ;-------------------------------------------------------------------------------
  875. start:
  876. mov ecx,core_data_seg_sel ;令DS指向核心数据段
  877. mov ds,ecx
  878.  
  879. mov ecx,mem_0_4_gb_seg_sel ;令ES指向4GB数据段
  880. mov es,ecx
  881.  
  882. mov ebx,message_0
  883. call sys_routine_seg_sel:put_string
  884.  
  885. ;显示处理器品牌信息
  886. mov eax,0x80000002
  887. cpuid
  888. mov [cpu_brand + 0x00],eax
  889. mov [cpu_brand + 0x04],ebx
  890. mov [cpu_brand + 0x08],ecx
  891. mov [cpu_brand + 0x0c],edx
  892.  
  893. mov eax,0x80000003
  894. cpuid
  895. mov [cpu_brand + 0x10],eax
  896. mov [cpu_brand + 0x14],ebx
  897. mov [cpu_brand + 0x18],ecx
  898. mov [cpu_brand + 0x1c],edx
  899.  
  900. mov eax,0x80000004
  901. cpuid
  902. mov [cpu_brand + 0x20],eax
  903. mov [cpu_brand + 0x24],ebx
  904. mov [cpu_brand + 0x28],ecx
  905. mov [cpu_brand + 0x2c],edx
  906.  
  907. mov ebx,cpu_brnd0 ;显示处理器品牌信息
  908. call sys_routine_seg_sel:put_string
  909. mov ebx,cpu_brand
  910. call sys_routine_seg_sel:put_string
  911. mov ebx,cpu_brnd1
  912. call sys_routine_seg_sel:put_string
  913.  
  914. ;准备打开分页机制
  915.  
  916. ;创建系统内核的页目录表PDT
  917. ;页目录表清零
  918. mov ecx, ;1024个目录项PDE
  919. mov ebx,0x00020000 ;页目录的物理地址
  920. xor esi,esi
  921. .b1:
  922. mov dword [es:ebx+esi],0x00000000 ;页目录表项清零
  923. add esi,
  924. loop .b1
  925.  
  926. ;在页目录内创建指向页目录自己的目录项,最后一项指向自己,那么线性地址高20位是0xFFFFF的时候,转成物理地址就是页目录自己
  927. mov dword [es:ebx+],0x00020003
  928.  
  929. ;页目录的第一项,内核第一个页表的物理地址:0x00021000
  930. mov dword [es:ebx+],0x00021003 ;写入目录项(页表的物理地址和属性)
  931.  
  932. ;创建与上面那个目录项相对应的页表,初始化页表项
  933. mov ebx,0x00021000 ;页表的物理地址
  934. xor eax,eax ;起始页的物理地址
  935. xor esi,esi ;esi=0
  936. .b2:
  937. mov edx,eax ;edx=eax; eax=0x1000*n
  938. or edx,0x00000003 ;edx=0x1000*n+3;u/s=1,允许所有特权级别的程序访问;
  939. mov [es:ebx+esi*],edx ;登记页的物理地址; 0x21000~0x21400都是PTE,隐射从0~1MB(256*4096=1Mb)的物理地址;
  940. add eax,0x1000 ;下一个相邻页的物理地址
  941. inc esi
  942. cmp esi, ;仅低端1MB内存对应的页才是有效的
  943. jl .b2
  944.  
  945. .b3: ;其余的页表项置为无效
  946. mov dword [es:ebx+esi*],0x00000000 ;0x21400~(0x21400+(1024-256)*4=0x22000)清零;
  947. inc esi
  948. cmp esi,
  949. jl .b3
  950.  
  951. ;令CR3寄存器指向页目录,并正式开启页功能
  952. mov eax,0x00020000 ;PCD=PWT=0PDT基址=0x00020000
  953. mov cr3,eax
  954.  
  955. mov eax,cr0
  956. or eax,0x80000000
  957. mov cr0,eax ;开启分页机制
  958.  
  959. ;在页目录内创建与线性地址0x80000000对应的目录项,有了这个项,0x800000000才会被映射到0x21000PET; 线性地址0x80000000~0x800FFFFF映射的物理地址:0x00000~0xFFFFF
  960. mov ebx,0xfffff000 ;页目录自己的线性地址;高5字节都是F,低3字节就是PDT内的偏移
  961. mov esi,0x80000000 ;映射的起始地址
  962. shr esi, ;取线性地址高10位(目录索引),esi=0x200
  963. shl esi, ;索引乘以4得到偏移
  964. mov dword [es:ebx+esi],0x00021003 ;写入目录项(页表的物理地址和属性)es:ebx+esi = 0xFFFFF800
  965.  
  966. ;将GDT中的段描述符映射到线性地址0x80000000
  967. sgdt [pgdt]
  968.  
  969. mov ebx,[pgdt+] ;ebx存放GDTbase
  970.  
  971. or dword [es:ebx+0x10+],0x80000000 ;
  972. or dword [es:ebx+0x18+],0x80000000 ;内核堆栈段
  973. or dword [es:ebx+0x20+],0x80000000 ;视频显示缓冲区
  974. or dword [es:ebx+0x28+],0x80000000 ;API
  975. or dword [es:ebx+0x30+],0x80000000 ;内核数据段
  976. or dword [es:ebx+0x38+],0x80000000 ;内核代码段
  977.  
  978. add dword [pgdt+],0x80000000 ;GDTR也用的是线性地址
  979.  
  980. lgdt [pgdt]
  981.  
  982. jmp core_code_seg_sel:flush ;刷新段寄存器CS,启用高端线性地址
  983.  
  984. flush:
  985. mov eax,core_stack_seg_sel
  986. mov ss,eax
  987.  
  988. mov eax,core_data_seg_sel
  989. mov ds,eax
  990.  
  991. mov ebx,message_1
  992. call sys_routine_seg_sel:put_string
  993.  
  994. ;以下开始安装为整个系统服务的调用门。特权级之间的控制转移必须使用门
  995. mov edi,salt ;C-SALT表的起始位置,内核API函数导出表,有函数名称、函数在API段内的偏移、API段的选择子
  996. mov ecx,salt_items ;C-SALT表的条目数量,ecx=4
  997. .b4:
  998. push ecx
  999. mov eax,[edi+] ;该条目入口点的32位偏移地址;API函数的段内偏移地址
  1000. mov bx,[edi+] ;该条目入口点的段选择子 API函数所在段的选择子
  1001. mov cx,1_11_0_1100_000_00000B ;特权级3的调用门(3以上的特权级才
  1002. ;允许访问),0个参数(因为用寄存器
  1003. ;传递参数,而没有用栈)
  1004. call sys_routine_seg_sel:make_gate_descriptor ;返回完整的描述符,保存在EDX:EAX
  1005. call sys_routine_seg_sel:set_up_gdt_descriptor ;上一步构造好的门描述符写回GDT
  1006. mov [edi+],cx ;将返回的门描述符选择子回填
  1007. add edi,salt_item_len ;指向下一个C-SALT条目
  1008. pop ecx
  1009. loop .b4
  1010.  
  1011. ;对门进行测试
  1012. mov ebx,message_2
  1013. call far [salt_1+] ;通过门显示信息(偏移量将被忽略);salt_1+256,低4字节是段内偏移,高2字节是选择子
  1014.  
  1015. ;为程序管理器的TSS分配内存空间
  1016. mov ebx,[core_next_laddr] ;从0x80100000开始分配,查找还没使用的线性地址
  1017. call sys_routine_seg_sel:alloc_inst_a_page ;给线性地址挂载物理页
  1018. add dword [core_next_laddr], ;线性地址增加0x1000;
  1019.  
  1020. ;在程序管理器的TSS中设置必要的项目;该线性地址已经挂载物理页,可以正常使用了
  1021. mov word [es:ebx+], ;反向链=0
  1022.  
  1023. mov eax,cr3
  1024. mov dword [es:ebx+],eax ;登记CR3(PDBR)
  1025.  
  1026. mov word [es:ebx+], ;没有LDT。处理器允许没有LDT的任务。
  1027. mov word [es:ebx+], ;T=0
  1028. mov word [es:ebx+], ;没有I/O位图。0特权级事实上不需要。
  1029.  
  1030. ;创建程序管理器的TSS描述符,并安装到GDT
  1031. mov eax,ebx ;TSS的起始线性地址
  1032. mov ebx, ;段长度(界限)
  1033. mov ecx,0x00408900 ;TSS描述符,特权级0
  1034. call sys_routine_seg_sel:make_seg_descriptor
  1035. call sys_routine_seg_sel:set_up_gdt_descriptor
  1036. mov [program_man_tss+],cx ;保存程序管理器的TSS描述符选择子
  1037.  
  1038. ;任务寄存器TR中的内容是任务存在的标志,该内容也决定了当前任务是谁。
  1039. ;下面的指令为当前正在执行的0特权级任务“程序管理器”后补手续(TSS)。
  1040. ltr cx
  1041.  
  1042. ;现在可认为“程序管理器”任务正执行中
  1043.  
  1044. ;创建用户任务的任务控制块,类似windows下的进程控制块PCB
  1045. mov ebx,[core_next_laddr] ;从0x80100000开始分配
  1046. call sys_routine_seg_sel:alloc_inst_a_page
  1047. add dword [core_next_laddr],
  1048.  
  1049. mov dword [es:ebx+0x06], ;用户任务局部空间的分配从0开始。
  1050. mov word [es:ebx+0x0a],0xffff ;登记LDT初始的界限到TCB
  1051. mov ecx,ebx
  1052. call append_to_tcb_link ;将此TCB添加到TCB链中,类似windowsEPROCESS的链条
  1053.  
  1054. push dword ;用户程序位于逻辑50扇区
  1055. push ecx ;压入任务控制块起始线性地址
  1056.  
  1057. call load_relocate_program
  1058.  
  1059. mov ebx,message_4
  1060. call sys_routine_seg_sel:put_string
  1061.  
  1062. call far [es:ecx+0x14] ;执行任务切换。
  1063.  
  1064. mov ebx,message_5
  1065. call sys_routine_seg_sel:put_string
  1066.  
  1067. hlt
  1068.  
  1069. core_code_end:
  1070.  
  1071. ;-------------------------------------------------------------------------------
  1072. SECTION core_trail
  1073. ;-------------------------------------------------------------------------------
  1074. core_end:

用户程序:

  1. program_length dd program_end ;程序总长度#0x00 = 0x1F88E
  2. entry_point dd start ;程序入口点#0x04 = 0x1F85B
  3. salt_position dd salt_begin ;SALT表起始偏移量#0x08 =0x10
  4. salt_items dd (salt_end-salt_begin)/ ;SALT条目数#0x0C = 0x1F8
  5.  
  6. ;-------------------------------------------------------------------------------
  7.  
  8. ;符号地址检索表
  9. salt_begin:
  10.  
  11. PrintString db '@PrintString' ;内核代码会对导入表做重定位,把内核API的实际偏移、选择子写回,覆盖@PrintString6个字节,下面就可以直接通过call far [PrintString]调用内核API函数了
  12. times -($-PrintString) db
  13.  
  14. TerminateProgram db '@TerminateProgram'
  15. times -($-TerminateProgram) db
  16. ;-------------------------------------------------------------------------------
  17.  
  18. reserved times * db ;保留一个空白区,以演示分页
  19.  
  20. ;-------------------------------------------------------------------------------
  21. ReadDiskData db '@ReadDiskData'
  22. times -($-ReadDiskData) db
  23.  
  24. PrintDwordAsHex db '@PrintDwordAsHexString'
  25. times -($-PrintDwordAsHex) db
  26.  
  27. salt_end:
  28.  
  29. message_0 db 0x0d,0x0a,
  30. db ' ............User task is running with '
  31. db 'paging enabled!............',0x0d,0x0a,
  32.  
  33. space db 0x20,0x20,
  34.  
  35. ;-------------------------------------------------------------------------------
  36. [bits ]
  37. ;-------------------------------------------------------------------------------
  38.  
  39. start:
  40.  
  41. mov ebx,message_0
  42. call far [PrintString]
  43.  
  44. xor esi,esi
  45. mov ecx,
  46. .b1:
  47. mov ebx,space
  48. call far [PrintString]
  49.  
  50. mov edx,[esi*]
  51. call far [PrintDwordAsHex]
  52.  
  53. inc esi
  54. loop .b1
  55.  
  56. call far [TerminateProgram] ;退出,并将控制权返回到核心
  57.  
  58. ;-------------------------------------------------------------------------------
  59. program_end:

  

x86架构:分页机制和原理的更多相关文章

  1. X86架构CPU的逻辑原理

    本篇只是初略介绍X86的逻辑运行原理,并不涉及物理层面和汇编层面的知识. 一.冯洛伊曼体系的运作过程: 1.CPU的历史就不扯了,有兴趣的朋友可以网上搜一下. 2.X86CPU是基于冯洛伊曼架构体系, ...

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

    第16章讲的是分页机制和动态页面分配的问题,说实话这个一开始接触是会把人绕晕的,但是这个的确太重要了,有了分页机制内存管理就变得很简单,而且能直接实现平坦模式. ★PART1:Intel X86基础分 ...

  3. linux x86内核中的分页机制

    Linux采用了通用的四级分页机制,所谓通用就是指Linux使用这种分页机制管理所有架构的分页模型,即便某些架构并不支持四级分页.对于常见的x86架构,如果系统是32位,二级分页模型就可满足系统需求: ...

  4. Linux x86架构下ACPI PNP Hardware ID的识别机制

    转:https://blog.csdn.net/morixinguan/article/details/79343578 关于Hardware ID的用途,在前面已经大致的解释了它的用途,以及它和AC ...

  5. x86 分页机制——虚拟地址到物理地址寻址

    x86下的分页机制有一个特点:PAE模式 PAE模式 物理地址扩展,是基于x86 的服务器的一种功能,它使运行 Windows Server 2003, Enterprise Edition 和 Wi ...

  6. Linux内存寻址之分段机制及分页机制【转】

    前言 本文涉及的硬件平台是X86,如果是其他平台的话,如ARM,是会使用到MMU,但是没有使用到分段机制: 最近在学习Linux内核,读到<深入理解Linux内核>的内存寻址一章.原本以为 ...

  7. Linux内存寻址之分页机制

    在上一篇文章Linux内存寻址之分段机制中,我们了解逻辑地址通过分段机制转换为线性地址的过程.下面,我们就来看看更加重要和复杂的分页机制. 分页机制在段机制之后进行,以完成线性—物理地址的转换过程.段 ...

  8. Linux分页机制之分页机制的实现详解--Linux内存管理(八)

    1 linux的分页机制 1.1 四级分页机制 前面我们提到Linux内核仅使用了较少的分段机制,但是却对分页机制的依赖性很强,其使用一种适合32位和64位结构的通用分页模型,该模型使用四级分页机制, ...

  9. Linux分页机制之概述--Linux内存管理(六)

    1 分页机制 在虚拟内存中,页表是个映射表的概念, 即从进程能理解的线性地址(linear address)映射到存储器上的物理地址(phisical address). 很显然,这个页表是需要常驻内 ...

随机推荐

  1. Poj 3613 Cow Relays (图论)

    Poj 3613 Cow Relays (图论) 题目大意 给出一个无向图,T条边,给出N,S,E,求S到E经过N条边的最短路径长度 理论上讲就是给了有n条边限制的最短路 solution 最一开始想 ...

  2. P3008 [USACO11JAN]Roads and Planes G 拓扑排序+Dij

    题目描述 Farmer John正在一个新的销售区域对他的牛奶销售方案进行调查.他想把牛奶送到T个城镇 (1 <= T <= 25,000),编号为1T.这些城镇之间通过R条道路 (1 & ...

  3. mybitis下choose..when. otherwise条件不起作用

    我的代码如下: <select id="findList" resultType="TyArticle"> SELECT <include r ...

  4. Java多线程编程基础知识汇总

    多线程简介 多任务   现代操作系统(Windows.Linux.MacOS)都可以执行多任务,多任务就是同时运行多个任务.例如在我们的计算机上,一般都同时跑着多个程序,例如浏览器,视频播放器,音乐播 ...

  5. ADB-常见命令使用详解

    ADB命令使用详解 ADB是一个 客户端-服务器端 程序, 其中客户端是你用来操作的电脑, 服务器端是android设备. 1.连接android设置adb connect 设备名例如:adb con ...

  6. Vue 项目部署出现css样式失效的解决方案

    解决方案1: 你的问题就是css权重问题 如果相同权重可能存在引入顺序问题 简单粗暴解决办法 1: 如果是单页面 写入index.html里面 2:直接修改源码的css 很简单~~~3:加个!impo ...

  7. [斯坦福大学2014机器学习教程笔记]第六章-决策界限(decision boundary)

    这一节主要介绍的是决策界限(decision boundary)的概念,这个概念可以帮组我们更好地理解逻辑回归的假设函数在计算什么. 首先回忆一下上次写的公式. 现在让我们进一步了解这个假设函数在什么 ...

  8. html2canvas 实现页面转图片并下载

    一 前言 最近做了一个周报,从不同的数据表抓取数据,然后展示到前端页面显示.这个过程不难,让我烦恼的是:要把周报的数据导出来,然后打印,打印也必须在一张纸上.想到这里,我整理了一下思绪,我要写几个存储 ...

  9. http连接池存在的问题

    连接的有效性检测是所有连接池都面临的一个通用问题,大部分HTTP服务器为了控制资源开销,并不会 永久的维护一个长连接,而是一段时间就会关闭该连接.放回连接池的连接,如果在服务器端已经关闭,客 户端是无 ...

  10. 测试工程师想进BAT必须具备的几项素质

    我发现一个奇怪的现象:总是听到身边的程序员朋友谈论BAT(中国大陆互联网的三大巨头:百度.阿里.腾讯)以及如何进入BAT,却鲜少有测试会去谈论或者考虑这些问题. 我不知道这是为什么,或者我就算知道也只 ...