UNIX环境高级编程》的第三章文件I/O的学习,本章包括了打开文件、读文件、写文件等等。下面是系统的介绍这些函数。

1.open函数

#include <fcntl.h>
int open(const char *pathname, int oflag, .../*mode_t mode*/);
Return filedescriptor if OK, -1 on error


第三个参数通常用于新文件被创建时候的。


pathname参数是要打开或者要创建文件的名字,oflag参数的选项详见下面的选项。

O_RDONLY      只读打开。

O_WRONLY      只写打开。

O_RDWR           读写打开。

有且仅有一个这三个参数之中的一个被指定。下面的参数则是可选的。

O_APPEND       每次写的时候追加到文件的结尾。详见3.11

O_CREAT          如果文件不存在在创建一个文件,这个选项需要第三个open函数的第三个参数----mode,指定新文件得到的允许位。详见4.5

O_EXCL             如果O_CREAT被指定而且文件已经存在则产生一个错误。详见3.11

O_TRUNC         如果文件存在而且已只读或者读写的方式打开,则文件截断为0。
O_NOCTTY        如果pathname指定的是一个终端设备,不为此进程分配此设备的控制终端。详见9.6

O_NONBLOCK  如果parhname指定的是一个FIFO,一个块设备,或者一个特殊字节设备,此选项为本次打开文件和后续的非阻塞I/O操作。

下面的三个参数也是可选的,他们是有关同步输出和输入的选项。

D_DSYNC        使每次write等待物理I/O操作完成,但是如果写操作并不影响读取刚写入的数据,则不等待文件属性被更新。

O_RSYNC        使每一个以文件描述符作为参数的read操作等待,直至任何文件同一部分进行的未写操作都完成

O_SYNC           使每次write都等待物理I/O操作完成,包括write操作引起的文件属性更新所需的I/O

2.creat函数

也可调用creat函数创建一个新文件。

#include <fcntl.h>
int creat(const char *pathname, mode_t mode);
//Return:file descriptor opened for write-only if OK,-1 on error

这个函数等价于

open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);

creat函数只能创建一个只写的文件,如果要创建一个临时文件,先读后写然后再调用creat,close,最后在open,最好的方式是调用open函数如下所示

open(pthname, O_RDWR | O_CREAT | O_TRUNC, mode);


3.close 函数

文件调用close函数结束

#include <unistd.h>
int close(int filedes);
//Return:0 if OK,-1 on error 4.lseek 函数

#include <unistd.h>
off_t lseek(int filedes, off_t offset, int whence);
// Return:new file offset if OK, -1 on error对offset参数的解释和whence有关
若whence是SEEK_SET,则将该文件的偏移量设置为距文件开始处offset个字节
若whence是SEEK_CUR,则将该文件的偏移量设置为其当前值加offset,offset可正也可为负。
若whence是SEEK_END,则将文件的偏移量设置为文件长度加offset,offset可为正或负。
若lseek成功执行,则返回文件的偏移量,可以用下列方式查看当前打开文件的偏移量。

off_t currpos;
currpos = lseek(fd, 0, SEEK_CUR);
#include "apue.h"

int
main(void)
{
if (lseek(STDIN_FILENO, 0, SEEK_CUR) == -1)
printf("cannot seek\n");
else
printf("seek OK\n");
exit(0);
}


UNIX环境高级编程第三章_编程



通常文件的当前偏移量是一个非负整数,但是某些设备允许负的偏移量,但是对于普通文件,偏移量必须是非负。

#include "apue.h"
#include <fcntl.h>

char buf1[] = "abcdefghij";
char buf2[] = "ABCDEFGHIJ";

int
main(void)
{
int fd;

if ((fd = creat("file.hole", FILE_MODE)) < 0)
err_sys("creat error");

if (write(fd, buf1, 10) != 10)
err_sys("buf1 write error");
/* offset now = 10 */

if (lseek(fd, 16384, SEEK_SET) == -1)
err_sys("lseek error");
/* offset now = 16384 */

if (write(fd, buf2, 10) != 10)
err_sys("buf2 write error");
/* offset now = 16394 */

exit(0);
}





UNIX环境高级编程第三章_descriptor_02


od命令查看文件的实际内容,-c是以字符的形式打印内容。

5.read 函数

#include <unistd.h>
ssize_t read(int filedes, void *buf, size_t nbytes);
// Return:number of byte read,0 if end offile, -1 on error

读普通文件时,在读到要求字节之前已达到了文件的尾端。例如,若在达到文件尾端执勤还有30个文件,而要求读100个字节,则read返回30,下一次再调用read时,它将返回0(文件尾部)。

当从终端设备读时,通常一次最多读一行(18章介绍如何改变这一点)。

当从网络读时,网络中的缓冲机制可能造成返回值小于所要求读的字节数。

当从管道或者FIFO读时,如从管道包含的字节少于所需的数量,那么read将只返回世界可用的字节数。

当从某些面向记录的设备读时,一次最多返回一个记录。

当从某一个信号造成中断,而已读了部分数据量时。读操作从文件的当前偏移量处开始,在成功返回之前,该偏移量将增加实际读到的字节数。

POSIX标准read函数如下。

int read(int filedes, char *buf, unsigned nbytes);

6.write函数

#include <unistd.h>

ssize_t write(int filedes, const void *buf, size_t nbytes);

// Return:number of byte written if ok, -1 on error

3.9 I/O效率

#include "apue.h"

#define BUFFSIZE 4096

int
main(void)
{
int n;
char buf[BUFFSIZE];

while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)
if (write(STDOUT_FILENO, buf, n) != n)
err_sys("write error");

if (n < 0)
err_sys("read error");

exit(0);
}


经过测试BUFSIZE为4096时所用的系统时间是最少的。


3.10 文件共享

内核使用三种数据结构表示打开的文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。

(1)每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项,与每个文件描述符相关联的是:

(a)文件描述符标志

(b)指向一个文件表项的指针

(2)内核为所有打开文件维持一张文件表。每个文件表项包含:

(a)文件状态标志

(b)当前文件偏移量

(c)指向该文件v节点表项的指针

(3)每个打开文件都有一个v节点结构。v节点包含了文件类型和对此文件进行各种操作的函数的指针。对于大多数文件。b节点还包含了该文件的i节点(i-node,索引节点)。这些信息是打开文件时从磁盘上读入内存的,所有所有关于文件的信息都是快速可供使用的。例如,i节点包含了文件的所有者、文件长度、文件所在的设备、指向文件实际数据块在磁盘上所在的指针等等。

UNIX环境高级编程第三章_descriptor_03



如果两个独立进程各自打开了同一个文件,则有图3-2中所示的安排。我们假定第一个进程的文件描述符3上大力该文件,而令一个进程则在文件描述符4上打开该文件。打开该文件的每个进程都得到了一个文件表项,但对一个给点的文件只有一个v节点表项。每个进程都有自己的文件表项的一个理由是:这种安排使每个基础都有自己的对该文件的当前偏移量。

在完成每个write后,在文件表项中的当期文件偏移量即增加缩写的字节数。如果这使当期文件偏移量超过了当期文件长度,则在i节点表项中的当前文件长度被设置为当前文件偏移量。

如果用O_APPEND标志打开一个文件,则相应标志也被设置到文件表项的文件状态标识中。每次对这种具有填写标志的文件执行写操作时。着文件表项中的当前文件偏移量首先被设置为i节点表项中的文件长度。这就使得每次写的数据都添加到文件的当前尾端处。

若一个文件用lseek定位到文件当前尾端,则文件表项中的当前文件偏移量被设置为i节点表项中的当前文件长度。

lseek函数只修改文件表项中的当前文件偏移量,没有进行任何I/O操作。

UNIX环境高级编程第三章_file_04


3.11 原子操作

假定有两个独立的进程A和B都对同一文件添加操作。每个进程都已打开了该文件,但未使用O_APPEND标志。此时,各数据结构之间的关系如图3-2中所示。每个进程都有它自己的文件表项,但是共享一个v节点表项。假定进程A调用了lseek,它将进程A的该文件当前偏移量设置为1500字节。然后内核切换进程使进程B允许。进程B执行lseek,也将进程A的该文件当前偏移量设置为1500字节。然后B调用write,它将B执行lseek,也将其对该文件的当前偏移量设置为1500字节。然后B调用write,它将B的该文件对该文件的当前偏移量曾至1600,。因为该文件的长度已经增加了,所以内核对v节点中的当前文件长度偏移量更新为1600.然后又进行进程切换使进程A恢复运行。当A调用write时,就从文件偏移量(1500字节)处数据写到文件中去。这样也就代换了进程B刚写到该文件。

#include <unistd.h>
ssize_t pread(int filedes, void *buf, size_t, off_t offset);
Return:number of bytes read,0 if end of file, -1 on error
ssize_t pwrite(int filedes, const void *buf, size_t nbytes, off_t offset);
Return:number of byte weitten if OK, -1 on error


3.12 dup和dup2函数

#include <unistd.h>
int dup(int filedes);
int dep(int filedes, int filedes2);
//如果成功返回新的文件描述符,若出错返回-1

dup返回的新文件描述符一定是当前可用文件描述符中最小数值,用dup2则可用用filedes2参数指定新描述符的数值。如果filedes2已经打开,则先将其关闭。如若filedes等于filedes2,则dup2返回filedes2,而不是关闭它。

UNIX环境高级编程第三章_终端_05

复制一个描述符的另一个方法是使用fcntl函数,3.14节将对函数进行说明。

dup(filedes);等效于

fcntl(filedes, F_DUPFD, 0);

而调用dup2(filedes, filrdes2);等效于

close(filedes2);

fcntl(filedes, F_DUPFD,filedes2);

在后一种情况下,dup2并不完全等同于close以及fcntl。它们之间的区别是:

(1)dup2是一个院子操作,而close及fcntl则包括两个函数调用。有可能在close和fcntl之间插入执行信号捕获函数,它可能修改文件描述符。

(2)dup2和fcntl有某些不同的errno。

3.13sync、fsync、fdatasync函数

UNIX环境高级编程第三章_descriptor_06

sync函数只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等于实际写磁盘操作结束。

通常称为update的系统守护进程会周期性地调用sync函数。这就保证了定期冲洗内核的块缓冲区。

fsync函数只对由文件描述符filedes指定的单一文件起作用,并且等待写磁盘操作结束,然后返回。fsync可用于数据库的应用程序,这种用于程序要确保将修改过的块立即写到磁盘上。

fdatasync函数类似于fsync,但它只影响文件的数据部分。而除数据外,fsync还会同步更新文件的属性。

3.14 fcntl函数

fcntl函数可以改变已打开的文件的性质。

#include <fcntl.h>

int fcntl(int filedes, int cmd,../*int arg*/);

第三个参数则是指向一个结构的指针

fcntl函数有5种功能:

(1)复制一个现有的描述符(cmd=F_DUPFD)。

(2)获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)。

(3)获得/设置文件状态标志(cmd=F_GETFL或F_SETFL)。

(4)获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)。

(5)获得/设置记录锁(cmd=F_GETLK、F_SETLK或F_SETLKW)。

#include "apue.h"
#include <fcntl.h>

int
main(int argc, char *argv[])
{
int val;

if (argc != 2)
err_quit("usage: a.out <descriptor#>");

if ((val = fcntl(atoi(argv[1]), F_GETFL, 0)) < 0)
err_sys("fcntl error for fd %d", atoi(argv[1]));

switch (val & O_ACCMODE) {
case O_RDONLY:
printf("read only");
break;

case O_WRONLY:
printf("write only");
break;

case O_RDWR:
printf("read write");
break;

default:
err_dump("unknown access mode");
}

if (val & O_APPEND)
printf(", append");
if (val & O_NONBLOCK)
printf(", nonblocking");
#if defined(O_SYNC)
if (val & O_SYNC)
printf(", synchronous writes");
#endif
#if !defined(_POSIX_C_SOURCE) && defined(O_FSYNC)
if (val & O_FSYNC)
printf(", synchronous writes");
#endif
putchar('\n');
exit(0);
}