一、整体逻辑
根据unix/linux哲学:一切皆文件,所以设备也有设备文件。操作系统要让用户像操作文件一样去使用终端设备,即实现了设备管理。
以下内容的操作系统指linux-0.11
二、操作系统管理显示器的过程
所谓操作系统管理显示器,即让用户通过printf("Hello world!\n");
即可在屏幕上打印出Hello world!
。
1.printf函数中最核心的2件事情
(1)创建缓存buf, 存储格式化输出
此处指存储
Hello world!\n
(2)write(1, buf, ...)
1)write根据文件描述符(这里是1),把内容输出到对应的设备上。
2)1对应的就是dev/tty0, tty0指的就是终端设备文件。
解释:
①1号进程执行init函数,安装根文件系统,接着打开了/dev/tty0
,并拷贝了2份,所以文件描述符0~2对应的都是是dev/tty0。
②tty0是终端设备文件,且是字符设备文件(由crw-rw-rw-的c可知)
2.write函数
引发系统调用中断,经处理系统调用中断后,跳转到其对应的系统调用函数sys_write执行
3.sys_write函数
(1)找到文件描述符对应的文件结构
(2)取该文件的信息(信息说明了该设备的属性,比如可能是字符设备文件)
(3)根据不同的属性,调用相应的写操作函数。
显示器是字符设备。
4.rw_char函数
inode->i_zone[],即设备号指示哪个字符设备。并且是WRITE字符。
本例是i_zone[0],根据上文指出了tty0是字符设备,且由图可知设备号是4。
5.根据dev = 4, 找到其处理函数
这里和系统调用的逻辑很像,根据系统调用号,找到对应的系统调用函数。而且也是一个函数指针构成的数组。
crw_table[4],即rw_ttyx
6.rw_ttyx函数
由于rw == WRITE,所以执行tty_write函数
7.tty_write函数,实现输出的核心函数
(1)每个tty设备有3个缓冲队列,分别是读缓冲队列(read_q)、写缓冲队列(write_q)和辅助缓冲队列(secondary)。
此处先介绍write_q;
(2)write_q : 利用缓冲技术解决CPU和外设速度不匹配问题
1)如果缓冲区满了,那么就要sleep了
2)如果缓冲区未满,那么就做以下2步:
①c = get_fs_byte(b); 从用户缓冲区读1个字符。
涉及到内核段获取用户段数据的知识
②PUTCH(c, tty->write_q); 把字符放入写缓冲区中;
3)之后,才是真的开始输出屏幕了,tty->write(tty);
8.tty->write的write是tty这个结构体的成员函数,要揭开其真面目,得看tty这个结构体的初始化
tty_struct在kernel/chr_drv/tty_io.c中初始化,可以看到,write的真面目就是
con_write
9.con_write函数(从写缓冲区读数据,也就是“消费者”)
kernel/chr_drv/console.c中
(1)GETCH(tty->write_q, c); 从写缓冲区读1个字符。
(2)向显示器输出
①ah存属性值,al存字符c
②mov ax, pos
pos即显卡的寄存器
统一寻址,用mov;独立寻址,用out。
到此为止,屏幕上就出现了Hello World!的H
10.总结
要想实现让用户仅仅通过printf语句就能够向屏幕输出字符串,则操作系统要完成如下的封装:
①向某个文件描述符write要输出的字符串;
②产生系统调用中断,sys_write根据文件描述符就知道是什么类型的设备,而显示器是字符设备,所以转去rw_char函数;
③字符设备也有很多种,由设备号来标识。所以rw_char函数根据设备号,找到其对应的处理函数rw_ttyx函数。
④rw_ttyx函数根据操作类型(读 or 写)执行不同的函数,如果是写,则执行tty_write函数。
⑤tty_write函数调用con_write函数向显示器的显卡寄存器写入字符。
三、操作系统管理键盘的过程
所谓操作系统管理键盘,即用户按下键盘的某个键,比如a,则在屏幕上显示a。
1.当用户按下键盘的某个键时,引起键盘中断。
2.处理键盘中断的程序:keyboard_interrupt,以下为该程序实现的主要功能
(1)从端口0x60,读扫描码; inb $0x60,%al
inb : 读入1个字节
(2)根据扫描码,调用对应扫描码处理子程序; call key_table(,%eax,4)
大部分的按键都是执行do_self子程序
(3)do_self子程序的核心功能:
根据扫描码取对应的ASCII码 ,call put_queue,即放入读字符队列(read_q)。
(4)call do_tty_interrupt(该函数调用了copy_to_cooked)
copy_to_cooked主要作用:把read_q读缓存队列中的字符处理后放入规范模式队列(辅助队列secondary)中
到此为止,键盘输入已经完成。接下来,就是回显,即在屏幕上显示a
3.若设置了回显标志,那么copy_to_cooked还需要将字符放入写队列write_q
而结合上文,在con_write函数中,便会从写队列write_q取出字符,并向显示器输出。
到此为止,用户按下键盘的某个键,比如a,则在屏幕上显示a。