效果圖 (修改了調度算法後,下面描述的問題已經沒有了,希望不會引入新的BUG)
這裏期待按下ESC掛起第一個進程,按下F1鍵恢復第一個進程,但是並未實現,
實際運行結果爲有時掛起第一個進程,有時掛起其餘兩個進程,有時則宕機。
百度網盤下載地址:
https://pan.baidu.com/s/1_-IznMWL3z1CROziiD6mCw
;mykernel01.s
;This is Kernel entry.
bits 32 ;32位模式
newstart:
mov esp, stack ;置堆棧指針爲本段程序,最後面定義的空間。
lidt [idtr] ;加載中斷描述符表寄存器。
lgdt [gdtr] ;加載全局描述符表寄存器。
; 加載段選擇子(從第0個算起)。
; 只有gs加載了第4個,段基地址爲
; 0xb8000,即是文本模式下顯存地址。
; 其他都加載爲第2個,即2 * 8,
; 段基地址爲0x10000。
mov ax, 2 * 8
mov ds, ax
mov es, ax
mov fs, ax
mov ss, ax
mov ax, 4 * 8
mov gs, ax
jmp dword 3 * 8 : mystart ;這是重新加載cs選擇子。
mystart:
;入棧5個參數
push divide_error ;第一個參數是函數入口地址。
push 0 ;特權級
push 14 ;描述符屬性,14可能是陷阱門
push 0 ;這是第0個異常,即除零異常。
push idt ;idt在本段的偏移地址。
call _set_gate ;調用設置門描述的函數。
add esp, 5 * 4 ;丟棄5個參數。
;入棧5個參數
push timer_int
push 0
push 14
push 0x20
push idt
call _set_gate ;調用設置門描述的函數
add esp, 5 * 4 ;丟棄5個參數
;入棧5個參數
push keyboard_int
push 0
push 14
push 0x21
push idt
call _set_gate ;調用設置門描述的函數
add esp, 5 * 4 ;丟棄5個參數
;入棧5個參數
push system_call
push 3
push 15
push 0x80
push idt
call _set_gate ;調用設置門描述的函數
add esp, 5 * 4 ;丟棄5個參數
; call old_set_gate ;這個程序僅供測試用。
call cls ;調用清除屏幕的過程。
;調用帶參數的打印函數。
;兩個參數一個是字符串指針
; 另一個是字符屬性。
; mov edx, 0 ;奇怪的機器,edx如果不賦值,也異常。
; mov eax, 1
mov ebx, 100 ;除零操作,目的是產生0號異常。
div bx ;這裏雖然未除以0,但是卻產生了異常。
mov eax, 0
int 0x80
push 0xf
push str3
call disp
add esp, 2 * 4
call _init_8259 ;初始化可編程中斷控制器
;完全是抄書了。
call _init_8253 ;初始化可編程定時器。還是抄書。
call _enable_timer_key ;始終中斷和鍵盤中斷。
;因爲內核程序是從0x10000(64k處)處開始,所以這裏實際
;是將全局描述符表中的對應描述符的基地址改爲從64k開始。
mov ebx, 0x10000
mov ecx, gdt
lea eax, [tss0]
mov edi, 5 * 8
call set_base
lea eax, [ldt0]
mov edi, 6 * 8
call set_base
lea eax, [tss1]
mov edi, 7 * 8
call set_base
lea eax, [ldt1]
mov edi, 8 * 8
call set_base
lea eax, [tss2]
mov edi, 9 * 8
call set_base
lea eax, [ldt2]
mov edi, 10 * 8
call set_base
;這裏與趙炯先生大作一樣,是切換到用戶態的典型模式。
pushf
and dword [esp],0xffffbfff ;據說是復位任務嵌套標誌,
popf ;但我不確定。
mov eax,5 * 8
ltr ax ;加載任務狀態段的描述符。
mov eax,6 * 8
lldt ax ;加載局部描述符表的描述符。
sti ;CPU的中斷允許標誌置位,只有置位才能開啓可屏蔽中斷。
;這是典型的利用中斷返回指令,切換到用戶態。
;據說linux0.11也是用的這種方式。
push long 2 * 8 + 7 ;將局部描述表的第2個描述符的選擇子入棧,
;即是進程的堆棧段入棧。(從0開始)
push long f0_u_stack ;堆棧指針入棧。
pushf ;標誌寄存器入棧。
push long 1 * 8 + 7 ;將局部描述表的第1個描述符的選擇子入棧,
;進程代碼段入棧。
push long myfunc0 ; 指令指針入棧。
iret ;中斷返回,從此進入用戶態,下面的代碼不會被執行。
myloop:
push 0xf
push my_0x
call disp
add esp, 8
push dword [myval]
call _myprint
add esp, 4
inc dword [myval]
mov eax, 1
int 0x80
push 0xf
push mysapce
call disp
add esp, 8
call mydelay
push 0xd
push my_0x
call disp
add esp, 8
push dword [myval]
call _myprint
add esp, 4
inc dword [myval]
mov eax, 0
int 0x80
push 0xf
push mysapce
call disp
add esp, 8
jmp myloop
; jmp $ ;無限循環。
myval: dd 0
mysapce: db " ", 0
my_0x: db "0x", 0
mydelay: ;重複執行,起到延時作用。
push ecx
mov ecx, 0xffff
.1:
loop .1
pop ecx
ret
;清除屏幕的子程序,沒有什麼難度。
cls:
push eax
push ecx
push edi
mov ecx, 2000
xor eax, eax
xor edi, edi
.1:
mov [gs:edi], ax ;gs是顯存段,edi是偏移。
inc edi
inc edi
loop .1
pop edi
pop ecx
pop eax
ret
scr_p: dd 0 ;保存字符顯示位置的全局變量
;這是屏幕上顯示字符的程序。
;它接收兩個參數,第一個是串地址,第二個是
;字符屬性。如果遇到換行符則自動換行,打印到屏幕
;結尾處,在清除屏幕,並從屏幕左上角開始顯示。
disp: ; void disp(char *str, int attr);
push ebp
mov ebp, esp
push eax
push esi
push edi ;保存用到的寄存器。
mov esi, [ebp + 2 * 4]
mov ah, [ebp + 3 * 4] ;取得兩個參數。
mov edi, [scr_p] ;取得位置變量。
get_ch:
mov al, [esi] ;要顯示字符串的位置。
cmp al, 0 ;看是否是字符串結束字符。
je exit ;是,程序就返回。
cmp edi, 4000 ;是否到屏幕結尾處。
je top ;是則置位置爲屏幕開頭處,並清屏。
cmp al, 0xa ;是換行符嗎,是則進行換行操作。
jne d_c ;換行無非是整數行增加160字節,因此除以160
push edx ;然後在乘以160,得到目前的整數行,然後再
push eax ;加上160。
push ebx
xor edx, edx
mov eax, edi
mov ebx, 160
div ebx
mul ebx
add eax, ebx
mov edi, eax
pop ebx
pop eax
pop edx
jmp inc_esi
d_c:
mov [gs:edi], ax ;顯示一個字符。
add edi, 2
inc_esi:
inc esi
jmp get_ch
top:
call cls
xor edi, edi
jmp get_ch
exit:
mov [scr_p], edi
pop edi
pop esi
pop eax
pop ebp
ret
;*************
;這段程序我們期待它能夠正常的進行二進制數到十六進制數的轉化
global _bin2ascii
extern _buf
_bin2ascii:
;char *bin2ascii(unsigned int value);
;ret_addr ebp \
; \ \ \
; 0 * 4 1 * 4 2 * 4
push ebp
mov ebp, esp
push ebx
push esi
push ecx
lea ecx, [ebp + 2 * 4] ;取入棧數值的地址
xor esi, esi ;esi清零
.1: cmp dword [ecx], 0 ;數值是否爲零,爲零則退出並做後處理
je .2
push 16 ;這裏是如調用除法函數,我們把數值除以16得到返回值爲餘數
push ecx
call _mydiv
add esp, 2 * 4
mov ebx, [mystr + eax] ;在表中查找16進制數對應的ascii,則保存在
mov [_mybuf + esi], bl ;臨時數組_mybuf中
inc esi
cmp dword [ecx], 0 ;如果循環除法等最終結果爲零,則轉化完畢,退出後處理
je .3
jmp .1
.2:
mov byte [_mybuf + esi], '0'
inc esi
.3:
mov byte [_mybuf + esi], 0
push _mybuf ;這裏是計算字符串的長度函數調用
call _mstrlen
add esp, 1 * 4
push eax ;這裏是字符串反置,並保存在_mybuf1中。
push _mybuf1
push _mybuf
call _cp_ch
add esp, 3 * 4
lea eax, [_mybuf1]
pop ecx
pop esi
pop ebx
pop ebp
ret
;除法程序,將value除以base,value保存爲商,而返回值爲餘數。
global _mydiv
_mydiv:
;unsigned int mydiv(unsigned int *value, unsigned int base);
;ret_addr ebp \ \
; \ \ \ \
; 0 * 4 1 * 4 2 * 4 3 * 4
push ebp
mov ebp, esp
push ebx
push edx
push esi
xor edx, edx
mov esi, [ebp + 2 * 4]
mov eax, [esi]
mov ebx, [ebp + 3 * 4]
div ebx
mov [esi], eax
mov eax, edx
pop esi
pop edx
pop ebx
pop ebp
ret
;計算字符串的長度,返回值爲長度。
global _mstrlen
_mstrlen:
;unsigned int mstrlen(char *str);
;ret_addr ebp \
; \ \ \
; 0 * 4 1 * 4 2 * 4
push ebp
mov ebp, esp
push ecx
push esi
xor ecx, ecx
mov esi, [ebp + 2 * 4]
.1:
inc ecx
lodsb
cmp al, 0
jne .1
dec ecx
mov eax, ecx
pop esi
pop ecx
pop ebp
ret
;反置字符串,返回值爲目標字符串首地址。
global _cp_ch
_cp_ch: ;char *cp_ch(char *s, char *d, unsigned int len);
;ret_addr ebp \ \ \
; \ \ \ \ \
; 0 * 4 1 * 4 2 * 4 3 * 4 4 * 4
push ebp
mov ebp, esp
push ecx
push esi
push edi
mov ecx, [ebp + 4 * 4]
mov esi, [ebp + 2 * 4]
add esi, ecx
dec esi
mov edi, [ebp + 3 * 4]
.1: mov al, [esi]
mov [edi], al
dec esi
inc edi
dec ecx
jnz .1
mov byte [edi], 0
mov eax, [ebp + 3 * 4]
pop edi
pop esi
pop ecx
pop ebp
ret
_myprint: ;這裏只是把上面兩個函數合併封裝了一下,沒什麼特別。
;void myprint(int val);
push ebp
mov ebp, esp
push dword [esp + 2 * 4]
call _bin2ascii
add esp, 1 * 4
push 0xf
push _mybuf1
call disp
add esp, 2 * 4
pop ebp
ret
;*************
global _mybuf, _mybuf1
_mybuf:
times 64 db 0
_mybuf1:
times 64 db 0
mystr: db "0123456789abcdef",0
;這段程序是參考了趙炯先生的大作《linux0.12內核完全註釋》
;中的一個簡單的多任務內核實例程序。
old_set_gate:
mov edx, timer_int ;將處理函數偏移地址放入edx中
mov eax, 0x00180000 ;將內核代碼段選擇符存入eax高16位
mov ax, dx ;將處理函數偏移地址低16位放入ax
mov dx, 0x8f00 ;門描述符屬性放入dx中,應該是陷阱門
mov edi, idt ;中斷描述符表的基地址
mov ecx, 0x20 ;異常、中斷的編號,0爲除零異常。
mov [edi + ecx * 8], eax ;把以上編排好的門描述符
mov [edi + 4 + ecx * 8], edx ;存入描述表相應位置
ret
;*******************這段程序是參考了linux 0.12中代碼改編的
;位於linux/include/asm/system.h中的 _set_gate宏函數
; void set_gate(int gate_addr, int int_num, int type, int dpl, int offset)
; \ \ \ \ \
;ret_addr 0 * 4 ebp 1 * 4 2 * 4 3 * 4 4 * 4 5 * 4 6 * 4
_set_gate:
push ebp ;保存ebp在堆棧中
mov ebp, esp ;取當前堆棧指針
push eax
push ecx
push edx
push ebx
mov edx, [ebp + 5 * 4] ;dpl是描述符特權級
shl edx, 13 ;左移13位後正好是門描述符特權級的位置
mov eax, [ebp + 4 * 4] ;type是描述符屬性
shl eax, 8 ;左移8位後正好是門描述符是哪種門的位置
add edx, eax ;這裏相加應該等價於or操作
add edx, 0x8000
mov eax, [ebp + 6 * 4] ;offset是入口程序相對於內核代碼段的偏移
and eax, 0xffff0000 ;低16位清零
add edx, eax
mov eax, [ebp + 6 * 4]
and eax, 0x0000ffff ;高16位清零
add eax, 0x00180000 ; 高16位的0x0008是內核代碼段的選擇符
mov ecx, [ebp + 3 * 4] ;門的向量號
mov ebx, [ebp + 2 * 4] ;中斷描述符表的基地址
mov [ebx + ecx * 8], eax ;把以上編排好的門描述符
mov [ebx + 4 + ecx * 8], edx ;存入描述表相應位置
pop ebx
pop edx
pop ecx
pop eax
pop ebp
ret
;*******************
;void set_tssldt_desc(int n, int addr, char type);
;ret_addr ebp \ \ \
; 0 * 4 1 * 4 2 * 4 3 * 4 4 * 4
_set_tssldt_desc: ;這是設置全局描述符表中,任務狀態段描述符和
push ebp ;局部描述符的程序,這裏沒有用到,也不知對不對。
mov ebp, esp
push esi
push eax
push ebx
mov esi, [ebp + 2 * 4]
mov eax, [ebp + 3 * 4]
mov ebx, [ebp + 4 * 4]
mov word [esi], 104
mov [esi + 2], ax
ror eax, 16
mov [esi + 4], al
mov [esi + 5], bl
mov byte [esi + 6], 0
mov [esi + 7], ah
ror eax, 16
pop ebx
pop eax
pop esi
ret
;void set_seg_desc(int gate_addr, char type, char dpl, int base, int limit)
;ret_addr ebp \ \ \ \ \
; 0 * 4 1 * 4 2 * 4 3 * 4 4 * 4 5 * 4 6 * 4
_set_seg_desc: ;這是按照段描述符的規格,設置段描述符的程序,沒有用到。
push ebp
mov ebp, esp
push esi
push eax
push ebx
push edx
mov esi, [ebp + 2 * 4]
mov eax, [ebp + 5 * 4]
and eax, 0x0000ffff
shl eax, 16
mov ebx, [ebp + 6 * 4]
and ebx, 0x0000ffff
add eax, ebx
mov edx, [ebp + 5 * 4]
mov ebx, edx
and edx, 0xff000000
shr ebx, 16
and ebx, 0x00ff
add edx, ebx
mov ebx, [ebp + 6 * 4]
and ebx, 0x000f0000
add edx, ebx
mov ebx, [ebp + 4 * 4]
shl ebx, 13
add edx, ebx
add edx, 0x00408000
mov ebx, [ebp + 3 * 4]
shl ebx, 8
add edx, ebx
mov [esi], eax
mov [esi + 4], edx
pop edx
pop ebx
pop eax
pop esi
pop ebp
ret
;本程序來自趙炯先生大作,只是把描述符中的地址項目,改成與我們的
;內核地址相對應,但是這是必須的,否則程序不會正常運行。
;當然如果將內核代碼移動到地址0處開始,則不用調用此程序。
set_base: ;無非是按照段描述符的規格,填寫物理地址。
add eax, ebx
add edi, ecx
mov [edi + 2], ax
ror eax, 16
mov [edi + 4], al
mov [edi + 7], ah
ror eax, 16
ret
;set_base example ;一個樣例
;setup base fields of descriptors.
; mov ebx, 0x10000
; mov ecx, gdt
; lea eax, [tss0]
; mov edi, 5 * 8 ;注意我們這裏的選擇符都是採用
; call set_base ;x * 8的格式的,也可以某個描述,然後
;減去描述表首地址,當然如果是局部描述符
;還用加上7,像這樣x * 8 + 7
; lea eax, [ldt0]
; mov edi, 6 * 8
; call set_base
;********************
;這段處理程序完全是抄襲linux0.12中斷處理程序彙編代碼
;趙炯先生的大作對此描述的非常詳細,不過要想徹底弄懂
;必須精通堆棧的操作,也就是我們前面講的東西。
;所以建議看大作之前,深度閱讀前面的堆棧內容。
;我這裏就不抄襲大作了,只是給出了代碼。
divide_error:
push do_divide_error
no_error_code:
xchg [esp], eax
push ebx
push ecx
push edx
push edi
push esi
push ebp
push ds
push es
push fs
push dword 0
lea edx, [esp + 11 * 4]
push edx
mov edx, 2 * 8
mov dx, ds
mov dx, es
mov dx, fs
call eax
add esp, 2 * 4
pop fs
pop es
pop ds
pop ebp
pop esi
pop edi
pop edx
pop ecx
pop ebx
pop eax
iret
do_divide_error:
add dword [esp + (11 + 2) * 4], 2 ;[esp + 52]中是中斷返回去到的指令的地址
;也就是出現除零異常div ebx(=0)的偏移地址
;因爲這條指令的代碼長度爲2字節,所以+2
;是跳過這條指令,繼續執行下面的指令。
push 0xc
push e_0_str
call disp
add esp, 2 * 4
ret
e_0_str: db "Divide Error!...", 0
_init_8259: ;這裏用到的代碼完全是linux0.12中的代碼了
;雖說是抄書,其實好多系統都是這麼設置的。
mov al, 0x11 ;初始化命令,據說是通知8259我要搞你了。
out 0x20, al ;分別向主從芯片發送初始化命令。
out 0xa0, al ;8259是兩片級聯的。
mov al, 0x20 ;主芯片的中斷從0x20開始,0x20是時鐘中斷
out 0x21, al
mov al, 0x28 ;從芯片從0x28開始。
out 0xa1, al
mov al, 0x04 ;這兩條應該是主的告訴從的你連接我的什麼引腳
out 0x21, al ;而從的又告訴主的我的哪個引腳接入你。
mov al, 0x02
out 0xa1, al
mov al, 0x01 ;這是告訴工作在什麼方式,最重要的一點是要求,
out 0x21, al ;發生中斷後,處理程序發送此次中斷結束信號。
out 0xa1, al ;否則只發生1次中斷就不在接收了。
mov al, 0xff ;這是屏蔽所有中斷信號,以待以後用到時開啓。
out 0x21, al
out 0xa1, al
ret
_init_8253:
mov al, 0x36 ;定時器的初始化說起來就是一句話,它是告訴
mov dx, 0x43 ;時鐘的發生裝置,多長時間發生一次中斷,
out dx, al ;據說這個對系統的性能有着十分關鍵的作用,
mov ax, 11931 ;也就是說如果發生的頻繁了,運行時間就全被
mov dx, 0x40 ;時鐘中斷用去了,如果是頻率比較低,那麼你就
out dx, al ;進程特別的遲鈍,不過我沒有編代碼嘗試過,又是
mov al, ah ;道聽途說罷了。設置頻率是100毫秒發生一次時鐘
out dx, al ;中斷,這些都是抄書了。
ret
_enable_timer_key: ;這是開啓時鐘、鍵盤中斷
mov dx, 0x21
in al, dx
and al, 0xfc ;關鍵就是這裏,0xfc的二進制是11111100
out dx, al ;也就是把第0位和第1位清零後,寫入端口
ret ;0x21就行了。
_current: dd 5 * 8
tss_table:
dd tss0
dd tss1
dd tss2
tss_table_end:
dd 0
addr_t_t:
dd tss_table
myaddr:
dq 0
timer_int: ;這裏時鐘中斷處理程序簡陋的沒法再……
pusha
push ds
push es
push fs
push gs
push ss
mov edx, 2 * 8
mov ds, edx
; mov ss, edx
mov al, 0x20 ;這裏既是發送本次中斷結束信號,
out 0x20, al ;之所以註釋掉是當然是只讓它發生一次
; push 0xc ;這樣屏幕就不會被時鐘中斷弄得亂糟糟了。
; push timer_str
; call disp
; add esp, 2 * 4
;全局變量中存放的是tss_table的值,
;但tss_table是常量,所以這裏設置了一個變量,增加4是指向下一個任務。
.2: add dword [addr_t_t], 4
;是否到達任務指針數組的尾部。
cmp dword [addr_t_t], tss_table_end
;到達則跳轉到.1處。
jae .1
;把數組的地址,存入ebx中,從而取ebx中的內容,[ebx]爲第一個任務狀態段的地址
mov ebx, dword [addr_t_t]
mov esi, [ebx]
;看是否是空任務,是則忽略。
cmp esi, 0
je .2
;如果不是空任務,則取其LDT選擇符,我自認爲這裏是唯一的標識,所以取這裏的值。
mov eax, [esi + 24 * 4]
;這裏是得到相對應得TSS選擇符。(可以參照全局表,和tss任務狀態段來理解)。
sub eax, 8
;將選擇符存入符合處理器遠跳轉指令的規格的數據結構。
mov [myaddr + 4], eax
;這是模仿linux0.11的任務切換方式,可參考趙炯先生大作的switch_to的那個宏函數,
;這裏與之完全相同。
;是否是當前進程,是則中斷返回,不切換任務。
mov edx, [_current]
cmp eax, edx
je int_end
;不是的情況下,切換任務。
mov [_current], eax
jmp far [myaddr] ;一定要用這個遠跳轉指令否則不能實現。
jmp int_end
.1:
mov dword [addr_t_t], tss_table - 4
jmp .2
int_end:
;這裏主要是用鍵盤使0號進程掛起,但我不清楚是否就是向進程發送的一個信號。
;但我企圖實現之。
cmp dword [kill_key], 1
jne .3
mov dword [tss_table + 0 * 4] , 0
.3:
cmp dword [kill_key], 0
jne .4
mov dword [tss_table + 0 * 4] , tss0
.4:
pop ss
pop gs
pop fs
pop es
pop ds
popa
iret
timer_str: db " TIMER is happended! ", 0xa,0
kill_key: dd 3
keyboard_int: ;鍵盤中斷處理程序。
pusha
push ds
push es
push fs
push gs
push ss
mov edx, 2 * 8
mov ss, edx
in al, 0x60 ;從60端口接收鍵盤緩衝區的數據,
mov [value], al
mov al, 0x20 ;如果不接收,下一次的數據就不會被
out 0x20, al ;處理,鍵盤中斷是按下時發生一次,
;鬆開時又發生一次。
cmp byte [value], 0xe0
je .1
cmp byte [value], 0xe1
je .1
test byte [value], 0x80
jne .1
cmp dword [value], 0x1 ;ESC掃描碼
jne .3
mov dword [kill_key], 1 ;按下ESC鍵則kill_key置1
.3:
cmp dword [value], 0x3b ;F1掃描碼
jne .2
mov dword [kill_key], 0 ;按下F1鍵則kill_key置0
.2:
push dword [value]
call _bin2ascii
add esp, 1 * 4
push 0x9
push _mybuf1
call disp
add esp, 2 * 4
.1:
pop ss
pop gs
pop fs
pop es
pop ds
popa
iret
value: dd 0
keyboard_str: db 0, 0, " KEYBOARD is happended! ", 0xa,0
;我們的系統調用,也是十分的簡陋了,好在沒有宕機。
mycolor: dd 0
v1: dd 0
v2: dd 0xffff
system_call:
pusha
push ds
push es
push fs
push gs
push ss
mov edx, 2 * 8
mov ss, edx
cmp eax, 0 ;當eax == 0是顯示以一種顏色顯示變量。
je .1
jne .2
.1:
mov dword [mycolor], 0xe
push 0xe
push my_0x
call disp
add esp, 8
push dword [v1]
inc dword [v1]
jmp .5
.2:
cmp eax, 1 ;當eax == 1是顯示以另一種顏色顯示變量。
je .3
jne .4
.3:
mov dword [mycolor], 0xb
push 0xb
push my_0x
call disp
add esp, 8
push dword [v2]
inc dword [v2]
jmp .5
.4:
jmp .6
.5:
call _bin2ascii
add esp, 1 * 4
push dword [mycolor]
push _mybuf1
call disp
add esp, 2 * 4
push 0xf
push mysapce
call disp
add esp, 8
.6: pop ss
pop gs
pop fs
pop es
pop ds
popa
iret
;這裏定義了打印的字符串。
str: db " Hello, World!", 0xa, 0
str1: db " Snail OS is starting......", 0xa, 0
str2: db " I hope My Os is beautiful!", 0xa,0
str3: db " Divide happended running ...", 0xa, 0xa, 0
str4: db 0xa, 0
times 1024 db 0 ;堆棧的定義,暫時就是這些吧,也不知多少纔好。
stack:
idtr: ;被lidt加載的中斷描述符表寄存器的內容
dw 256 * 8 - 1 ;中斷描述附表段限長,從0開始的字節長度,
;據說最長就是這些了,因爲只有256箇中斷。
dd idt + 0x10000 ;idt的物理地址。
align 8 ;8字節對齊
idt: ;中斷描述符偏移地址
times 256 dq 0 ;dq是8字節的定義法
gdtr:
dw 20 * 8 - 1
dd gdt + 0x10000
gdt:
; 分別是第0、1、2、3、4個描述符
dq 0x0000000000000000 ; 0 * 8
dq 0x00c09a000000ffff ; 1 * 8
dq 0x00c092010000ffff ; 2 * 8
dq 0x00c09a010000ffff ; 3 * 8
dq 0x00c0920b800000ff ; 4 * 8
other_desc:
dw 0x68,tss0,0xe900,0x0 ; 5 * 8 ;他可以被定義成
dw 0x40,ldt0,0xe200,0x0 ; 6 * 8 ;TSS0_selector = other_desc - gdt
;不過我更鐘情這種 2 * 8,這就看個人喜好了。
dw 0x68,tss1,0xe900,0x0 ; 7 * 8
dw 0x40,ldt1,0xe200,0x0 ; 8 * 8
dw 0x68,tss2,0xe900,0x0 ; 9 * 8
dw 0x40,ldt2,0xe200,0x0 ; 10 * 8
times 9 dq 0x0000000000000000 ; 11 * 8 -- 19 * 8
align 8
ldt0:
dq 0x0000000000000000 ; 0 * 8 + 7 ;局部描述符的選擇子屬性爲7
dq 0x00c0fa01000003ff ; 1 * 8 + 7 ;具說應該是特權級爲3,屬性爲1是
dq 0x00c0f201000003ff ; 2 * 8 + 7 ;局部描述符,然後進行或操作就
dq 0x00c0f20b800003ff ; 3 * 8 + 7 ;應該是7,這個不肯定,早忘得
;差不多了。
;這是任務狀態段,任務切換時必須要用到的。它的長度最短應該是 26 * 4字節
;這個其實到沒有什麼好說的,要實現多任務,就必須按照某種固定格式設置,
;我的這個就是參照大作自己做的,幾乎沒有什麼改變。如果覺得難,多敲幾次‘
;就不會有什麼問題了。
tss0:
dd 0
dd krn_stk0, 2 * 8
dd 0,0,0,0,0
dd myfunc0,0x200
dd 0,0,0,0
dd f0_u_stack,0,0,0
dd 2 * 8 + 7, 1 * 8 + 7, 2 * 8 + 7, 2 * 8 + 7, 2 * 8 + 7, 2 * 8 + 7
dd 6 * 8, 0x8000000
times 128 * 4 db 0
krn_stk0: ;進程的內核棧,定義在這裏。
;同上
ldt1:
dq 0x0000000000000000 ; 0 * 8 + 7
dq 0x00c0fa01000003ff ; 1 * 8 + 7
dq 0x00c0f201000003ff ; 2 * 8 + 7
dq 0x00c0f20b800003ff ; 3 * 8 + 7
tss1:
dd 0
dd krn_stk1, 2 * 8
dd 0,0,0,0,0
dd myfunc1,0x200
dd 0,0,0,0
dd f1_u_stack,0,0,0
dd 2 * 8 + 7, 1 * 8 + 7, 2 * 8 + 7, 2 * 8 + 7, 2 * 8 + 7, 2 * 8 + 7
dd 8 * 8,0x8000000
times 128 * 4 db 0
krn_stk1:
;同上
ldt2:
dq 0x0000000000000000 ; 0 * 8 + 7
dq 0x00c0fa01000003ff ; 1 * 8 + 7
dq 0x00c0f201000003ff ; 2 * 8 + 7
dq 0x00c0f20b800003ff ; 3 * 8 + 7
tss2:
dd 0
dd krn_stk2, 2 * 8
dd 0,0,0,0,0
dd myfunc2,0x200
dd 0,0,0,0
dd f2_u_stack,0,0,0
dd 2 * 8 + 7, 1 * 8 + 7, 2 * 8 + 7, 2 * 8 + 7, 2 * 8 + 7, 2 * 8 + 7
dd 10 * 8,0x8000000
times 128 * 4 db 0
krn_stk2:
;我們的第一個進程。
myfunc0:
mov eax, 2 * 8 + 7
mov ds, eax
mov eax, 3 * 8 + 7 ;這裏如果不改成自己顯存段,會宕機,
mov gs, eax ;不過這個問題,無確實沒有思考明白。
;暫且這樣吧,待大俠答疑解惑!
.1:
mov eax, 0
int 0x80 ;系統調用
mov ecx, 0x7fffff
.2:
loop .2
jmp .1
ret
times 64 db 0
f0_u_stack:
;進程本身的堆棧。
;我們的第二個進程。
myfunc1:
mov eax, 2 * 8 + 7
mov ds, eax
mov eax, 3 * 8 + 7
mov gs, eax
.1:
mov eax, 1
int 0x80
mov ecx, 0x8fffff
.2:
loop .2
jmp .1
ret
times 64 db 0
f1_u_stack:
;進程本身的堆棧。
;我們的第三個進程。
myfunc2:
mov eax, 2 * 8 + 7
mov ds, eax
mov eax, 3 * 8 + 7
mov gs, eax
mov ah, 0xf
.2:
mov al, 'A'
.1:
mov [gs:0 + 160],ax
inc al
mov [gs:2 + 160],ax
inc al
mov [gs:4 + 160],ax
inc al
mov [gs:6 + 160],ax
inc al
mov [gs:8 + 160],ax
inc al
cmp al, 'Z'
ja .2
mov ecx, 0x1fffff
.3:
loop .3
jmp .1
ret
times 64 db 0
f2_u_stack: ;進程本身的堆棧。
; myboot01.s
; 此程序被BIOS加載的到0x7c00處所以
; 這裏是跳轉到我們的代碼處,注意在
; 16位指令中,0x7c0表示要跳轉到
; 段地址是0x7c00物理地址開始的段
; 而便宜地址是start16。這樣我們就
; 跳過了前面的描述符定義的數據。
; 當然,如果你不願意定義在前面,也
; 可以把描述表等定義在代碼的後面
; 這樣就不用跳轉了。這裏是參考了linux
jmp 0x7c0:start16
; 這裏定義了一個含有5個描述符的全局
; 描述符表。
gdt:
; 分別是第0、1、2、3、4個描述符
dq 0x0000000000000000 ; 0 * 8
dq 0x00c09a000000ffff ; 1 * 8
dq 0x00c092010000ffff ; 2 * 8
dq 0x00c09a010000ffff ; 3 * 8
dq 0x00c0920b800000ff ; 4 * 8
other_desc:
times 15 dq 0x0000000000000000 ; 5 * 8 -- 19 * 8
; 在內存中定義被全局描述符表寄存器加載的
; 內容
gdtr:
dw 20 * 8 - 1 ; 段限長
addr:
dd 0x7c00 + gdt ; 段基地址
start16:
; 把所有的段寄存器都改成與代碼段寄存器同。
mov ax, 0x7c0
mov ds, ax
mov es, ax
mov ss, ax
mov fs, ax
mov gs, ax
mov sp, 0x0 ;堆棧指針的段偏移爲0
cli ; 關閉中斷
; int 0x13的典型用法
mov dx, 0x0080 ; dh, dl = 0x80第一個硬盤
mov cx, 0x0002 ; 從第二個扇區開始讀取
mov ax, 0x1000 ; 緩衝區段地址0x10000
mov es, ax ; es:bx爲緩衝區地址
xor bx, bx ; 置bx爲0,也即是偏移爲0。
mov ax, 0x0239 ;ah爲讀取的功能號0x2,
; 讀取?個扇區。
int 0x13
jnc next ;進位標誌沒有被置位,則成功讀取。
jmp $ ;讀取失敗進入無限循環。
next:
lgdt [gdtr] ;加載全局描述符表寄存器。
;打開A20地址線,這是關於鍵盤控制器的
;古老的故事,忘得差不多了,還是自己
;參考別的書籍吧。
in al, 0x92
or al, 0x2
out 0x92, al
;典型的開啓保護模式方式,即給系統寄存器
;cr0的第0位置1。
mov eax, cr0
or eax, 1
mov cr0, eax
; 加載段選擇子(從第0個算起)
; 只有gs加載了第4個,段基地址爲
; 0xb8000,即是文本模式下顯存。
; 其他都加載爲第2個
; 段基地址爲0x10000
mov ax, 2 * 8
mov ds, ax
mov es, ax
mov fs, ax
mov ss, ax
mov ax, 4 * 8
mov gs, ax
; 跳轉到代碼段,即是加載代碼段選擇子
; 第3個,段基地址爲0x10000,爲64K處
; 段內偏移爲0。
jmp dword 3 * 8 : 0
jmp $ ;根本就不可能運行到這裏。
times 510 - ($ - $$) db 0 ;引導扇區除了前面代碼和最後
;兩個字節外,都填充0。
db 0x55, 0xaa ;最後兩字節填充的內容,高字節0xaa。
rem myauto1.cmd
@echo off
nasm -fbin -o myboot01.bin myboot01.s
dd if=myboot01.bin of=hd10meg.img
nasm -fbin -o mykernel01.bin mykernel01.s
dd if=mykernel01.bin of=hd10meg.img seek=1 count=23