5.1深入理解计算机系统——系统级I/O
一、UNIX I/O
在UNIX系统中有一个说法,一切皆文件。所有的I/O设备,如网络、磁盘都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行。这种将设备映射为文件的方式,允许UNIX内核引出一个简单、低级的应用接口,称为UNIX I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。
- 打开文件 打开文件操作完成以后才能对文件进行一些列的操作,打开完成过以后会返回一个文件描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。
- 改变当前的文件位置。
- 读写文件
- 关闭文件 应用完成了对文件的访问之后,就通知内核关闭这个文件,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。进程终止,内核也会关闭所有打开的文件并释放他们的存储器资源。
二、打开和关闭文件
关于打开文件的基本操作,这里就不再累述,就是关于几个函数的解释,在上面的三篇文章中有解释。
int open(char *filename,int flags,mode_t mode);
其中打开标志flags有三种基本标志:O_RDONLY、O_WRONLY、O_RDWR。也可以和其他三种(O_CREAT、O_TRUNC、O_APPEND)组合使用。mode参数指定了新文件的访问权限位。(这次终于看到完全的mode参数的使用方法了)
三、读和写文件
在系统I/O中读写文件用的系统函数为read()和write()函数来执行。
#include <unistd.h> ssize_t read(int fd,void * buf,size_t n); ssize_t write(int fd,void *buf,size_t n);
read函数从描述符为fd的当前文件位置拷贝最多n个字节到存储器位置buf。返回值-1表示一个错误,而返回值0表示EOF。否则,返回值表示的是实际传送的字节数量。而write函数从存储器位置buf拷贝至多n个字节到描述符fd的当前文件位置。返回值要么为-1要么为写入的字节数目。
/* $begin cpstdin */
#include "csapp.h" int main(void)
{
char c; while(Read(STDIN_FILENO, &c, 1) != 0)
Write(STDOUT_FILENO, &c, 1);
exit(0);
}
/* $end cpstdin */
关于在文件中定位使用的函数为lseek,在I/O库中使用的函数为fseek。
(ps:size_t和ssize_t的区别,前者是unsigned int,而后者是int)
有些情况下,read和write传送的字节比应用程序要求的要少,出现这种情况的原因如下:
- 读时遇到EOF。此时read返回0来发出EOF信号。
- 从终端读文本行。如果打开文件是与终端相关联,那么每个read函数将以此传送一个文本行,返回的不足值等于文本行的大小。
- 读和写网络套接字。可能会出现阻塞现象。(我一定会在进程间通信的时候弄清楚这个事情的前前后后,后后前前!!!)
实际上,除了EOF,在读磁盘文件时,将不会遇到不足值,而且在写磁盘文件时,也不会遇到不足值。然而,如果你想创建健壮的网络应用,就必须反复调用read和write处理不足值,直到所有需要的字节都传送完毕。(这一点在UNIX网络编程中已经领略过了!!)
四、用RIO包健壮地读写
这个包会处理上面的不足,RIO提供了方便、健壮和高效的I/O。提供了两类不同的函数:
- 无缓冲的输入输出函数 直接在存储器和文件之间传送数据,没有应用级缓冲,它们对将二进制数据读写到网络和从网络读写二进制数据尤其有用。
- 带缓冲的输入函数
ssize_t rio_readn(int fd,void *usrbuf,size_t n); ssize_t rio_writen(int fd,void *usrbuf,size_t n);
对同一个描述符,可以任意交错地调用rio_readn和rio_writen。一个问本行的末尾都有一个换行符,那么像读取一个文本中的行数怎么办,使用read读取换行符这个方法不是很妥当,可以调用一个包装函数(rio_readineb),它从一个内部读缓冲区拷贝一个文本行,当缓冲区为空时,会自动地调用read重新填满缓冲区。也就是说,这些函数都是缓冲区操作而言的。
五、读取文件元数据
应用程序能够通过调用stat和fstat函数检索到关于文件的信息(有时也称为文件的元数据)
#include <sys/stat.h> #include <unistd.h> int stat(const char *filename,struct stat *buf); int fstat(int fd,struct stat *buf);
若成功,返回0,若出错则为-1.stat以一个文件名为输入,并且填充buf结构体。fstat函数只不过是以文件描述符而不是文件名作为输入。
struct stat {
#if defined(__ARMEB__)
unsigned short st_dev;
unsigned short __pad1;
#else
unsigned long st_dev;
#endif
unsigned long st_ino;
unsigned short st_mode;
unsigned short st_nlink;
unsigned short st_uid;
unsigned short st_gid;
#if defined(__ARMEB__)
unsigned short st_rdev;
unsigned short __pad2;
#else
unsigned long st_rdev;
#endif
unsigned long st_size;
unsigned long st_blksize;
unsigned long st_blocks;
unsigned long st_atime;
unsigned long st_atime_nsec;
unsigned long st_mtime;
unsigned long st_mtime_nsec;
unsigned long st_ctime;
unsigned long st_ctime_nsec;
unsigned long __unused4;
unsigned long __unused5;
};
其中st_size成员包含了文件的字节大小。st_mode为文件访问许可位。UNIX提供的宏指令根据st_mode成员来确定文件的类型:S_ISREG(),这是一个普通文件么;S_ISDIR(),这是一个目录文件么;S_ISSOCK()这是一个网络套接字么。使用一下这个函数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int fd,size;
struct stat buf_stat;
memset(&buf_stat,0x00,sizeof(buf_stat));
fd=stat("stat.c",&buf_stat);
printf("%d\n",(int)buf_stat.st_size);
return 0;
}
六、共享文件
内核用三个相关的数据结构来表示打开的文件:
- 描述符表(descriptor table)每个进程都有它独立的描述符表,它的表项是由进程打开的文件描述符来索引的。每个打开的描述符表项指向文件表中的一个表项。
- 文件表(file table) 打开文件的描述符表项指向问价表中的一个表项。所有的进程共享这张表。每个文件表的表项组成包括由当前的文件位置、引用计数(既当前指向该表项的描述符表项数),以及一个指向v-node表中对应表项的指针。关闭一个描述符会减少相应的文件表表项中的应用计数。内核不会删除这个文件表表项,直到它的引用计数为零。
- v-node表(v-node table)同文件表一样,所有的进程共享这张v-node表,每个表项包含stat结构中的大多数信息,包括st_mode和st_size成员。
下面看几张图。
描述符1和4通过不同的打开文件表表项来引用两个不同的文件。这是典型的情况,没有共享文件,并且每个描述符对应一个不同的文件。
多个描述符也可以通过不同的文件表表项来应用同一个文件。如果同一个文件被open两次,就会发生上面的情况。关键思想是每个描述符都有它自己的文件位置,所以对不同描述符的读操作可以从文件的不同位置获取数据。
父子进程也是可以共享文件的,在调用fork()之前,父进程如第一张图,然后调用fork()之后,子进程有一个父进程描述符表的副本。父子进程共享相同的打开文件表集合,因此共享相同的文件位置。一个很重要的结果就是,在内核删除相应文件表表项之前,父子进程必须都关闭了他们的描述符。
七、I/O重定向
函数为:
函数解释:
(即:让描述符oldfd实现newfd的功能)
eg,dup2(field,1) 将标准描述符输出重定向到field描述符
假设在调用dup2(4,1)之前,我们的状态图10-11所示,其中描述符1(标准输出)对应于文件A(比如一个终端),描述符4对应于文件B(比如一个磁盘文件)。A和B的引用计数都等于1。图10-14显示了调用dup2(4,1)之后的情况。两个描述符现在都指向了文件B;文件A已经被关闭了,并且它的文件表和v-node表表项也已经被删除了;文件B的引用计数已经增加了。从此之后,任何写到标准输出的数据都被重定向到文件B。
解析图如下:
八、I/O使用的抉择方法
上图中展现了几种I/O的关系模式,在应用程序中应该使用哪些函数呢?标准I/O函数是磁盘和终端设备I/O的首选。但是对网络套接字上尽量使用健壮的RIO或者系统I/O
小结:
Unix内核使用三个相关的数据结构来表示打开的文件。描述符表中的表项指向打开文件表中的表项,而打开文件表中的表项又指向v-node表中的表项。每个进程都有它自己单独的描述符表,而所有的进程共享同一打开文件表和v-node表。
5.1深入理解计算机系统——系统级I/O的更多相关文章
- 深入理解计算机系统——系统级I/O
一.UNIX I/O 在UNIX系统中有一个说法,一切皆文件.所有的I/O设备,如网络.磁盘都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行.这种将设备映射为文件的方式,允 ...
- 深入理解计算机系统10——系统级I/O
系统级I/O 输入/输出 是在主存和外部设备之间拷贝数据的过程. 外部设备可以是:磁盘驱动器.终端和网络. 输入和输出都是相对于主存而言的. 输入是从I/O设备拷贝数据到主存.输出时从主存拷贝数据到I ...
- 《深入理解计算机系统V2》学习指导
<深入理解计算机系统V2>学习指导 目录 图书简况 学习指导 第一章 计算机系统漫游 第二章 信息的表示和处理 第三章 程序的机器级表示 第四章 处理器体系结构 第五章 优化程序性能 第六 ...
- 系统级I/O学习记录
重要知识点 输入/输出(I/O) I/O是主存和外部设备(如磁盘驱动器.终端和网络)之间拷贝数据的过程. 输入操作是从I/O设备拷贝数据到主存. 输出操作是从主存拷贝到I/O设备. Unix I/O ...
- 系统级I/O 第八周11.1~11.8
第十章 系统级I/O 输入输出I/O是在主存和外部设备(如磁盘,网络和终端)之间拷贝数据的过程.输入就是从I/O设备拷贝数据到主存,而输出就是从主存拷贝数据到I/O设备. 所有语言的运行时系统都提供执 ...
- 《深入理解计算机系统》【PDF】下载
<深入理解计算机系统>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230382303 内容提要 本书主要介绍了计算机系统的基本概念,包 ...
- 深入理解计算机系统 (Randal E.Bryant / David O'Hallaron 著)
第1章 计算机系统漫游 (已看) 1.1 信息就是位+上下文 1.2 程序被其他程序翻译成不同的格式 1.3 了解编译系统如何工作是大有益处的 1.4 处理器读并解释存储在内存中的指令 1.4.1 系 ...
- 《深入理解计算机系统》(CSAPP)读书笔记 —— 第一章 计算机系统漫游
本章通过跟踪hello程序的生命周期来开始对计算机系统进行学习.一个源程序从它被程序员创建开始,到在系统上运行,输出简单的消息,然后终止.我们将沿着这个程序的生命周期,简要地介绍一些逐步出现的关键概念 ...
- 《深入理解计算机系统》学习笔记整理(CSAPP 学习笔记)
简介 本笔记目前已包含 CSAPP 中除第四章(处理器部分)外的其他各章节,但部分章节的笔记尚未整理完全.未整理完成的部分包括:ch3.ch11.ch12 的后面几小节:ch5 的大部分. 我在整理笔 ...
随机推荐
- 在eclipse中使用枚举简单类型enum
在JAVA中终于可以使用枚举类型了,就像在C或C++使用的简单枚举. 首先就在eclipse中试试它吧. 没想到,却报了错误.我装了jdk1.5(5.0),也在eclipse中设置了,怎么会不认识en ...
- lucene学习-创建索引
本文的lucene是基于lucene3.5版本. 使用lucene实现搜索引擎开发,核心的部分是建立索引和搜索.本节主要是记录创建索引部分的内容. 创建的索引结构如图所示. 创建索引的步骤分为以下几个 ...
- JavaScript API 设计准则
好的 API 设计:在自描述的同时,达到抽象的目标. 设计良好的 API ,开发者可以快速上手,没必要经常抱着手册和文档,也没必要频繁光顾技术支持社区. 流畅的接口 方法链:流畅易读,更易理解 //常 ...
- VIM快速使用
1.VIM键盘图[转] 2.vi复制多行文本的方法 2.1 方法1:光标放到第6行, 输入:2yy 光标放到第9行, 输入:p 此方法适合复制少量行文本的情况,复制第6行(包括)下面的2行数据,放到第 ...
- EasyPlayer-RTSP-Android安卓播放器播放RTSP延迟优化策略,极低延时!
EasyPlayer-RTSP-Android安卓RTSP播放器低延迟播放延时优化策略 EasyPlayer-RTSP-Android播放器是一款专门针对RTSP协议进行过优化的流媒体播放器,其中我们 ...
- mongodb基础操作
查询选择器>db.customers.find({age:{$lt:102}})查询age小于102的数据$lte表示小于或等于$gt表示大于$gte表示大于或等于>db.customer ...
- [转载]Apache在windows下的安装配置
Apache在windows下的安装配置 转载自:http://blog.sina.com.cn/s/blog_536f16b00100cfat.html 1 Apache的下载 Apache ...
- Navigation
Makedown常用语法 1.常用标题 # 代表h1(一级标题) ## 代表h2(二级标题) ### 代表h3(三级标题) #### 代表h4(四级标题) ##### 代表h5(五级标题) ##### ...
- VCL控件组件大都应该重载TWinControl的虚函数WndProc来进行处理窗口消息的工作
TWinControl的构造函数中会调用MakeObjectInstance并且传递MainWndProc作为窗口消息处理函数,而MainWndProc则会调用虚函数WndProc来处理窗口消息.留个 ...
- speech sdk 文字转语音
1.下载SDK包 https://www.microsoft.com/en-us/download/details.aspx?id=10121 2.直接上代码 // SpeechRecognition ...