用匯編語言實現一個操作系統雛形(SnailOS 0.00)

效果圖 (修改了調度算法後,下面描述的問題已經沒有了,希望不會引入新的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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章