上一篇日志主要講解了對(duì)8259A以及中斷向量表的初始化。
下面的程序主要是時(shí)鐘中斷、硬盤(pán)中斷以及系統(tǒng)調(diào)用入口函數(shù)的實(shí)現(xiàn)。
1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2 ; 每個(gè)進(jìn)程的內(nèi)核態(tài)堆棧頂部棧幀應(yīng)該是這樣的
3 ; ss
4 ; esp
5 ; eflags
6 ; cs
7 ; eip
8 ; eax
9 ; ecx
10 ; edx
11 ; ebx
12 ; ebp
13 ; esi
14 ; edi
15 ; ds
16 ; es
17 ; fs
18 ; gs
19 ; 其中ss、esp、eflags、cs、eip是在發(fā)生中斷時(shí)CPU自動(dòng)壓棧的
20 ; 而其他的是由中斷程序壓棧的,這個(gè)順序不能改變,否則后果自負(fù)
21 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
22
23 CS_OFFSET equ 0x30
24 ESP_OFFSET equ 0x38
25 SS_OFFSET equ 0x3c
26
27 ; 一個(gè)宏,因?yàn)樗械膇rq中斷函數(shù)都是先保存現(xiàn)場(chǎng)并將數(shù)據(jù)段等堆棧段
28 ; 切換到內(nèi)核態(tài),因此,該操作所有的irq中斷入口函數(shù)均相同
29 ; 故寫(xiě)成宏節(jié)省空間^_!
30 %macro save_all 0
31 push eax
32 push ecx
33 push edx
34 push ebx
35 push ebp
36 push esi
37 push edi
38 push ds
39 push es
40 push fs
41 push gs
42 mov si, ss
43 mov ds, si
44 mov es, si
45 mov gs, si
46 mov fs, si
47 %endmacro
48
49 ; 一個(gè)宏,恢復(fù)現(xiàn)場(chǎng)
50 %macro recover_all 0
51 pop gs
52 pop fs
53 pop es
54 pop ds
55 pop edi
56 pop esi
57 pop ebp
58 pop ebx
59 pop edx
60 pop ecx
61 pop eax
62 %endmacro
63
64 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
65 ; 時(shí)鐘中斷處理程序
66 ; 這是整個(gè)系統(tǒng)中最要求“速度快”的程序,因?yàn)闀r(shí)鐘中斷沒(méi)隔1/HZ(s)
67 ; 就發(fā)生一次,大概它是整個(gè)系統(tǒng)調(diào)用最頻繁的函數(shù),所以需要該函數(shù)
68 ; 盡量短,沒(méi)有必要的函數(shù)調(diào)用盡量避免。
69 ; 另外判斷中斷重入minix和linux采取的方法也是不一樣的,minix采用
70 ; 一個(gè)全局變量,類似于信號(hào)量的概念;而linux的方法則比較簡(jiǎn)單,它
71 ; 直接獲取存儲(chǔ)在內(nèi)核堆棧中的cs段寄存器的RPL值來(lái)判斷被中斷的程序
72 ; 是內(nèi)核態(tài)程序的還是用戶態(tài)的進(jìn)程;我們打算采用linux的辦法,雖然
73 ; minix方法更酷,但是linux的顯然更加簡(jiǎn)單:)
74 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
75 int_clock:
76 save_all
77 ; 增加心跳數(shù)
78 inc dword [boot_heartbeat]
79
80 ; 發(fā)送EOI指令結(jié)束本次中斷
81 mov ax, 0x20
82 out 0x20, al
83 sti
84
85 mov eax, [esp + CS_OFFSET]
86 and eax, 0x03
87 cmp eax, 0x0 ; 如果CS段寄存器的RPL為0,則說(shuō)明是由內(nèi)核態(tài)進(jìn)入時(shí)鐘中斷,則是中斷重入
88 je return
89 call pre_schedule
90 return:
91 recover_all
92 iretd
93
94 int_keyboard:
95 save_all
96
97 recover_all
98 iretd
99
100 int_serial_port2:
101 save_all
102
103 recover_all
104 iretd
105
106 int_serial_port1:
107 save_all
108
109 recover_all
110 iretd
111
112 int_lpt2:
113 save_all
114
115 recover_all
116 iretd
117
118 int_floppy:
119 save_all
120
121 recover_all
122 iretd
123
124 int_lpt1:
125 save_all
126
127 recover_all
128 iretd
129
130 int_rtc:
131 save_all
132
133 recover_all
134 iretd
135
136 int_ps_2_mouse:
137 save_all
138
139 recover_all
140 iretd
141
142 int_fpu_fault:
143 save_all
144
145 recover_all
146 iretd
147
148 ;硬盤(pán)中斷處理程序
149 int_at_win:
150 save_all
151
152 mov byte [gs:0xb8006], 'e'; 試驗(yàn)硬盤(pán)中斷是否成功:)
153
154 ; 發(fā)送EOI指令給從8259A結(jié)束本次中斷
155 mov ax, 0x20
156 out 0xa0, al
157 nop
158 nop
159 ; 發(fā)送EOI指令給主8259A結(jié)束本次中斷
160 out 0x20, al
161 nop
162 nop
163
164 ; 調(diào)用該函數(shù)使buf_info緩沖區(qū)生效
165 call validate_buffer
166
167 recover_all
168 iretd
169
170 ; 默認(rèn)的中斷處理函數(shù),所有的未定義中斷都會(huì)調(diào)用此函數(shù)
171 int_default:
172 save_all
173 recover_all
174 iretd
175
176 ; 注意從系統(tǒng)調(diào)用返回時(shí)不需要從棧中彈出eax的值,因?yàn)閑ax保存著調(diào)用
177 ; 對(duì)應(yīng)系統(tǒng)調(diào)用之后的返回值
178 %macro recover_from_sys_call 0
179 pop gs
180 pop fs
181 pop es
182 pop ds
183 pop edi
184 pop esi
185 pop ebp
186 pop ebx
187 pop edx
188 pop ecx
189 add esp, 4 * 1
190 %endmacro
191
192 ; 系統(tǒng)調(diào)用框架,系統(tǒng)調(diào)用采用0x30號(hào)中斷向量,利用int 0x30指令產(chǎn)
193 ; 生一個(gè)軟中斷,之后便進(jìn)入sys_call函數(shù),該函數(shù)先調(diào)用save_all框
194 ; 架保存所有寄存器值,然后調(diào)用對(duì)應(yīng)系統(tǒng)調(diào)用號(hào)的入口函數(shù)完成系統(tǒng)調(diào)用
195 ; 注意!!!!!系統(tǒng)調(diào)用默認(rèn)有三個(gè)參數(shù),分別利用ebx、ecx、edx來(lái)
196 ; 傳遞,其中eax保存系統(tǒng)調(diào)用號(hào)
197 sys_call:
198 save_all
199
200 sti
201
202 push ebx
203 push ecx
204 push edx
205 call [sys_call_table + eax * 4]
206 add esp, 4 * 3
207
208 recover_from_sys_call
209
210 cli
211
212 iretd
213
目前不打算對(duì)時(shí)鐘中斷處理函數(shù)、硬盤(pán)中斷處理函數(shù)以及系統(tǒng)調(diào)用入口框架做解釋,因?yàn)楹笮虿糠謱?huì)專門(mén)分章節(jié)進(jìn)行講解。這里只說(shuō)在發(fā)生類似時(shí)鐘中斷、硬盤(pán)中斷以及軟中斷int X的系統(tǒng)調(diào)用時(shí),CPU如何處理的。
初始情況下假設(shè)CPU正在用戶態(tài)執(zhí)行某一個(gè)進(jìn)程a,此時(shí)的CS、DS、ES、FS、SS均指向用戶態(tài)進(jìn)程a的段基地址。
當(dāng)時(shí)鐘中斷等中斷抑或int X的系統(tǒng)調(diào)用到來(lái)的時(shí)候,CPU會(huì)自動(dòng)從TSS中尋找用戶態(tài)進(jìn)程a預(yù)先保存的ring0下的SS0和ESP0,然后將SS和ESP寄存器值轉(zhuǎn)換成SS0和ESP0,即切換到核心態(tài)堆棧段(注意,每個(gè)進(jìn)程都可能會(huì)有一個(gè)自己獨(dú)立的ring0堆棧段,這樣可以更好的實(shí)現(xiàn)進(jìn)程切換時(shí)對(duì)內(nèi)核態(tài)的保護(hù),linux對(duì)此的做法是在創(chuàng)建一個(gè)進(jìn)程的時(shí)候在進(jìn)程頁(yè)的末尾申請(qǐng)一塊空間作為該進(jìn)程對(duì)應(yīng)的ring0堆棧段),然后將用戶態(tài)下的SS、ESP、EFLAGS、CS、EIP的值保存在新的SS0:ESP0堆棧段中。注意以上過(guò)程都是CPU自動(dòng)完成的,然后再通過(guò)save_all宏手工將eax、ecx、edx、ebx、ebp、esi、edi、ds、es、fs、gs壓入堆棧,然后再執(zhí)行相應(yīng)的中斷處理程序。完成之后會(huì)通過(guò)recover_all再按序恢復(fù)所有的常規(guī)寄存器。然后調(diào)用iretd命令從堆棧中彈出EIP、CS、EFLAGS、ESP、SS寄存器,然后再重新恢復(fù)進(jìn)程a的運(yùn)行。這一過(guò)程需要對(duì)GDT、IDT、TSS以及保護(hù)模式下的中斷門(mén)、陷阱門(mén)有所了解才可以。
不過(guò)還有一種情況此處沒(méi)有涉及:當(dāng)發(fā)生進(jìn)程切換的時(shí)候現(xiàn)場(chǎng)保護(hù)與恢復(fù)的過(guò)程如何呢?這一過(guò)程將在后面敘述。
1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2 ; 以下為庫(kù)函數(shù)
3 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
4
5 ; 對(duì)端口進(jìn)行寫(xiě)操作
6 ; void out_byte(unsigned short port, unsigned char value);
7 out_byte:
8 mov edx, [esp + 4 * 1]
9 mov al, [esp + 4 * 2]
10 out dx, al
11 nop
12 nop
13 ret
14
15 ; 對(duì)端口進(jìn)行讀操作
16 ; uint8 in_byte(unsigned short port);
17 in_byte:
18 mov edx, [esp + 4 * 1]
19 xor eax, eax
20 in al, dx
21 nop
22 nop
23 ret
24
25 ; 對(duì)從指定端口進(jìn)行讀操作,讀出的n個(gè)字節(jié)數(shù)據(jù)放入buf緩沖區(qū)中
26 ; void read_port(uint16 port, void* buf, int n);
27 read_port:
28 mov edx, [esp + 4 * 1] ; port
29 mov edi, [esp + 4 * 2] ; buf
30 mov ecx, [esp + 4 * 3] ; n
31 shr ecx, 1
32 cld
33 rep insw
34 ret
35
36 ; 對(duì)從指定端口進(jìn)行寫(xiě)操作,數(shù)據(jù)源在buf緩沖區(qū)中,寫(xiě)n個(gè)字節(jié)
37 ; void write_port(uint16 port, void* buf, int n);
38 write_port:
39 mov edx, [esp + 4 * 1] ; port
40 mov edi, [esp + 4 * 2] ; buf
41 mov ecx, [esp + 4 * 3] ; n
42 shr ecx, 1
43 cld
44 rep outsw
45 ret
46
47 ; 安裝指定中斷號(hào)的中斷處理程序
48 ; extern int install_int_handler(uint8 INT_IV, void* handler);
49 install_int_handler:
50 mov eax, [esp + 4 * 1] ; 中斷向量號(hào)
51 mov ebx, [esp + 4 * 2] ; 中斷程序入口
52 cmp eax, 256
53 jae failed
54 cmp eax, 0
55 jbe failed
56 push PRIVILEGE_KERNEL
57 push ebx
58 push INT_GATE_386
59 push eax
60 call init_idt
61 add esp, 4 * 4
62 failed:
63 ret
64
65 ; 卸載指定中斷號(hào)的中斷處理程序
66 ; extern int uninstall_int_handler(uint8 INT_IV);
67 uninstall_int_handler:
68 mov eax, [esp + 4 * 1] ; 中斷向量號(hào)
69 cmp eax, 256
70 jae failed
71 cmp eax, 0
72 jbe failed
73 push PRIVILEGE_KERNEL
74 push int_default
75 push INT_GATE_386
76 push eax
77 call init_idt
78 add esp, 4 * 4
79 ret
80
81 ; 安裝指定中斷號(hào)的系統(tǒng)調(diào)用入口
82 ; extern int install_sys_call_handler(uint8 INT_IV, void* handler);
83 install_sys_call_handler:
84 mov eax, [esp + 4 * 1] ; 中斷向量號(hào)
85 mov ebx, [esp + 4 * 2] ; 中斷程序入口
86 cmp eax, 256
87 jae failed_inst_sys
88 cmp eax, 0
89 jbe failed_inst_sys
90 push PRIVILEGE_USER
91 push ebx
92 push INT_TRAP_386
93 push eax
94 call init_idt
95 add esp, 4 * 4
96 failed_inst_sys:
97 ret
98
99 ; 打開(kāi)對(duì)應(yīng)向量號(hào)的硬件中斷
100 ; 注意,這里傳入的參數(shù)是硬件中斷對(duì)應(yīng)的中斷向量號(hào)
101 ; 需要將該中斷向量號(hào)轉(zhuǎn)化為在8259A上的索引號(hào)
102 ; void enable_hwint(uint8 IV);
103 enable_hwint:
104 mov ecx, [esp + 4 * 1]
105 cmp cl, IRQ0_IV
106 jae master_1
107 jmp ret_1
108 master_1:
109 cmp cl, IRQ8_IV
110 jae slave_1
111 push MASTER_CTL_MASK_8259
112 call in_byte
113 add esp, 4 * 1
114 sub cl, IRQ0_IV
115 mov bl, 1
116 shl bl, cl
117 xor bl, 0xff
118 and al, bl
119 push eax
120 push MASTER_CTL_MASK_8259
121 call out_byte
122 add esp, 4 * 2
123 jmp ret_1
124 slave_1:
125 cmp cl, IRQ15_IV
126 ja ret_1
127 push SLAVE_CTL_MASK_8259
128 call in_byte
129 add esp, 4 * 1
130 sub cl, IRQ8_IV
131 mov bl, 1
132 shl bl, cl
133 xor bl, 0xff
134 and al, bl
135 push eax
136 push SLAVE_CTL_MASK_8259
137 call out_byte
138 add esp, 4 * 2
139 ret_1:
140 ret
141
142 ; 關(guān)閉對(duì)應(yīng)向量號(hào)的硬件中斷
143 ; 注意,這里傳入的參數(shù)是硬件中斷對(duì)應(yīng)的中斷向量號(hào)
144 ; 需要將該中斷向量號(hào)轉(zhuǎn)化為在8259A上的索引號(hào)
145 ; void disable_hwint(uint8 IV);
146 disable_hwint:
147 mov ecx, [esp + 4 * 1]
148 cmp cl, IRQ0_IV
149 jae master_2
150 jmp ret_2
151 master_2:
152 cmp cl, IRQ8_IV
153 jae slave_2
154 push MASTER_CTL_MASK_8259
155 call in_byte
156 add esp, 4 * 1
157 sub cl, IRQ0_IV
158 mov bl, 1
159 shl bl, cl
160 or al, bl
161 push eax
162 push MASTER_CTL_MASK_8259
163 call out_byte
164 add esp, 4 * 2
165 jmp ret_2
166 slave_2:
167 cmp cl, IRQ15_IV
168 ja ret_2
169 push SLAVE_CTL_MASK_8259
170 call in_byte
171 add esp, 4 * 1
172 sub cl, IRQ8_IV
173 mov bl, 1
174 shl bl, cl
175 or al, bl
176 push eax
177 push SLAVE_CTL_MASK_8259
178 call out_byte
179 add esp, 4 * 2
180 ret_2:
181 ret
182
183 [SECTION .data]
184 idt:
185 ; idt表共可存放256個(gè)中斷門(mén)描述符
186 times 256 * 8 db 0
187
188 idtr: dw $ - idt - 1
189 dd idt
190
上面這段函數(shù)比較簡(jiǎn)單,是一些庫(kù)函數(shù),主要包括對(duì)端口進(jìn)行讀、寫(xiě)操作;以及安裝或者卸載中斷處理程序,方法就是通過(guò)接受中斷號(hào),將IDT中對(duì)應(yīng)的中斷描述符置空或初始化;還有安裝系統(tǒng)調(diào)用,安裝系統(tǒng)調(diào)用和安裝中斷處理程序幾乎相同,唯一的區(qū)別就是門(mén)描述符的類型以及門(mén)描述符的特權(quán)級(jí)不同,中斷處理程序是中斷門(mén),對(duì)應(yīng)的門(mén)描述符DPL為0,系統(tǒng)調(diào)用是陷阱門(mén),對(duì)應(yīng)的DPL為3,這是因?yàn)橹袛嚅T(mén)只需要檢查CPL>=處理程序的DPL,而陷阱門(mén)除了檢查該條件以外還檢查CPL<=陷阱門(mén)描述符的DPL。這樣做的原因是陷阱門(mén)是由程序引起的,諸如系統(tǒng)調(diào)用之類的,需要從程序中跳入;而中斷是硬件引起的。
再后面函數(shù)就是打開(kāi)或關(guān)閉8259A上的硬件中斷。
關(guān)于init.s文件的描述就到此為止。之后還會(huì)對(duì)進(jìn)程切換做進(jìn)一步的闡釋。