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: ;堆栈顶部,向下扩展
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函数永远不会返回,除非我的操作系统厌恶了生存想要崩溃了~^_!