哈工大操作系统实验8 终端设备的控制

哈工大操作系统实验8 终端设备的控制

该篇文章是哈工大操作系统实验8——终端设备的控制完成笔记,其中包含了详细的步骤和相关代码,并有截图说明。实验内容都成功通过了,但是因为内容较多,记录中难免会有疏忽,如有发现错误,欢迎大家留言和我联系。

本次的实验比较简单,但了解到Linux系统的IO核心是文件和终端设备驱动编写的方法,收获也是很大。欢迎大家一键三连:点赞、关注加收藏,感谢大家的支持。

理论知识

实验内容推荐大家学习对应的视频课程:

  • L26 I/O 与显示器
  • L27 键盘

另外推荐阅读《注释》书籍:

  • 第10章 字符设备驱动程序(char driver):介绍了字符设备的知识,终端设备就是字符设备,字符设备的实际读和写就在这里处理的;
  • 第12.13节 char_dev.c程序:如果read和write函数操作的是一个字符设备,则调用该程序进行处理;
  • 和12.14节 read_write.c程序:Linux0.11中终端设备的读取和写入统一封装到文件操作里了,read和write函数是最顶层的调用。

实验内容:

image

按下F12显示星号功能

思路分析

从实验内容来看,不难拆解出两个思路:按下F12系统是怎么处理的?显示器是怎么显示的? 解决这两个问题,实验就可以做了。

《注释》书籍中的这张图在这次实验中很有帮助,完整的说明了从按下键盘到显示器显示的流程。

image

根据这个流程图,我跟了一下代码的处理流程,思路上更加清晰了。代码流程如下:

// 键盘按下的代码处理流程是怎么样的?
./kernel/chr_drv/keyboard.S 中的 _keyboard_interrupt
    -> call key_table 
        -> a-z:call do_self -> call put_queue 写入到read_q队列
        -> F1~F12:call func -> call put_queue 写入到read_q队列
    -> call ./kernel/chr_drv/tty_io.c 中的 do_tty_interrupt 
        -> call copy_to_cooked 从read_q队列中读取数据
            -> 写入到 secondary 队列,
            -> 如果有回显,则写入 write_q 队列,并调用 call tty->write() 在控制台上进行显示。
                // 这里的tty实际是console,所以write函数对应con_write。相关文件:./kernel/chr_drv/console.c
                // 线索可以查看 keyboard.S 68~69行、tty_io.c 51~92行。
            -> 唤醒 secondary 队列中等待的进程

// secondary 队列中等待的进程是怎么来的?
tty_io.c文件tty_read函数中当secondary队列为空时会进入睡眠。256~260行。

// tty_read 函数是谁在调用?
./fs/read_write.c 中的sys_write和sys_read函数
    -> ./fs/char_dev.c中 rw_char 函数  // 线索:./fs/char_dev.c 85~104行
        -> ./fs/char_dev.c中 rw_tty 函数
            -> call ./fs/char_dev.c中 rw_ttyx 函数
                -> call tty_read
// 结论就很清晰了,最顶层是系统调用write和read,如果是字符设备则最终会调用到 tty_read和tty_write
// 如果有兴趣继续跟进代码,就会发现Linux系统的IO核心就是文件。
// 最顶层都是对文件的读和写,根据不同的文件类型调用不同设备的方法进行处理。
// Linux0.11实现了块设备、字符设备和常规文件。本次实验内容主要涉及字符设备。

按下F12处理

按下键盘的中断处理程序是 ./kernel/chr_drv/keyboard.S 中的 keyboard_interrupt 函数。其中断设置路径如下:

main.c中main() 
    -> tty_io.c中 tty_init() 
    -> console.c中 con_init() 
    -> set_trap_gate(0x21,&keyboard_interrupt);

通过查看 keyboard_interrupt 函数处理,可以知道按下F12的处理函数是func

keyboard_interrupt:     # 键盘中断处理函数
	...
	call key_table(,%eax,4) # 调用key_table定义好的函数
	...

...

key_table:
    ...
	.long func,none,none,none		/* 58-5B f12 ? ? ? */  # f12定义的处理函数就是func
    ...

本来想着要兼容现有的F12按键处理功能,要在func上修改,后来查看func实现,发现里面啥事没干,就是打印出系统当前所有进程的信息(214行),然后打印一个自己的字符码L(230行)。F1~F12都是实现一样的功能。参考下图:

image

所以就考虑编写一个新的函数,替换掉F12的按键处理函数。有了思路就可以开始编写代码了。

1) 在 ./kernel/chr_drv/tty_io.c 中增加如下代码:

// ./kernel/chr_drv/tty_io.c 文件
unsigned short f12_flag=0; // 记录F12的标志
// 按下F12的处理函数
void f12_handler(void)
{
    f12_flag = f12_flag ? 0 : 1;
}

2) 在 ./include/linux/tty.h 中声明 f12_flag 和 f12_handler,在有引入 tty.h 这个头文件中的地方都可用:

extern unsigned short f12_flag;
void f12_handler(void);

3) 修改 ./kernel/chr_drv/keyboard.S 文件中按下F12的处理函数为 f12_handler :

/* 525行 */
.long f12_handler,none,none,none		/* 58-5B f12 ? ? ? */

至此,按下F12的功能就处理好了。

终端显示星号

经过上面的处理,按下F12按键后 f12_flag 会在0和1之间进行切换,如果为1,那么终端显示的时候要是星号。

从上面贴的《注释》书籍10-5的图,不难知道终端显示的处理在 ./kernel/chr_drv/console.c 中的 con_write 函数,在该函数中增加判断即可。

if (c>31 && c<127) { // 455行
    if (x>=video_num_columns) {
        x -= video_num_columns;
        pos -= video_size_row;
        lf();
    }
    // 新增代码,若字符为大小写字母或者数字,则改为*
    if (f12_flag)
        if((c>='A'&&c<='Z') || (c>='a'&&c<='z') || (c>='0'&&c<='9'))
            c = '*';

编译运行

# 在 oslab 目录下 

$ cd ./linux-0.11
$ make all
$ ../run

在Linux0.11中测试改动是否生效,结果如下图:

image

实验报告

完成实验后,在实验报告中回答如下问题:

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

上面有分析过了:

  • 就是打印出系统当前所有进程的信息(214行);
  • 然后打印一个自己的字符码L(230行)。

这里贴出代码再看看就更容易理解了。

/*
 * this routine handles function keys
 */
func:
	pushl %eax
	pushl %ecx
	pushl %edx
	call 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 # 168~170,将F12的字符码加入到read_q队列中。
	xorl %ebx,%ebx
	jmp put_queue
end_func:
	ret

/* 这里就是F1~F12字符码的定义,最后1个就是F12的字符码 */
/*
 * function keys send F1:'esc [ [ A' F2:'esc [ [ B' etc.
 */
func_table:
	.long 0x415b5b1b,0x425b5b1b,0x435b5b1b,0x445b5b1b
	.long 0x455b5b1b,0x465b5b1b,0x475b5b1b,0x485b5b1b
	.long 0x495b5b1b,0x4a5b5b1b,0x4b5b5b1b,0x4c5b5b1b   # F12的字符码:ESC [ [ L

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

2.1) 上面的实现并没有过滤向文件输出的字符。

2.2) 上面的实现是针对字符设备的处理,如果需要实现往文件输出的字符也过滤,实际就是在常规文件的写入处理,找到哪里是写入常规文件的代码即可

《注释》书籍的12-12图可以给到很大的帮助。

image

Linux系统中,IO的设计思路核心就是文件,通过对文件的读写实现IO功能。底层的系统调用就是 read 和 write,然后针对不同的文件类型,调用不同的具体实现。例如:

  • 本实验前面实现的终端变成星号,就是字符设备 rw_char() 实现的;
  • 常规文件就是 file_read() 和 file_write() 实现的。

明白思路后,查看代码可以发现 file_write() 的实现在 ./fs/file_dev.c 文件,根据要求修改代码:

    i += c;
    while (c-->0) { // 从用户缓冲区 buf 中复制 c 个字节到高速缓冲区中 p 指向开始的位置处。
        // 下面三行就是改动后的代码。
        char temp_c = get_fs_byte(buf++);
        if (f12_flag) temp_c = '*';
        *(p++) = temp_c;
    }
    brelse(bh);

编译运行后,测试一下,修改成功。

image

参考资料

从以下资料得到了不少帮助,特此表示感谢。

完。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晴空闲雲

感谢家人们的投喂

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值