這個文件是從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函數永遠不會返回,除非我的操作系統厭惡了生存想要崩潰了~^_!
posted on 2011-12-20 00:25
myjfm 閱讀(498)
評論(0) 編輯 收藏 引用 所屬分類:
操作系統