哈工大操作系统实验---lab7:终端设备的控制

文章目录

        • 实验目的:
        • 实验内容:
        • 实验过程:
          • 第一种实现方式
          • 第二种实现方式
        • 实验问题:

实验目的:

  • 加深对操作系统设备管理基本原理的认识,实践键盘中断、扫描码等概念;
  • 通过实践掌握Linux 0.11对键盘终端和显示器终端的处理过程。

实验内容:

本实验的基本内容是修改Linux 0.11的终端设备处理代码,对键盘输入和字符显示进行非常规的控制。
具体内容:
在初始状态,一切如常。用户按一次F12后,把应用程序向终端输出所有字母都替换为*。用户再按一次F12,又恢复正常。第三次按F12,再进行输出替换。依此类推。


实验过程:

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

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

哈工大操作系统实验---lab7:终端设备的控制_第1张图片

第一种实现方式

第一种方式博主暂未实现,我的思路是在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。
哈工大操作系统实验---lab7:终端设备的控制_第2张图片
就能够看到该实验已经完成了,输入一个ls,正常输出,因为也不会改变flag 的值,,然后按下F12(有的笔记本是Fn+F12)就还是会正常输出,再按一次,将所有的字符替换成*,然后继续按,又会到正常。

有的同学可能会好奇,包括我,为什么按下F12就会输出那一堆东西呢,这是因为F12本身对应的的确是一个输入,根据键盘驱动的过程,最后会将字符保存到secondary队列中,但是因为这里设置了一个回显标志,所以会在从read_qsecondary之前,把字符放到write_q队列中,就会输出一堆的东西了。
下面是kernel/tty_io.c的函数copy_to_cooked()的源代码,
哈工大操作系统实验---lab7:终端设备的控制_第3张图片
哈工大操作系统实验---lab7:终端设备的控制_第4张图片

补一个显示器驱动的过程
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源代码
(上述资料,如果有需要的话,请主动联系我))

该实验的参考资料
网课
官方文档
参考实验报告
返回顶部

你可能感兴趣的:(哈工大操作系统实验---lab7:终端设备的控制)