实验环境:终端设备的控制

实验任务:修改 Linux 0.11 的终端设备处理代码,对键盘输入和字符显示进行非常规的控制。在初始状态,一切如常。用户按一次F12 后,把应用程序向终端输出所有字母都替换为“*”。用户再按一次F12,又恢复正常。第三次按 F12,再进行输出替换。依此类推。

实验本质:

  • IO的使用:让外设工作起来表现为CPU给外设的硬件寄存器发送指令,控制器完成真正的工作,向CPU发中断信号,然后CPU进行中断处理。操作系统要给用户提供一个简单视图-----文件视图,将CPU与外设链接起来。

实验思路:

  • 键盘中断初始化

    键盘中断的初始化在boot/main.c文件的init()函数中,在这个函数中会调用tty_init()函数对显卡的变量等进行设置(如:video_size_rowvide_mem_startvideo_port_regvideo_port_valvideo_mem_end等等),并对键盘中断0x21设置,将中断过程指向keyboard_interrupt函数(这是通过set_trap_gate(0x21, &keyboard_interrupt)语句来实现的。

  • 键盘中断发生

    当键盘中断发生时,intb $0x60,%al 指令会取出键盘的扫描码,放在al寄存器中,然后查key_table表进行扫描码处理,key_table表中定义了相关所有扫描码对应键的处理函数(如do_selffuncaltctrlnone等),比如f1-f12键的处理则要先运行一段处理函数func,调用sched.c中定义的show_stat函数显示当前进程情况,默认情况linux-0.11中所有的功能键都进行这样的动作,然后下一步将控制键以及功能键进行转义,比如ctrl_a将会被转义为^af1会被转义为esc[[Af2被转义为esc[[B,而其他键也经过类似处理,处理完毕后将跳到put_queue将扫描码放在键盘输入队列中,当然如果没有对应的扫描码被找到,则会通过none标签直接退回,不会被放入队列。然后再调用do_tty_interrupt函数进行最后的处理do_tty_interrupt直接调用copy_to_cooked函数对队列中的字符进行判断处理,最后调用con_write函数将其输出到显卡内存。在此同时,也会将字符放入辅助队列中,以备缓冲区读写程序使用。

  • 字符输出流程

    无论是输出字符到屏幕上还是文件中,系统最终都会调用write函数来进行,而write函数则会调用sys_write系统调用来实现字符输出,如果是输出到屏幕上,则会调用tty_write函数,而tty_write最终也会调用con_write函数将字符输出;如果是输出到文件中,则会调用file_write函数直接将字符输出到指定的文件缓冲区中。所以无论是键盘输入的回显,还是程序的字符输出,那只需要修改con_write最终写到显存中的字符就可以了。但如果要控制字符输出到文件中,则需要修改file_write函数中输出到文件缓冲区中的字符即可。
    在这里插入图片描述
    读队列read_q 用于存放从键盘或串行终端输入的原始( raw )字符序列;写队列 write_q 用于存放写到控制台显示屏或串行终端去的数据;辅助队列 secondary 用于存放经过行规则程序处理(过滤)过的数据,或称为熟(cooked)模式数据。上层终端读函数tty_read用于读取secondary队列中的字符。
    在这里插入图片描述
    对于一个控制台,当用户在键盘上键入了一个字符时,会引起键盘中断响应(中断请求信号 IRQ1, 对应中断号 INT 33 ),此时键盘中断处理程序就会从键盘控制器读入对应的键盘扫描码,然后根据使用的键盘扫描码映射表译成相应字符,放入 tty 读队列 read_q中。然后调用中断处理程序的 C函数 do_tty_interrupt(),它又直接调用行规则函数 copy_to_cooked()对该字符进行过滤处理,并放入 tty 辅助队列 secondary 中,同时把该字符放入tty 写队列 write_q 中,并调用写控制台函数 con_write() 。此时如果该终端的回显( echo )属性是设置的,则该字符会显示到屏幕上。 do_tty_interrupt()copy_to_cooked()函数在tty_io.c中实现。

    有关控制台终端操作的驱动程序,主要涉及两个程序。一个是键盘中断处理程序 keyboard.S ,主要用于读入用户键入的字符并放入 read_q缓冲队列中;另一个是屏幕显示处理程序 console.c,用于从 write_q队列中取出字符并显示在屏幕上。

添加F12功能键盘处理

  • 修改kernel/chr_drv/tty_io.c文件,在文件末尾添加:

    int switch_show_char_flag = 0;
    void press_f12_handle(void)
    {
    	if (switch_show_char_flag == 0)
    	{
    		switch_show_char_flag = 1;
    	}
    	else if (switch_show_char_flag == 1)
    	{
    		switch_show_char_flag = 0;
    	}
    }
    
  • 修改/include/linux/tty.h文件,在末尾添加:

    extern int switch_show_char_flag;
    void press_f12_handle(void);
    
  • 修改kernel/chr_drv/keyboard.S文件,修改key_table代码如下:

    /* .long func,none,none,none		58-5B f12 ? ? ? */
    .long press_f12_handle,none,none,none
    

添加字符*显示处理

  • 修改linux-0.11/kernel/chr_drv/console.c文件,修改con_write函数:

    case 0:
    				if (c>31 && c<127) {
    					if (x>=video_num_columns) {
    						x -= video_num_columns;
    						pos -= video_size_row;
    						lf();
    					}
    
      				if (switch_show_char_flag == 1)
      				{
      					if((c>='A'&&c<='Z')||(c>='a'&&c<='z')||(c>='0'&&c<='9'))
      						c = '*';
      				}
    					
    					__asm__("movb attr,%%ah\n\t"
    						"movw %%ax,%1\n\t"
    						::"a" (c),"m" (*(short *)pos)
    						);
    					pos += 2;
    					x++;
    
  • 重新编译内核,make all

  • 运行bochs,测试结果如下:
    在这里插入图片描述

实验问题

  • 在原始代码中,按下 F12,中断响应后,中断服务程序会调用 func?它实现的是什么功能?

将F12转义成转义字符序列 [ [ L , 对F1-F12处理类似 [ [ A -> [ [ L

  • 在你的实现中,是否把向文件输出的字符也过滤了?如果是,那么怎么能只过滤向终端输出的字符?如果不是,那么怎么能把向文件输出的字符也一并进行过滤?

没有把向文件输出的字符过滤,只过滤向终端输出的字符是通过con_write函数的修改来实现的。过滤向文件输出的字符则通过修改file_write函数来实现。
具体修改可参考:
while (c–>0)
{
tmp = get_fs_byte(buf++);
if(f12_flag == 1)
{
if((tmp>=‘A’&&tmp<=‘Z’)||(tmp>=‘a’&&tmp<=‘z’)||(tmp>=‘0’&&tmp<=‘9’))
tmp = ‘*’;
}
*(p++) = tmp;
}

参考链接:
https://blog.csdn.net/Watson2016/article/details/72188549
https://blog.csdn.net/realfancy/article/details/90544792

Logo

更多推荐