系统级I/O
为什么要学习Unix I/O
1.了解Unix I/O将帮助你理解其他的系统概念
2.有时候除了用Unix I/O别无选择
10.1 Unix I/O
一个Unix文件就是一个m字节的序列,所有的I/O设备都被模型化为文件,所有的输入输出操作都被当做对文件的读写来执行
1 打开文件:
1)、描述符:一个应用程序通过要求内核来打开相应的文件来宣告他想访问一个I/O设备,内核返回的一个小的非负整数
- 作用:在后续对此文件的所有操作中标示这个文件,应用程序只需记住这个描述符。
2)、每个进程开始时都有三个打开的文件:标准输入,标准输出(描述符为1)和标准错误(描述符为2)
2 改变当前的文件位置:
对于每个打开的文件,内核保持着一个文件位置k,初值为0。
这个文件位置是从文件开头起始的字节偏移量
应用程序通过执行seek操作显示式的设置文件的当前位置
读写文件:
1)、读操作:从文件拷贝n>0个字节到存储器。从文件位置k开始,将k增加到k+n
- 对于给定的m字节的文件,若k>=m时会出发一个EOF条件
2)、写操作:从存储器拷贝n>0个字节到文件
关闭文件:
1)、当应用完成访问文件之后,通知内核关闭这个文件
2)、内核释放文件打开时创建的数据结构,并将描述符恢复
10.2 打开和关闭文件:
进程是通过调用open函数来打开或创建文件的
int open(char *filename,int flags,mode_t maode);
open函数将filename转换成一个文件描述符,并且返回描述符数字
-
flags参数:
O_RDONLY:只读
O_WRONLY:只写
O_RDWR:可读可写O_CREAT:如果文件不存在就创建一个新的截断的空文件
O_TRUNC:如果文件已经存在就截断他
O_AAPEND:在每次操作前设到文件的结尾处 -
如何以读的方式打开一已存在的文件:
fd = Open("foo.txt",O_RDONLY,0);
-
如何打开一个一存在文件并在后面加数据:
fd = Open("foo.txt",O_WRONLY|O_APPEND,0);
mode定义了一个新文件的访问权限位
进程通过调用close函数关闭一个文件
int close(int fd);
10.3读和写文件:
应用程序分别通过read和write函数来执行输入和输出
ssize_t read(int fd,void buf.size_t n);
ssize_t write(int fd,const void buf.size_t n);
传送字节不应用程序要求的少,出现不足值错误的情况:
1 读时遇到EOF
2 从终端读文本行
3 读和写网络套接字
10.4 用RIO包健壮的读写
RIO包提供两种不同的函数:
10.4.1 无缓冲的输入输出函数:
直接在存储器和文件之间传送数据,对二进制数据和网络之间的传送尤其有用
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
10.4.2 带缓冲的输入函数:
一个文本行就是由一个换行符结尾的ASCII码字符序列。高效的从文件中读取文本行和二进制数据
包装函数:从一个内部读缓冲区拷贝一个文本行,当缓冲区变空时会自动调用read函数填满缓冲区
读程序的核心是rio_read函数
10.5 读取文件元数据
元数据:应用程序能够调用stat和fstat函数,检索到关于文件的信息元数据
参数:
stat函数以文件名作为输入
fstat函数以文件描述符作为输入
stat数据结构中的重要成员:st_mode,st_size
st_size成员包含了文件的字节数大小
st_mode成员编码了文件访问许可位和文件类型
文件类型:
- 普通文件:二进制或文本数据,宏指令:S_ISREG()
- 目录文件:包含其他文件的信息,宏指令:S_ISDIR()
- 套接字:通过网络和其他进程通信的文件,宏指令:S_ISSOCK()
- Unix提供的宏指令根据st_mode成员来确定文件的类型
10.6 共享文件
内核用三个相关的数据结构来表示打开的文件
- 描述符表:每个进程有独立的描述符表,表项由进程打开的文件描述符来索引的
- 文件表:由文件位置、引用计数以及一个指向v-node表中对应表项的指针组成
所有的文件共享一张表,关闭一个描述符会减少相应的引用计数。 - v-node表:同文件表。灭个表项包含stat结构中的信息,包括st_mode和st_size成员
关键思想是:每个描述符都有自己的文件位置,所以对不同描述符的读操作可以从文件的不同位置获取数据
父子进程重要的结果:在内核删除了相应文件表表项之前,父子进程都必须关闭他们的描述符
10.7 I/O重定向
Unix外壳提供了I/O重定向操作符,允许用户将磁盘文件和标准输入输出联系起来
工作方式:使用dup2函数
dup2函数拷贝描述符表表项oldfd到描述符表表项newfd,覆盖描述符表表项以前的内容
如果newfd已经打开了dup2会在拷贝oldfd之前关闭newfd
10.8 标准I/O
标准I/O库讲一个打开的文件模型化一个流。流就是一个指向FILE类型的结构的指针
incldue
extern FILE stdin;
extern FILE stdout;
extern FILE *stderr;
10.10 小结
1、Unix提供了少量的系统级函数,包括:
- 打开、关闭、读写文件、提取文件元数据、执行I/O重定向
2、读写程序出现不足值,应调用RIO包发福进行读写操作,自动处理不足值
3、标准I/O库和Unix I/O库: - 标准I/O库是基于Unix I/O库产生的,比Unix I/O库简单
- Unix I/O库兼容限制小,比标准I/O库更适用于网络程序
错误处理
A.1 Unix系统中的错误处理
- 系统级函数调用使用三种不同风格的返回错误:Unix风格、posix风格和DNS风格
1.Unix风格的错误处理
早期开发的函数返回值既包括错误代码也包括有用的结果
错误代码通常具有的格式:
2.POSIX风格的错误
只用返回值来表示成功或失败,任何有用的结果都返回在通过引用传递进来的函数参数中
错误代码通常具有的格式:
3.
在失败是返回NULL指针,并设置全局变量h_errno
错误代码通常具有的格式:
4.小结
包容不同错误处理风的错误报告函数
include "csapp.h"
void unix_error(char msg);
void posix_error(int code,char msg);
void dns_error(char msg);
void app_error(char msg);
A.2 错误处理包装函数
1.Unix风格的错误处理包装函数
返回一个错误时,包装函数打印一条消息后退出;否则向调用者返回一个PID
kill函数的包装函数
2.POSIX风格的错误处理包装函数
错误返回码中不会包含有用的结果,所以成功时包装函数返回void
pthread_detach函数的包装函数
3.DNS风格的错误的包装函数
gethostbyname函数的包装函数