键盘中断初始化和处理
提取的代码如下:
// con_init 函数,初始化控制台(包括键盘)的中断
void con_init(void) {set_trap_gate(0x21, &keyboard_interrupt);
}
// 键盘中断处理函数
.globl _keyboard_interrupt
_keyboard_interrupt:inb $0x60, %al // 从端口0x60读扫描码call key_table(%eax, 4) // 调用key_table+eax*4push $0call _do_tty_interrupt
总结:
这段代码描述了键盘中断初始化和处理的过程:
-
键盘中断初始化 (
con_init
函数):-
con_init
函数设置键盘中断门(trap gate),将键盘中断处理函数keyboard_interrupt
地址加载到中断向量0x21
。 -
这个函数是控制台(包括键盘)初始化的一部分,确保键盘输入能够触发中断。
-
-
键盘中断处理 (
_keyboard_interrupt
函数):-
当键盘被敲击时,产生中断信号。
-
中断处理函数
_keyboard_interrupt
被调用,从端口0x60
读取键盘扫描码。 -
调用
key_table
函数处理扫描码,key_table
是一个查找表,用于将扫描码转换为字符。 -
将
0
压栈,然后调用_do_tty_interrupt
函数进一步处理字符输出。
-
当用户敲击键盘时,触发中断,操作系统捕获中断并调用相应的中断处理函数来读取和处理键盘输入。这是实现键盘输入功能的关键步骤。
处理键盘输入的扫描码
提取的代码如下:
// 在 kernel/chr_drv/keyboard.s 中定义的 key_table
key_table:.long none, do_self, do_self, do_self // 扫描码00-03.long do_self, ..., func, scroll, cursor 等等
// do_self 函数
do_self:lea alt_map, %ebxtestb $0x20, mode // alt键是否同时按下jne 1flea shift_map, %ebxtestb $0x03, modejne 1flea key_map, %ebx
1:
总结:
这段代码展示了 Linux 内核如何处理键盘输入的扫描码,并将它们转换为字符输出。具体来说:
-
定义
key_table
函数数组:-
key_table
是一个函数数组,用于处理不同的键盘扫描码。每个条目对应一个扫描码的处理函数。
-
-
处理扫描码:
-
扫描码
02
对应按键1
,01
对应ESC
,12
对应E
等。
-
-
处理函数
do_self
:-
do_self
函数用于处理特定的扫描码。 -
它首先加载
alt_map
到ebx
寄存器。 -
检查
mode
寄存器,确定alt
键是否被按下。 -
如果
alt
键被按下,它加载shift_map
到ebx
寄存器。 -
否则,它加载
key_map
到ebx
寄存器。 -
key_map
、shift_map
和alt_map
是映射表,用于将扫描码转换为相应的字符。
-
-
映射表:
-
映射表(如
key_map
)用于将扫描码转换为字符。例如,a
的key_map
映射为a
,而shift_map
映射为A
。
-
这个过程展示了如何通过映射表和条件逻辑将键盘扫描码转换为字符,并将它们输出到屏幕上。这是键盘输入处理的核心部分,它确保了用户输入能够被正确识别和显示。
从 key_map
中取出对应的 ASCII 码
提取的代码如下:
#if defined(KBD_US)
key_map: .byte 0,27 .ascii "1234567890-="
shift_map: .byte 0,27 .ascii "!@#$%^&*()_+"
#elif defined(KBD_GR)...
#endif
# 继续do_self,从1f开始,ebx放的是map起始地址
1: movb (%ebx, %eax), %al // 扫描码索引,ASCII码->alorb %al, %al je none // 没有对应的ASCII码testb $0x4c, mode // 看caps是否亮je 2f cmpb $'a, %al jb 2fcmpb $'}, %al ja 2f subb $32, %al // 变大写
2: testb $??, mode // 处理其他模式,如ctrl同时按下
3: andl $0xff, %eax call put_queue
none: ret
总结:
这段代码展示了如何从 key_map
中取出对应的 ASCII 码,并根据键盘的模式(如大写锁定 Caps Lock
或 Shift
键)进行相应的处理。以下是详细步骤:
-
定义键盘映射表:
-
key_map
和shift_map
是两个映射表,分别用于处理未按下Shift
键和按下Shift
键时的键盘输入。 -
这些映射表将键盘扫描码转换为 ASCII 码。
-
-
从
key_map
中取出 ASCII 码:-
根据扫描码索引从
key_map
中取出对应的 ASCII 码。 -
如果没有找到对应的 ASCII 码,则跳转到
none
标签。
-
-
处理大写锁定:
-
检查
Caps Lock
模式是否激活(testb $0x4c, mode
)。 -
如果激活,将小写字母转换为大写字母(
cmpb $'a, %al
和subb $32, %al
)。
-
-
处理其他模式:
-
检查是否有其他模式激活,如
Ctrl
键同时按下(testb $??, mode
)。 -
根据需要处理这些模式。
-
-
将字符放入队列:
-
将处理后的 ASCII 码放入输出队列(
put_queue
)。
-
-
返回:
-
如果没有找到对应的 ASCII 码,则返回(
none: ret
)。
-
处理键盘输入并将字符放入队列中
提取的代码如下:
// put_queue 函数
put_queue:movl _table_list, %edxmovl head(%edx), %ecx
1:movb %al, buf(%edx, %ecx)...
// do_tty_interrupt 函数
void do_tty_interrupt(int tty) {copy_to_cooked(tty_table + tty);
}
// copy_to_cooked 函数
void copy_to_cooked(struct tty_struct *tty) {GETCH(tty->read_q, c);if (L_ECHO(tty)) { // 回显,也可以不回显PUTCH(c, tty->write_q);tty->write(tty); } // 立刻显示到屏幕上PUTCH(c, tty->secondary); // 完成copy_to_cooked... wake_up(&tty->secondary.proc_list);
}
总结:
这段代码描述了 Linux 内核如何处理键盘输入并将字符放入队列中的过程:
-
put_queue
函数:-
该函数负责将字符放入
con.read_q
队列中。 -
它首先将
_table_list
地址加载到edx
寄存器,然后获取head
指针到ecx
寄存器。 -
接着,它从缓冲区
buf
中取出字符并放入队列中。
-
-
do_tty_interrupt
函数:-
该函数处理键盘中断,调用
copy_to_cooked
函数来处理键盘输入。
-
-
copy_to_cooked
函数:-
该函数从
tty->read_q
队列中获取字符。 -
如果
L_ECHO
标志被设置(表示回显),则将字符放入tty->write_q
队列中。 -
然后调用
tty->write(tty)
函数将字符立刻显示到屏幕上。 -
将字符放入
tty->secondary
队列中,完成copy_to_cooked
操作。 -
最后,唤醒等待在
tty->secondary.proc_list
队列中的进程。
-
这个过程展示了如何将键盘输入的字符处理并放入队列中,以便后续显示在屏幕上。
键盘处理的步骤总结
-
中断初始化:
-
键盘作为控制台(console)的一部分,其中断通过
con_init
函数进行初始化,设置中断门(trap gate)以响应键盘事件。
-
-
中断处理:
-
当用户敲击键盘时,产生中断信号,操作系统通过中断处理程序
keyboard_interrupt
来响应这个中断。 -
中断处理程序从键盘的端口(通常是
0x60
)读取扫描码。
-
-
扫描码转换:
-
根据读取到的扫描码,调用
key_table
函数数组来查找对应的处理函数或映射表。 -
扫描码对应键盘上的不同按键,如
02
对应数字键1
,01
对应ESC
键等。
-
-
字符映射:
-
对于显示字符,使用
key_map
映射表将扫描码转换为 ASCII 码。 -
如果按下
Shift
或Alt
等修饰键,可能会使用shift_map
或alt_map
来获取大写字母或特殊字符。
-
-
队列处理:
-
将转换得到的 ASCII 码放入
write_q
队列中,等待显示设备(如显示器)来读取并显示。 -
如果设置了回显(
L_ECHO
),则同时将字符放入read_q
队列中,以便用户可以看到自己输入了什么。
-
-
显示字符:
-
con_write
函数负责从write_q
队列中取出字符并通过显示器驱动程序tty_write
输出到屏幕上。
-
-
缓冲和回显:
-
copy_to_cooked
函数处理read_q
队列中的字符,可能包括回显逻辑,即将字符回显到屏幕上。
-
到显示器和键盘的交互
这张图展示了Linux内核中字符设备(特别是TTY设备)的读写流程。以下是流程的总结:
写流程(从上到下):
-
系统调用 (write) :应用程序通过系统调用
write
向内核请求写操作。 -
字符设备接口 (crw_table[]) :系统调用被路由到字符设备接口,具体到
char_dev.c
文件中的相关函数。 -
TTY设备写 (tty_write) :字符设备接口调用
tty_write
函数,该函数位于tty_io.c
文件中。 -
write_q队列:写操作的数据被放入
write_q
队列中。 -
显示器写 (con_write) :数据从
write_q
队列被取出并通过con_write
函数写入显示器,该函数位于console.c
文件中。 -
显示器:最终数据被显示在显示器上。
读流程(从下到上):
-
主机键盘:用户通过键盘输入数据。
-
keyboard.S:键盘输入的数据被处理并放入
read_q
队列中。 -
read_q队列:键盘输入的数据存储在
read_q
队列中。 -
TTY设备读 (tty_read) :应用程序通过系统调用
read
请求读操作,该请求被路由到tty_read
函数,该函数位于tty_io.c
文件中。 -
字符设备接口 (crw_table[]) :
tty_read
函数通过字符设备接口返回数据给应用程序。 -
系统调用 (read) :最终数据被返回给应用程序。
回显流程:
-
回显:键盘输入的数据不仅被放入
read_q
队列,还会被回显到write_q
队列中。 -
显示器写 (con_write) :回显的数据通过
con_write
函数写入显示器,显示用户输入的内容。
总结:
-
写操作:从应用程序的
write
系统调用开始,经过字符设备接口、TTY设备写、写队列,最终写入显示器。 -
读操作:从键盘输入开始,经过键盘处理、读队列、TTY设备读、字符设备接口,最终返回给应用程序的
read
系统调用。 -
回显:键盘输入的数据会被同时写入读队列和写队列,实现输入内容的即时显示。
这个流程展示了Linux内核中字符设备(特别是TTY设备)的读写机制,确保了数据的正确传输和显示。