tty是TeleTYpe的缩写,原来指的是电传打印机,后来这一名称被类Unix/Linux操作系统广泛使用,表示的是虚拟终端设备。在多任务操作系统中,每一个任务的输出内容都可以显示在虚拟终端中。我们要实现tty1 ~ tty10这样的10个虚拟终端设备。运行在不同终端下的任务的输出结果显示在不同的终端中。参考Linux系统,我们将采用同样的方式,通过Ctrl + Alt + F1 (F1 ~ F10)这样的组合按键在tty1 ~ tty10之间切换。
我们知道,显存的首地址为0xb8000,共有80 * 25 * 2个字节,实现多tty切换的原理是为多个tty申请独自的内存空间,并为程序的输出存放相关内容。当tty切换时,将当前显存的数据复制到当前tty的内存中,并将准备切换tty内存数据复制到当前显存中。首先定义10个tty并为其申请足够大小的内存空间,用于存放显存数据:
for (int i = 0; i < TTY_COUNT; i++) { sys_var->ttys[i].tty_id = i; sys_var->ttys[i].cursor_pos = 0; sys_var->ttys[i].mm_addr = alloc_mm(TTY_MEM_SIZE); sys_var->ttys[i].ch_index_w = 0; sys_var->ttys[i].ch_index_r = 0; sys_var->ttys[i].sem_keybuff_w.value = TTY_KEY_BUFF_SIZE; sys_var->ttys[i].sem_keybuff_w.list_block = NULL; sys_var->ttys[i].sem_keybuff_r.value = 0; sys_var->ttys[i].sem_keybuff_r.list_block = NULL; for (int j = 0; j < TTY_KEY_BUFF_SIZE; j++) { sys_var->ttys[i].ch[j] = 0; } u8 *p = sys_var->ttys[i].mm_addr; for (int j = 0; j < TTY_MEM_SIZE; j++) { p[j] = 0; } } tty_cur = &sys_var->ttys[0];
切换tty的过程也非常简单,就是将显存数据与tty内存数据相互复制:
//切换tty之前将显存数据保存到原tty内存区 mmcopy((void *) 0xb8000, sys_var->ttys[tty_cur->tty_id].mm_addr, TTY_MEM_SIZE); //切换tty tty_cur = &sys_var->ttys[tty_id]; //切换tty之后将新tty内存区数据更新至显存 mmcopy(tty_cur->mm_addr, (void *) 0xb8000, TTY_MEM_SIZE); u32 x = tty_cur->cursor_pos % 80; u32 y = tty_cur->cursor_pos / 80; set_cursor(tty_cur->tty_id, x, y);
我们的目的是让不同的程序运行在不同的tty中,它们的标准输出要显示在不同的tty内存中。程序运行的控制单元为pcb,所以在pcb中加入tty_id用来记录当前程序运行在哪一个tty中:
typedef struct process_control_block { //tty编号 int tty_id; //进程号 u32 process_id; //0 进程,1 线程 int pcb_type; //任务描述段 s_tss tss; ... ... } s_pcb;
当程序调用stdio的putchar、puts和printf函数时,中断服务要取得当前程序所使用的tty_id并在此tty中显示相关信息:
int tty_id = pcb_cur->tty_id; //显示字符 if (params[0] == 0) { putchar(tty_id, (char) params[1]); } //显示字符串 else if (params[0] == 1) { int *count = (int *) params[2]; count = addr_parse(cr3, count); char *str = (char *) params[1]; str = addr_parse(cr3, str); *count = puts(tty_id, str); }
内核中的所有与标准输出相关的功能均需要修改成向tty设备输出。所有输出相关函数都增加了一个int tty_id的参数:
void draw_cursor(int tty_id, int x, int y); void clear_cursor(int tty_id, int x, int y); void set_cursor(int tty_id, u32 x, u32 y); u32 get_cursor(int tty_id); void scroll_up(int tty_id, int row); void putascii(int tty_id, u32 x, u32 y, char ch); void putchar(int tty_id, char ch); void backspace(int tty_id); int puts(int tty_id, char *str); int printf(int tty_id, char *fmt, ...);
在显示字符到标准输出设备上时修改如下:
void putascii(int tty_id, u32 x, u32 y, char ch) { //定义显存地址 char *video_addr = NULL; if (tty_id == tty_cur->tty_id) { video_addr = (char *) 0xb8000; } else { video_addr = sys_var->ttys[tty_id].mm_addr; } //写入显存 u32 where = (y * 80 + x) * 2; //显示字符的实际物理地址 u8 *p = (u8 *) (video_addr) + where; //字符的ascii码 *p = ch; //颜色 *(p + 1) = 0x07; }
其它函数也需要进行相关修改,这里不再逐个列出。
system系统程序中载入10个shell程序,并为它们分别指定运行在tty1 ~ tty10中。10个shell程序的pcb中都指定了它们所使用的tty_id:
//启动shell程序 for (int i = 0; i < TTY_COUNT; i++) { install_program("/usr/bin/shell", "", i); }
在shell程序载入普通程序时,为其分配shell程序自己所在的tty_id:
int tty_id = get_tty_id(); int params[7]; params[0] = 0; params[1] = (int) path; params[2] = (int) args; params[3] = (int) 1; params[4] = (int) &sem_addr; params[5] = (int) &status; params[6] = (int) tty_id; __asm__ volatile("int $0x80" :: "a"(params));
最后在键盘中断服务中加入Ctrl + Alt + F1 (F1 ~ F10)的组合按键:
//切换tty F1 - F10 if (status == 0 && (key_ind - KEY_F1) >= 0 && (key_ind - KEY_F1) < TTY_COUNT && kb_key_ctrl && kb_key_alt && !kb_key_shift) { switch_tty(key_ind - KEY_F1); }
编译运行并查看运行结果,默认为tty1:
按Ctrl + Alt + F2切换到tty2:
在tty1中使用lidq登录并运行example_sem命令:
切换到tty2,使用root登录并执行example_args命令:
源代码的下载地址为:
https https://github.com/magicworldos/lidqos.git git git@github.com:magicworldos/lidqos.git subverion https://github.com/magicworldos/lidqos branch v0.31
Copyright © 2015-2023 问渠网 辽ICP备15013245号