美文网首页
1.文件IO

1.文件IO

作者: 大雄good | 来源:发表于2016-12-12 22:22 被阅读0次

    文件IO

    Unix系统中的文件I/O通常只用到5个函数:openreadwritelseekclose。这一节描述的函数都是不带缓冲的I/O,不带缓冲是指每个readwrite都会执行一个系统调用,而不是从缓冲中读取数据。

    1.文件描述符

    对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负数,范围0~OPEN_MAX-1。默认情况下0,1,2对应进程标准输入、输出和错误,这些魔法数宏定义在<unistd.h>

    2.函数open和openat

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

    #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 */);
    

    path是打开或创建的文件名字,oflag是函数模式(比如只读),通过位与|操作设定,mode用于指定插入模式(比如从尾端追加)。两个函数的区别在于fd,共3种可能:

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

    openat的提出主要是解决:

    • 线程可以使用相对路径名打开目录中的文件,而不再只能打开当前工作目录
    • 避免TOCTTOU错误:两个基于文件的调用,由于两个调用不是原子操作,函数调用之间文件可能发生改变,导致第一个调用结果不再有效

    3.函数creat

    creat同样用于创建一个新文件:

    #include<fcntl.h>
    int creat(const char *path, mode_t mode);
    /* 返回值:成功返回文件描述符,失败返回-1 */
    

    函数等效于:

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

    creat的缺陷是它以只写方式打开创建的文件。

    4.函数close

    close函数用于关闭打开的文件:

    #include<unistd.h>
    int close(int fd);
    /* 返回值:成功0,失败-1 */
    

    关闭文件时,会释放该进程加在该文件上的所有记录锁。当一个进程终止时,内核自动关闭它所有打开的文件,所以很多程序并没显式调用close

    5.lseek

    lseek用于指定读写操作的偏移量处(不指定默认为0):

    #include<unistd.h>
    off_t lseek(int fd, off_t offset, int whence);
    /* 返回值:成功返回偏移量,失败返回-1 */
    

    whence有3个值:

    • SEEK_SET,则偏移量为距离文件开始处offset字节
    • SEEK_CUR,则偏移值设置为当前值+offset(可正可负)
    • SEEK_END,则偏移值设置为文件长度+offset(可正可负)

    利用lseek测试输入是否能使用偏移值:

    #include<apue.h>
    int main()
    {
        if(-1 == lseek(STDIN_FILENO, 0, SEEK_CUR))
            printf("cannot seek\n");
        else
            printf("seek OK\n");
        exit(0);
    }
    

    管道,FIFO或网络套接字都不能使用lseek,所以结果为(只测试了管道和普通文件):

    lseek测试

    注意:

    • 由于偏移量可能是负值,因此比较lseek返回值时应当测试它是否等于-1,而不是是否小于0。
    • 文件空洞问题,文件偏移量可以大于文件长度,因此下一次写将加长该文件,在文件中构成空洞,文件空洞并不要求占用磁盘上的存储区,具体处理方式与文件系统有关。

    空洞文件测试:

    #include "apue.h"
    #include <fcntl.h>
    
    char    buf1[] = "abcdefghij";
    char    buf2[] = "ABCDEFGHIJ";
    
    int
    main(void)
    {
        int     fd;
    
        if ((fd = creat("file.hole", FILE_MODE)) < 0)
            err_sys("creat error");
    
        if (write(fd, buf1, 10) != 10)
            err_sys("buf1 write error");
        /* offset now = 10 */
    
        if (lseek(fd, 16384, SEEK_SET) == -1)
            err_sys("lseek error");
        /* offset now = 16384 */
    
        if (write(fd, buf2, 10) != 10)
            err_sys("buf2 write error");
        /* offset now = 16394 */
    
        exit(0);
    }
    

    运行该程序可以得到:


    hole

    6.read和write

    read函数用于打开文件中读取数据:

    #include<unistd.h>
    ssize_t read(int fd, void *buf, size_t nbytes);
    /* 返回值:读取的字节数,若到尾部返回0;若出错返回-1 */
    

    write函数用于向打开文件写数据:

    #include<unistd.h>
    ssize_t write(int fd, const void *buf, size_t nbytes);
    /* 返回值:若成功,返回写入字节数;出错返回-1 */
    

    7.文件共享

    内核使用三种数据结构表示打开文件,它们间关系决定了文件共享方面一个进程对另一个进程的影响。

    打开文件的内核数据结构

    从上图可以看出,每个进程都有一个进程表项,里面存放文件表项的指针;文件表项包含当前文件状态(读、写、同步、阻塞等),偏移值v-node表项指针v-node表项包括v-node信息(文件类型和操作函数),以及inode(索引节点),索引节点包含文件信息(文件所有者、长度、指向实际磁盘位置等),Linux中没有v-node只有索引节点。

    如果两个独立进程各种打开同一个文件,则有下图关系:

    共享文件的进程

    假定第一个进程在文件描述符3上打开该文件,另一个在文件描述符4上打开该文件,这样每个进程各自获得一个文件表项(这样做,可以让每个进程拥有自己的偏移值),但是这两个文件表项指向相同的v-node。

    8.原子操作

    Unix为文件IO提供原子操作,例如在打开文件时设置O_APPEND,这样使得内核每次写操作时都将当前偏移量设置为文件尾端。

    同时提供preadpwrite函数,他们分别执行的是lseek后调用read和write,但是在调用函数时,无法中断其定位和读写操作。

    9.dup和dup2

    下面两个操作可以用来复制一个现有文件描述符:

    #include<unistd.h>
    int dup(int fd);
    int dup2(int fd, int fd2);
    /* 返回值:成功返回文件描述符;失败返回-1*/
    

    dup2是一个原子操作。

    10.fcntl

    fcntl函数可以改变已经打开文件的属性:

    #include<fcntl.h>
    int fcntl(int fd, int cmd, ... /* int arg */)
    /* 返回值:若成功,则依赖于cmd,若出错,返回-1 */
    

    fcntl函数有以下5个功能(cmd控制):

    • 复制一个已有描述符
    • 获取/设置文件描述符标志
    • 获取/设置文件状态标志
    • 获取/设置异步I/O所有权
    • 获取/设置记录锁

    11.其他函数

    其余函数ioctl/dev/fd没怎么见过,前者可以实现各种功能,后者通过打开文件的方式复制文件描述符

    小结

    这节内容挺多的,主要是对文件I/O进行介绍,很多函数功能介绍,不太容易记住,所以需要多实验。

    相关文章

      网友评论

          本文标题:1.文件IO

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