(1)一个Unix文件就是一个m个字节的序列:B0,B1,B2,B3...Bk...Bm-1。
(2)所有的I/O设备,如网络、磁盘盒终端,都被模型化为文件,而所有的输入和输出都被当做对相应的文件的读和写来执行。这是一种应用接口,成为Unix I/O。
这使得所有的输入和输出都能以一种统一且一致的方式来执行:
①打开文件
②改变当前的文件位置。
③读写文件。
④关闭文件。
(3)
①输入是从I/O设备拷贝数据到主存,输出是从主存拷贝数据到I/O设备。
②一个文件就是一个字节序列。
③所有的I/O设备,如网络、磁盘、和终端,都被模型化为文件,而所有的输入和输出都被当做想对应的文件的读写来执行。
(1)进程是通过调用
open
函数来打开一个已存在的文件或者创建一个新文件的
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
(2)创建一个新文件,文件的拥有者有读写权限,而所有其他的用户都有读权限
umask(DEF_UMASK);
fg=Open("foo.txt",O_CREAT|O_TRUNC|O_WEONLY,DEF_MODE);
(3)通过调用close函数关闭一个打开的文件
int close(int fd);//
返回值成功为0,出错为-1
(1)应用程序是通过分别调用read和write函数来执行输入和输出的
(2)read函数从描述符为fd的当前文件位置拷贝最多n个字节到存储器位置buf,返回值-1表示一个错误。而返回值0表示EOF。否则,返回值表示的是实际传送的字节数量
(3)write函数从存储器位置buf拷贝至多n个字节到描述符fd的当前文件位置
(4)通过调用lseek函数,应用程序能够显示地修改当前文件的位置
(5)在某些情况下,read和write传送的字节比应用程序要求的要少,这些不足值不表示有错误
①
读时遇到EOF。假设我们猪呢比读一个文件,该文件从当前文件位置开始只含有20多个字节,而我们以50个字节的片进行读取。这样一来,下一个read返回的不足值为20,此后的read将通过返回不足值0来发出EOF信号。
②
从终端读文本行。如果打开文件是与终端相关联的(如键盘和显示器),那么每个read函数将以此传送一个文本行,返回的不足值等于文本行的大小。
③
读和写网络套接字。如果打开的文件对应于网络套接字,那么内部缓冲约束和较长的网络延迟会引起read和write返回不足值。对Unix管道调用read和write时,也有可能出现不足值,这种进程间的通信机制不在我们讨论的范围之内。
实际上,除了EOF,在读磁盘文件时,将不会遇到不足值,而且在写磁盘文件时,也不会遇到不足值。如果想创建简装的诸如web服务器这样的网络应用,就必须通过反复调用read和write处理不足值,直到所有需要的字节都传送完毕。
(1)RIO包会自动处理不足值。RIO提供了两类不同的函数
①无缓冲的输入输出函数。这些函数直接在存储器和文件之间传送数据,没有应用级缓冲,他们对将二进制数据读写到网络和从网络读写二进制数据尤其有用。
②带缓冲的输入函数。这些函数允许你高效地从文件中读取文本行和二进制数据,这些文件的内容缓存在应用级缓冲区内,类似于像printf这样的标准I/O函数提供的
(2)RIO的无缓冲的输入输出函数
①通过调用rio_readn和rio_writen函数,应用程序可以在存储器和文件之间直接传送数据
②rio_readn函数从描述符fd的当前文件位置最多传送n和字节到存储器位置usrbuf
③如果rio_readn和rio_writen函数被一个从应用信号处理程序的发放你会中断,那么每个函数都会手动地重启read或write。为了尽可能有较好地可移植性,允许被中断的系统调用,并在必要时重启他们
(3)RIO的带缓冲的输入函数
①一个文本行就是一个由换行符结尾的ASCII码字符序列。在Unix系统中,换行符(‘\n')与ASCII码换行符(LF)相同,数字值为0x0a
#include "csapp.h"
void rio_readinitb(rio_t *rp,int fd);//无返回
ssize_t rio_readlineb(rio_t *rp,void *usrbuf,size_t maxlen);
ssize_t rio_readnb(rio_t *rp,void *usrbuf,size_t n);//返回值:若成功为读得字节数,若EOF则为0,若出错为-1
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)
nread=0;
else
return -1;
}
else if (nread==0)
break;
nleft-=nread;
bufp+=nread;
}
return(n-nleft);
}
ssize_t rio_writen(int fd,void *usrbuf,size_t n)
{
size_t nleft=n;
ssize_t nwritten;
char *bufp=usrbuf;
while(nleft>0){
if((nwritten=write(fd,bufp,nleft))<=0){
if(errno==EINTR)
nwritten=0;
else
return -1;
}
nleft -=nwritten;
bufp+=nwritten;
}
return n;
}
(4)RIO读程序核心:rio-read函数
static ssize_t rio_read(rio_t *rp,char *usrbuf,size_t n)
{
int cnt;
while(rp->rio_cnt<=0)//如果缓冲区为空,先调用函数填满缓冲区再读数据
{
rp->rio_cnt=read(rp->rio_fd,rp->rio_buf,sizeof(rp->rio_buf));//调用read函数填满缓冲区
if(rp->rio_cnt<0)//排除文件读不出数据的情况
{
if(error != EINTR)
{
return -1;
}
}
else if(rp->rio_cnt=0)
return 0;
else
rp->rio_bufptr = rp->rio_buf;//更新现在读到的位置
}
cnt=n;
if(rp->rio_cnt<n)
cnt=rp->rio_cnt;//以上三步,将n与rp->rio_cnt中较小的值赋给cnt
memcpy(usrbuf,rp->rio_bufptr,cnt);把读缓冲区的内容拷贝到用户缓冲区
rp->rio_bufptr+=cnt;
rp->rio_cnt-=cnt;
return cnt;
}
10.5 读取文件元数据
(1)应用程序能够通过调用stat和fstat函数,检索到关于文件的信息(元数据)
(2)文件类型
①普通文件:二进制或文本数据,宏指令:S_ISREG()
②目录文件:包含其他文件的信息,宏指令:S_ISDIR()
③套接字:通过网络和其他进程通信的文件,宏指令:S_ISSOCK()
(1)内核用三个相关的数据结构来表示打开的文件
①描述符表
②
文件表
③v
-node表
①典型
②共享
③继承
(1)Unix外壳提供了I/O重定向操作符,允许用户将磁盘文件和标准输入输出联系起来
(2)重定向使用dup2函数
(1)Unix外壳提供I/O重定向操作符,允许用户将磁盘文件和标准输入输出联系起来
(2)I/O重定向工作:一种方式是使用dup2函数
(3)dup2函数拷贝描述符表表项oldfd到描述符表表项newfd,覆盖描述符表表项newfd以前的内容。如果newfd已经打开,dup2会在拷贝oldfd之前关闭newfd