有用过LINUX的同学都知道,在文本模式下,按下shift+ctrl+Fn键,0<n<7,就会切换到对应的console,各个console的输入和输出各自独立,互不干扰。下面我们就在自己的OS中加入这一功能。
为了尽量简单起见,保持视频适配器最简单的模式80*25模式,此模式下显存的大小为32K,我们计划弄3个console,所以每个console就分得10K,原理在上一篇已经讲过,当发生console的切换时,就向适配器的相应端口写入要显示的console的起始地址。
首先阐述一下编程思路,原来的程序中只有一个console,所以中断进程就不断的从scan code缓冲区中取得scan code转化成对应的key值,并直接打印出来。而现在有3个console,那么此时key值应该送往当前的console来打印,为了逻辑清晰,为每个console分配1个缓冲区,用来存放key的值。
创建1个头文件tty.h,建立一个TTY结构:
- /*====================================================================
- tty.h
- ====================================================================*/
- #ifndef _TTY_H_
- #define _TTY_H_
- struct s_console;
- typedef struct s_tty
- {
- u32 tty_buffer[256];
- u32 *p_tty_head;
- u32 *p_tty_tail;
- int count;
- struct s_console *console;
- }TTY;
- #endif
此结构跟scan code缓冲区的结构很相似,但多了1个s_console结构,这个结构是干嘛的呢,我们知道每个console对应显存不同的位置,占据显存的内存的字节数,console当前的光标位置等信息,需要把这些数据也组织起来,创建1个console.h头文件:
- /*====================================================================
- console.h
- ====================================================================*/
- #ifndef _CONSOLE_H_
- #define _CONSOLE_H_
- typedef struct s_console
- {
- u32 start_addr;
- u32 current_addr;
- u32 memory_size;
- u32 cursor;
- }CONSOLE;
- #endif
结构定义好了,接着就是为它们分配内存空间了,由于有3个console,所以分配一个TTY的数组和CONSOLE的数组,数组元素个数都为3。此外,还要记录当前的console是哪一个,所以还要分配一个全部变量来记录这件事儿。在global.h和global.c中添加:
- extern TTY TTY_Table[];
- extern CONSOLE Console_Table[];
- extern int Current_Console;
- TTY TTY_Table[3];
- CONSOLE Console_Table[3];
- int Current_Console;
接下来是初始化这些个数组和变量,由于是跟TTY相关的,那么就在终端进程的执行体中来做:
- TTY *p_tty;
- for(p_tty = TTY_Table ; p_tty < TTY_Table + 3 ; p_tty++)
- {
- p_tty->p_tty_head = p_tty->p_tty_tail = p_tty->tty_buffer;
- p_tty->count = 0;
- p_tty->console = Console_Table + (p_tty - TTY_Table);
- p_tty->console->start_addr = 1024 * 10 * (p_tty - TTY_Table);
- p_tty->console->current_addr = p_tty->console->start_addr;
- p_tty->console->cursor = p_tty->console->start_addr;
- p_tty->console->memory_size = 1024 * 10;
- }
- Current_Console = 0;
上述代码把数组中各元素的初始值都设置好,注意console的初始值,每个console占了10K,第1个console的起始地址从0xb8000开始,下一个就是0xb8000+10k。把当前console设置为0。
进程体接着执行一个死循环,先调用Keyboard_Read韩素来解析扫描码,再调用In_Process函数来把解析后得到的key放到当前console的输出缓冲区中,这两个函数的接口都已经改变,需要在proto.h中修改它们的原型:
- void Keyboard_Read(TTY *p_tty);
- void In_Process(TTY *p_tty,u32 key_value);
它们的函数体也有所改变,在Keyboard_Read函数中改变的是:
- In_Process(p_tty,key);
In_Process的函数体变化比较大:
- void In_Process(TTY *p_tty,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();
- */
- if(p_tty->count < 256)
- {
- Disable_Int();
- *p_tty->p_tty_tail = key_value;
- p_tty->p_tty_tail++;
- if(p_tty->p_tty_tail == p_tty->tty_buffer + 256)
- {
- p_tty->p_tty_tail = p_tty->tty_buffer;
- }
- p_tty->count++;
- Enable_Int();
- }
- }
- else
- {
- /* 略 */
- }
- }
代码逻辑已经改变,变成把解析得到的key值放到当前console的输出缓冲区中。
OK,下面的工作就该是把各个console的输出缓冲区的东东打印到各自对应的地方了。先把死循环的代码贴上来:
- char c_disp;
u8 *disp_addr; - while(1)
- {
- for(p_tty = TTY_Table ; p_tty < TTY_Table + 3 ; p_tty++)
- {
- if(p_tty->console == &Console_Table[Current_Console])
- {
- Keyboard_Read(p_tty);
- }
- if(p_tty->count > 0)
- {
- Disable_Int();
- c_disp = *p_tty->p_tty_head;
- p_tty->p_tty_head++;
- if(p_tty->p_tty_head == p_tty->tty_buffer + 256)
- {
- p_tty->p_tty_head = p_tty->tty_buffer;
- }
- p_tty->count--;
- Enable_Int();
- disp_addr = (u8*)(0xb8000 + p_tty->console->cursor);
- *disp_addr++ = c_disp;
- *disp_addr++ = 0xa;
- p_tty->console->cursor += 2;
- Disable_Int();
- Out_Byte(0x3d4,0xf);
- Out_Byte(0x3d5,(p_tty->console->cursor / 2) & 0xff);
- Out_Byte(0x3d4,0xe);
- Out_Byte(0x3d5,((p_tty->console->cursor /2) >> 8) & 0xff);
- Enable_Int();
- }
- }
- }
代码是实现了功能,但没像作者那么分成好几个小的功能函数,来显得结构清晰,这是后话。
首先是TTY的一个遍历,如果遍历到的TTY包含的console是当前的console,则调用Keyboard_Read函数来把解析后的key填充到当前的console的输出缓冲区中。然后判断当前遍历的TTY的console的输出缓冲区有没有什么要输出的,有就取出来,打印到相应的位置。注意到,在打印的时候没有使用Disp_Color_Str函数,因为此函数是根据0xB8000来打印的,如果当前console的start_addr不是0xB8000,那就有点麻烦了。既然在CONSOLE结构中已经有了打印的相关信息了,那么就直接根据这些信息来打印了。利用指针来直接把输出缓冲区中的值写到内存去。最后再把光标定位到相应的位置。
我们还没有写切换console的代码,这个待会儿再实现。添加了两个头文件,要修改MAKEFILE,而且包含proto.h的源文件中要先包含这两个新添加的头文件。
make,运行,结果跟上次没什么两样。