第5課:中斷和異常2
下載源代碼
聲明:轉(zhuǎn)載請(qǐng)保留:
譯者:http://www.shnenglu.com/jinglexy
原作者:xiaoming.mo at skelix dot org
MSN & Email: jinglexy at yahoo dot com dot cn
目標(biāo)
在上一節(jié)課中,我們介紹了如何處理異常。本課中,我們講學(xué)習(xí)如何在skelix中使用時(shí)鐘中斷和鍵盤。
可編程定時(shí)器通常使用主板上的8253/8254芯片。經(jīng)過(guò)設(shè)置后,它每隔一段時(shí)間會(huì)觸發(fā)一個(gè)中斷,我們將利用它來(lái)實(shí)現(xiàn)多任務(wù)的搶占。這個(gè)定時(shí)器芯片有3個(gè)獨(dú)立的計(jì)數(shù)器模塊,可以使用0x40~0x42端口來(lái)操作它們,每個(gè)計(jì)數(shù)器有自己的16為計(jì)數(shù)寄存器,并且可以在6中狀態(tài)下工作運(yùn)行,這個(gè)計(jì)數(shù)器可以使用BCD或者16進(jìn)制。通過(guò)0x43端口可以訪問(wèn)這3個(gè)計(jì)數(shù)寄存器,該控制端口操作字格式如下:
位 7,6
|
Select Counter ,選擇對(duì)那個(gè)計(jì)數(shù)器進(jìn)行操作。 “00” 表示選擇 Counter 0 , “01” 表示選擇 Counter 1 , “10” 表示選擇 Counter 2 , “11” 表示 Read-Back Command (僅對(duì)于 8254 ,對(duì)于 8253 無(wú)效)
|
位 5,4
|
Read/Write/Latch 格式位。 “00” 表示鎖存( Latch )當(dāng)前計(jì)數(shù)器的值; “01” 只讀寫計(jì)數(shù)器的高字節(jié) ( MSB ); “10” 只讀寫計(jì)數(shù)器的低字節(jié)( LSB ); “11” 表示先讀寫計(jì)數(shù)器的 LSB ,再讀寫 MSB
|
位 3-1
|
Mode bits ,控制各通道的工作模式。 “000” 對(duì)應(yīng) Mode 0 ; “001” 對(duì)應(yīng) Mode 1 ; “010” 對(duì)應(yīng) Mode 2 ; “011”對(duì)應(yīng) Mode 3 ; “100” 對(duì)應(yīng) Mode 4 ; “101” 對(duì)應(yīng) Mode 5
|
位 0
|
控制計(jì)數(shù)器的存儲(chǔ)模式。 0 表示以二進(jìn)制格式存儲(chǔ), 1 表示計(jì)數(shù)器中的值以 BCD 格式存儲(chǔ)
|
根據(jù)Intel手冊(cè),系統(tǒng)上電后,8254狀態(tài)是不可知的。工作模式,計(jì)數(shù)值,輸出基數(shù)都是不可知的。只有對(duì)它進(jìn)行編程,才能使用各個(gè)計(jì)數(shù)器模塊?,F(xiàn)在我們來(lái)看下程序:
05/timer/time.c
void timer_install(int hz)
{ // 設(shè)置定時(shí)器多長(zhǎng)時(shí)間發(fā)送一個(gè)中斷給cpu
unsigned int divisor = 1193180/hz;
由于外部晶振電路頻率是1193180, 所以設(shè)置計(jì)數(shù)器值為1193180/hz,
表示當(dāng)計(jì)數(shù)從0累加到1193180/hz后發(fā)一個(gè)方波脈沖給cpu
outb(0x36,
0x43); //
二進(jìn)制,工作模式為3,先寫LSB再寫MSB
outb(divisor&0xff, 0x40);
outb(divisor>>8, 0x40);
outb(inb(0x21)&0xfe, 0x21); // 設(shè)置PIC1 的掩碼第0位:允許時(shí)鐘中斷
}
volatile unsigned int timer_ticks = 0;
void do_timer(void)
{ //
時(shí)鐘中斷處理程序
int x , y;
++timer_ticks;
get_cursor(&x, &y);
set_cursor(71, 24);
kprintf(KPL_DUMP, "%x", timer_ticks);
set_cursor(x, y);
outb(0x20,
0x20); //
發(fā)送eoi:通知PIC1 中斷例程已處理完成,可以接收新的中斷了
因?yàn)?span lang="EN-US">timer只鏈接在PIC1上,所以不需要 oub(0x20, 0xa0) 來(lái)告知PIC2 了
}
另外我們還需要改一些其他文件:
05/timer/include/isr.h
#define VALID_ISR
(32+1) // 我們添加了一個(gè)新的ISR例程到isr表中
05/timer/isr.s
isr: .long
divide_error, isr0x00, debug_exception, isr0x01
.long breakpoint,
isr0x02, nmi, isr0x03
.long overflow,
isr0x04, bounds_check, isr0x05
.long
invalid_opcode, isr0x06, cop_not_avalid, isr0x07
.long
double_fault, isr0x08, overrun, isr0x09
.long invalid_tss,
isr0x0a, seg_not_present, isr0x0b
.long
stack_exception, isr0x0c, general_protection, isr0x0d
.long page_fault,
isr0x0e, reversed, isr0x0f
.long
coprocessor_error, isr0x10, reversed, isr0x11
.long reversed,
isr0x12, reversed, isr0x13
.long reversed,
isr0x14, reversed, isr0x15
.long reversed,
isr0x16, reversed, isr0x17
.long reversed,
isr0x18, reversed, isr0x19
.long reversed,
isr0x1a, reversed, isr0x1b
.long reversed, isr0x1c,
reversed, isr0x1d
.long reversed,
isr0x1e, reversed, isr0x1f
.long do_timer,
isr0x20 # <<<<< 添加的項(xiàng) >>>>>>
isrNoError
0x1b
isrNoError
0x1c
isrNoError
0x1d
isrNoError
0x1e
isrNoError
0x1f
isrNoError
0x20 #
<<<<< 添加的項(xiàng) >>>>>>
05/timer/init.c
void
init(void) {
char wheel[] = {'\\', '|', '/', '-'};
int i = 0;
idt_install();
pic_install();
timer_install(100); //
每秒中100次時(shí)鐘中斷,linux是這樣的,windows是200次
sti();
// 不要忘了使能中斷哦
for (;;)
{ __asm__
("movb %%al,
0xb8000+160*24"::"a"(wheel[i]));
if (i == sizeof wheel)
i = 0;
else
++i;
}
}
我們還是使用以前的Makefile,當(dāng)然需要加入新的模塊到 KERNEL_OBJS
中:
05/timer/Makefile
KERNEL_OBJS= load.o init.o isr.o timer.o libcc.o scr.o
kprintf.o exceptions.o
運(yùn)行make編譯一把,在讓vmware執(zhí)行一下final.img,是不是很有成就感?
鍵盤
好啦,我們已經(jīng)知道在屏幕上顯示一些東西了,現(xiàn)在學(xué)習(xí)按鍵處理,然后在顯示出來(lái)。
當(dāng)一個(gè)鍵按下時(shí),一個(gè)8位掃描碼會(huì)發(fā)送給計(jì)算機(jī)。例如,‘a’鍵按下后,掃描碼0x1e(取決于鍵盤布局,不要告訴我你用的日本鍵盤,鄙視一個(gè))發(fā)送,當(dāng)翻開(kāi)按鍵時(shí),掃描碼最高位置一并發(fā)送:0x1e | 0x80 = 0x9e。對(duì)于一些特殊按鍵,如break,home鍵等,這里不做處理,有興趣的同學(xué)可以查找相關(guān)資料。一些可見(jiàn)按鍵對(duì)于本課來(lái)說(shuō)已足夠。
下面是我使用的按鍵碼表,如果你確實(shí)用到了特殊鍵盤,最好找到對(duì)應(yīng)的資料。
掃描碼
|
鍵
|
掃描碼
|
鍵
|
.
|
......
|
......
|
......
|
......
|
|
|
|
01
|
ESC
|
02
|
1!
|
03
|
2@
|
04
|
3#
|
05
|
4$
|
06
|
5%
|
07
|
6^
|
08
|
7&
|
09
|
8*
|
0A
|
9(
|
0B
|
0)
|
0C
|
-_
|
0D
|
=+
|
0E
|
<--
|
0F
|
TAB
|
10
|
qQ
|
11
|
wW
|
12
|
eE
|
13
|
rR
|
14
|
tT
|
15
|
yY
|
16
|
uU
|
17
|
iI
|
18
|
oO
|
19
|
pP
|
1A
|
[{
|
1B
|
]}
|
1C
|
Enter
|
1D
|
LCTL
|
1E
|
aA
|
1F
|
sS
|
20
|
dD
|
21
|
fF
|
22
|
gG
|
23
|
hH
|
24
|
jJ
|
25
|
kK
|
26
|
lL
|
27
|
;:
|
28
|
'"
|
29
|
`~
|
2A
|
LSHT
|
2B
|
\|
|
2C
|
zZ
|
2D
|
xX
|
2E
|
cC
|
2F
|
vV
|
30
|
bB
|
31
|
nN
|
32
|
mM
|
33
|
,<
|
34
|
.>
|
35
|
/?
|
36
|
RSHT
|
37
|
**
|
38
|
LALT
|
39
|
SPACE
|
3B-44
|
F1-F10
|
57
|
F11
|
58
|
F12
|
正如你看到的,ctrl,shift,alt鍵以普通掃描碼發(fā)送,所以我們可以重新映射這些鍵以顯示到屏幕上。
訪問(wèn)鍵盤控制器仍然經(jīng)由端口:
05/keyboard/kb.c
void
do_kb(void) {
int com;
void (*key_way[0x80])(void) = {
/*00*/unp, unp, pln, pln, pln, pln,
pln, pln,
/*08*/pln, pln, pln, pln, pln, pln, pln,
pln,
/*10*/pln, pln, pln, pln, pln, pln, pln,
pln,
/*18*/pln, pln, pln, pln, pln, ctl, pln,
pln,
/*20*/pln, pln, pln, pln, pln, pln, pln,
pln,
/*28*/pln, pln, shf, pln, pln, pln, pln,
pln,
/*30*/pln, pln, pln, pln, pln, pln, shf,
pln,
/*38*/alt, pln, unp, fun, fun, fun, fun,
fun,
/*40*/fun, fun, fun, fun, fun, unp, unp,
unp,
/*48*/unp, unp, unp, unp, unp, unp, unp,
unp,
/*50*/unp, unp, unp, unp, unp, unp, unp,
fun,
/*58*/fun, unp, unp, unp, unp, unp, unp,
unp,
/*60*/unp, unp, unp, unp, unp, unp, unp,
unp,
/*68*/unp, unp, unp, unp, unp, unp, unp,
unp,
/*70*/unp, unp, unp, unp, unp, unp, unp,
unp,
/*78*/unp, unp, unp, unp, unp, unp, unp,
unp,
};
上面是一堆函數(shù)指針,當(dāng)獲取到掃描碼后,我們使用這個(gè)表找到相關(guān)的處理函數(shù),進(jìn)行相關(guān)的處理。unp是未定義的鍵,pln是可顯示字符,ctl是控制鍵ctrl,shf是控制鍵shift,alt是控制鍵alt,fun函數(shù)用于F1到F12。
com = 0;
scan_code =
inb(0x60); //
從8042的0x60端口獲取掃描碼
(*key_way[scan_code&0x7f])(); //
0x7f是放開(kāi)按鍵的掩碼
/* 按鍵已處理 */
outb((com=inb(0x61))|0x80, 0x61); //
當(dāng)我們從0x60端口讀完掃描碼后,這個(gè)掃描碼并不會(huì)自動(dòng)刪除,
outb(com&0x7f,
0x61);
// 同時(shí)也阻止了我們讀下一系列按鍵,所以我們需要通知鍵盤控制器按鍵已處理,
// 做法很簡(jiǎn)單:只需要通過(guò)0x61端口的最高位disable和re-enable鍵盤即可,
// 位 7: 0=Enable keyboard; 1=Disable
keyboard
outb(0x20,
0x20);
// 發(fā)送EOI:中斷處理已完成
}
static unsigned char shf_p =
0; // 保存Ctrl, Shift 和 Alt鍵的狀態(tài)
static unsigned char ctl_p = 0;
static unsigned char alt_p = 0;
static unsigned char scan_code;
// 當(dāng)前處理的掃描碼
/* 這個(gè)函數(shù)用于打印可打印字符 */
static void
pln(void) {
static const char key_map[0x3a][2] = {
/*00*/{0x0, 0x0}, {0x0, 0x0}, {'1', '!'},
{'2', '@'},
/*04*/{'3', '#'}, {'4', '$'}, {'5', '%'},
{'6', '^'},
/*08*/{'7', '&'}, {'8', '*'}, {'9',
'('}, {'0', ')'},
/*0c*/{'-', '_'}, {'=', '+'},
{'\b','\b'},{'\t','\t'},
/*10*/{'q', 'Q'}, {'w', 'W'}, {'e', 'E'},
{'r', 'R'},
/*14*/{'t', 'T'}, {'y', 'Y'}, {'u', 'U'},
{'i', 'I'},
/*18*/{'o', 'O'}, {'p', 'P'}, {'[', '{'},
{']', '}'},
/*1c*/{'\n','\n'},{0x0, 0x0}, {'a', 'A'},
{'s', 'S'},
/*20*/{'d', 'D'}, {'f', 'F'}, {'g', 'G'},
{'h', 'H'},
/*24*/{'j', 'J'}, {'k', 'K'}, {'l', 'L'},
{';', ':'},
/*28*/{'\'','\"'},{'`', '~'}, {0x0,
0x0}, {'\\','|'},
/*2c*/{'z', 'Z'}, {'x', 'X'}, {'c', 'C'},
{'v', 'V'},
/*30*/{'b', 'B'}, {'n', 'N'}, {'m', 'M'},
{',', '<'},
/*34*/{'.', '>'}, {'/', '?'}, {0x0,
0x0}, {'*', '*'},
/*38*/{0x0, 0x0}, {' ', ' '} };
定義可打印字符碼表,鍵key_map[?][0]是shift未按下的掃描碼對(duì)應(yīng)的字符,
key_map[?][1]責(zé)對(duì)應(yīng)按下shift的鍵碼。
if (scan_code & 0x80)
return;
// 已經(jīng)按下這個(gè)鍵了,那就什么也不做
print_c(key_map[scan_code&0x7f][shf_p], WHITE,
BLACK); // 打印它:黑紙白字,清清楚楚
}
/* Ctrl鍵 */
static void
ctl(void) {
ctl_p ^= 0x1;
}
/* Alt鍵 */
static void
alt(void) {
alt_p鍵 ^= 0x1;
}
/* Shift鍵 */
static void
shf(void) {
shf_p ^= 0x1;
}
/* F1, F2 ~ F12鍵 */
static void
fun(void) {
}
/* 暫不實(shí)現(xiàn)特殊鍵 */
static void
unp(void) {
}
當(dāng)有shift鍵按下時(shí),我們打印大寫字母。
void
kb_install(void) {
outb(inb(0x21)&0xfd, 0x21);
}
我們幾乎實(shí)現(xiàn)完了,最后在中斷入口表中添加一項(xiàng):
05/keyboard/isr.s
.long do_timer, isr0x20, do_kb, isr0x21 # 注意:加入了按鍵處理項(xiàng)
isrNoError
0x20
isrNoError
0x21 # 這里也加一個(gè)宏定義
05/keyboard/include/isr.h
#define VALID_ISR
(32+2) # ISR個(gè)數(shù)再加1
05/keyboard/init.c
timer_install(100);
kb_install(); /* 安裝鍵盤處理 */
sti();
在Makefile的 KERNEL_OBJS 加入新的模塊:
05/keyboard/MakefileKERNEL_OBJS= load.o init.o isr.o
timer.o libcc.o scr.o kprintf.o exceptions.o kb.o
運(yùn)行make編譯一把,再用vmware執(zhí)行,看看是不是可以處理按鍵了,退格鍵也可以了。
你甚至可以再上面寫一個(gè)hello, world,只是不能編譯:)