[Unix.C]文件I/O

时间:2021-01-23 09:28:12

大多数unix文件I/O操作只需要用到5个函数:open、read、write、lseek和close。此处所说明的函数均为不带缓存的I/O操作(下同)。不带缓存指的是每个read和write都调用内核的一个系统调用。这些不带缓存的函数并不是ANSI C的组成部分,但是是POSIX.1和XPG3的组成部分。

文件描述符(File Descriptors)

对内科而言,所有打开的文件都由fd引用。fd是一个非负整数。当打开一个已存在的文件或创建一个新文件时,内核向进程返回一个fd。当读/写一个文件时,我们将open/creat返回的fd作为一个参数传递给read/write。

按照惯例,unix将描述符0与进程的标准输入关联,fd 1与进程的标准输出惯例,fd 2与标准出错输出关联。

在POSIX应用程序中,幻数0、1、2被符号常量STDINFILENO、STDOUTFILENO、STDERR_FILENO替代。这些常量定义在unistd.h中。

open函数

#include <fcntl.h>

int open(const char *pathname, int flags,.../*, mode_t mode*/);

仅当创建一个新文件时才使用到第三个参数。pathname打开/创建的文件名。flags用来说明这个函数的多个选项,用下列一个或多个常量进行或运算构成flags参数:

  • O_RDONLY 只读打开
  • O_WRONLY 只写打开
  • O_RDWR 读写打开
  • O_APPEND 每次写时都追加到文件尾
  • O_CREAT 若文件不存在则创建它,使用此选项时需要指定mode参数,用于说明该文件的访问权限
  • O_EXCL 如果O_CREAT同时被指定并且文件已存在,则返回一个错误。该选项用来测试文件是否已存在,若文件不存在则文件的创建是一个原子操作
  • O_TRUNC 若文件以存在且使用只写/读写打开时,将文件长度截断为0
  • O_NOCTTY 如果pathname指向的是一个终端设备,不将此设备分配为该进程的控制终端
  • O_NONBLOCK 如果p at hname指的是一个FI FO、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的 I/O操作设置非阻塞方式
  • O_SYNC 每次write都要等待物理I/O操作完成

The file descriptor returned by open is guaranteed to be the lowest-numbered unused descriptor.

creat函数

A new file can also be created by calling the creat function.

#include <fcntl.h>

int creat(const char *pathname, mode_t mode);

Returns: file descriptor opened for write-only if OK, 1 on error

Note that this function is equivalent to open (pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);

close函数

关闭一个打开的文件

#include <unistd.h>

int close(int filedes);

关闭一个文件时也释放该进程加在该文件上的所有记录锁。

当一个进程终止时,它所有的打开文件都由内核自动关闭。很多程序都使用这一功能而不显式地用close关闭打开的文件。

lseek函数

每个打开文件都有一个与其相关联的“当前文件位移量”。它是一个非负整数,用以度量从文件开始处计算的字节数。通常,读、写操作都从当前文件位移量处开始,并使位移量增加所读或写的字节数。按系统默认,当打开一个文件时,除非指定O_APPEND选择项,否则该位移量被设置为0。

可以调用lseek显式地定位一个打开文件。

#include <unistd.h>

off_t lseek(int filedes, off_t offset, int whence);

返回:若成功为新的文件位移,若出错为-1

对参数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);

这种方法也可用来确定所涉及的文件是否可以设置位移量。如果文件描述符引用的是一个管道或FIFO,则lseek返回-1,并将er rno设置为EPIPE。

lseek仅将当前的文件位移量记录在内核内,它并不引起任何I/O操作。然后,该位移量用于下一个读或写操作。

文件位移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将延长该文件,并在文件中构成一个空洞,这一点是允许的。文件中任何没有被写过的字节都被读为0。

read函数

用read函数从打开文件中读数据。

#include <unistd.h>

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

返回:读到的字节数,若已到文件尾为 0,若出错为- 1

如read成功,则返回读到的字节数。如已到达文件的尾端,则返回0。

有多种情况可使实际读到的字节数少于要求读字节数:

 

  •  读普通文件时,在读到要求字节数之前已到达了文件尾端。例如,若在到达文件尾端之前还有30个字节,而要求读100个字节,则read返回30,下一次再调用read时,它将返回0(文件尾端)。
  •  当从终端设备读时,通常一次最多读一行。
  •  当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数。
  •  某些面向记录的设备,例如磁带,一次最多返回一个记录。

 

读操作从文件的当前位移量处开始,在成功返回之前,该位移量增加实际读得的字节数。

write函数

用write函数向打开文件写数据。

#include <unistd.h>

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

返回:若成功为已写的字节数,若出错为- 1

其返回值通常与参数nbytes的值相同,否则表示出错。 write出错的一个常见原因是:磁盘已写满,或者超过了对一个给定进程的文件长度限制。

对于普通文件,写操作从文件的当前位移量处开始。如果在打开该文件时,指定了O_APPEND选择项,则在每次写操作之前,将文件位移量设置在文件的当前结尾处。在一次成功写之后,该文件位移量增加实际写的字节数。

dup和dup2函数 

一个已存在的文件描述符可以通过下面的两个函数进行复制。

#include <unistd.h>

int dup(int filedes);

int dup2(int filedes, int filedes2);

都返回: 若成功为新的文件描述符,若失败-1

dup返回的是当前可用fd中的一个最小值。dup2允许我们通过参数filedes2指定返回的fd。如果filedes2是一个以打开的fd,则先关闭它;如果filedes和filedes2相等,则直接返回filedes2。

这些函数返回的新文件描述符与参数 filede s共享同一个文件表项。

另外一个复制fd的方法是通过fcntl函数,

dup(filedes);等价于fcntl(filedes, F_DUPFD, 0);

类似地,dup2(filedes, filedes2);等价于close(filedes2);fcntl(filedes, F_DUPFD, filedes2);

对后一种情况来讲,dup2并不完全等价于close后面跟上fcntl。dup2是一个原子操作。

sync, fsync, and fdatasync Functions 

Traditional implementations of the UNIX System have a buffer cache or page cache in the kernel through which most disk I/O passes. When we write data to a file, the data is normally copied by the kernel into one of its buffers and queued for writing to disk at some later time. This is called delayed write. 

The kernel eventually writes all the delayed-write blocks to disk, normally when it needs to reuse the buffer for some other disk block. To ensure consistency of the file system on disk with the contents of the buffer cache, the sync, fsync, and fdatasync functions are provided. 

#include <unistd.h>

int fsync(int filedes);

int fdatasync(int filedes);

Returns: 0 if OK, 1 on error

void sync(void);

The sync function simply queues all the modified block buffers for writing and returns; it does not wait for the disk writes to take place. 

The function sync is normally called periodically (usually every 30 seconds) from a system daemon, often called update. This guarantees regular flushing of the kernel's block buffers. The command sync(1) also calls the sync function. 

The function fsync refers only to a single file, specified by the file descriptor filedes, and waits for the disk writes to complete before returning. The intended use of fsync is for an application, such as a database, that needs to be sure that the modified blocks have been written to the disk. 

The fdatasync function is similar to fsync, but it affects only the data portions of a file. With fsync, the file's attributes are also updated synchronously. 

fcntl Function 

The fcntl function can change the properties of a file that is already open. 

#include <fcntl.h>

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

Returns: depends on cmd if OK (see following), 1 on error

The fcntl function is used for five different purposes. 

  • Duplicate an existing descriptor (cmd = F_DUPFD)
  • Get/set file descriptor flags (cmd = F_GETFD or F_SETFD)
  • Get/set file status flags (cmd = F_GETFL or F_SETFL)
  • Get/set asynchronous I/O ownership (cmd = F_GETOWN or F_SETOWN)
  • Get/set record locks (cmd = F_GETLK, F_SETLK, or F_SETLKW) 

/dev/fd

比较新的系统都提供名为/dev/fd的目录,其目录项是名为 0、1、2等的文件。打开文件/dev/fd/n等效于复制描述符 n (假定描述符n是打开的)。