作者突然笔锋一转,转到了显示器,毕竟谈到输入也必须谈到输出,键盘最是常用的最古老的输出方式,而显示器也最常用的输出方式,所以来谈谈输出。
先讲了80*25模式的彩色文本模式,这个在王爽的汇编书已经很熟悉,快速的扫一遍。
然后是操作显卡的各种端口了,我们已经接触过很多硬件的操作了,什么8259A啊,8253啊,8042啊。其实很简单,无非就是in和out,这一部分有两个任务,让光标跟随字符和操作显示的起始位置,这是为了实现TTY必须完成的工作。
下面就是端口的信息:
这里一个端口对应着很多个寄存器,端口地址就好像一个数组的首地址,必须提供相应的索引来选择填充哪个寄存器或者从哪个寄存器中读数组,下面是我们要操作的CRT Controller Registers:
加粗的就是我们想要操纵的寄存器。
先来完成第一个任务,让光标跟随输入的字符,这是我们平时最常见的功能。Cursor Location Low Register存放光标位置的低8位,索引是0xF,Cursor Location High Register存放光标位置的高8位,索引是0xE。我们只是把光标位置填充进去对应的寄存器就可以了,那么光标位置从哪来呢?还记得Disp_Pos这个变量吧,把这个变量的值填进去就好,注意一个字符占两个字节,所以要Disp_Pos要先除以2,因为位置是按字符的个数来计的。
就在tty.c中的In_Process函数来添加代码吧,因为要用到Disp_Pos变量,所以要添加相应的头文件:
- #include "protect.h"
- #include "proc.h"
- #include "global.h"
- void In_Process(u32 key_value)
- {
- char disp[2];
- Memory_Set(disp,2,0);
- if(!(key_value & FLAG_EXT))
- {
- disp[0] = key_value & 0xff;
- Disp_Color_Str(disp,0xa);
- Disable_Int();
- Out_Byte(0x3d4,0xf);
- Out_Byte(0x3d5,(Disp_Pos / 2) & 0xff);
- Out_Byte(0x3d4,0xe);
- Out_Byte(0x3d5,((Disp_Pos /2) >> 8) & 0xff);
- Enable_Int();
- }
- }
13到18行是新添加的,因为想让写端口的语句一气呵成不被打断,所以在这段代码的两头用Disable_Int函数和Enable_Int函数包起来。操作系统中说的锁是可以这样用中断的开关来实现的,呵呵。如果这过程可以被打断的话,那么如果写端口的语句执行过程中被打断的话,发生时钟中断,CPU交给其它的进程,则此时字符已经被打印了,而光标并没有跟着移动,这样就显得很奇怪了,要等到CPU重新被终端进程夺回来时光标才会移动到相应的位置。
make,运行,结果如图所示,看到光标跟随的效果了:
接下来是设置显示的开始位置了,我们知道在80*25模式下显存的大小为32K,而一个屏幕显示的容量为80*25*2=4000B,约等于4K。而在默认情况下是从0xB8000处开始显示的,也就是说Start Address Low Register和Start Address High Register中的值就都为0,就是说相对于0xB8000开始显示的。那么事情就好办了,重新填充这两个寄存器就可以了。
那我们在shift+down按下后相对于0xB8000后的15行开始显示,而在shift+up按下后恢复原状,下面是添加后的In_Process函数:
- void In_Process(u32 key_value)
- {
- char disp[2];
- Memory_Set(disp,2,0);
- if(!(key_value & FLAG_EXT))
- {
- disp[0] = key_value & 0xff;
- Disp_Color_Str(disp,0xa);
- Disable_Int();
- Out_Byte(0x3d4,0xf);
- Out_Byte(0x3d5,(Disp_Pos / 2) & 0xff);
- Out_Byte(0x3d4,0xe);
- Out_Byte(0x3d5,((Disp_Pos /2) >> 8) & 0xff);
- Enable_Int();
- }
- else
- {
- int raw_key = key_value & 0x1ff;
- switch(raw_key)
- {
- case DOWN:
- if((key_value & FLAG_SHIFT_L) || (key_value & FLAG_SHIFT_R))
- {
- Disable_Int();
- Out_Byte(0x3d4,0xd);
- Out_Byte(0x3d5,(80 * 15) & 0xff);
- Out_Byte(0x3d4,0xc);
- Out_Byte(0x3d5,((80 * 15) >> 8) & 0xff);
- Enable_Int();
- }
- break;
- case UP:
- if((key_value & FLAG_SHIFT_L) || (key_value & FLAG_SHIFT_R))
- {
- Disable_Int();
- Out_Byte(0x3d4,0xd);
- Out_Byte(0x3d5,0);
- Out_Byte(0x3d4,0xc);
- Out_Byte(0x3d5,0);
- Enable_Int();
- }
- default:
- break;
- }
- }
- }
加了个else分支,如果key为不可打印的,则跳到这来,建立一个局部变量,把key的低9位保留下来,其它位置0,再把这个中间值赋给这个变量,这是为了跟相应键的宏比较,以判断按下的是哪个键。再判断是否按下了shift键,是的话进入写端口的一系列语句,同样要保证原子性,原因不再赘述。
make,运行,下图为按下shift+down时的情况:
按一下shift+up,又恢复原状,如图所示: