您当前的位置: 首页 > 

风间琉璃•

暂无认证

  • 2浏览

    0关注

    337博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

IO设备-显示器与键盘

风间琉璃• 发布时间:2021-10-04 15:32:25 ,浏览量:2

文章目录
  • 前言
  • 一、文件视图
  • 二、显示器输出
  • 三、键盘输入
  • 总结

提示:以下是本篇文章正文内容

一、文件视图

对于管理外设,首先我们要让外设设备工作运行

使用外设:

(1)向外设对应的端口地址发送 CPU 命令

(2)CPU 通过端口地址发送对外设的工作要求,通常就是命令“out ax, 端口号”,其中 AX 寄存器存放的就是让外设工作的具体内容

(3)外设开始工作,工作完成后产生中断 ,CPU 会在中断处理程序中处理外设的工作结果 在这里插入图片描述

但是对不同的设备进行操作很麻烦,需要查寄存器地址、内容的格式和语义, 所以操作系统要给用户提供一个简单视图—文件视图

在这里插入图片描述 控制外设:

int fd = open(“/dev/xxx”);//打开外设文件 - 不同的设备文件名对应不同的外设
for (int i = 0; i filp 数据中存放当前进程打开的文件,如果一个文件不是当前进程打开的,那么就一定是其父进程打开后再由子进程继承来的

void main(void)
{ if(!fork()){ init(); }

void init(void)
{open(“dev/tty0”,O_RDWR,0);dup(0);dup(0);
execve("/bin/sh",argv,envp)}

系统初始化时init()打开了终端设备,dup()是复制,tty0是终端设备。 在 init 函数中我们调用 open 打开一个名为“/dev/tty0”的文件,由于这是该进程打开的第一个文件,所以对应的文件句柄 fd = 0,接下来使用了两次 dup,使得 fd = 1,fd = 2 也都指向了“/dev/tty0” 的 FCB(文件控制块)。

open系统调用

在linux/fs/open.c中
int sys_open(const char* filename, int flag)
{ 
	i=open_namei(filename,flag,&inode);
	cuurent->filp[fd]=f; //第一个空闲的fd
	f->f_mode=inode->i_mode; f->f_inode=inode;
	f->f_count=1; 
	return fd;
 }

用open()把设备信息(dev/tty0)的读进来备用,open_namei根据文件名字读入inode,inode是存放在磁盘上的设备信息,flip存储在进程的PCB中。

核心就是建立下面的关系: 在这里插入图片描述 每个进程(PCB)都有一个自己的file_table,存放inode

inode找到了,继续完成sys_write()

在linux/fs/read_write.c中
int sys_write(unsigned int fd, char *buf,int cnt)
{ 
	inode = file->f_inode;
	if(S_ISCHR(inode->i_mode))
	return rw_char(WRITE,inode->i_zone[0], buf,cnt); 
	...

根据 inode 中的信息判断该文件对应的设备是否是一个字符设备,显示器是字符设备

如果是字符设备,sys_write 调用函数 rw_char() 中去执行,写设备传入write,inode->i_zone[0] 中存放的就是该设备的主设备号4和次设备号0

在linux/fs/char_dev.c中
int rw_char(int rw, int dev, char *buf, int cnt)
{ 
	crw_ptr call_addr=crw_table[MAJOR(dev)];
	call_addr(rw, dev, buf, cnt); 
	...
}

rw_char() 函数中以主设备号(MAJOR(dev))为索引从一个函数表 crw_table 中找到和终端设备对应的读写函数 rw_ttyx,并调用

crw_table定义

static crw_ptr crw_table[]={...,rw_ttyx,}; 
typedef (*crw_ptr)(int rw, unsigned minor, char *buf, int count)

函数 rw_ttyx 中根据是设备读操作还是设备写操作调用相应的函数, 显示器和键盘构成了终端设备 tty,显示器只写,键盘只读

static int rw_ttyx(int rw, unsigned minor, char *buf, int count)
{ 
	return ((rw==READ)? tty_read(minor,buf): tty_write(minor,buf));
}

printf是输出所以调用tty_write(minor,buf)

在linux/kernel/tty_io.c中
int tty_write(unsigned channel,char *buf,int nr)
{ 
	struct tty_struct *tty;tty=channel+tty_table;
	sleep_if_full(&tty->write_q); //输出就是放入队列
	...
}

这个函数就是实现输出的核心函数,由于CPU速度快,但是往显示器上写内容速度很慢,所以先将内容写到缓冲区里,即一个队列中,等到合适的时候,由操作系统统一将队列中的内容输出到显示器上,如果缓冲区已满,就睡眠等待

如果没有满,继续看tty_write

在linux/kernel/tty_io.c中
int tty_write(unsigned channel, char *buf, int nr)
{ 
	...
	char c, *b=buf;
	while(nr>0&&!FULL(tty->write_q)) 
	{
		c = get_fs_byte(b);
		if(c==‘\r’){PUTCH(13,tty->write_q);continue;}
		if(O_LCUC(tty)) c = toupper(c);
		b++; nr--; PUTCH(c,tty->write_q);
	} //输出完事或写队列满!
	tty->write(tty);
}

如果队列没有满,就从用户缓存区读出一个字符(get_fs_byte()) ,进行一些判断和操作后,将字符 放入队列tty->write_q 中(PUTCH()),如果读出的字符是 \r )或 写队列满后,跳出循环。 继续调用 tty->write()

tty_write结构体

在include/linux/tty.h中
struct tty_struct
{ 
	void (*write)(struct tty_struct *tty); 
	struct tty_queue read_q, write_q; 
}

在 tty 结构体中可以看到 write 函数,根据对 tty 结构体的初始化可以看出,tty->write 调用的函数是 con_write。 tty_table的定义及初始化

struct tty_struct tty_table[] = 
{
	{con_write,{0,0,0,0,””},{0,0,0,0,””}},{},…};

con_write向终端写数据(内嵌汇编)

在linux/kernel/chr_drv/console.c中
void con_write(struct tty_struct *tty)
{ 	
	GETCH(tty->write_q,c);
	if(c>31&&c            
关注
打赏
1665385461
查看更多评论
0.0901s