linux终端设备处理代码,哈工大操作系统实验---lab7:终端设备的控制

文章目录

实验目的:

实验内容:

实验过程:

第一种实现方式

第二种实现方式

实验问题:

实验目的:

加深对操作系统设备管理基本原理的认识,实践键盘中断、扫描码等概念;

通过实践掌握Linux 0.11对键盘终端和显示器终端的处理过程。

实验内容:

本实验的基本内容是修改Linux 0.11的终端设备处理代码,对键盘输入和字符显示进行非常规的控制。

具体内容:

在初始状态,一切如常。用户按一次F12后,把应用程序向终端输出所有字母都替换为*。用户再按一次F12,又恢复正常。第三次按F12,再进行输出替换。依此类推。

实验过程:

该实验有两种方法可以使得输出的字符可以由F12来控制:

在将secondary队列的字符放入write_q输出缓冲队列之前

在字符已经放入了write_q输出缓冲队列以后,在con_write将其中的字符放入显存之前。

4b5cb11cdbc354cb32564e5f434dc551.png

第一种实现方式

第一种方式博主暂未实现,我的思路是在tty_io.c中定义一个flag,然后写一个change函数,在secondary队列向write_q转字符的时候,提前加上判断:

if(Flag_12 == 1 && ( (c >= 48 && c<= 57) || (c>=65 && c<=90) || (c>=97 && c<=122) ) ) c = '*';

编译也是没有问题的,但是依然达不到字符转成*的效果。

第二种实现方式

首先说一下键盘驱动的过程:

当按下一个按键(比如F12),会引发键盘中断,调用中断处理函数keyboard_interrupt(汇编代码),然后在其中会调用key_table(表),然后根据键入的内容(也就是F12对应的扫描码),调用相应的函数do_self(汇编代码),该函数首先将扫描码转换成对应的字符,然后找到read_q输入缓冲队列并将字符放进去,当do_self函数处理完毕,会返回到keyboard_interrupt后面,最后会调用do_tty_interrupt返回文件视图,其中又会直接调用copy_to_cooked函数,功能就是将read_q队列中的字符一个个取出放到secondary队列,然后在该函数中,由于secondary中有字符了,就会调用wake_up函数,就会唤醒tty_read的读操作,然后就会一直到scanf,最终就是从键盘中断,到达了文件视图。

过程如下:

keyboard_interrupt -> inb 0x60,al -> do_self -> read_q -> copy_to_cooked -> secondary -> wake_up -> tty_read -> rw->ttyx -> rw->char -> sys_read -> read -> scanf

好,回到本实验

在上述调用do_self函数的时候,会根据扫描码找到对应的处理方式,看源码可以知道,最终会调用func函数,现在要使得按下按键F12,能够使得输出的字符在*和普通字符之间转换,就是要改写这个func函数,这里只需要在最后添加:

push %eax

push %ecx

push %edx

call change_F12_flag

pop %edx

pop %ecx

pop %eax

可以看到核心就是在按下F12的时候,会调用一个change_F12_flag函数,所以接下来就是去写这个函数,这个函数需要放在kerner/chr_drv/console.c中,因为这个文件就是放的一堆处理控制台输出的函数,

int F12_flag = 0;//定义一个全局遍历F12_flag,0表示正常输出,1表示输出“*”

void change_F12_flag(void)

{

if(F12_flag)

{

F12_flag = 0;

}

else

{

F12_flag = 1;

}

}

最后改写con_write函数,可以到看该函数开了一个循环while(nr--)在不断的取出write_q队列中的字符,然后进行处理之后放入显存,直接看到case 0:,可以发现它处理的就是acill码为32-126之间的字符,而且可以看到一个关键的嵌入式汇编代码:

__asm__("movb attr,%%ah\n\t"

"movw %%ax,%1\n\t"

::"a" (c),"m" (*(short *)pos)

);

这里就是将write_q的字符放入显存的代码,所以我们需要在前面加点东西,

if(F12_flag == 1 && ( (c >= 48 && c<= 57) || (c>=65 && c<=90) || (c>=97 && c<=122) ) )

c = '*';

如果flag=1,而且又是数字\大写字母\小写字母,我就将它转换成*,

如果flag!=1,就是正常的字符。

然后就编译内核make all,发现没有报错,最后./run来运行os。

235a2903319f3f467e6fbbda11ceb02a.png

就能够看到该实验已经完成了,输入一个ls,正常输出,因为也不会改变flag 的值,,然后按下F12(有的笔记本是Fn+F12)就还是会正常输出,再按一次,将所有的字符替换成*,然后继续按,又会到正常。

有的同学可能会好奇,包括我,为什么按下F12就会输出那一堆东西呢,这是因为F12本身对应的的确是一个输入,根据键盘驱动的过程,最后会将字符保存到secondary队列中,但是因为这里设置了一个回显标志,所以会在从read_q到secondary之前,把字符放到write_q队列中,就会输出一堆的东西了。

下面是kernel/tty_io.c的函数copy_to_cooked()的源代码,

e54f1bb2dbf7cf5d2668dc23b9eb4397.png

ea3050f387a19a524348c681cfda6a69.png

补一个显示器驱动的过程,

从printf()打印字符开始,printf()只是一个简单的函数调用,跟踪代码可以发现系统调用是write()函数,然后它的内核实现是sys_write(),该函数会去寻找需要写入的设备对应的文件,这里也就是显示器对应的文件, 显示器对应的句柄标识fd=1,所以查表可以得到文件的FCB,然后通过FCB可以得到文件对应设备的属性和类型,对应的信息存放在inode变量里面,继续往下看sys_write(),后面会根据inode变量判断该文件是不是字符文件,显示器是字符文件,然后会直接调用rw_char(),在该函数中又会调用和终端设备对应的读写函数rw_ttyx(),在这个函数中又会判断是读还是写,写就直接调用tty_write()函数,tty_write()要做的就是将之前的buff缓冲区的内容写到write_q输出缓冲队列上,最后会调用con_write()来讲缓冲队列中的字符真正的写到显示器上,con_write()函数的核心代码是一段汇编,其中的mov ax,[pos]会将字符一个个的输出到显示器上,从printf()这一文件视图到最终的设备驱动也就完成了。

过程如下:

printf -> write -> sys_write -> rw_char -> rw_ttyx -> write_q -> con_write -> mov ax,[pos]

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]))//获取显示器设备对应文件的pcb指针

return -EINVAL;

inode = file->f_inode;//通过fcb得到文件的属性和类型放入inode

if (S_ISCHR (inode->i_mode))// 如果是字符型文件,则进行写字符设备操作

return rw_char (WRITE, inode->i_zone[0], buf, count, &file->f_pos);

...

}

int rw_char(int rw,int dev, char * buf, int count, off_t * pos)

{

//根据主设备号(传入的i_zone[0]为主设备号,i_zone[1]为次设备号)从函数表crw_table中查找和显示器对应的读写函数rw_ttyx

if (!(call_addr=crw_table[MAJOR(dev)]))

...

}

static int rw_ttyx(int rw,unsigned minor,char * buf,int count,off_t * pos)

{

return ((rw==READ)?tty_read(minor,buf,count):

tty_write(minor,buf,count));

}

int tty_write (unsigned channel, char *buf, int nr)

{

PUTCH (c, tty->write_q);//在该函数里面可以看到将字符放入write_q队列的语句

...

tty->write (tty);//调用con_write()函数

}

con_write (struct tty_struct *tty)

{

GETCH (tty->write_q, c);//从write_q缓冲队列中取出字符

...

_asm {//内嵌式汇编: 真正将字符显示到显示器上

mov al,c;

mov ah,attr;

mov ebx,pos

mov [ebx],ax;

}

}

实验问题:

在原始代码中,按下F12,中断响应后,中断服务程序会调用func?它实现的是什么功能?

my answer: 按下F12,中断处理程序一直会到func函数,它会调用call _show_stat,功能就是用于显示各任务状态信息的。

func:

push eax

push ecx

push edx

call _show_stat // 调用显示各任务状态函数(kernl/sched.c, 37)。

pop edx

pop ecx

pop eax

sub al,3Bh // 功能键'F1'的扫描码是0x3B,因此此时al 中是功能键索引号。

jb end_func // 如果扫描码小于0x3b,则不处理,返回。

cmp al,9 // 功能键是F1-F10?

jbe ok_func // 是,则跳转。

sub al,18 // 是功能键F11,F12 吗?

cmp al,10 // 是功能键F11?

jb end_func // 不是,则不处理,返回。

cmp al,11 // 是功能键F12?

ja end_func // 不是,则不处理,返回。

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

my answer: 没有过滤掉,因为我只是改写了con_write()函数,只是过滤了向终端输出的字符,要过滤掉文件的字符就是需要在copy_to_cooked()函数中提前处理字符(不过我这个没有实现出来)。

HIT-OS-LAB参考资料:

1.《操作系统原理、实现与实践》-李治军、刘宏伟 编著

2.《Linux内核完全注释》

3.两个哈工大同学的实验源码

4.Linux-0.11源代码

(上述资料,如果有需要的话,请主动联系我))

该实验的参考资料

网课

官方文档

参考实验报告

返回顶部

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值