UNIX环境编程学习——文件IO

泄露秘密 提交于 2020-03-04 02:07:10

文件描述符

  • 文件描述符是一个非负整数
  • 所有打开的文件都通过文件描述符引用
  • 按照惯例,UNIX系统shell把文件描述符0与进程的标准输入关联,文件描述符1与标准输出关联,文件描述符2与标准错误关联。使用时可替换为符号常量 STD_FILENOSTDOUT_FILENO以及STDERR_FILENO,常量定义在头文件<unistd.h>

函数open和openat

#include <fcntl.h>

int open(const char *path,int aflag, ... /* mode_t mode */);
int openat(int fd,const char *path, int aflag, ... /* mode_t mode */);

//两函数的返回值:成功返回文件描述符,失败返回-1

参数:
path:打开或创建的文件路径。
aflag:说明对该文件的处理方式

  • O_RDONLY 只读打开
  • O_WRONLY 只写打开
  • O_RDWR 读写打开
  • O_EXEC 只执行打开
  • O_SEARCH 只搜索打开

以下为一些可选常量

  • O_APPEND 每次写时都追加到文件的尾端
  • O_NONBLOCK 文件的本次打开操作和后续的I/O操作设置为非阻塞

对于openat函数中的fd

  1. path为绝对路径,fd被忽略。
  2. path为相对路径,fd指出相对路径名在文件系统中的开始地址。
  3. path为相对路径且fd被指定为特殊值AT_FDCWD,即表示相对路径从当前工作目录中获取。

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

函数creat

#include <fcntl.h>
//创建一个新文件
int creat(const char *path,mode_t mode);
//返回值:若成功返回为只写打开的文件描述符,出错返回-1

//此函数等效
open(path,O_WRONLY|O_CREAT|O_TRUNC,mode);

现在可以直接用open加上设置参数来完成creat函数的作用。

函数close

#include <unistd.h>
//关闭文件
int close(int fd);
//返回值:若成功返回0,出错返回-1

函数lseek

#include <unistd.h>
//设置文件偏移量
off_t lseek(int fd,off_t offset,int whence);
//返回值:若成功返回新的文件偏移量,出错返回-1

参数:

  • fd : 对应的文件描述符
  • offset: 偏移量
  • whence 关于偏移开始点的设置

参数whence的若干值如下:

  • SEEK_SET 文件开始处
  • SEEK_CUR 文件当前处
  • SEEK_END 文件结尾处,此时offset可以为负值

注意:

  1. 返回的文件便宜量是相对于文件开始处的偏移量,offset的值是根据whence参数确定的偏移量。
  2. 读,写操作都从当前文件偏移量处开始,并使偏移量增加所读写的字节数。
  3. 当打开一个文件时,除非指定O_APPEND选项,否则偏移量设置为0.
  4. 可能存在偏移量为负的情况,故因此对于函数返回值的检验应判断其是否等于-1。
  5. 对文件的偏移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将加长该文件,并且在文件中构成一个空洞,位于文件中但没有写过的字节都被读为0。
  6. 文件中的空洞不占用存储区。

函数read与write

#Include <unistd.h>
//读取数据
ssize_t read(int fd,void *buf,size_t nbytes);
//返回值: 成功则返回读取到的字节数,若到达文件尾则返回0,出错返回-1

//写入数据
ssize_t write(int fd,const void *buf,size_t nbypes);
//返回值:成功则返回写入的字节数,若出错返回-1

注意:

  1. 读普通文件时,在读到要求字节数之前已到达文件尾端,则直接返回读取到的字节数,同时下一次调用read时将会返回0。
  2. 当从终端设备读时,通常一次最多读一行。(可以改变)
  3. 当从网络读时,网络中的缓冲机制可能造成返回值小于所要求读的字节数。(读缓冲区数据不够,read会阻塞)
  4. 当从管道或者FIFO读取时,若管道包含的字节少于所需的数量,read将返回实际读取的字节数。

文件共享

内核使用3种数据结构表示打开文件。

  1. 每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述符表。
  2. 内核为所有打开文件维持一张文件表,其中有若干文件表项。
  3. 每个打开文件(或设备)都有一个v节点结构与一个i节点结构。其中v节点包含了文件类型和对此文件进行各种操作函数的指针。i节点包含了文件的所有者,文件长度,指向文件实际数据块在磁盘上所在位置的指针等。
    大致结构

linux系统只使用i节点而不使用v节点。

注意:

  1. 一个文件可以对应多个文件表项,这意味着可以多个进程同时操作一个文件。同时每个进程拥有自己对应的文件偏移量,文件状态设置等参数。
  2. 在write的完成后,在文件表项中,当前文件偏移量即增加写入的字节数,若这导致文件偏移量大于当前文件长度,此时文件长度将会被设置为当前文件偏移量。
  3. 如果用O_APPEND标志打开一个文件,则相应标志会被蛇者到文件表项的文件状态标志中。每次对拥有这标志的文件进行写操作时,文件表项中的当前文件偏移量首先会被设置成i节点表项中的文件长度。
  4. 使用lseek函数只能修改文件表项中的当前文件偏移量。

函数 dup和dup2

#include <unistd.h>

//两个函数均用来负值一个现有的文件描述符
int dup(int fd);
int dup2(int fd,inf fd2);

//返回值:成功则返回新的文件描述符,出错返回-1

差别:由dup返回的新文件描述符一定是当前可用文件描述符中的最小数值。对于dup2,可以用fd2参数指定新描述符的值。若fd2已经打开,则先将其关闭。若fd等于fd2,则dup2返回fd2。

注意:这复制的两个文件描述符共用同一个文件表项,此时文件表项中的文件状态标志与当前文件偏移量是公用的。

函数sync,fsync和fdatasync

#include <unistd.h>

int fsync(int fd);
int fdatasynv(int fd);
//返回值:若成功返回0,出错返回-1

void sync(void);
  • sync函数将所有修改过的块缓冲区排入写队列,然后就返回,并不等待实际写磁盘操作结束。通常,称为update的系统守护进程会周期性地调用(一般为30s)sync函数,保证了定期冲洗(flush)内核的块缓冲区。
  • fsync函数只对文件描述符fd指定的一个文件起作用,并且等待写磁盘操作结束才返回。fsync可用于数据库这样的应用程序,这种应用程序需要确保修改过的块立即写到磁盘上。
  • fdatasync函数类似于fsync,但他只影响文件的数据部分,而除数据外,fsync还会同步更新文件的属性。

函数fcntl

#include <fcntl.h>

//文件控制函数
int fcntl(int fd,int cmd,.../*int arg*/);
//返回值:成功则返回值依赖于不同的cmd,出错返回-1

fcntl函数的功能

  1. 复制一个已有的描述符(cmd = F_DUPFDF_DUPFD_CLOEXEC
  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_SETLKF_SETLKW

fcntl各功能具体分析

  • F_DUPFD 复制文件描述符fd,新文件描述符作为函数值返回。
  • F_GETFD 对应于fd的文件描述符标志作为函数值返回。
  • F_SETFD 对于fd设置文件描述符标志,新标志值按第三个参数设置
  • F_GETFL与F_SETFL 与上述两者相似,不同在于是获得与修改文件状态标志。
    注意:
  • 在设置文件描述符标志或者文件状态标志前,应当先获取当前的标志值,然后修改它在将其设置为新的标志值,不应当只调用设置函数,避免关闭以前设置的其他无关标志位。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!