天衣有缝

冠盖满京华,斯人独憔悴~
posts - 35, comments - 115, trackbacks - 0, articles - 0
   :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

5课:中断和异常2      下载源代码


声明:转载请保留

译者http://www.cppblog.com/jinglexy

原作者:xiaoming.mo at skelix dot org

MSN & Email: jinglexy at yahoo dot com dot cn

目标


在上一节课中,我们介绍了如何处理异常。本课中,我们讲学习如何在skelix中使用时钟中断和键盘。


可编程定时器通常使用主板上的8253/8254芯片。经过设置后,它每隔一段时间会触发一个中断,我们将利用它来实现多任务的抢占。这个定时器芯片有3个独立的计数器模块,可以使用0x40~0x42端口来操作它们,每个计数器有自己的16为计数寄存器,并且可以在6中状态下工作运行,这个计数器可以使用BCD或者16进制。通过0x43端口可以访问这3个计数寄存器,该控制端口操作字格式如下:

 

 7,6

Select Counter ,选择对那个计数器进行操作。 “00” 表示选择 Counter 0 “01” 表示选择 Counter 1 “10” 表示选择 Counter 2 “11” 表示 Read-Back Command (仅对于 8254 ,对于 8253 无效)

5,4

Read/Write/Latch 格式位。 “00” 表示锁存( Latch )当前计数器的值; “01” 只读写计数器的高字节 ( MSB ); “10” 只读写计数器的低字节( LSB ); “11” 表示先读写计数器的 LSB ,再读写 MSB

3-1

Mode bits ,控制各通道的工作模式。 “000” 对应 Mode 0 “001” 对应 Mode 1 “010” 对应 Mode 2 “011”对应 Mode 3 “100” 对应 Mode 4 “101” 对应 Mode 5

  0

控制计数器的存储模式。 0 表示以二进制格式存储, 1 表示计数器中的值以 BCD 格式存储

 

根据Intel手册,系统上电后,8254状态是不可知的。工作模式,计数值,输出基数都是不可知的。只有对它进行编程,才能使用各个计数器模块。现在我们来看下程序:
05/timer/time.c

 

void timer_install(int hz) {        // 设置定时器多长时间发送一个中断给cpu
    unsigned int divisor = 1193180/hz;

由于外部晶振电路频率是1193180, 所以设置计数器值为1193180/hz

表示当计数从0累加到1193180/hz后发一个方波脉冲给cpu


    outb(0x36, 0x43);               //
二进制,工作模式为3,先写LSB再写MSB
    outb(divisor&0xff, 0x40);
    outb(divisor>>8, 0x40);
    outb(inb(0x21)&0xfe, 0x21);     //
设置PIC1 的掩码第0位:允许时钟中断
}

volatile unsigned int timer_ticks = 0;

void do_timer(void) {               //
时钟中断处理程序
    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);              //
发送eoi:通知PIC1 中断例程已处理完成,可以接收新的中断了

因为timer只链接在PIC1上,所以不需要 oub(0x20, 0xa0) 来告知PIC2

}

 

另外我们还需要改一些其他文件:
05/timer/include/isr.h

#define VALID_ISR    (32+1)        // 我们添加了一个新的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      # <<<<<
添加的项 >>>>>>
        isrNoError        0x1b
        isrNoError        0x1c
        isrNoError        0x1d
        isrNoError        0x1e
        isrNoError        0x1f
        isrNoError        0x20          # <<<<<
添加的项 >>>>>>

 

05/timer/init.c

void
init(void) {
    char wheel[] = {'\\', '|', '/', '-'};
    int i = 0;

    idt_install();
    pic_install();
    timer_install(100);            //
每秒中100次时钟中断,linux是这样的,windows200

    sti();                         // 不要忘了使能中断哦

    for (;;) {        __asm__ ("movb    %%al,    0xb8000+160*24"::"a"(wheel[i]));
        if (i == sizeof wheel)
            i = 0;
        else
            ++i;
    }
}

 

我们还是使用以前的Makefile,当然需要加入新的模块到 KERNEL_OBJS 中:
05/timer/Makefile

KERNEL_OBJS= load.o init.o isr.o timer.o libcc.o scr.o kprintf.o exceptions.o

运行make编译一把,在让vmware执行一下final.img,是不是很有成就感?

键盘

好啦,我们已经知道在屏幕上显示一些东西了,现在学习按键处理,然后在显示出来。

当一个键按下时,一个8位扫描码会发送给计算机。例如,‘a’键按下后,扫描码0x1e(取决于键盘布局,不要告诉我你用的日本键盘,鄙视一个)发送,当翻开按键时,扫描码最高位置一并发送:0x1e | 0x80 = 0x9e。对于一些特殊按键,如breakhome键等,这里不做处理,有兴趣的同学可以查找相关资料。一些可见按键对于本课来说已足够。

下面是我使用的按键码表,如果你确实用到了特殊键盘,最好找到对应的资料。

扫描码

扫描码

.

......

......

......

......

 

 

 

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

正如你看到的,ctrlshiftalt键以普通扫描码发送,所以我们可以重新映射这些键以显示到屏幕上。

访问键盘控制器仍然经由端口:

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,
    };

上面是一堆函数指针,当获取到扫描码后,我们使用这个表找到相关的处理函数,进行相关的处理。unp是未定义的键,pln是可显示字符,ctl是控制键ctrlshf是控制键shiftalt是控制键altfun函数用于F1F12

    com = 0;
    scan_code = inb(0x60);                //
80420x60端口获取扫描码

    (*key_way[scan_code&0x7f])();         // 0x7f是放开按键的掩码


    /*
按键已处理 */
    outb((com=inb(0x61))|0x80, 0x61);     //
当我们从0x60端口读完扫描码后,这个扫描码并不会自动删除,

    outb(com&0x7f, 0x61);                 // 同时也阻止了我们读下一系列按键,所以我们需要通知键盘控制器按键已处理,

                                          // 做法很简单:只需要通过0x61端口的最高位disablere-enable键盘即可,

                                          //  7: 0=Enable keyboard; 1=Disable keyboard
    outb(0x20, 0x20);                     //
发送EOI:中断处理已完成
}

static unsigned char shf_p = 0;           //
保存Ctrl, Shift  Alt键的状态
static unsigned char ctl_p = 0;
static unsigned char alt_p = 0;
static unsigned char scan_code;           //
当前处理的扫描码

/*
这个函数用于打印可打印字符 */
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未按下的扫描码对应的字符,

key_map[?][1]责对应按下shift的键码。


    if (scan_code & 0x80)
        return;

    // 已经按下这个键了,那就什么也不做

    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) {
}

/*
暂不实现特殊键 */
static void
unp(void) {
}

 

当有shift键按下时,我们打印大写字母。
void
kb_install(void) {
    outb(inb(0x21)&0xfd, 0x21);
}

 

我们几乎实现完了,最后在中断入口表中添加一项:
05/keyboard/isr.s

        .long    do_timer, isr0x20, do_kb, isr0x21  # 注意:加入了按键处理项
        isrNoError        0x20
        isrNoError        0x21      #
这里也加一个宏定义

05/keyboard/include/isr.h

#define VALID_ISR    (32+2)         # ISR个数再加1


05/keyboard/init.c

    timer_install(100);
    kb_install();      /*
安装键盘处理 */
    sti();

 

MakefileKERNEL_OBJS 加入新的模块:

05/keyboard/MakefileKERNEL_OBJS= load.o init.o isr.o timer.o libcc.o scr.o kprintf.o exceptions.o kb.o

 

运行make编译一把,再用vmware执行,看看是不是可以处理按键了,退格键也可以了。

你甚至可以再上面写一个hello, world,只是不能编译:)

 

 


只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理