自制操作系统 - CSI控制序列

CSI控制序列

Posted by 王富杰 on Monday, March 25, 2024

一、CSI控制序列

控制字符是指 ASCII 码表开头的 32 个字符 (0x00 ~ 0x1F) 以及 DEL(0x7F)。控制序列是指由一些非控制字符构成的一个特殊字符序列,终端在收到这个序列时并不是将它们直接显示在屏幕上,而是采取一定的控制操作,比如:移动光标、删除字符、删除行、插入字符、插入行等。

ANSI 控制序列由以下一些基本元素组成:

  • 控制序列引入码(Control Sequence Introducer - CSI):表示一个转移序列,提供辅助的控制并且本身是影响随后一系列连续字符含义解释的前缀。通常,一般 CSI 都使用 ESC[
  • 参数(Parameter):零个或多个数字字符组成的一个数值
  • 数值参数(Numeric Parameter):表示一个数的参数,使用 n 表示
  • 选择参数(Selective Parameter):用于从一功能子集中选择一个子功能,一般用 s 表示;通常,具有多个选择参数的一个控制序列所产生的作用,如同分立的几个控制序列;例如:CSI sa;sb;sc F * 的作用是与 CSI sa F CSI sb F CSI sc F 完全一样的
  • 参数字符串(Parameter String):用分号 ; 隔开的参数字符串
  • 默认值(Default):当没有明确指定一个值或者值是 0 的话,就会指定一个与功能相关的值
  • 最后字符(Final character):用于结束一个转义或控制序列

下图是一个控制序列的例子:取消所有字符的属性,然后开启下划线和反显属性。ESC [ 0;4;7m。 其中 E 表示 0x1B,如果 n 是 0 的话,则可以省略: E[0j == E[J。 在python中打印彩色字体就会用到控制序列,如:

print("\033[31m这是红色文字\033[0m")

其中\033是控制字符 ESC 的八进制表示。

二、控制序列实现

控制序列的实现需要调整的是控制台驱动程序,首先我们将控制台封装成一个结构。这样做的好处是当系统支持多个显示器时,直接创建多个结构体即可。如下:

typedef struct console_t
{
    u32 mem_base; // 内存基地址
    u32 mem_size; // 内存大小
    u32 mem_end;  // 内存结束位置

    u32 screen;   // 当前屏幕位置
    u32 scr_size; // 屏幕内存大小

    union
    {
        u32 pos;   // 当前光标位置
        char *ptr; // 位置指针
    };
    u32 x;        // 光标坐标 x
    u32 y;        // 光标坐标 y
    u32 saved_x;  // 保存的 x
    u32 saved_y;  // 保存的 y
    u32 width;    // 屏幕宽度
    u32 height;   // 屏幕高度
    u32 row_size; // 行内存大小

    u8 state;         // 当前状态
    u32 args[ARG_NR]; // 参数
    u32 argc;         // 参数数量
    u32 ques;         //

    u16 erase; // 清屏字符
    u8 style;  // 当前样式
} console_t;

这里有一个状态,在实现时会console_write会根据传入的字符来改变状态,例如传入ESC就会切换到ESC状态。然后接着判断下一个字符是否是 [, 最后根据识别到的序列来做响应的操作。

我们前边clear命令是通过系统调用进行实现的,有了控制序列后,就可以通过控制序列进行清屏,不再需要系统调用了。

三、错误处理机制

之前我们一直没有对错误进行过处理,都是使用断言如果出现错误就panic,这样是不对的,我们应该提供错误处理机制。首先需要定义一系列错误号:

#define TIMELESS -1 // 无限时间

#define ERROR 99        // 一般错误
#define EOK 0           // 没错
....
#define EMLINK 31       // 连接太多
#define EDEADLK 35      // 避免资源死锁
#define ENAMETOOLONG 36 // 文件名太长
#define ENOLCK 37       // 没有锁定可用
#define ENOSYS 38       // 功能还没有实现
#define ENOTEMPTY 39    // 目录不空
#define ETIME 62        // 超时

这里先只提供错误的处理机制。在进程的阻塞和解除阻塞时,当前的实现是进程阻塞发送在睡眠或者磁盘处理,等睡眠结束或者磁盘处理完成就会就绪。但是实际正常来说阻塞不会就绪,例如磁盘损坏就需要加入超时机制。这里task_block就可以加入阻塞超时参数,默认调用可以传入TIMELESS。但是这里还没有真正处理处理,只是增加了机制。

「真诚赞赏,手留余香」

WangFuJie Blog

真诚赞赏,手留余香

使用微信扫描二维码完成支付