深入探究文件I/O

Posted zsd0101

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入探究文件I/O相关的知识,希望对你有一定的参考价值。

  • open函数相关概念
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

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

mode是指open如果创建文件时的文件的访问权限,如果没有创建文件,则不需要mode参数;

flag相关常量有:

技术图片

 以上,flag分为了三组:

1)文件访问模式标志:O_RDONLY  O_WRONLY  O_RDWR,每次open只能用其中一个,调用fcntl()中的F_GETFL可以检索文件访问模式;

2)文件创建标志:不能检索,无法修改;

3)已打开文件的状态标志:用fcntl()中的F_GETFL和F_SETFL可以分别检索和修改此类标志,有时简称文件状态标志;

 

  • 原子操作

     open()时使用O_EXCL标志,保证调用者就是文件的创建者;使用O_APPEND标志调用open(),确保多个进程对同一文件尾部增加数据时不会覆盖彼此的输出;

 

  • 文件控制操作、打开文件的状态标志

fcntl()函数:

#include <unistd.h>
#include <fcntl.h>

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

man手册已经对其使用范围解释很清楚了:

F_GETFL (void)
Return (as the function result) the file access mode and the file status
flags; arg is ignored.

F_SETFL (int)
Set the file status flags to the value specified by arg. File access
mode (O_RDONLY, O_WRONLY, O_RDWR) and file creation flags (i.e.,
O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC) in arg are ignored. On Linux, this
command can change only the O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and
O_NONBLOCK flags. It is not possible to change the O_DSYNC and O_SYNC
flags; see BUGS, below.

以上,我们有时会需要F_SETFL去设置文件状态标志,例如标准输入输出(不是我们打开的)、socket()接口(不是通过open打开的)等等。

 

  • 文件描述符和打开文件之间的关系

下图表示的是文件描述符、打开的文件句柄、打开文件表、i-node表之间的关系,中英对照看一下,这里的概念有些模糊,文件描述符应该是我们常用的fd,如调用open返回的正整数,打开文件句柄应该是内核里面系统级的索引概念:

技术图片

 

 

 技术图片

 

  • 复制文件描述符

关于shell中的I/O重定向语法,此文章有很详细的解释:https://www.bbsmax.com/A/GBJrLoQ3d0/

dup(),dup2(),dup3(),fcntl()+F_DUPFD,都是关于复制文件描述符以及关于close-on-exec处理的

#include <unistd.h>

int dup(int oldfd);
int dup2(int oldfd, int newfd);

dup()将参数中的文件描述符oldfd复制,返回新的描述符,返回的新的描述符指向oldfd对应的打开文件,且是当前最小的未使用文件描述符;

而dup2()位oldfd创建副本,且副本的编号由newfd决定,若newfd已打开则强行关闭

 

  • 在文件特定偏移量处的I/O

pread()和pwrite()

#include <unistd.h>

ssize_t pread(int fd, void *buf, size_t count, off_t offset);

ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);

这两个函数类似于read和write,但是可以指定操作起始偏移,且使用后不会改变当前文件偏移;

 

分散输入和集中输出

#include <sys/uio.h>

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);

ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

这两个系统调用不操作单个缓冲区,可一次传入多个缓冲区数据,*iov只缓冲区,iovcnt只*中包含的缓冲区个数:

struct iovec {
    void  *iov_base;    /* Starting address */
    size_t iov_len;     /* Number of bytes to transfer */
};

技术图片

 

 readv()和writev()都是原子操作,使用时都要检查返回值判断数据操作是否完整,注意它们也会改变文件偏移;

类似于pread()和pwrite(),在指定的文件偏移量处执行分散输入/集中输出的函数是preadv()和pwritev();

#include <sys/uio.h>

ssize_t preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);

ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset);

 

  • 截断文件:truncate()和ftruncate()
#include <unistd.h>
#include <sys/types.h>

int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);

将当前文件大小设置为length参数指定的值;

若当前文件长度大于length,丢弃超出部分,若小于length,则文件尾部添加一系列空字节或一个文件空洞;

truncate()以路径名字符串指定文件,要求该文件路径名个目录有可执行权限,且有写权限;

ftruncate()以文件描述符指定文件,需要先以可写方式打开文件,该系统调用不会修改文件偏移量;

 

  • 非阻塞I/O

内核缓冲区保证了普通I/O文件不会陷入阻塞,所以打开普通文件时一般会忽略O_NONBLOCK标志;

 

  • 大文件I/O

存放文件偏移量的数据类型off_t为一个有符号长整型,32位机中文件大小限制在2^31 - 1个字节(2GB)以内,有两种方式可以使32位机获取控制大文件的要求:

1、过渡性LFS API:
    编译时程序时要定义 _LARGEFILE64_SOURCE,并且使用如下API:fopen64()  open64()  lseek64()  truncate64()  stat64()  mmap64() setrlimit64()

    增加了一些数据类型,如:struct stat64,off64_t

2、_FILE_OFFSET_BITS宏:

    在编译程序时,将宏_FILE_OFFSET_BITS的值设定为64,可以在编译时:cc -D_FILE_OFFSET_BITS 64,或者C文件中添加:#define _FILE_OFFSET_BITS 64。

    之后,相关的32位数据类型和函数会自动转换为64位版本,此种方式可以执行更强,要注意的是应用程序代码更规范(例如文件偏移变量数据类型必须是off_t,而不能用long long);

    注意此时使用printf,以如下形式:printf("offset=%lld ",(long long )offset );

 

  • /dev/fd目录

内核对每个进程体哦概念股一个特殊的虚拟目录/dev/fd,其中内容便是和进程中打开的文件描述符对应的编号,例如/dev/fd/0就对应进程的标准输入;

打开/dev/fd目录中一个文件等同于复制相应的文件描述符,以下两行功能相同:

fd = open("/dev/fd/1", O_WRONY);

fd = dup(1);

 

  • 创建临时文件
#include <stdlib.h>

int mkstemp(char *template);

    用于创建临时文件,生成一个唯一文件名并打开该文件,打开时使用了O_EXCL标志,*template是路径形式,必须是字符数组,且最后六个字符必须是XXXXXX,打开后不久需要用unlink将其文件名删除,close()后该文件将被删除 :

技术图片

 

#include <stdio.h>

FILE *tmpfile(void);

tmpfile()会创建一个名称唯一的临时文件,并以读写方式将其打开, 打开时使用了O_EXCL标志,执行成功后返回一个文件流共stdio库函数使用,该函数在打开文件后内部会调用unlink删除文件名,文件流关闭后自动删除该文件;

以上是关于深入探究文件I/O的主要内容,如果未能解决你的问题,请参考以下文章

深入探究文件I/O

TLPI(liunx/unix系统编程手册)笔记 深入探究文件I/O

Linux_Unix系统编程chapter5 深入探究文件IO

Nodejs cluster模块深入探究

java finally深入探究

JavaNIO的深入研究4内存映射文件I/O