极光城

linux键盘IO探究和实验

linux键盘IO探究和实验[基于哈工大操作系统实验] Linux Keyboard IO Walk-Through

Docs Text & Full Code

我们使用的键盘,看似非常简单自然,但是其功能的实现却相当有学问,本文将以哈工大操作系统IO设备实验为基础,讲解一下linux0.11下键盘功能的实现,和简单修改的方法。

一,实现原理

键盘功能主要由如下几个程序实现:

keyboard.S     : 大S,引入c++预编译,使得汇编程序可以用预处理命令。接受键盘中断反馈的键盘码。

tty_io.c           :读汇编程序吃剩下的东西,嚼完往下扔。

console.c       :对了,嚼完的被他接住了….他再嚼两下,吐到您的屏幕上。

  也就是说,一个字符从敲击至到达屏幕,经过的路程简要来说是这样的:

keyboard.S   —read_queue—->  tty_io.c   ——–write_queue—–>  console.c  —-调用__asm__()—> 屏幕。

不关心这些东西,想直接过实验的同学可以直接跳到五。

二,keyboard.S 程序和 f12 功能键

先看一下keyboard.s程序:

/*
 *  linux/kernel/keyboard.S
 *
 *  (C) 1991  Linus Torvalds
 *  Thanks to Alfred Leung for US keyboard patches
 *      Wolfgang Thiel for German keyboard patches
 *      Marc Corsini for the French keyboard
 */
#include <linux/config.h>
.text
.globl keyboard_interrupt
/*
 * these are for the keyboard read functions
 */
size    = 1024      /* must be a power of two ! And MUST be the same               as in tty_io.c !!!! */
head = 4
tail = 8
proc_list = 12
buf = 16
mode:   .byte 0     /* caps, alt, ctrl and shift mode */
leds:   .byte 2     /* num-lock, caps, scroll-lock mode (nom-lock on) */
e0: .byte 0
/*
 *  con_int is the real interrupt routine that reads the
 *  keyboard scan-code and converts it into the appropriate下方的中断服务函数,将按键扫描码,转换成ASCII码
 *  ascii character(s).
 */
keyboard_interrupt:/*这个是键盘中断函数,没几行代码,每次按键盘,就进入这里*/
    pushl %eax
    pushl %ebx
    pushl %ecx
    pushl %edx
    push %ds
    push %es
    movl $0x10,%eax
    mov %ax,%ds
    mov %ax,%es
    xor %al,%al     /* %eax 寄存器放的,就是这次按键按下的按钮编码 */
    inb $0x60,%al
    cmpb $0xe0,%al     /* 简单比较处理一下 */
    je set_e0
    cmpb $0xe1,%al
    je set_e1
    call key_table(,%eax,4)/* 将 eax 按钮号码交给 【key_table】 映射到相应处理函数。keytable 在程序末尾*/
    movb $0,e0
e0_e1:  inb $0x61,%al
    jmp 1f
1:  jmp 1f
1:  orb $0x80,%al
    jmp 1f
1:  jmp 1f
1:  outb %al,$0x61
    jmp 1f
1:  jmp 1f
1:  andb $0x7F,%al
    outb %al,$0x61
    movb $0x20,%al
    outb %al,$0x20
    pushl $0
    call do_tty_interrupt
    addl $4,%esp
    pop %es
    pop %ds
    popl %edx
    popl %ecx
    popl %ebx
    popl %eax
    iret
set_e0: movb $1,e0
    jmp e0_e1
set_e1: movb $2,e0
    jmp e0_e1
/*
 * This routine fills the buffer with max 8 bytes, taken from
 * %ebx:%eax. (%edx is high). The bytes are written in the
 * order %al,%ah,%eal,%eah,%bl,%bh ... until %eax is zero.
 */
put_queue:               /*写队列函数,调用时,把接收到的一组按钮吐到read_queue里面给tty_io*/
    pushl %ecx
    pushl %edx
    movl table_list,%edx        # read-queue for console
    movl head(%edx),%ecx
1:  movb %al,buf(%edx,%ecx)
    incl %ecx
    andl $size-1,%ecx
    cmpl tail(%edx),%ecx        # buffer full - discard everything
    je 3f
    shrdl $8,%ebx,%eax
    je 2f
    shrl $8,%ebx
    jmp 1b
2:  movl %ecx,head(%edx)
    movl proc_list(%edx),%ecx
    testl %ecx,%ecx
    je 3f
    movl $0,(%ecx)
3:  popl %edx
    popl %ecx
    ret
                          /*控制特殊键盘按钮的处理函数 ,alt shift ctrl 什么的*/
ctrl:   movb $0x04,%al
    jmp 1f
alt:    movb $0x10,%al
1:  cmpb $0,e0
    je 2f
    addb %al,%al
2:  orb %al,mode
    ret
unctrl: movb $0x04,%al
    jmp 1f
unalt:  movb $0x10,%al
1:  cmpb $0,e0
    je 2f
    addb %al,%al
2:  notb %al
    andb %al,mode
    ret
lshift:
    orb $0x01,mode
    ret
unlshift:
    andb $0xfe,mode
    ret
rshift:
    orb $0x02,mode
    ret
unrshift:
    andb $0xfd,mode
    ret
 
caps:   testb $0x80,mode
    jne 1f
    xorb $4,leds
    xorb $0x40,mode
    orb $0x80,mode
set_leds:
    call kb_wait
    movb $0xed,%al      /* set leds command */
    outb %al,$0x60
    call kb_wait
    movb leds,%al
    outb %al,$0x60
    ret
uncaps: andb $0x7f,mode
    ret
scroll:
    xorb $1,leds
    jmp set_leds
num:    xorb $2,leds
    jmp set_leds
/*
 *  curosr-key/numeric keypad cursor keys are handled here.
 *  checking for numeric keypad etc.
 */
cursor:                    /*光标处理,不用管,代码省略*/
...
...
cur2:           /* e0 forces cursor movement */
...
 
cur:    
...
ok_cur:
...
#if defined(KBD_FR)
num_table:
    .ascii "789 456 1230."
#else
num_table:
    .ascii "789 456 1230,"
#endif
cur_table:
    .ascii "HA5 DGC YB623"
/*
 * 下面这个函数有意思了,他就是处理function1-12的函数,从下面的映射表key_table可以看出,任意F被按下都跳到这里处理
 */
func:
    pushl %eax
    pushl %ecx
    pushl %edx
    call show_stat  /*这里调用了一个位于【sched.c】中的c语言系统调用函数,打开sched.c可以看到,这是输出进程状态的函数【show_stat()】,也就是说,linux0.11中,不管是f几,都要show_stat*/
    popl %edx
    popl %ecx
    popl %eax
    subb $0x3B,%al
    jb end_func
    cmpb $9,%al
    jbe ok_func
    subb $18,%al
    cmpb $10,%al
    jb end_func
    cmpb $11,%al
    ja end_func
ok_func:
    cmpl $4,%ecx        /* check that there is enough room */
    jl end_func
    movl func_table(,%eax,4),%eax
    xorl %ebx,%ebx
    jmp put_queue
end_func:
    ret
 
/*
 * function keys send F1:'esc [ [ A' F2:'esc [ [ B' etc.  这里是【F1到F12的扫描码表】,可见 F12其实是一个esc [[ L 信号 如果处理不当,L就会变成字符溢出 
 很显然,这里Linus先生压根没处理F12按键,所以L字符溢出到了显示屏幕上*/
func_table:
    .long 0x415b5b1b,0x425b5b1b,0x435b5b1b,0x445b5b1b
    .long 0x455b5b1b,0x465b5b1b,0x475b5b1b,0x485b5b1b
    .long 0x495b5b1b,0x4a5b5b1b,0x4b5b5b1b,0x4c5b5b1b
 
#if defined(KBD_FINNISH)   /*此处省略 外文键盘处理的具体代码*/
key_map:
...
...
shift_map:
...
...
alt_map:
...
...
#elif defined(KBD_US)
 
key_map:
...
...
 
shift_map:
...
...
alt_map:
...
...
#elif defined(KBD_GR)
 
key_map:
...
...
...
shift_map:
...
...
alt_map:
...
...
 
#elif defined(KBD_FR)
 
key_map:
...
...
shift_map:
...
...
alt_map:
...
...
#else
#error "KBD-type not defined"
#endif
/*
 * do_self handles "normal" keys, ie keys that don't change meaning
 * and which have just one character returns.下面这个函数,被大多普通按键调用。
 */
do_self:
    lea alt_map,%ebx
    testb $0x20,mode        /* alt-gr */
    jne 1f
    lea shift_map,%ebx
    testb $0x03,mode
    jne 1f
    lea key_map,%ebx
1:  movb (%ebx,%eax),%al
    orb %al,%al
    je none
    testb $0x4c,mode        /* ctrl or caps */
    je 2f
    cmpb $'a,%al
    jb 2f
    cmpb $'},%al
    ja 2f
    subb $32,%al
2:  testb $0x0c,mode        /* ctrl */
    je 3f
    cmpb $64,%al
    jb 3f
    cmpb $64+32,%al
    jae 3f
    subb $64,%al
3:  testb $0x10,mode        /* left alt */
    je 4f
    orb $0x80,%al
4:  andl $0xff,%eax
    xorl %ebx,%ebx
    call put_queue
none:   ret
/*
 * minus has a routine of it's own, as a 'E0h' before  减号按钮得到了李刚的特殊待遇,单独处理
 * the scan code for minus means that the numeric keypad
 * slash was pushed.
 */
minus:  cmpb $1,e0
    jne do_self
    movl $'/,%eax
    xorl %ebx,%ebx
    jmp put_queue
/*
 * 这是【按键处理映射表】,这里你可以看到他们的编码,和对应的实际按键字母。
 * 大部分按键对应do_self普通处理。
 * func按键映射在下面
 */
key_table:
    .long none,do_self,do_self,do_self  /* 00-03 s0 esc 1 2 */
    .long do_self,do_self,do_self,do_self   /* 04-07 3 4 5 6 */
    .long do_self,do_self,do_self,do_self   /* 08-0B 7 8 9 0 */
    .long do_self,do_self,do_self,do_self   /* 0C-0F + ' bs tab */
    .long do_self,do_self,do_self,do_self   /* 10-13 q w e r */
    .long do_self,do_self,do_self,do_self   /* 14-17 t y u i */
    .long do_self,do_self,do_self,do_self   /* 18-1B o p } ^ */
    .long do_self,ctrl,do_self,do_self  /* 1C-1F enter ctrl a s */
    .long do_self,do_self,do_self,do_self   /* 20-23 d f g h */
    .long do_self,do_self,do_self,do_self   /* 24-27 j k l | */
    .long do_self,do_self,lshift,do_self    /* 28-2B { para lshift , */
    .long do_self,do_self,do_self,do_self   /* 2C-2F z x c v */
    .long do_self,do_self,do_self,do_self   /* 30-33 b n m , */
    .long do_self,minus,rshift,do_self  /* 34-37 . - rshift * */
    .long alt,do_self,caps,func     /* 38-3B alt sp caps f1 */
    .long func,func,func,func       /* 3C-3F f2 f3 f4 f5 这里映射F按键 , 用func 处理*/
    .long func,func,func,func       /* 40-43 f6 f7 f8 f9 */
    .long func,num,scroll,cursor        /* 44-47 f10 num scr home */
    .long cursor,cursor,do_self,cursor  /* 48-4B up pgup - left */
    .long cursor,cursor,do_self,cursor  /* 4C-4F n5 right + end */
    .long cursor,cursor,cursor,cursor   /* 50-53 dn pgdn ins del */
    .long none,none,do_self,func        /* 54-57 sysreq ? < f11 */
    .long func,none,none,none       /* 58-5B f12 ? ? ? 这里映射了F12 用func处理,下面没有好玩的了*/
    .long none,none,none,none       /* 5C-5F ? ? ? ? */
    ...
        ... 

三,tty_io.c程序

关于按键处理,这个程序中仅一个函数有意思

/*
 *  linux/kernel/tty_io.c
 *
 *  (C) 1991  Linus Torvalds
 */
 
/*
 * these are the tables used by the machine code handlers.
 * you can implement pseudo-tty's or something by changing
 * them. Currently not done.
 */
struct tty_queue * table_list[]={
    &tty_table[0].read_q, &tty_table[0].write_q,
    &tty_table[1].read_q, &tty_table[1].write_q,
    &tty_table[2].read_q, &tty_table[2].write_q
    };
/*copy_to_cooked函数处理从按键读出来的东西,在read_q中,放到要写入屏幕的队列write_q中*/
/*函数中那一堆PUTCH 就把字符转移到write_q了*/ 
void copy_to_cooked(struct tty_struct * tty)
{
    signed char c;
 
    while (!EMPTY(tty->read_q) && !FULL(tty->secondary)) {
        GETCH(tty->read_q,c);/*这里从队列里取出按键码,供下文处理*/
 
/*if(c=='L'){printk("F12或者L被按下了");}*/、  
 
/*if(c=='L') continue;*//*下面都是比较常规的处理代码了,主要功能是将read_q 中的东西放到write_q中*/
...
... 

四,console.c程序

这个程序从write_q中拿出字母,放(tù)到您的屏幕上。

/*
 *  linux/kernel/console.c
 *
 *  (C) 1991  Linus Torvalds
 */
 
/*
 *  console.c
 *
 * 本程序实现了如下的函数
 *  'void con_init(void)'
 *  'void con_write(struct tty_queue * queue) 这个是我们主要讨论的
 * 
 */
 
#include <linux/sched.h>
#include <linux/tty.h>
#include <asm/io.h>
#include <asm/system.h>
 
/*
 * These are set up by the setup-routine at boot-time:
 */
 
#define ORIG_X          (*(unsigned char *)0x90000)
#define ORIG_Y          (*(unsigned char *)0x90001)
#define ORIG_VIDEO_PAGE     (*(unsigned short *)0x90004)
#define ORIG_VIDEO_MODE     ((*(unsigned short *)0x90006) & 0xff)
#define ORIG_VIDEO_COLS     (((*(unsigned short *)0x90006) & 0xff00) >> 8)
#define ORIG_VIDEO_LINES    (25)
#define ORIG_VIDEO_EGA_AX   (*(unsigned short *)0x90008)
#define ORIG_VIDEO_EGA_BX   (*(unsigned short *)0x9000a)
#define ORIG_VIDEO_EGA_CX   (*(unsigned short *)0x9000c)
 
#define VIDEO_TYPE_MDA      0x10    /* Monochrome Text Display  */
#define VIDEO_TYPE_CGA      0x11    /* CGA Display          */
#define VIDEO_TYPE_EGAM     0x20    /* EGA/VGA in Monochrome Mode   */
#define VIDEO_TYPE_EGAC     0x21    /* EGA/VGA in Color Mode    */
 
#define NPAR 16
 
extern void keyboard_interrupt(void);
 
static unsigned char    video_type;     /* Type of display being used   */
static unsigned long    video_num_columns;  /* Number of text columns   */
static unsigned long    video_size_row;     /* Bytes per row        */
static unsigned long    video_num_lines;    /* Number of test lines     */
static unsigned char    video_page;     /* Initial video page       */
static unsigned long    video_mem_start;    /* Start of video RAM       */
static unsigned long    video_mem_end;      /* End of video RAM (sort of)   */
static unsigned short   video_port_reg;     /* Video register select port   */
static unsigned short   video_port_val;     /* Video register value port    */
static unsigned short   video_erase_char;   /* Char+Attrib to erase with    */
 
static unsigned long    origin;     /* Used for EGA/VGA fast scroll */
static unsigned long    scr_end;    /* Used for EGA/VGA fast scroll */
static unsigned long    pos;
static unsigned long    x,y;
static unsigned long    top,bottom;
static unsigned long    state=0;
static unsigned long    npar,par[NPAR];
static unsigned long    ques=0;
static unsigned char    attr=0x07;
static void sysbeep(void);
 
/*
 * 此处省略一堆函数
 * 
 */
#define RESPONSE "\033[?1;2c"
 
/* 下面这个这个函数就是控制台打印函数(console_write)
*它的功能是从 待写队列write_q 中取出字母简单处理,然后打印到屏幕上
*它的运作原理叫做“循环匹配”,如果直接遇到了一个普通字符,就_asm_()打印出来。如果遇到了屏幕处理命令(回删,翻页,光标移动),就连续吃掉下一个字符来匹配命令
*如果匹配到了,就执行,如果吃了相应长度的字符但是没有命令匹配,就当白吃了,那些字符也不会吐出来再显示了。
*/
void con_write(struct tty_struct * tty)
{
    int nr;
    char c;
 
    nr = CHARS(tty->write_q);
    while (nr--) {
        GETCH(tty->write_q,c);/*每次循环,在这里读取一个char,如果你在这里把c赋值成别的了,那么屏幕就只会显示这一种字符了,比如全是‘*’ */
        /********if(c=='L')continue;//这几句是我写的,原程序没有
        if((c>='a'&&c<='z')||(c>='A'&&c<='Z')||(c>='0'&&c<='9'))        
        if(f12){c='*';}*********/
        switch(state) {
            case 0:
                if (c>31 && c<127) {/*普通字符就打印*/
                    if (x>=video_num_columns) {
                        x -= video_num_columns;
                        pos -= video_size_row;
                        lf();
                    }
                    __asm__("movb attr,%%ah\n\t"
                        "movw %%ax,%1\n\t"
                        ::"a" (c),"m" (*(short *)pos)
                        );
                    pos += 2;
                    x++;
                } else if (c==27)/*如果是escape功能字符,就进入读命令模式*/
                    state=1;
                else if (c==10 || c==11 || c==12)
                    lf();
                else if (c==13)
                    cr();
                else if (c==ERASE_CHAR(tty))
                    del();
                else if (c==8) {
                    if (x) {
                        x--;
                        pos -= 2;
                    }
                } else if (c==9) {
                    c=8-(x&7);
                    x += c;
                    pos += c<<1;
                    if (x>video_num_columns) {
                        x -= video_num_columns;
                        pos -= video_size_row;
                        lf();
                    }
                    c=9;
                } else if (c==7)
                    sysbeep();
                break;
            case 1:/*再读一个字符,进入这里,匹配各种命令*/
                state=0;
                if (c=='[')/*如果是[字符,就进入长命令读取状态*/
                    state=2;
                else if (c=='E')
                    gotoxy(0,y+1);
                else if (c=='M')
                    ri();
                else if (c=='D')
                    lf();
                else if (c=='Z')
                    respond(tty);
                else if (c=='7')
                    save_cur();
                else if (c=='8')
                    restore_cur();/*举个例子,"escape 8" 就是光标退行的意思*/
                break;
            case 2:/*长命令读取*/
                for(npar=0;npar<NPAR;npar++)
                    par[npar]=0;
                npar=0;
                state=3;/*从长命令读取转到长命令处理模式*/
                if ((ques=(c=='?')))/*如果c是?,就再读取一个后处理*/
                    break;
            case 3:/*普通长命令处理*/
                if (c==';' && npar<NPAR-1) {
                    npar++;
                    break;
                } else if (c>='0' && c<='9') {
                    par[npar]=10*par[npar]+c-'0';
                    break;
                } else state=4;/*进入特殊长命令匹配状态*/
            case 4:/*特殊长命令匹配*/
                state=0;
                switch(c) {
                    case 'G': case '`':
                        if (par[0]) par[0]--;
                        gotoxy(par[0],y);
                        break;
                    case 'A':
                        if (!par[0]) par[0]++;
                        gotoxy(x,y-par[0]);
                        break;
                    case 'B': case 'e':
                        if (!par[0]) par[0]++;
                        gotoxy(x,y+par[0]);
                        break;
                    case 'C': case 'a':
                        if (!par[0]) par[0]++;
                        gotoxy(x+par[0],y);
                        break;
                    case 'D':
                        if (!par[0]) par[0]++;
                        gotoxy(x-par[0],y);
                        break;
                    case 'E':
                        if (!par[0]) par[0]++;
                        gotoxy(0,y+par[0]);
                        break;
                    case 'F':
                        if (!par[0]) par[0]++;
                        gotoxy(0,y-par[0]);
                        break;
                    case 'd':
                        if (par[0]) par[0]--;
                        gotoxy(x,par[0]);
                        break;
                    case 'H': case 'f':
                        if (par[0]) par[0]--;
                        if (par[1]) par[1]--;
                        gotoxy(par[1],par[0]);
                        break;
                    case 'J':
                        csi_J(par[0]);
                        break;
                    case 'K':
                        csi_K(par[0]);
                        break;
                    case 'L':
                        csi_L(par[0]);
                        break;
                    case 'M':
                        csi_M(par[0]);
                        break;
                    case 'P':
                        csi_P(par[0]);
                        break;
                    case '@':
                        csi_at(par[0]);
                        break;
                    case 'm':
                        csi_m();
                        break;
                    case 'r':
                        if (par[0]) par[0]--;
                        if (!par[1]) par[1] = video_num_lines;
                        if (par[0] < par[1] &&
                            par[1] <= video_num_lines) {
                            top=par[0];
                            bottom=par[1];
                        }
                        break;
                    case 's':
                        save_cur();
                        break;
                    case 'u':
                        restore_cur();
                        break;
                }
        }
    }
    set_cursor();
}
...
...

五,实现F12按键功能的改变 现在我们知道了这些按键字符的处理流程,我们要做的就是设置一个标志flag,在识别到F12的地方翻转flag,在写屏幕的地方判断flag,来决定是否要输出’*’

那么只要这样就可以了:

1,在大家都有的头文件中加入一个全局变量的外部引用:

asm\system.h 末尾加入:

extern int f12;

然后在console.c中定义这个变量的本体:

int f12=0;

这样就搞定了全局变量。

2,然后在:

keyboard.S: func:子函数中,每一次F12就会进入这里,每一次进入这里就会调用show_stat(),那么直接把sched.c改了:

sched.c:

void show_stat(void)
{
    int i;
    if(f12)f12=0;else f12=1;/*实现标志翻转*/
/****   for (i=0;i<NR_TASKS;i++)原来的显示进程信息都不要了,没什么实际用处,看着闹心。
        if (task[i])
            show_task(i,task[i]);******/
}

3,最后在console.c中判断标志进行输出:

void con_write(struct tty_struct * tty)
{
    int nr;
    char c;
 
    nr = CHARS(tty->write_q);
    while (nr--) {
        GETCH(tty->write_q,c);
        if(c=='L')continue;/*将f12按钮产生的多余输出字符屏蔽*/
        if((c>='a'&&c<='z')||(c>='A'&&c<='Z')||(c>='0'&&c<='9'))/*用星号代替普通字符*/       
        if(f12){c='*';}
 
....
....

编译内核:make clean(清理工程) make all(强制重建工程)

恭喜你,大功告成。

实验主要目的是要分的同学可以飘走做实验了。

六,高端的实验方案

上面提出的办法可以说是实现基本功能最简单的办法了,实际上只修改了3个文件。(实验要求规定可以修改任意文件,数量小于7个即可)。

但是这种方法比较简陋,如果不屏蔽F12按钮按下自动产生的L字符,虽然能过,但是比较不好看。

然而如果按照如上方法屏蔽了,则L字符就不会再出现了,即使你按下L也不显示。

而且,按下F12后,输出队列中仍然有一个L,虽然看不见,这时如果打ls命令回车,其实是Lls,所以第一次敲入的命令是无效的,之后就正常了。

怎么能让这个L问题处理得好一点呢,我们可以这么做: 通过增加一个标志位int block(如法炮制);来跟踪f12。目的是:仅仅屏蔽按下f12之后紧接着输出的L,其他的不屏蔽。

在show_stat()中:

void show_stat(void)
{
    int i;block=1;/*加入一句按下标志*/
    if(f12)f12=0;else f12=1;
 
...

在console.c中不再判断L:

void con_write(struct tty_struct * tty)
{
    int nr;
    char c;
 
    nr = CHARS(tty->write_q);
    while (nr--) {
        GETCH(tty->write_q,c);
        if((c>='a'&&c<='z')||(c>='A'&&c<='Z')||(c>='0'&&c<='9'))        
        if(f12){c='*';}
...
...

在tty_io.c:copy_to_cooked()中:

int tty_read(unsigned channel, char * buf, int nr)
{
    struct tty_struct * tty;
    char c, * b=buf;
    int minimum,time,flag=0;
    long oldalarm;
 
    if (channel>2 || nr<0) return -1;
    tty = &tty_table[channel];
    oldalarm = current->alarm;
    time = 10L*tty->termios.c_cc[VTIME];
    minimum = tty->termios.c_cc[VMIN];
    if (time && !minimum) {
        minimum=1;
        if ((flag=(!oldalarm || time+jiffies<oldalarm)))
            current->alarm = time+jiffies;
    }
    if (minimum>nr)
        minimum=nr;
    while (nr>0) {
        if (flag && (current->signal & ALRMMASK)) {
            current->signal &= ~ALRMMASK;
            break;
        }
        if (current->signal)
            break;
        if (EMPTY(tty->secondary) || (L_CANON(tty) &&
        !tty->secondary.data && LEFT(tty->secondary)>20)) {
            sleep_if_empty(&tty->secondary);
            continue;
        }
        do {
            GETCH(tty->secondary,c);if(c=='L'&&block){block=0;continue;}/*在这里做的修改*/
            if (c==EOF_CHAR(tty) || c==10)
                tty->secondary.data--;
            if (c==EOF_CHAR(tty) && L_CANON(tty))
                return (b-buf);
            else {
                put_fs_byte(c,b++);
                if (!--nr)
                    break;
            }
        } while (nr>0 && !EMPTY(tty->secondary));
        if (time && !L_CANON(tty)) {
            if ((flag=(!oldalarm || time+jiffies<oldalarm)))
                current->alarm = time+jiffies;
            else
                current->alarm = oldalarm;
        }
        if (L_CANON(tty)) {
            if (b-buf)
                break;
        } else if (b-buf >= minimum)
            break;
    }
    current->alarm = oldalarm;
    if (current->signal && !(b-buf))
        return -EINTR;
    return (b-buf);
}

七,大气的解决方案

高端的整改之后,按下F12不会再向缓冲区搞出L了,(虽然搞出也没什么关系),但是你可能发现,不管按下F几,都能实现*转换的功能,这可怎么办。

首先,这是因为我们的标志置位操作是在show_stat中做得,原代码中,只要按下F键就都执行show_stat,那么你只要比较一下得到的F码,然后再决定是否跳入show_stat就行了。

func:
        ;;;不需要保护现场了
    subb $0x3B,%al
    jb end_func
    cmpb $9,%al
    jbe ok_func
    subb $18,%al
    cmpb $10,%al
    jb end_func
    cmpb $11,%al
    ja end_func  ;;;如果不是F12就跳走了
    ;;保护现场
        pushl %eax
    pushl %ecx
    pushl %edx
call show_stat    ;;;;如果是f12,就执行这个函数
    popl %edx   ;;;恢复现场
    popl %ecx
     popl %eax
ok_func:
    cmpl $4,%ecx /* check that there is enough room */
    jl end_func
    movl func_table(,%eax,4),%eax
    xorl %ebx,%ebx
    jmp put_queue
end_func:
    ret

八,上档次的解决方法

事实上,经过高端大气的解决办法之后,我们已经实现了F12改状态,非F12不改,且L字符输入输出都无影响的效果。

但是有些时候就很有意思,比如实验要求说:“上传修改后的4个系统文件,没修改的上传原文件,修改其他文件也一并上传,总数小于7即可”。

然后大家做完了检查的时候又说:”包含其他文件的都不行啊,都算错,扣分。”

asm/system.h 这个头文件是没办法了,必须包含,否则没有全局变量,根本无法跨文件处理,怎么样才能不使用shed.c呢?

其实改了show_stat后,它唯一作用就是改了flag12标志位,赋值语句而已,几条汇编即可:

在keyboard.S中:

#include<asm/system.h> 
;上述包含写在keyboard.S的最上边就行,其中包含了你定义的C语言变量  int  f12。
func:   ;;此处不需保护现场了 
    subb $0x3B,%al 
    jb end_func 
    cmpb $9,%al 
    jbe ok_func 
    subb $18,%al 
    cmpb $10,%al 
    jb end_func 
    cmpb $11,%al ;;;比较F12
    ja end_func
;;保护现场
        pushl %eax
    pushl %ecx
    pushl %edx
        cmpb _f12, $0x01   ;;;比较f12和1
    jne fp1
        movb $0x00,_f12    ;;;如果f12==1 就f12=0
        jmp fp2
fp1:
    movb $0x01,_f12    ;;;如果f12==0 就 f12=1
fp2:   
    popl %edx   ;;恢复现场
    popl %ecx
    popl %eax
ok_func:
    cmpl $4,%ecx /* check that there is enough room */
    jl end_func
    movl func_table(,%eax,4),%eax
    xorl %ebx,%ebx
    jmp put_queue
end_func:
    ret

这样就完成了高大上的改造,大部分用汇编完成。 其实看到这里的同学你真的很厉害,治学精神很值得敬佩。 关于键盘IO实验,其实现方法多种多样,又在read_write中改的,有在tty_io中改的,也可以像这样直接几行汇编解决的,也可以全部用汇编在keyboard.S中解决,相互组合近十余种方法。 最后要告诉同学们的是,我们做实验,并不需拘泥于这些方法,更不必为了实验的分数来和实验课老师较劲,会的东西都是你的,以后都可以变成钱,就这么简单。

讲技术,说人话 Aurora极光城