这节内容学起来比前面轻松很多,这里笔记尽量少讲一些观念的东西,尽量搞源码。
UNIX I/O
在UNIX系统中有一个说法,一切皆文件
。所有的I/O设备,如网络、磁盘都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行。这种将设备映射为文件的方式,允许UNIX内核引出一个简单、低级的应用接口,称为UNIX I/O
,这使得所有的输入和输出都能以一种统一且一致的方式来执行。
即
1.打开文件
内核返回一个小的非负整数,叫做描述符
等于内核分配一个文件名,来标示当前的文件。
内核记录有关这个打开文件的所有信息。应用程序只需要记住标示符。
Unix外壳创建进程时都有三个打开的文件
标准输入(标示符0)
标准输出(标示符1)
标准错误(标示符2)
头文件<unistd.h>定义了常量代替显式的描述符值
STDIN_FILENO
STDOUT_FILENO
STDERR_FILENO
2.改变当前的文件位置
文件位置
是从文件开头起始的字节偏移量,表示上次读到的位置。
3.读写文件
4.关闭文件
无论进程以何种原因终止,内核都会关闭所有打开的文件并释放它们的存储器资源。
打开和关闭文件
1.打开文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(char *filename,int flags,mode_t mode);
返回
:若返回成功则为新文件描述符<这个是从零开始的,每打开一个文件就递增这个文件描述符,Linux shell每个进程开始时都会打开stdin(0),stdout(1),stderr(2)
flags
(指明进程如何访问这个文件):O_RNONLY:只读
O_WRONLY:只写
O_RDWR:可读可写
O_CREAT:如果文件不存在,就创建它的一个截断的(truncated)空文件。
O_TRYBC:如果文件已经存在,就截断它。
O_APPEND:在每次写操作前,设置文件位置到文件的结尾处。
mode
(制定了新文件的访问权限位)<书P625>
2.关闭文件
#include <unistd.h>
int close(int fd);
若成功返回0,失败则返回-1.
读和写文件
#include <unistd.h>
ssize_t read(int fd,void *buf,size_t n);
//若成功则返回读的字节数,若EOF则为0,出错则为1
ssize_t write(int fd,const void *buf,size_t n);
//若成功则返回写的字节数,若出错则为-1
读写时要注意的是需要判断是否读完了。看例子:
/* $begin cpstdin */
#include "csapp.c"
int main(void)
{
char c;
while(Read(STDIN_FILENO, &c, 1) != 0)
Write(STDOUT_FILENO, &c, 1);
exit(0);
}
/* $end cpstdin */
这是从终端中一个个字符地读入,直到读到EOF为止。
结果就是
为了追踪程序中函数的调用,这边用一个强大的strace
来跟踪,在终端中输入strace ./cpstdin
即可。
由于这样是会输出所有的函数过程,看着很乱,可以用strace -e trace=read,write ./cpstdin
来追踪read和write函数。
这样可以清晰的看到,读取abc和’\n’,write在终端上。
这样做虽然能够实现想要的功能,但是开销太大。因为每读一个字符都要调用系统级函数
read
和write
,系统级调用的开销比较大,因为将操作的过程全抛给操作系统 执行上下文切换 等等工作。
这个过程通常要20000到40000
个时钟周期,可以说是开销巨大。
因而后面尽量一整块数据一起读来改进效率。
在网络通信中,如果网络延迟较大或者数据太大,经常不能一次就成功读写
完所有的数据,因而确保可靠,必须用while循环来保证完全读写。下面我
们将一下健壮的I/O包。
用RIO包健壮地读写
无缓冲的输入输出指的是直接在内存和文件之间传送数据,有缓冲的输入函数值的是输入的数据先保存在读缓冲区中,再从读缓冲区读入内存。直接在csapp.c文件中看源码吧。
1.RIO的无缓冲的输入输出函数
#include "csapp.h"
ssize_t rio_readn(int fd,void *usrbuf,size_t n);
ssize_t rio_writen(int fd,void *usrbuf,size_t n);
// 参数以及返回值的意义和read,write的一样
下面来看rio_readn的源码吧
// rio_readn - robustly read n bytes (unbuffered)
/* $begin rio_readn */
ssize_t rio_readn(int fd, void *usrbuf, size_t n)
{
size_t nleft = n;
ssize_t nread;
char *bufp = usrbuf;
while (nleft > 0) {
if ((nread = read(fd, bufp, nleft)) < 0) {
if (errno == EINTR) /* interrupted by sig handler return */
nread = 0; /* and call read() again */
else
return -1; /* errno set by read() */
}
else if (nread == 0)
break; /* EOF */
nleft -= nread;
bufp += nread;
}
return (n - nleft); /* return >= 0 */
}
/* $end rio_readn */
如上代码所示,无缓冲的输入函数rio_readn
每次都会要求系统读取剩余的所有数据,但是可能没有成功读取所有嘛,所有用一个while循环,表示只有要求的字节数都读了或者遇到EOF才停止。
下面来看rio_writen的源码吧!
/*
* rio_writen - robustly write n bytes (unbuffered)
*/
/* $begin rio_writen */
ssize_t rio_writen(int fd, void *usrbuf, size_