22.IO与显示器
Posted PacosonSWJTU
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了22.IO与显示器相关的知识,希望对你有一定的参考价值。
【README】
1.本文内容总结自 B站 《操作系统-哈工大李治军老师》,内容非常棒,墙裂推荐;
2.显示器是输入型外设;
3.本章主要内容是讲 显示器是如何被驱动的;或操作系统是如何让用户来使用显示器的;
4.Printf是如何把字符打在显示器上的;
【1】cpu操作外设背景
1)终端设备管理
- 包括 键盘和显示器;
【图解】
本章主要讲中断 IO设备 ,包括键盘和显示器;
2)如何让外设工作起来 ;
【图解】
1)外设工作原理:
- 步骤1:Cpu向外设控制器发出指令(如写命令),向外设控制器中的寄存器(或存储器)读写数据;如显卡控制器;
- 步骤2:写入完成后,外设控制器发出中断请求通知cpu写入完成; 随后,外设控制器会根据寄存器(或存储器)的内容实际来操控外设;如显存;
- 步骤3:cpu在处理外设控制器中断时,可能会读数据到内存,当然还会进行其他后续处理;
补充: cpu向外设控制器发送的指令示例。
- out xx, al , 其中xx是外设控制器端口,即cpu向外设端口发送命令;
2)总结:cpu让外设工作起来做3件事情
- 事情1: 向外设控制器发送out指令;
- 事情2: 外设控制器执行指令完成后,发出中断请求,cpu处理中断;
- 事情3: 为了让使用外设简单,操作系统要提供一种统一的文件视图(因为各个厂商外设控制器设计不一样,如接口不一样,数据格式不一样,所以要提供统一的文件视图,或统一接口),即把外设看做一个文件;
【2】CPU操作外设
【2.1】操作外设的程序
printf 函数: 就是操作外设的程序;
【图解】
1)一段操作外设的程序
// 一段操纵外设的程序
// 打开外设对应的文件(dev,device设备)
int fd = open("/dev/xx");
for (int i=0; i<10; i++)
write(fd, i, sizeof(int));
close(fd);
2)操作系统为用户提供统一接口:
- 无论什么设备都是,操作接口都是 open, read, write, close ;
3)根据设备文件找到控制器的地址,内容格式等;
- 不同设备对应不同的设备文件 (/dev/xxx)
3)统一的文件视图
操作系统无论操作哪种设备,都需要调用统一的文件接口(文件视图),包括open,read,write,close ;
【2.2】输出到显示器
1)Printf 函数:
- Printf不是真相,printf底层调用了 系统调用write 把数据写入到显存,
- 而write系统调用或接口 最终会形成类似 out xx,al 指令发送给显存控制器 ;
【图解】
1)系统调用 write
// 在 linux/fs/read_write.c 中
// fd等于1,则写到显存; fd等于2,则写到打印机(举例)
// buf 对应内存缓冲区,要写出的数据先缓存到缓冲区,然后再写出到外设,如显存;
// count 写入字节数
int sys_write(unsigned int fd, char *buf, int count)
struct file* file;
// fd等于1,打开显示器(显存)的文件
// current 指的是当前进程pcb
file = current->filp[fd];
// 获取文件信息
inode = file -> f_inode;
补充: inode
- inode (index node)是指在许多“类Unix文件系统”中的一种数据结构,用于描述文件系统对象(包括文件、目录、设备文件、socket、管道等)。
2)fd=1的filp(文件指针)从哪里来 ?
【图解】
file = current->filp[fd];
其中fd=1;即fd=1对应到设备dev/tty0 ,而tty就是中断设备;
Open方法也是一个系统调用 sys_open ;
小结:
- write(1, buf, ...) 中的1 是 open(“dev/tty0”) 产生的;
- 所以就形成了 父进程pcb文件指针 指向 dev/tty0的inode的链条;
- 核心就是把 dev/tty0的设备信息放入(通过open系统调用)inode,并把inode读入内存;
- 根据inode把数据写入外设文件;
补充:inode (index node)是一种结构体,用于描述文件信息 或用于描述文件系统对象(包括文件、目录、设备文件、socket、管道等)。
3)向显存(屏幕)写出数据
【图解】
Write系统调用
// write 系统调用
在 linux/fs/read_write.c 中
int sys_write(unsigned int fd, char *buf, int cnt)
// 获取外设 /dev/tty0 的inode
inode = file -> f_inode;
// 判断是否字符设备; 计算机设备分为字符设备或块设备;
if (S_ISCHR( inode -> i_mode ))
// 读写字符,WIRTE 是写;
// inode->i_zone[0]表示字符设备列表中的第几个设备的设备号(本例取4) ;
return wr_char(WRITE, INODE->I_ZONE[0],buf, cnt) ;
// rw_char
// 在 linux/fs/char_dev.c 中
int rw_char(int rw, int dev, char *buf, int cnt)
// crw_table 表示字符设备操作的处理函数列表,其中 call_addr是函数指针;
crw_ptr call_addr = crw_table[MAJOR(dev)];
call_addr(rw, dev, buf, cnt); ...
4)字符设备处理函数列表 crw_table (第4个字符设备处理函数是 rw_ttyx )
【图解】
- 补充:终端设备指的是键盘和显示器; 其中键盘是读的,显示器是写的;
- tty_write:写出数据到字符设备的核心函数; 其中 tty->write_q 是把数据写入到缓冲区(队列write_q);
4) tty_write(unsigned channel, char* buf, int nr) 写出数据的核心函数
【图解】
缓冲区写完之后,tty->wirte(tty) 把缓冲区数据刷新到显存(显卡);
5)tty->write(*write) 把缓冲区数据刷新到显存
【图解】
- con_write == console_write ;指的是显示器写函数(显存写入函数,属于显存驱动程序);
- 从write_q队列或缓冲区中取出字符,并写入到显存;
- Movb _attr, %%ah, 把属性值 送入 ax寄存器高位;
- Movw %%ax, %1 把 ax 寄存器内容 写入到 外设控制器的存储空间(%1 指向pos指针);其中al存储了缓冲区字符,pos指针指向了显存基址(显存与内存统一编址,在同一个内存空间中);
- con_write函数每次只能写出一个字符到显存,多个字符循环调用 con_write 即可;
补充1:
- 有些外设控制器的寄存器地址(或存储器地址)可以与内存地址空间统一编址;寻址用mov;
- 如果外设控制器的寄存器地址独立编址,寻址用out;
- 即 mov 和 out 是一样的,没有本质区别;
- 又显存特别大,通常是与内存统一编址,所以使用的是 mov汇编指令;
补充2:如何写设备驱动程序?
- 步骤1:写出核心的out指令;
- 步骤2:然后将相应函数注册到这些表上;
- 步骤3:创建一个dev/xxx 外设文件;这个文件和你注册表上的设备名对应上;
【小结】
操作系统中最核心的kernel内核还是cpu进程管理与内存管理; 外围的设备驱动与文件系统很简单;
6)mov pos
把缓存区的数据写出到显存;
【图解】
- 显存的初始地址pos 是由 con_init() 函数初始化的,pos从0xA0000 开始 ;
6)修改 pos 显存
- pos +=2
- Pos 指向了显存基址;每写入一个字符到显存后,pos指针都会加2;因为写入一个字符占1个字节,字符属性占1个字节,所以是加2,往后移动2个字节;
【3】printf 整个过程
【图解】
- 步骤1:调用printf 库函数;
- 步骤2:调用系统调用 sys_open 打开终端文件 "dev/tty0" 获得设备号fd(显示器的设备号是1),和文件指针file;
- 步骤3:传入设备号fd 调用系统调用 sys_write;sys_write 通过文件指针找到外部设备文件的inode,判断该文件是否是字符文件;
- 步骤4:若是字符文件,则通过 inode 从 字符设备操作函数数组 crw_table 中查询 设备操作函数指针 rw_ttyx (外设文件读写函数);
- 步骤5:rw_ttyx 设备操作函数 通过传入的 WRITE标识 调用 tty_write() 函数写出数据到外设文件;
- 步骤6:tty_write函数 先把数据写出到缓冲区(或队列) write_q;
- 步骤7:写出数据到缓冲区 write_q 完成后,调用 显示器写函数 con_write();
- 步骤8: con_write() 把缓冲区的字符循环写入到 显存; 写入显存命令为 mov pos,c ; pos+=2;其中 pos指针指向了显存基址;每写出一个字符,pos自加2;因为写入了2个字节,pos需要向后移动2个字节;1个字节是字符内容,1个字节是字符属性;
以上是关于22.IO与显示器的主要内容,如果未能解决你的问题,请参考以下文章