字符显示的控制

实验分析

F12 状态设定

在 include/linux/sched.h 中定义一个 int 类型变量为 f12_state,用来标志当前是否将所有字母替换为 * 显示。当 f12_state 为 1 时,所有字符将替换为 * 显示,否则不替换。

int f12_state;

在 kernel/sched.c 里实现状态切换函数 switch_f12 。

f12_state = 0; //初始时正常显示
void switch_f12(void)
{
    if (f12_state == 1)
        f12_state = 0;
    else
        f12_state = 1;
}
终端显示字符修改

现在要根据 f12_state 的值修改终端中显示的字符。找到 kernel/chr_drv/console.c 中的 con_write 函数。 该函数用到了三个变量: int nr: 缓冲队列中现有的字符数 char c: 当前正在处理字符 int state: 转义状态 外层循环 while (nr--) 对队列中的每个字符进行处理。 事实上,我们暂时只关心 state == 0 (转义初始状态) 并且 c 处于空格(' ', 32)和波浪号('~', 127)中间的情况。这种情况程序会调用汇编向终端写字符。

加入如下代码,在写字符之前根据 f12_state 的状态判断是否要将字符修改为 * 。根据要求,只将字母显示为 * 。

if((c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') && f12_state==1)
    c = '*';

最终代码如下:

void con_write(struct tty_struct * tty)
{
    int nr;
    char c;
    nr = CHARS(tty->write_q);
    while (nr--) {
        GETCH(tty->write_q,c);
        switch(state) {
            case 0:
                if (c>31 && c<127) {
                    if (x>=video_num_columns) {
                        x -= video_num_columns;
                        pos -= video_size_row;
                        lf();
                    }
                    if((c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') && f12_state==1)
                        c = '*';
                    __asm__("movb attr,%%ah\n\t"
                        "movw %%ax,%1\n\t"
                        ::"a" (c),"m" (*(short *)pos)
                        );
                    pos += 2;
                    x++;
                } else if
                ......
响应 F12 切换状态

kernel/chr_drv/keyboard.S 是键盘驱动汇编程序,主要包括键盘中断处理程序。在英文惯用法中,make 表示键被按下;break表示键被松开。该程序通过对按键键盘扫描码的识别来响应键盘中断。 根据我们的需要,找到 210 行的 func 标号下的代码。

func: /* 该子程序处理功能键 */
    pushl %eax /* 保护现场 */
    pushl %ecx
    pushl %edx
    call show_stat /* 调用显示各任务状态函数(sched.c) */
    popl %edx
    popl %ecx
    popl %eax
    subb $0x3B,%al /* 对 0x3B 做减法,0x3B 为 F1 的扫描码 */
    jb end_func 
    cmpb $9,%al
    jbe ok_func /* 0x3B ~ 0x44 分别为 F1 ~ F9 扫描码,满足则跳转至 ok_func */
    subb $18,%al
    cmpb $10,%al 
    jb end_func
    cmpb $11,%al /* 0x57, 0x58 分别为 F11, F12 扫描码,满足则跳转至 ok_func */
    ja end_func
ok_func: /* 为 F1 ~ F12 功能键,则跳转至此处。此时 al 恰为 0 ~ 11 */
    cmpl $4,%ecx        /* check that there is enough room */
    jl end_func
    movl func_table(,%eax,4),%eax
    xorl %ebx,%ebx
    jmp put_queue /* 依次将 func_table 对应的四个字符放入缓冲队列中 */
end_func:
    ret
func_table: /* 分别为 'esc [ [ A', 'esc [ [ B', ... , 'esc [ [ L' */
    .long 0x415b5b1b,0x425b5b1b,0x435b5b1b,0x445b5b1b
    .long 0x455b5b1b,0x465b5b1b,0x475b5b1b,0x485b5b1b
    .long 0x495b5b1b,0x4a5b5b1b,0x4b5b5b1b,0x4c5b5b1b

其中 0x58 为 F12 键的扫描码。现做出如下修改:判断为 0x58 时,不再调用 show_stat 函数,亦不再将 F12 对应字符放入缓冲队列,改为调用 switch_f12 。

func:

    cmpb $0x58,%al /* 判断是否 F12 键 */
    jne continue_func /* 不是则照常执行 */
    pushl %eax
    pushl %ecx
    pushl %edx
    call switch_f12 /* 切换状态 */
    popl %edx
    popl %ecx
    popl %eax
    jmp end_func /* 不再执行其他操作 */
continue_func:
    
    pushl %eax
    pushl %ecx
    pushl %edx
    call show_stat
    ...

至此,所要求功能已经实现。 1

问题回答

1. 在原始代码中,按下F12,中断响应后,中断服务程序会调用func?它实现的是什么功能?
首先调用 kernel/sched.c show_stat() 函数显示各任务状态。然后判断 al 是否在 F1 ~ F12 之间,如为真,依次将 'esc [ [ X' 字符序列送到缓冲队列中。其中 ‘X' 分别为 'A', 'B', ... , 'L' (对应 F1 ~ F12)。 在 kernel/chr_drv/console.c con_write(struct tty_struct*) 函数中,state 用来表示转义状态。esc 后接 '[' 表明是一个控制序列引导码 CSI,跳转到状态 2 处理,实现不可打印的功能。猜测后面继续跟 '[' 表示功能键,A则表示 F1。 但是,根据目前的 con_write 函数,程序遇到第二个 '[' 时会连续越过 state == 3, state == 4 两个状态重新置 0,随后紧跟的字母会通过 state == 0 状态输出到终端中。猜测其作用范围不局限于 con_write 函数,linux 0.11可能在其他地方亦有控制序列引导码的调用,能够正确识别 F1 ~ F12 所需的功能。又或是目前的 linux 版本仅将 CSI 保留用于下一版本的扩展,并未实现功能键实际识别功能。

2. 在你的实现中,是否把向文件输出的字符也过滤了?如果是,那么怎么能只过滤向终端输出的字符?如果不是,那么怎么能把向文件输出的字符也一并进行过滤?
3

否。上文修改仅限于命令行显示,没有过滤向文件输出。事实上无论向文件输出还是向命令行输出,都要经由 fs/read_write.c sys_write(unsigned int, char*, int) 函数。在这里修改即可实现向文件输出时亦过滤。代码如下:

int sys_write(unsigned int fd,char * buf,int count)
{
    struct file * file;
    struct m_inode * inode;

    if (fd>=NR_OPEN || count < 0 || !(file=current->filp[fd]))
        return -EINVAL;
    if (!count)
        return 0;

    if (f12_state == 1)
    {
        int tmpi;
        for (tmpi = 0; tmpi < count; tmpi++)
        {
            char tmpc = get_fs_byte(buf + tmpi);
            if (tmpc >= 'A' && tmpc <= 'Z' || tmpc >= 'a' && tmpc <= 'z')
                put_fs_byte('*', buf + tmpi);
        }
    }

    inode=file->f_inode;
    if (inode->i_pipe)
        return (file->f_mode&2)?write_pipe(inode,buf,count):-EIO;
    if (S_ISCHR(inode->i_mode))
        return rw_char(WRITE,inode->i_zone[0],buf,count,&file->f_pos);
    if (S_ISBLK(inode->i_mode))
        return block_write(inode->i_zone[0],&file->f_pos,buf,count);
    if (S_ISREG(inode->i_mode))
        return file_write(inode,file,buf,count);
    printk("(Write)inode->i_mode=%06o\n\r",inode->i_mode);
    return -EINVAL;
}

其中 if (f12_state == 1) 代码段为新加入的内容。此时注释掉前文对 console.c 的修改,仍然能对终端显示 * 号,并过滤向文件输出的字符。 2

部分图片出自《Linux 内核完全注释》


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值