linux键盘IO探究和实验[基于哈工大操作系统实验] Linux Keyboard IO Walk-Through
我们使用的键盘,看似非常简单自然,但是其功能的实现却相当有学问,本文将以哈工大操作系统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极光城