本实验的基本内容是修改Linux 0.11的终端设备处理代码,对键盘输入和字符显示进行非常规的控制。
具体内容:
在初始状态,一切如常。用户按一次F12
后,把应用程序向终端输出所有字母都替换为*
。用户再按一次F12
,又恢复正常。第三次按F12
,再进行输出替换。依此类推。
该实验有两种方法可以使得输出的字符可以由F12
来控制:
secondary
队列的字符放入write_q
输出缓冲队列之前write_q
输出缓冲队列以后,在con_write
将其中的字符放入显存之前。第一种方式博主暂未实现,我的思路是在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。
就能够看到该实验已经完成了,输入一个ls
,正常输出,因为也不会改变flag
的值,,然后按下F12
(有的笔记本是Fn+F12
)就还是会正常输出,再按一次,将所有的字符替换成*
,然后继续按,又会到正常。
有的同学可能会好奇,包括我,为什么按下F12
就会输出那一堆东西呢,这是因为F12
本身对应的的确是一个输入,根据键盘驱动的过程,最后会将字符保存到secondary
队列中,但是因为这里设置了一个回显标志,所以会在从read_q
到secondary
之前,把字符放到write_q
队列中,就会输出一堆的东西了。
下面是kernel/tty_io.c
的函数copy_to_cooked()
的源代码,
补一个显示器驱动的过程,
从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;
}
}
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 // 不是,则不处理,返回。
HIT-OS-LAB参考资料:
1.《操作系统原理、实现与实践》-李治军、刘宏伟 编著
2.《Linux内核完全注释》
3.两个哈工大同学的实验源码
4.Linux-0.11源代码
(上述资料,如果有需要的话,请主动联系我))
该实验的参考资料
网课
官方文档
参考实验报告
返回顶部