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与显示器的主要内容,如果未能解决你的问题,请参考以下文章

x86 AT&T 寻址模式中基址寄存器的省略

程序基址与变量地址偏移初探

汇编第一节-寄存器与内存寻址

CE修改器使用教程 [提高篇]

cheat engine怎么使用?谁能给我做个具体的教程?从使用方法到如何取游戏的基址,希望能够讲的详细点。

8086汇编语言学习 8086寻址方式