【輸入輸出系統】
在學習輸入輸出之前還是回顧一下系統API與程序的優先級問題:
×系統API
程序在調用API的時候也需要進行特權級轉換,這個跟切換程序表沒啥兩樣。是以定義一個API的主要步驟可以是(這裡指的的宏核心):
在IDT增加一個自定義的向量号并初始化成中斷門描述符。中斷處理函數基址假設是sys_call.(該函數在Kernel.asm屬于核心),指定s描述符屬性為DPL=3,最低特權,這樣使用者就可以通路它了。
sys_call:
call save ;儲存寄存器并且檢查重入。
sti ;由于是INT 軟中斷,是以不需要控制8259A
call [sys_call_table + eax * 4] ;eax是功能号
mov [esi + EAXREG - P_STACKBASE],eax ;将eax傳回到程序表裡去
cli
sys_call_table是一個函數數組的基址,這些函數可以用C語言寫.可以看到這個中斷号不此一個API還可以根據傳來的參數選擇調用其他核心函數。那麼接下來就寫觸發API的函數:
get_api: //用彙編代碼寫。C語言調用就行了。
mov eax,0
int 0x90 ;就是這個中斷觸發sys_call 然後根據eax 選擇API。
×優先級
解決程序的優先級:在先前的時鐘中斷進行中,我們也就是簡單的将p_proc_ready指向下一個程序表。每個程序都是公平的,那麼現在就來具體說說實作優先級,在時鐘中斷有一個全局變量是ticks;每10ms會自動加1,那麼我們就根據ticks的方式來計算我們的優先級。程序的執行順序是從程序表基址開始的,但是ticks越大代表程序需要處理的次數越多。
可以在程序表加入兩個字段:ticks(計數器)、priority(優先級) 它們的值一樣;
假如有3個程序:
ticks_1=priority=150;
ticks_2=priority=50;
ticks_3=priority=30;
那麼優先級是怎麼來計算的呢?優先級排程:
(在時鐘中斷代碼處,p_proc_ready->ticks--;表示目前運作的程序優先級減減)
for(p=proc_table;p<=proc_table+ NR_TASKS;p++) //用一個循環周遊所有程序表
{
//找出程序表中ticks值最大的那個,找到後将它指定為要運作的程序.
if(p->ticks > greatest_ticks)
{
greatest_ticks=p->ticks;
p_prco_ready =p ;
}
//這就是一個優先級别排程的核心代碼,其作用就是比較程序表裡所有的ticks.這裡的程序分别是A=150,B=50,C=30.,由于比較是安順序的,是以A在前面的100次ticks都是獨立運作的,直到50ticks的時候,A開始與B同步運作,再減到30的時候A、B、 C都同步運作了. 如果在A、B、C三個程序循環輸出都延遲200ms,也就是20ticks。那麼可以這樣求出三個程序共運作了多少次循環:
A= 100/20 + (50-30)*2/20 +30*3/20 =11次
B= 0/20 + (50-30)*2/20 +30*3/20 =6次 //0表示沒有參與
C= 0/20 + 0/20 +30*3/20 =4次
}優先級排程主要靠程序表的ticks來實作。當ticks最大時,那麼就是一直是此程序運作。如果相等那麼就會同步運作..想要程序絕對同步(程序一個一個運作)的話。隻需要在中斷處理開始的時候:
if(p_proc_ready->ticks>0)return ;//這樣就代表目前程序的ticks還沒有減完的不會去切換程序.
在不是絕對同步的情況下。那麼所有程序隻要在ticks相等後就會一起運作了。
一、鍵盤
接收鍵盤中斷的IRQ就是8259A主片的IRQ1,當今最流行的鍵盤分别是USB和PS/2。
鍵盤中存在着一枚叫做鍵盤編碼器(Keyboard Encoder)的晶片,它通常是Intel 8048以及相容晶片.作用是監視鍵盤的輸入。并把适當的資料傳送給計算機。另外在計算機主機闆上還有一個鍵盤控制器8042(keyboard controller),用來接收和解碼來自鍵盤的資料,并與8259A以及軟體等進行通信。那麼一個鍵盤資訊擷取流程就是:
8048encoder(監視到鍵盤動作後發送掃描碼)--->8042controller(轉換成scan code set1,并放置到輸入緩沖區)--->告之8259A已經産生了IRQ1中斷,此時8042将不再接收掃描碼,直到緩沖區被清空才會收到更多的掃描碼.那麼我們就必需先處理輸入緩沖區才能讓8042繼續接收掃描碼。
8024有3個寄存器,其大小都是1個位元組:輸出緩沖區(0x60)、輸入緩沖區(0x60)、狀态寄存器(0x64)、控制寄存器(0x64)、 可以看到就兩個端口号,需要用in、out兩種指令進行讀取通路。
若想要擷取鍵盤的具體資訊那就需要根據scan_code_set1這個掃描碼表格去判斷了,這裡處理組合鍵有點複雜!!!掃描碼的長度不是統一的這就需要定義一個緩沖區來存放掃描碼。然後再将它轉換成ASCII碼或者功能鍵。
鍵盤緩沖區可以這樣:
typedef s_kb{
char* p_head; 緩沖區的頭部,用來訓示空閑的位元組位置,自增指針。
char* p_tial;指向鍵盤任務應處理的位元組,通常是tial---head這段就是目前要處理的掃描碼.
int length; 緩沖區目前掃描碼資料有多少位元組
char* buf[NR_KB_SIZE];//緩沖區
};KB_INPUT;
那麼現在就可以在鍵盤IRQ1(8259A)中用8042接收掃描碼并且讀出輸入緩沖區的資料到KB_INPUT,然後在對照scan_code_set1表格分析,得到合适的鍵盤資訊!!!
二、TTY(終端)
俗稱顯示器,其實它叫做終端。跟其他的硬體一樣,也需要進行端口的操作.隻是TTY把結果用可視化展現給人看而已!!!
那麼你肯定想到了,就是操作寄存器了。TTY的寄存器組,這裡就用VGA模式的來說明:
VGA寄存器非常之多不過操作端口就一個0x3d5(CRT Controller Register).通路寄存器的方式需要兩端口操作:
out 0x3d4,idex ;這一步是确定0x3d5操作的索引
out 0x3d5,new_value;這一步輸出到索引寄存器中.
80×25模式下的顯示器,顯存有32KB。基位址是b8000,那麼一個螢幕需要4KB.這就代表該顯存能存放8個螢幕的内容。這就可以讓我們的程序切換顯存的基址,來達到終端的效果。也就是一個程序對應一個顯存的基位址。不同程序的讀寫當然也是以顯存基址為标準了。
TTY,VGA模式的操作也就是簡單的兩項:不過它的索引涉及到了很多内容比如光标、顯存基址。
三、C語言的Printf
C函數庫裡面的一個函數,先看看函數原型:
int printf(
const char* [, ]...
);
wsprintf(char *buf ,const *fmt,va_list args) //va_list是無類型的一個變量并且最後參數是可變參數。
C函數的參數壓棧是第一個參數最後壓棧,那麼fmt就是在參數的低位址下面。那麼再調用函數後.fmt的esp是确定的再往上就是參數了。那麼 用fmt的堆棧位址+4就可以間接通路到傳進來的參數。若要确定參數的個數那麼在調用函數之前。記下棧的位置就可以了!!!!這個函數是将後面的參數格式化輸出到緩沖區:
printf()原理是一樣的。隻不過它輸出到顯存。
【總結】
硬體與軟體。
從8259A --->8253(時鐘晶片)--->8048(KerboardCode)鍵盤編碼晶片(監視鍵盤)--->8042(keyboardController)鍵盤控制器,接收8048發送的scan_code_set1--->TTY(終端).
其大部分是對端口的I/O操作.
從引導程式-Boot.bin-->Loader.bn-->kernel.bin核心--->程序--->優先級--->I/O.
其大部分是圍繞CPU的保護模式。。當然這裡還引用了很多自主的邏輯。如程序表、程序排程、優先級排程,而這是自主的邏輯又必需遵循CPU的規則。應該這麼說這些自主的邏輯都是從保護模式的規則裡提煉抽取出來的。
好了。。放松一下.............