Unix环境高级编程之文件I/O


Unix环境下,可以通过Posix提供的一组 不带缓冲的I/O完成文件相关I/O。

函数open和openat

调用者两个函数可以创建或者打开一个文件。

#include <fcntl.h>
int open(const char *path, int oflag, ... /* mode_t mode */);
int openat(int fd, const char *path, int oflag, ... /* mode_t mode */);

open和openat函数返回的文件描述符一定是最小的未用描述符数值。

其中fd参数将open和openat区分开来,具体如下:

  • path参数指定的是绝对路径名,则fd参数被忽略,openat函数相当于open函数
  • path参数指定的是相对路径名,则fd参数指出了相对路径名在文件系统中的开始地址。fd参数是通过打开相对路径名所在的目录来获取
  • path参数指定了相对路径名,而fd参数具有特殊值AT_FDCWD,则路径名在当前工作目录中获取

oflag参数

以下5个常量必须指定一个且只能指定一个
O_RDONLY、O_WRONLY、O_RDWR、O_EXEC(只执行打开)、O_SEARCH(只搜索打开,应用于目录,尚未支持)
下列常量则可选
O_APPEND、O_CLOEXEC、O_CREAT(需要指定第3个参数mode)、O_EXCL、O_DIRECTORY、O_NOFOLLOW、O_NONBLOCK、O_SYNC、O_DSYNC、O_RSYNC、O_TRUNC

model参数
当创建文件时,mode参数提供新建文件的权限。系统并不在该次打开文件时检查权限,mode参数是常见的UNIX权限位集合,像八进制数0644。当O_CREAT给出时则需要mode参数,一定要谨记。

create函数

#include <fcntl.h>
int creat(const char *path, mode_t mode);

create函数可以创建一个文件,并返回一个只写方式打开的文件描述符。相当于一下调用open方法:

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

close函数

#include <unistd.h>
int close(int fd);

用于关闭文件描述符。
事实上,进程关闭的时候,其中打开的文件都会被自动关闭,但是一般建议直接显示关闭。

lseek函数

#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
  • 每个打开文件(通过open)都有一个与其关联的“当前文件偏移量”。它通常是一个非负整数(有可能为负),用于度量从文件开始处计算的字节数
  • offse参数的解释与whence参数的值有关:SEEK_SET(距开始处)、SEEK_CUR(距当前值)、SEEK_END(距文件末尾值,及偏移量是文件长度加上偏移值)
  • 若lseek执行成功,返回新的文件偏移量,故可通过 lseek( fd, 0, SEEK_CUR ); 确定当前偏移量
  • 管道、FIFO或网络套接字不可以设置偏移量,故lseek返回-1,errno被设置为ESPIPE

注意:
文件的偏移量是可以大于文件长度的,这样实际写入数据的时候就会从偏移位置开始,然后其中没有被写过的字节直接被写0,并增加文件长度。这样的文件被称为具有空洞的文件,计算文件长度的时候,不会计算空洞的长度。

read函数

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);
  • 读操作从文件的当前偏移处开始,在成功返回之前,该偏移量将增加实际读到的字节数
  • 以下情况下使得读到的字节数少于要求读的字节数
    • 读普通文件时,到达文件尾端
    • 从终端设备读时,通常一次最多读一行
    • 从网络读时,网络中的缓冲机制可能造成返回值小于所要求读的字节数
    • 从管道或FIFO读时,如若管道包含的字节少于所需的数量,那么read将只返回实际可用的字节数
    • 从某些面向记录的设备(如磁带)读时,一次最多返回一个记录
    • 当一信号造成中断,而已经读了部分数据量时

write函数

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);

其返回值通常与参数nbytes的值相同,否则表示错误。
对于普通文件,写操作从文件的当前偏移处开始。如果在打开文件时,指定了O_APPEND选项,则在每次写操作之前,将文件偏移量设置在文件的当前结尾处

文件共享

进程表

文件描述符标志,目前只有一个FD_CLOEXEC
指向一个文件表项的指针

文件表项
每次调用open打开一个文件新增一个文件表项(不同进程可打开同一个文件,导致多个文件表项)

文件状态标志(读、写、添写、同步、非阻塞等,受open时指定的oflag参数影响,也可通过fcntl函数指定FD_SETFL改变)
当前文件偏移量
指向该文件v节点表项的指针

i-node表

i节点包含文件的相关信息,如文件的所有者、文件长度、指向文件实际数据块在磁盘上所在位置的指针等。
Linux没有v节点,而是采用了一个通用i节点。无论是v节点还是通用i节点,它们都是指向一个与文件系统相关的i节点

注意:
文件描述符标志只作用于一个进程的一个文件描述符,而文件状态标志则作用于指向该文件表项的任何进程中的所有描述符

原子操作

先lseek再write,不等价于,指定了O_APPEND的write,因为带O_APPEND的write操作是一个原子操作。
函数pread、pwrite

#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);

调用pread相当于调用lseek后调用read,但又有区别:

调用pread时,无法中断其定位和读操作
不更新当前文件偏移量
调用pwrite相当于调用lseek后调用write,也有类似的区别

dup和dup2函数

#include <unistd.h>
int dup(int fd);
int dup2(int fd, int fd2);

dup返回的新文件描述符一定是当前可用文件描述符的最小数值
dup2可以用fd2参数指定新描述符的值

如果fd2已经打开,则先将其关闭
如果fd等于fd2,则dup2返回fd2,而不关闭它
否则,fd2的FD_CLOEXEC文件描述符标志就被清除,这样fd2在进程调用exec时是打开状态

sync、fsync和fdatasync函数

传统的Unix系统实现在内核中设有缓冲区高速缓存或页高速缓存,大多数磁盘I/O都通过缓冲区进行。当我们向文件写入数据时,内核通常先将数据复制到缓冲区中,然后排入队列,晚些时候再写入磁盘。这种方式被称为延迟写

#include <unistd.h>
int fsync(int fd); // 只对fd指定的一个文件其作用,并且等待写磁盘操作结束后才返回
int fdatasync(int fd); // 类似于fsync,但只影响文件的数据部分。而除数据之外,fsync还会同步更新文件的的属性
void sync(void); // 只是将所有修改过的块缓冲区排入写队列,然后就返回,不等待实际写磁盘操作结束

fcntl函数

改变已打开文件的属性

#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* int arg */ );

fcntl函数有以下5种功能

复制一个已有的描述符(cmd=F_DUPFD或F_DUPFD_CLOEXEC)
获取/设置文件描述符标志(cmd=F_GETFD或F_SETFD),某个进程的文件描述符
获取/设置文件状态标志(cmd=F_GETFL或F_SETFL),系统打开文件表中某个文件表项的文件状态标志(参见open函数的oflag参数)
获取/设置异步I/O所有权标志(cmd=F_GETOWN或F_SETOWN)
获取/设置记录锁(cmd=F_GETLK或F_SETLK

/dev/fd

打开文件/dev/fd/n等效于复制描述符n(假设描述符n是打开的)
Linux实现中的/dev/fd是个例外,它把文件描述符映射成指向底层物理文件的符号链接,例如,当打开/dev/fd/0时,事实上正在打开与标准输入关联的文件

/dev/fd文件主要由shell使用,它允许使用路径名作为调用参数的程序,能用处理其他路径名的相同方式处理标准输入和输出。

猜你喜欢

转载自blog.csdn.net/liudonghai11/article/details/85543165