首页 > 技术文章 > 操作系统模拟输入输出

iterationjia 2020-11-30 11:12 原文

1 学习chapter 7

建议仔细看orange第七章代码,下面只是记录了一些可能用到的细节。

80*25 屏幕上每个字符占2字节 一个屏幕总字节数 80*25*2 = 4000字节
闪烁用两字节中最高位置为1 color 背景+颜色+ascii disp_color_str(str,color)

VGA REGISTER
访问时先向address register写索引,再向data register写value
out_byte(端口号,索引/value)
CRT CONTROL ADDRESS 0X3D4 DATA 0X3D5

CRT CONTROL REGISTERS
设置光标位置
Cursor Location High Register 索引号0Eh
Cursor Location Low Register 索引号0Fh

代码7.19 代码7.20

可以把7.21涉及到的滚屏代码删除

现在tty任务是简单的,task_tty()是一个循环,不断调用keyboard_read(),而keyboard_read()从键盘缓冲区得到数据后会调用in_process(),将字符直接显示出来

图7.15及周围的1,2,3
代码7.22和代码7.23及周围内容
图7.16及周围内容

V_MEM_BASE在const.h被定义为0xB8000,索引V_MEM_BASE+disp_pos就是当前显示位置的地址

以chapter7/n为标准,注释掉的部分:
代码7.40 scroll_screen 此时超出屏幕滚动向下暂不考虑
7.41 响应shift上下
7.38 处理alt+fn
7.37 切换控制台
7.36 切换控制台的代码
7.39 选择第0个console写法改变
console.c的flush刷新修改
//7.35 修改后的out_char考虑多控制台

2 修改chapter7/n

很明显,使用/n代码可以完成大小写、换行,退格,正常输入等操作,我只用学习一下,并补充自己的代码。就可以完成这次实验。

2.1 修改bochsrc

  • 添加display_library: sdl 图形库
  • 把键盘映射表keyboard_mapping的x11-pc-us.map改成sdl-pc-us.map
  • 给vgaromimage添加file=

2.2 修改makefile

  • 不修改会报错__stack_chk_fail未定义的引用
    • 给CFLAGS = -I include/ -c -fno-builtin 添加 -fno-stack-protector
  • 如果报错'dlopen failed for module 'x': file not found'
    • 注意运行还需要bochs-x,需要安装(sudo apt-get install bochs-x)

2.3 关于头文件

  • proto.h放了.asm,.c文件的函数声明,include这个头文件就可以方便的使用这些函数。
  • global.h放了所有在global.c中声明的全局变量,include这个头文件就可以方便的使用这些全局变量。
  • keymap.h主要放了一个关于scan_code的数组,使用这个就能方便的对应scan_code找到字符。其中每3个值一组,分别是单独按某键,shift+某键和有0xE0前缀的扫描码对应的字符。
  • keyboard.h建立了一个s_kb/KB_INPUT的struct,是一个键盘输入缓冲区。此外,还定义了所有不能直接显示的字符,都加上了FLAG_EXT,这有利于解析输入时的判断。
  • tty.h建立了一个s_tty/TTY的struct。是一个TTY输入缓冲区,它包含一个指向struct console的指针。
  • console.h建立了一个s_console/CONSOLE的struct,用来保存当前console的重要信息,包括当前显示的位置,当前控制台对应显存位置,当前控制台占有的显存大小和当前光标位置。其中对应显存位置和显存大小是静态不变的。

2.4 修改kernel(实验主要部分)

涉及到keyboard.c,tty.c,console.c,global.c,main.c,主要修改前三个。我删掉了一些跟这次实验无关的kernel代码,毕竟代码量少看着也简单一些,比如多终端的一部分、LED亮灯,部分特殊键位的解析与处理。

2.4.1 每20秒清屏一次

  • 清屏:通过学习protect.c的268行清空屏幕前五行的方式,我们也通过打印Screen_size个空格并移动光标到最开始处的方式来清屏。发现console.c中有init_screen的操作,不妨给它加一个清空console的clear方法来清屏。
  • 每20秒:在main.c中的任务TestA中加入清屏代码,死循环中延时20秒。但代码运行出错,研究过发现这个testA放到task里才能调用init_screen方法,于是在global.c中把testA移动到NR_TASKS,同时要修改proc.h中NR_TASKS和NR_PROCS的数目,不然main.c中判断是任务还是用户进程会出错。

2.4.2 解析输入和显示输出

在说具体的输入输出处理前,我觉得还是要来看一下整个程序的流程,看这张图一目了然。

  • 如图7.16所示,task_tty()作为一个死循环任务,通过循环来处理每一个tty的读和写操作(当然我们的tty只有一个),读写操作全都放在了tty_do_read()和tty_do_write()两个函数中,这样就让task_tty()很简洁,而且逻辑清晰。

    • 读操作会调用keyboard_read(),tty_do_read()在我修改后事实上也只有这么一行调用了,keyboard_read()处理完会调用in_process()来put_key(),把字符放进tty缓冲区,用FLAG_EXT来判断是否是特殊字符,如果不是,正常put就行;如果是,要把特殊字符还原成\n之类再put。
    • 写操作tty_do_write(),先利用tty缓冲区内的put内容.把字符读出来,然后会调用out_char(),它会将字符写入指定CONSOLE。
  • keyboard_read()中主要是利用keymap将扫描码转换成字符,并对特殊字符处理

    • 首先,要去判断是make code还是break code。
    • 对于如大小写、0-9这样的输入,在keyboard中通过对caps和shift的判断,来确定大小写,具体表现在是选择keymap.h中的column第0列还是第1列。
  • keyboard_read()会调用tty中的in_process(),in_process()具体操作在上文已经介绍过,put_key()放入缓冲区。

  • 写操作tty_do_write(),先利用tty缓冲区内的put内容.把字符读出来,然后会调用console.c中的out_char(),out_char()会操作显存,但怎么操作要判断字符类型。

    • 如果是0-9,大小写之类会直接*p_vem++ = ch;*p_vem++ = DEFULT_CHAR_COLOR
    • 如果是\n,重新计算一下cursor位置即可,并且最好打印带颜色0x1的空格,方便一起删除;如果是\t,打印4个颜色为0x0的空格即可,带特殊颜色也方便删除时一起删;如果是\b,则要考虑上一个字符是什么,颜色是0x0则为tab,回退4个默认空格;颜色是0x1则为\n,回退一行默认空格;除这两种颜色外则为字符输入,回退1个默认空格。全过程也对cursor进行了同样的增减。
  • 修改*p_vem可以直接改变显存内存地址,从而改变我们在console上看到的内容。但cursor与current_addr则必须自己在outchar()的最后flush(),flush()对这两个变量进行了set操作,才能在console上体现出来,主要涉及到对地址寄存器和数据寄存器的端口进行操作,原理在orange书上268页(页脚标号)。

  • 需要注意的是,输入不应该有上限,所以我们的页面要能滚动。需要在flush()前给页面添加检测是否到达页面底部,如果到达,就要触发scroll_screen()操作,改变current_addr的值,这个过程也需要set current_addr。

2.4.3 查找操作
  • 原理:定义全局变量mode=0,并实现类似状态机切换。奇数次按下esc,mode从0变为1;输入关键字,按下回车,mode从1变为2,屏蔽除esc外所有输入;再按esc,mode从2变为0。

  • 查找操作涉及keyboard,tty和console,主要是在console。

    • keyboard中在解析扫描码为ESC的时候,mode = !mode来做0,1切换;在tty中的in_process(),put进缓冲区前,如果是ENTER并且mode=1.则mode=2,如果是ESC并且mode=2,则mode=0。这样我们就依据按键完成了状态的切换。
    • 由于在mode不为0时,不清空屏幕,这一点在main.c的TESTA中得到了体现。
    • 由于查找字符串应该是红色,所以在out_char解析ESC时我直接改变了DEFAUT_COLOR为0x04红色,直到mode==0时再改为0x07白色。
    • 由于mode为2时屏蔽除esc外所有输入,这点在tty.c的in_process()中得到了体现。
    • 由于在mode转为0时,关键字应该被删除,光标归位,字符串颜色全恢复白色。因此我在这种情况中先恢复DEFAULT_COLOR,然后删掉了所有输入的关键字,把匹配字符串恢复颜色,通过在之前保存console struct并此时恢复来还原未查找前console状态。
    • 为了在console中方便的查找,而不去看缓存,我使用了一个input数组来记录所有输入(当然每20秒清空屏幕时也清空input),使用了str数组来记录输入的查找字符串。这些在out_char的各种情况中都得到了体现。
    • 最后直接通过遍历字符串找到匹配关键字的字符字串,给其改为红色。对于不能体现的空格和TAB,则给其加上背景红色。

3 代码源码

github源码

推荐阅读