美文网首页
第三章 文件I/O

第三章 文件I/O

作者: Myth52125 | 来源:发表于2017-09-07 17:08 被阅读0次

    文件描述符

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

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

    open和openat

    #include <fcntl.h>
    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 <fnctl.h>
    int creat(const char* path,mode_t mode)
    //出错返回-1
    

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

    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,否则返回新的稳健偏移量
    

    whence

    偏移的起点

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

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

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

    read()

    #include <unistd.h>
    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 <unistd.h>
    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 <unistd.h>
    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 <unistd.h>
    int dup(int fd);
    int dup2(int fd,init fd2);
    //出错返回-1
    

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

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

    缓冲

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

    fcntl()

    #include <fcntl.h>
    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

          本文链接:https://www.haomeiwen.com/subject/vnkpjxtx.html