- 前言
- 一、文件视图
- 二、显示器输出
- 三、键盘输入
- 总结
提示:以下是本篇文章正文内容
一、文件视图对于管理外设,首先我们要让外设设备工作运行
使用外设:
(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
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?