WinixJ---kernel/init.s文件详解

WinixJ---kernel/init.s文件详解
这个文件是从loader真正跳入内核后的routine。因此它最急需的工作当然是进行保护模式的重新初始化,包括重新加载gdt,以及开启分页机制、加载idt,跳入到c代码运行,先贴代码吧:

  1  PARAM_ADDR    equ  0xf0000  ; 在laoder中保存的bios参数的地址,此地址是相当于段基地址0x0的偏移量
  2  PDE_ADDR    equ  0x100000  ; 页目录表从1M地址开始, 大小4K,含有1024个表项
  3  PTE_ADDR    equ  0x101000  ; 页表紧接着页目录表开始
  4 
  5  extern  set_idt
  6  extern  cbegin
  7 
  8  global _start ; kernel程序入口,需要导出
  9  global gdt
 10  global gdtr
 11  global stacktop ; 内核态的堆栈顶
 12 
 13  [SECTION .text]
 14  _start:
 15      lgdt [gdtr] ; 重新加载新的全局描述符表
 16      jmp  0x08 :new_gdt ; 使新的全局描述符表立即生效
 17  new_gdt:
 18      ;设置各个段寄存器
 19      mov ax,  0x10
 20      mov ds, ax
 21      mov es, ax
 22      mov gs, ax
 23      mov fs, ax
 24      mov ss, ax
 25      mov esp, stacktop ; 重新设置堆栈
 26 
 27      call set_idt ; 设置中断
 28 
 29      call setup_paging ; 开启分页机制
 30 
 31      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 32      ; 从下面这一指令之后便真正跳入c函数内执行
 33      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 34      call cbegin ; 跳转到cbegin函数执行,cbegin函数负责进程方面的初始化工作
 35 
 36 
 37 
 38  setup_paging:
 39      xor eax, eax
 40      xor ebx, ebx
 41      xor ecx, ecx
 42      xor edx, edx
 43      mov esi, PARAM_ADDR
 44      add esi,  0x2
 45      mov word ax, [esi] ; 扩展内存的大小(即1MB以后的内存的大小),以KB为单位,因此扩展内存最大64MB
 46      cmp ax,  0x08  ; 扩展内存大小不能小于8KB,因为我们保证至少要能存放页目录表和一个页表
 47      jb extended_mem_shortage
 48      cmp ax,  0xfbff  ; 如果扩展内存大于63MB内存则报错
 49      ja too_many_mem
 50      add ax,  0x400  ; 扩展内存大小加上1MB等于总内存数量
 51      mov ebx,  0x1000
 52      div ebx ; 总内存数量(XKB)除以每个页表代表的内存数量(4KKB),即用X / 4K即得总共应该分的页数
 53      mov ecx, eax ; 商在eax中,需要分的页数
 54      test edx, edx
 55      jz no_remainder
 56      inc ecx
 57  no_remainder:
 58      push ecx
 59 
 60      ; 开始初始化页目录表
 61      mov edi, PDE_ADDR
 62      mov eax, PTE_ADDR  +   0x007  ; 页表首地址0x100000,属性是在内存中存在且普通用户可读可写
 63  write_pde:
 64      stosd
 65      add eax,  0x1000
 66      loop write_pde
 67 
 68      ; 开始初始化页表
 69      pop eax ; 页表个数
 70      ; 页表总数乘以每个页表含有的表项数目(1024个)则得到总共需要的表项数目
 71      mov ecx,  10
 72  goon_shl:
 73      shl eax,  1  ; 页表总数乘以每个页表含有的表项数目(1024个)则得到总共需要的表项数目
 74      jc too_many_mem
 75      loop goon_shl
 76      mov ecx, eax
 77      mov edi, PTE_ADDR
 78      mov eax,  0x007  ; 从物理地址0x0开始,属性是在内存中存在且普通用户可读可写
 79  write_pte:
 80      stosd
 81      add eax,  0x1000  ; 每页内存大小为4KB
 82      loop write_pte
 83 
 84      mov eax, PDE_ADDR
 85      mov cr3, eax ; cr3高20位存放pde的地址的高20位
 86      mov eax, cr0
 87      or eax,  0x80000000
 88      mov cr0, eax ; 打开cr0的最高一位PG位
 89      ret
 90 
 91  extended_mem_shortage:
 92      hlt
 93 
 94  too_many_mem:
 95      hlt
 96 
 97  [SECTION .data]
 98  gdt:
 99          ; 第一个描述符空,不使用
100          db  0x00 ,     0x00
101          db  0x00 ,     0x00 ,     0x00
102          db  0x00 ,     0x00
103          db  0x00
104 
105          ; 第二个描述符的段基地址为0;
106          ; 界限为0xfffff,段界限粒度为4KB;
107          ; 是可执行可读的32位代码段
108          db  0xff ,     0xff
109          db  0x00 ,     0x00 ,     0x00
110          db  0x9a ,     0xcf
111          db  0x00
112 
113          ; 第三个描述符的段基地址为0;
114          ; 界限为0xfffff,段界限粒度为4KB;
115          ; 是可读可写32位数据段
116          db  0xff ,     0xff
117          db  0x00 ,     0x00 ,     0x00
118          db  0x92 ,     0xcf
119          db  0x00
120          
121          ; gdt表共可存放256个段描述符
122          times  253   *   8  db  0
123 
124  gdtr:                dw $  -  gdt  -   1
125                      dd gdt
126 
127  [SECTION .bss]
128  stackbase resb     4   *   1024  ;4KB堆栈大小
129  stacktop: ;堆栈顶部,向下扩展

可以看到新的gdt总共有256项,其中第一项依然是不使用的,不过有些操作系统为了节省内存,居然将gdtr放到第一项中(比如menuetOS),这个想法比较特别~
不过我觉得8字节没有必要这么节省拉:)所以我们选择第一个gdt项空。之后是cs段descriptor、ss段descriptor,这两个位置最好不要变动,因为1、大部分的操作系统都是这么安排的,因为之后的进程切换、中断处理的时候都需要用到0x8和0x16这两个选择子;2、Intel奔腾2之后的机型都支持sysenter和sysexit的快速系统自陷即系统调用指令,这两个指令是需要cs和ds、ss选择子为0x8和0x16的,如果改变位置则这两个高级指令无法使用了;3、也没有必要非得把这两个descriptor放到别的地方,已经够简洁了~另外在bss段开辟了4KB大小的堆栈供内核使用。
jmp 0x8:net_gdt这条指令是来自于《操作系统设计与实现》,它将会刷新cs段寄存器高速缓存,启用新的gdt。
set_idt暂时先不说,因为它在下一个文件int.s中,下一节再讲解。
之后是启动分页机制,特别注意页目录表在1MB内存起始处,之后放置的是页表,不过这只是随意写的代码而已,因为之后当写 内存管理器的时候这儿的分页机制会被修改,而且内核空间页表和用户空间页表是不同的,理应不能放到一起。
最后是跳转到cbegin函数执行c函数,注意的是该c函数永远不会返回,除非我的操作系统厌恶了生存想要崩溃了~^_!

你可能感兴趣的:(WinixJ---kernel/init.s文件详解)