当前位置 博文首页 > 熊沐:Linux C 文件IO

    熊沐:Linux C 文件IO

    作者:熊沐 时间:2021-05-31 18:23

    文件IO

    2021-05-31 12:46:14 星期一

    目录
    • 文件IO
      • 基础IO
        • open
          • 错误
        • creat
        • read
          • 一个例子
        • write
        • close
        • lseek
          • 文件空洞
        • unlink删除
        • iotcl
      • 文件和目录
        • 链接
      • 错误打印
        • perror
        • strerror
      • 原子IO
        • fcntl
      • 文件IO缓冲
        • stdio的缓冲

    文件描述符:是有限资源

    文件描述符 POSIX名称 用途 stdio流
    0 STDIN_FILENO 标准输入 stdin
    1 STDOUT_FILENO 标准输出 stdout
    2 STDERR_FILENO 标准错误 stderr

    基础IO

    open

    #include <fcntl.h>
    
    int open(const char *path, int oflag, mode_t mode);
    
    • path文件的路径和名称
    • flag文件的打开模式,组合使用时需要使用位运算或
      • 基本模式
        • 只读 只写 读写
          O_RDONLY O_WRONLY O_RDWR
          00 01 02
      • 附加模式(只列常用)
        • O_APPEND:总是在文件末尾添加数据
        • O_EXCL:配合O_CREAT标志
          • 表明如果文件存在则不会打开文件,并使open调用失败,否则能够创建并打开文件。这样确保了调用open()的进程即为创建文件的进程.
          • 同时不允许path是符号链接
        • O_CREAT:没有文件存在时会创建文件,需要mode参数指明文件权限,一共9个
        • O_TRUNC:如果文件存在为普通文件且该进程对改文件有写权限,则清空文件内容
    • mode
      • S_IRUSR:文件所有者有读权限
      • S_IWUSR:文件所有者有写权限
      • S_IXUSR:文件所有者有执行权限
      • S_IRGRP:同组用户有读权限
      • S_IWGRP:同组用户有写权限
      • S_IXGRP:同组用户有执行权限
      • S_IROTH:其他用户有读权限
      • S_IWOTH:其他用户有写权限
      • S_IXOTH:其他用户有执行权限
    • 返回文件描述符fd或者-1

    错误

    creat

    旧版使用,新版都用open进行创建文件

    read

    open返回的fd文件描述符中读取bytes字节的数据到buf中,返回实际读取的字节数,不成功返回-1

    #include <unistd.h>
    
    ssize_t read(int fd, void *buf, size_t bytes);
    
    • read普通文件:调用成功返回实际读取的字节数,遇到文件EOF时返回0,出现错误返回-1
    • read读取终端,遇到\n即返回

    注意:read系统调用是逐字节读取的,所以无法遵守C语言中的字符串的规则。比如C语言中字符串以\00x0)作为结束,但是read认为这里的字节值是0x0并继续读下去.所以使用read读数据时,通常的方式是:每次读取数据,再将的实际读取到的size处设置成C语言认可的字符串结束符,即buffer[size] = '\0';

    #define MAX_READ 16
    
    char buffer[MAX_READ + 1];
    ssize_t size;
    size = read(fd, buffer, MAX_READ);
    if (size == -1)
        exit(0);
    buffer[size] = '\0';
    close(fd);
    

    一个例子

    该文件从终端读取一行(因为read读终端时以\n作为结束)字符并打印出来,同时打印每一个字符

    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <string.h>
    
    #define MAX_READ 16
    
    int main(int argc, char **argv)
    {
        char buffer[MAX_READ + 1];
        memset(buffer, 0x23, sizeof(buffer));  // fill with #
        buffer[MAX_READ] = '\0';
        
        ssize_t numRead, i;
        numRead = read(STDIN_FILENO, buffer, MAX_READ);
        if (numRead == -1)
            perror("read");
    
        // buffer[numRead] = '\0';
        printf("the input data was:%s\n", buffer);
        for (i = 0; buffer[i] != '\0' && i < MAX_READ; i++)
            printf("%ld->%x\n", i, buffer[i]);
        return 0;
    }
    
    $ ./4.4
    abcd
    the input data was:abcd
    ############
    0->61
    1->62
    2->63
    3->64
    4->a
    5->23
    6->23
    ...
    16->0
    

    由此可见read只是以二进制的形式照搬数据,并不对数据进行处理,因此,对数据的处理留给了程序员

    write

    bytes字节的buf数据写到open返回的fd文件描述符所指的文件中,返回实际写的字节数,不成功返回-1写入已打开的文件。调用成功并不代表已经写入磁盘,可能先进入缓存(这样减少磁盘活动量、加快write调用)。

    #include <unistd.h>
    
    ssize_t write(int fd, void *buf, size_t bytes);
    

    close

    #include <unistd.h>
    
    int close (int fd);
    

    close函数也有错误处理,编程时也应该错误检查。

    lseek

    内核打开的文件时会记录文件偏移量,第一字节的偏移量为0,文件打开时,会将偏移量设置为0

    十分重要:有时候读文件读不出来,可能就是因为文件偏移量在文件末尾处,这时候需要重置

    #include <unistd.h>
    
    off_t lseek(int fd, off_t offset, int whence);
    
    • fd文件描述符
    • offset表示偏移量
      • 正值为正向移动,向继续往下读的方向
    • wherece表示文件读写指针从哪里开始计数
      • SEEK_SET表示起始位置
      • SEEK_CUR表示当前位置
      • SEEK_END表示末尾位置的后一个字节(这里直接写数据的话是恰好和文件连接)
    • 返回新的文件偏移量或-1(执行失败)

    文件空洞

    从文件结尾后到新写入的数据间的空间,他不占用磁盘空间,直到写入了数据。这时文件的名义的大小可能比磁盘存储的总量大。具体的在14节

    ls -l file        查看文件逻辑大小
    
    du -c file     查看文件实际占用的存储块多少
    
    od -c file     查看文件存储的内容
    

    unlink删除

    只是删除path到文件的一个链接,其文件对于的i-node减1,为0时,改文件才从磁盘删除。

    #include <unistd.h>
    
    int unlink(const char *path);
    

    iotcl

    文件和目录

    目录是另一种文件,只是内容是包含的文件信息和目录信息。

    链接

    每一个文件对应一个inode,文件的链接数对应inode中的链接数,记录着这个文件的链接数值,即指向该inode的文件数,文件和inode是多对一的关系。本质上rm指令调用系统调用unlink函数,将这个文件的inode的链接数-1,为0时才真正删除

    • 硬链接
      • 相链接的文件总是同步
    • 软链接
      • 理解为Windows的快捷方式
    $ touch a.txt
    $ echo "hello" > a.txt
    
    $ ls -li
    total 4
    2104443 -rw-rw-r-- 1 dwr dwr 6 Apr 10 22:20 a.txt
    # inode编号 文件权限 用户 组用户 不知道 创建月 日 时 文件名
    $ ln a.txt a.txt.bak  # 建立硬链接
    $ ls -li
    total 8
    2104443 -rw-rw-r-- 2 dwr dwr 6 Apr 10 22:20 a.txt
    2104443 -rw-rw-r-- 2 dwr dwr 6 Apr 10 22:20 a.txt.bak
    
    $ ln -s a.txt a.txt.s  # 建立软链接
    $ ls -li
    total 8
    2104443 -rw-rw-r-- 2 dwr dwr 6 Apr 10 22:20 a.txt
    2104443 -rw-rw-r-- 2 dwr dwr 6 Apr 10 22:20 a.txt.bak
    2099642 lrwxrwxrwx 1 dwr dwr 5 Apr 10 22:25 a.txt.s -> a.txt
    
    #include <unistd.h>
    
    int link(const char *__from, const char *__to);
    int symlink(const char *__from, const char *__to);
    

    错误打印

    perror

    <errno.h>
    

    根据设置的errno值打印对应的错误信息,打印规则是先打印s中用户定义的错误输出,在打印系统调用错误的输出提示。一定要在系统调用之后紧跟打印,否则会被覆盖

    void perror(const char *s);
    

    strerror

    将错误代码转换为字符串错误信息。

    char *strerror(int errno);
    

    原子IO

    fcntl

    #include <fcntl.h>
    
    int fcntl(int __fd, int __cmd, ...)
    

    文件IO缓冲

    这里是Unix系统编程手册第13章内容,不全待完善

    read()write()系统调用在操作磁盘文件时不会直接发起磁盘访问,而是仅仅在用户空间缓冲区与内核缓冲区高速缓存(kernel buffer cache)之间复制数据。write()在后续某个时刻,内核会将其缓冲区中的数据写入(刷新至)磁盘。

    Linux 内核对缓冲区高速缓存的大小没有固定上限。内核会分配尽可能多的缓冲区高速缓存页,而仅受限于两个因素;可用的物理内存总量,以及出于其他目的对物理内存的需求(例如,需要将正在运行进程的文本和数据页保留在物理内存中)。若可用内存不足,则内核会将

    解释一下书中对IO系统调用的实验:

    • 总用时=CPU用时+磁盘读写用时
    • CPU用时=用户CPU用时(用户模式下执行的代码)+系统CPU用时(内核模式(系统调用和数据在用户和内核模式下传输)下执行的代码)

    stdio的缓冲

    C语言中IO函数可以理解为系统IO调用+数据缓冲,免于编写者自己处理对数据的缓冲

    int setvbuf(FILE *stream, char *buf, int modes, size_t n)
    

    控制stdio库函数的缓冲形式,需要最先调用,之后的stdio操作才有效

    • stream
      • 表示配置缓冲的文件流
    • buf
      • 不为空则使用size大小作为缓冲区(这个buf空间应该是堆内存上,避免函数调用和返回对栈进行修改)
      • 为空则自动分配(根据mode选择是否分配)size大小空间
    • modes
      • _IONBF:not,不缓冲,每一次调用stdio函数都立即调用系统调用
      • _IOLBF:line,行缓冲,遇到换行符或缓冲区满则调用系统调用,指向终端设备的流默认使用该模式
      • _IOFBF:file,全缓冲,指向磁盘的流默认使用该模式
    • 出错返回非0,成功返回0
    bk