第三章 文件I/O

文件描述符

所有打开的文件都通过文件描述符引用。操作(读写)该文件描述符就相当于操作该文件。
文件描述符是一个非负的整数。

同时文件描述符0,1,2都已经对应标准输出(输出在终端上),标准输入(从标准终端输入),标准错误。
对应STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO
这三个常量定义在中。

open和openat

#include 
int open(const char * path ,int flag, mode_t =NULL)
int openat(int fd,const char * path ,int flag, mode_t =NULL)

两者的主要区别是,open只能相对于程序运行的目录,而openat可以使相对于fd指定的目录进行打开操作,同时fd的特殊值AT_FDCWD指的是当前目录。
同时可以避免TOCTTOU,意思是如果一个操作需要两个文件操作才能完成,那么中间可能cpu调度的原因,第一个文件操作已经被更改,从而后一个操作也会不正确。可以用来获取特殊权限。

flag

这个参数主要指定了打开模式:
一下五个只能出现一个:

  1. O_RDONLY
    只读打开,0
  2. O_WDONLY
    只写打开,1
  3. O_RDWE
    读写打开,2
  4. O_EXEC
    只 执行打开。
  5. SEARCH
    只搜索打开,用于目录

一下这些常量是可选的使用|运算与上面的结合。

  1. O_APPEND
    每次写入到尾端
  2. O_CLOEXEC
    当调用exec()函数成功后,文件描述符会自动关闭
  3. O_CREAT
    如果打开的文档不存在,就自动创建,同时需要指定mode_t参数
  4. O_DICECTORY
    如果打开的不是目录jiubaocuo
  5. O_EXCL
    如果同时指定 O_CREATO_EXCL,当文件存在的时候就出错。
  6. O_NOCTTY
  7. O_NOFOLLOW
    如果打开的是一个连接符号,就出错
  8. O_NONBLOCK
    非阻塞打开,如果没读到数据就出错返回。
  9. O_SYNC
    写入数据是,需要等到数据真正写入到硬盘时才返回,包括文件属性的更新。
    ext4下,同步写和延时写差距不大?hfs下差距很大。和系统有关?
  10. O_DSYNC
    写入数据是,需要等到数据真正写入到硬盘时才返回,如果不影响读取刚写入的数据,则不需要等待文件属性更新。
  11. O_RSYNC
    读操作会等待所有对该文件的写操作完成以后才执行。
  12. O_DICECT
    看不懂

creat()

#include 
int creat(const char* path,mode_t mode)
//出错返回-1

缺点是都是以只写操作打开该文档,所以如果要读,需要关闭该文件描述符重新打开。
一般使用open(path,O_RDWR|O_CREAT,mode)代替。

close()

#include 
int close(int fd);
//成功返回0 ,出错返回-1

关闭文档,释放所有记录锁。
进程关闭以后,会关闭所有该进程所打开的文件。

lseek()

#include 
off_t lseek(int fd,off_t offset,int whence);
//出错返回-1,否则返回新的稳健偏移量

whence

偏移的起点

  1. SEEK_SET
    将文件的偏移量设置为距文件开头offset个字节,也就是说,起点是开头
  2. SEEK_CUR
    偏移起点文当前起点,offset可正可负
  3. SEEK_END
    偏移起点为文件的总长度。也就是说起点在末尾。

如果将文件描述符对一个管道FIFO或者套接字使用,那么返回-1,并且,errno为ESPIPE
因为这三个都是流式的,只能读写,不能设置偏移量。

如果将偏移量定位在结尾之后开始写入数据,那么中间的空洞被读为0。

read()

#include 
ssize_t read(int fd,void *buf,size_t n);
//成功返回读到的字节数,到文件尾返回0,出错返回-1

ssize_t是一个带符号的整数。而size_t是一个不带符号的整数。

读到的字节小于设置的字节数原因:

  1. 读到末尾
    本次返回读到的数据,下一次返回0
  2. 从终端设备读,每次只能一行
  3. 从网络中读是,网络的缓存机制可能造成
  4. 管道和FIFO中时,管道中的字节数少于所需数量
  5. 信号中断前已经读到数据

读文件从文件当前偏移量开始,成功返回之前,改变偏移量。

write()

#include 
ssize_t write(int fd,const buf*,size_t n);
//成功返回写入的字节数,错误返回-1

从偏移量位置开始写。
如果设置了O_APPEND则每次写操作之前将文件偏移量设置在文件的结尾处。

根据局部性原理,系统会预读比所指定数量多的数据,并假设它们很快会被用到。

文件共享

多进程间操作同一文件。

文件描述符标志和文件状态标志

  1. 文件描述符标志
    是体现进程的文件描述符的状态,fork进程时,文件描述符被复制;
    目前只有一种文件描述符:FD_CLOEXEC。指明是否复制进新的进程。
  2. 文件状态标志
    是体现进程打开文件的一些标志,fork时不会复制file 结构,而是两个进程文件描述符指向同一个file(当FD的exec标志为0时)

内和使用三种结构表示打开的文件

  1. 内核的记录项:文件描述符标志,文件表项,V节点表项
    每个进程在进程表中都有一个记录项,每个描述符占一项。
    每一项都有一个标志,该标志表示该文件描述符的特性,可以使用fnctl()获取和改变。
    一个指向文件表项的指针(指向内核中)
  2. 内核的文件表
    内核记录文件的状态标志,文件表项中记录着文件状态标志:阻塞,同步的方式。在该进程中打开的文件的偏移量(不同进程中可以不同)。还有一个指向V节点表项的指针。
  3. V节点结构
    V节点表项是所有进程共有一份,表示文件的信息:长度,修改时间等。
    应该是存在硬盘上。
    不同进程公用一份。

原子操作的IO

读写

#include 
ssize_t pread(int fd,void *buf,size_t n,off_t offset);
ssize_t pwrite(int fd,const void *buf,size_t n,off_t offset);
//返回值同以前

但是有用吗?offset的值如何确定?

复制文件描述符

#include 
int dup(int fd);
int dup2(int fd,init fd2);
//出错返回-1

第二个函数,在fd2出复制fd1,如果fd2处已经有一个文件打开了,那么先关闭,在打开。是一个院子操作。
如果相同,那么步步操作,直接返回。

复制以后,两个文件描述符共享一个文件表项。

缓冲

#incldue 
int sync(void);
int fsync(int fd);
int fdataasync(int fd)
//错误返回-1
  1. sync
    将所有修改过的缓存,排入写队列。也就是实际写入硬盘,但是不等待写入结束。
    对所有文件
    系统周期性执行该函数。
  2. fsync
    对指定fd,将修改过的缓存写入硬盘,并且修改文件属性,结束后返回
  3. fdatasync
    对指定fd,将修改过的缓存写入硬盘,结束后返回,可以不用等待修改文件属性结束。

fcntl()

#include 
int fcntl(int fd, int cmd,...)

第三个参数是一个整数(在get时,不需要该参数,设为0),或是一个指针
功能如下:

  1. 复制文件描述符cmd = F_DUPFD 或 F_DUPFD_CLOEXEC
    cmd = F_DUPFD返回未用最小的整数作为新的描述符返回,但是该文件描述符有自己独立的文件描述符标志,同时清除了O_CLOEXEC标志。而dup()打开的是共享的。
    cmd = F_DUPFD_CLOEXECdup2`,需要第三个参数
  2. 设置获取文件描述符状态cmd = F_GETFD 和 F_SETFD
    目前可设置和获取的只有一种,是否在新的线程中复制一份。
    set时需要第三个参数。0不关闭,1关闭。
  3. 设置获取文件状态标志cmd = F_GETFL 和 F_SETFL
    指的是,读写,阻塞,写模式(append),同步读写.
    也就是打开open的参数。
    在获取的时候,由于读写的五中模式互斥,所以需要使用一个屏蔽字O_ACCMODE与运算,取得访问方式位,然后挨个比较
int val = fcntl(fd,F_GETFL,0);
switch (val & O_ACCMODE)
  case O_RDONLY:
  case .....

而其余可选的状态标志,不需要。直接进行与运算

if (val & O_APPEN)
{
}

但是设置的时候只能是堵塞模式,缓存模式。设置的时候需要先获取,然后进行|=然后再设置。不然会清除之前的设置。

  1. 设置获取异步IO cmd = F_GETOWN 和 F_SETOWN
  2. 获取设置记录所 cmd = F_GETLK F_SETLK F_SETLKW

/dev/fd/n

打开文件,等于复制文教描述符。

int fd=open("/dev/fd/1",O_RDWR);

虽然设置了读写,但是只能使用之前打开的模式,比如之前是写模式,那么使用这种方式打开也不能读。

你可能感兴趣的:(第三章 文件I/O)