读写文件
在某些情况下,read和write传送的字节比应用程序要求的少。这些不足值不表示有错误。出现这种情况的原因如下:
• 读普通文件时,在读到要求字节数之前已到达了文件尾端。例如,若在到达文件尾端之前还有3 0个字节,而要求读1 0 0个字节,则r e a d返回3 0,下一次再调用r e a d时,它将返回0 (文件尾端)。
• 当从终端设备读时,通常一次最多读一行。
• 当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数。
• 某些面向记录的设备,例如磁带,一次最多返回一个记录。
为了创建健壮的诸如web服务器这样的网络应用,就必须通过反复调用read和write处理不足值,直到所有的需要的字节都传送完毕。下面的RIO(robust io)包,它会自动处理上文所述的不足值。RIO提供两类不同的函数:
Ø 无缓冲的输入输出函数
这些函数直接在存储器和文件之间传送数据,没有应用级缓冲。它们对将二进制数据写到网络和从网络读写二进制数据尤其有用。
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; /*手动重启read()*/ else return -1; } else if(nread == 0) break; nleft -= nread; bufp += nread; } return (n-nleft); /*返回值>=0*/ }
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; /*手动重启write()*/ else return -1; } else if(nwritten == 0) break; nleft -= nwritten; bufp += nwritten; } return n; }
Ø 带缓冲的输入函数
这些函数允许你高效的从文件读取文本行和二进制数据,这些文件的内容缓冲存在应用级缓冲区内,到缓冲的RIO输入函数是线程安全的,它在同一个描述符上可以被交替的调用。
一个文本行就是一个由换行符结尾的ASCII码字符序列。
我们可以用read函数来一次一个字节从文件传送到用户存储器,检查每个字节来查找换行符,但是这种方法效率不高,每次读取文件中的一个字节都要陷入内核。
下面封装了一个rio_readlineb 函数,可以高效的读取一个文本行。
typedef struct { int rio_fd; int rio_cnt; char *rio_bufptr; char *rio_buf[RIO_BUFFSIZE]; }rio_t; void rio_readinitb(rio_t *rp,int fd) { rp->rio_fd = fd; rp->rio_cnt = 0; rp->rio_bufptr = rp->rio_buf; } 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,rio_buf,sizeof(rp->rio_buf)); if(rp->rio_cnt < 0){ if(errno != 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; memcpy(usrbuf,rp->rio_bufptr,cnt); rp->rio_bufptr += cnt; rp->rio_cnt -= cnt; return cnt; } ssize_t rio_readlineb(rio_t *rp,void *usrbuf,size_t maxlen) { int n,rc; char c,*bufp = usrbuf; for(n = 1;n < maxlen;n++){ if((rc = rio_read(rp,&c,1)) == 1){ *bufp++ = c; if(c == '\n') break; }else if(rc == 0){ if(n == 1) return 0; else break; }else return -1; } *bufp = 0; return n; }
U N I X支持在不同进程间共享打开文件。在介绍 d u p函数之间,需要先说明这种共享。为此先说明内核用于所有I / O的数据结构。
内核使用了三种数据结构,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。
(1) 每个进程在进程表中都有一个记录项,每个记录项中有一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:
(a) 文件描述符标志。
(b) 指向一个文件表项的指针。
(2) 内核为所有打开文件维持一张文件表。每个文件表项包含:
(a) 文件状态标志(读、写、增写、同步、非阻塞等 )。
(b) 当前文件位移量。
(c) 指向该文件v节点表项的指针。
(3) 每个打开文件(或设备)都有一个 v节点结构。 v节点包含了文件类型和对此文件进行各种操作的函数的指针信息。对于大多数文件, v节点还包含了该文件的 i节点(索引节点)。这些信息是在打开文件时从盘上读入内存的,所以所有关于文件的信息都是快速可供使用的。例如, i节点包含了文件的所有者、文件长度、文件所在的设备、指向文件在盘上所使用的实际数据块的指针等等。
1.如果两个独立进程各自打开了同一文件。我们假定第一个进程使该文件在文件描述符 3上打开,而另一个进程则使此文件在文件描述符 4上打开。打开此文件的每个进程都得到一个文件表项,但对一个给定的文件只有一个 v节点表项。每个进程都有自己的文件表项的一个理由是:这种安排使每个进程都有它自己的对该文件的当前位移量。
a.假设磁盘文件foobar.txt由6个ASCII码字符“foobar”组成。那么,下列程序的输出是什么?
#include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> int main() { int fd1,fd2; char c; fd1 = open("foobar.txt",O_RDONLY,0); fd2 = open("foobar.txt",O_RDONLY,0); read(fd1,&c,1); read(fd2,&c,1); printf("c=%c\n",c); exit(0); }
描述符fd1和fd2都有各自的打开的文件表项,所以每个描述符对于foobar.txt都有自己的文件位置。输出c=f
2.父子进程共享文件。f o r k的一个特性是所有由父进程打开的描述符都被复制到子进程中,父、子进程每个相同的打开描述符共享一个文件表项。这种共享文件的方式使父、子进程对同一文件使用了一个文件位移量。
b.假设磁盘文件foobar.txt由6个ASCII码字符“foobar”组成。那么,下列程序的输出是什么?
#include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/wait.h> int main() { int fd; char c; fd = open("foobar.txt",O_RDONLY,0); if(fork()==0){ read(fd,&c,1); exit(0); } wait(NULL); read(fd,&c,1); printf("c=%c\n",c); exit(0); }
子进程会继承父进程的描述符表,以及所有进程共享的同一个打开文件表。因此,描述符fd在父子进程中都指向同一个文件表项。当子进程读取文件第一个字节时,文件位置加1,所以父进程会读取第二个字节,输出c=o
IO 重定向
Io重定向如何工作的呢?一种方式是使用dup2函数
int dup2(int oldfd,int newfd);
dup2函数拷贝描述符表表项oldfd到描述符表表项newfd ,覆盖描述符表表项newfd以前的内容。如果newfd已经打开了,dup2 会在拷贝oldfd之前关闭newfd。
c.假设磁盘文件foobar.txt由6个ASCII码字符“foobar”组成。那么,下列程序的输出是什么?
#include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> int main() { int fd1,fd2; char c; fd1 = open("foobar.txt",O_RDONLY,0); fd2 = open("foobar.txt",O_RDONLY,0); read(fd2,&c,1); dup2(fd2,fd1); read(fd1,&c,1); printf("c=%c\n",c); exit(0); }因为将fd1重定向到fd2了,所以输出c=o。
参考 《Unix环境高级编程》
《深入理解计算机系统》