跟我一起写操作系统

    返回首页    发表留言
本文作者:李德强
          第十五章 多tty切换
 
 

        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号