标准I/O库-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板

标准I/O库

在这里插入图片描述

1、标准I/O库简介

什么是标准I/O库

  • 标准C库当中用于文件I/O操作相关的一套库函数,使用标准I/O需要包含头文件

标准I/O和文件I/O之间的区别

  • 标准I/O是库函数,而文件I/O是系统调用

  • 标准I/O是对文件I/O的封装

  • 可移植性:标准I/O相对于文件I/O具有更好的额移植性

  • 效率:标准I/O在效率上要优于文件I/O

2、FILE 指针和fopen函数

FILE指针

  • 标准I/O使用FLIE指针作为文件句柄,与文件I/O中的文件描述符相似

打开文件–fopen()函数

  • 标准I/O中使用fopen()函数打开文件,是对open函数的封装

  • #include <stdio.h>
    FILE *fopen(const char *path, const char *mode);

    • path:参数 path 指向文件路径,可以是绝对路径、也可以是相对路径

    • mode:参数 mode 指定了对该文件的读写权限

    • 返回值:调用成功返回一个指向 FILE 类型对象的指针(FILE *),该指针与打开或创建的文件相关联,后续的标准 I/O 操作将围绕 FILE 指针进行。如果失败则返回 NULL,并设置 errno

    • 新建文件的权限

      • 无法手动指定文件的权限,但却有一个默认值:
        S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH (0666)(110 110 110)

        • 所有用户都只有可读可写权限,没有可执行权限

关闭文件–fclose()函数

  • 关闭一个由 fopen()打开的文件

  • #include <stdio.h>
    int fclose(FILE *stream);

    • stream 为 FILE 类型指针

    • 调用成功返回 0;失败将返回 EOF(也就是-1),并且会设置 errno

3、读文件和写文件

读文件–fread()函数

  • #include <stdio.h>
    size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);1

    • ptr:fread()将读取到的数据存放在参数 ptr 指向的缓冲区中

    • size:fread()从文件读取 nmemb 个数据项,每一个数据项的大小为 size 个字节,所以总共读取的数据大小为 nmemb * size 个字节

    • nmemb:参数 nmemb 指定了读取数据项的个数

    • stream:FILE 指针

    • 返回值:调用成功时返回读取到的数据项的数目nmemb,如果发生错误或到达文件末尾,则 fread()返回的值将小于参数 nmemb,可以使用 ferror()或 feof()函数来判断

写文件–fwrite()函数

  • #include <stdio.h>
    size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

    • ptr:将参数 ptr 指向的缓冲区中的数据写入到文件中

    • size:参数 size 指定了每个数据项的字节大小

    • nmemb:参数 nmemb 指定了写入的数据项个数

    • stream:FILE 指针

    • 返回值:调用成功时返回读取到的数据项的数目nmemb,如果发生错误,则 fwrite()返回的值将小于参数 nmemb(或者等于 0)

设置读写位置–fseek()函数

  • 用于设置文件读写位置偏移量

  • #include <stdio.h>
    int fseek(FILE *stream, long offset, int whence);

    • stream:FILE 指针

    • offset:偏移量,以字节为单位

    • 用于定义参数 offset 偏移量对应的参考值,该参数为下列其中一种(宏定义):

      • SEEK_SET

        • 读写偏移量将指向 offset 字节位置处(从文件头部开始算)
      • SEEK_CUR

        • 读写偏移量将指向当前位置偏移量 + offset 字节位置处,offset 可以为正、也可以为负
      • SEEK_END

        • 读写偏移量将指向文件末尾 + offset 字节位置处,同样 offset 可以为正、也可以为负
    • 返回值:成功返回 0;发生错误将返回-1,并且会设置 errno

      • 与 lseek()函数的返回值意义不同

        • lseek()函数返回值

          • 成功将返回从文件头部开始算起的位置偏移量(字节为单位)

          • 错误将返回-1

4、feof和ferror函数

判断是否到达文件末尾–feof()函数

  • 当文件的读写位置移动到了文件末尾时,end-of-file 标志将会被设置

  • #include <stdio.h>
    int feof(FILE *stream);

    • 如果 end-of-file 标志被设置了,则调用
      feof()函数将返回一个非零值,如果 end-of-file 标志没有被设置,则返回 0

判断是否发生错误–ferror()函数

  • 当对文件的 I/O 操作发生错误时,错误标志将会被设置

  • #include <stdio.h>
    int ferror(FILE *stream);

    • 如果错误标志被设置了,则 调用 ferror()函数
      将返回一个非零值,如果错误标志没有被设置,则返回 0

清除标志–clearerr()函数

  • 用于清除 end-of-file 标志和错误标志

    • 当调用 feof()或 ferror()校验这些标志后,通常需 要清除这些标志,避免下次校验时使用到的是上一次设置的值,此时可以手动调用 clearerr()函数清除标志

    • 对于 end-of-file 标志,当调用 fseek()成功时也会清除文件的 end-of-
      file 标志

  • #include <stdio.h>
    void clearerr(FILE *stream);

    • 此函数没有返回值,调用将总是会成功!

5、格式化I/O

格式化输出

  • printf()

    • 用于将格式化数据写入到标准输出

    • int printf(const char *format, …);

      • 函数调用成功返回打印输出的字符数;失败将返回一个负值!
  • fprintf()

    • int fprintf(FILE *stream, const char *format, …);

    • 函数调用成功返回写入到文件中的字符数;失败将返回一个负值!

  • dprintf()

    • int dprintf(int fd, const char *format, …);

    • 函数调用成功返回写入到文件中的字符数;失败将返回一个负值!

  • sprintf()

    • int sprintf(char *buf, const char *format, …);

    • 一般会使用这个函数进行格式化转换

    • 会在字符串尾端自动加上一个字符串终止字符’\0’

    • 可能会造成由参数 buf 指定的缓冲区溢出,调用者有责任确保该缓冲区足够大,因为缓冲区溢出会造成程序不稳定甚至安全隐患!

    • 函数调用成功返回写入到 buf 中的字节数;失败将返回一个负值!

  • snprintf()

    • int snprintf(char *buf, size_t size, const char *format, …);

    • 使用参数 size 显式的指定缓冲区的大小,如果写入到缓冲区的字节数大于参数 size 指定的大小,超出的部分将会被丢弃!如果缓冲区空间足够大,snprintf()函数就会返回写入到缓冲区的字符数,与 sprintf()函数相同,也会在字符串末尾自动添加终止字符’\0’

    • 若发生错误,snprintf()将返回一个负值!

格式化输入

  • scanf()

    • 将用户输入(标准输入)的数据进行格式化转换

    • int scanf(const char *format, …);

    • 函数调用成功后,将返回成功匹配和分配的输入项的数量;如果较早匹配失败,则该数目可能小于所提供的数目,甚至为零。发生错误则返回负值。

  • fscanf()

    • 从 FILE 指针指定文件中读取数据,并将数据进行格式化转换

    • int fscanf(FILE *stream, const char *format, …);

    • 函数调用成功后,将返回成功匹配和分配的输入项的数量;如果较早匹配失败,则该数目可能小于所提供的数目,甚至为零。发生错误则返回负值

  • sscanf()

    • 从参数 str 所指向的字符串中读取数据,并将数据进行格式化转换

    • int sscanf(const char *str, const char *format, …);

    • 函数调用成功后,将返回成功匹配和分配的输入项的数量;如果较早匹配失败,则该数目可能小于所提供的数目,甚至为零。发生错误则返回负值。

6、文件I/O的内核缓冲

文件I/O的内核缓冲

- 系统调用 write() 与磁盘操作并不是同步的

- 提高文件 I/O 的速度和效率

- Linux内核对于文件I/O的内核缓冲区大小没有固定上限,因此内核会尽可能分配更多的内存作为文件I/O的内核缓冲区。系统可用的物理内存越多,内核缓冲区也会相应变大,这样操作更大的文件时就能依赖更大空间的内核缓冲区。

刷新文件I/O的内核缓冲

  • 系统调用(fsync、fdatasync、sync)

    • fsync()函数

      • 将参数 fd 所指文件的内容数据和元数据写入磁盘,只有在对磁盘设备的写入操作完成之后,fsync()函数才会返回

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

        • fd 表示文件描述符,函数调用成功将返回 0,失败返回-1 并设置 errno
      • 元数据

        • 元数据并不是文件内容本身的数据,而是一些用于记录文件属性相关的数
          据信息,譬如文件大小、时间戳、权限等等信息,这些信息存储在磁盘设备中
    • fdatasync()函数

      • fdatasync()与 fsync()类似,不同之处在于 fdatasync()仅将参数 fd 所指文件的内容数据写入磁盘,并不包括文件的元数据;同样,只有在对磁盘设备的写入操作完成之后,fdatasync()函数才会返回

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

    • sync()函数

      • 将所有文件 I/O 内核缓冲区中的文件内容数据和元数据全部更新到磁盘设备中

      • #include <unistd.h>
        void sync(void);

      • 在 Linux 实现中,调用 sync()函数仅在所有数据已经写入到磁盘设备之后才会返回;然后在其它系统中,sync()实现只是简单调度一下 I/O 传递,在动作未完成之后即可返回

  • O_DSYNC和O_SYNC标志 控制文件 I/O 内核缓冲标志

    • O_DSYNC 标志

      • 当在调用open()函数时指定O_DSYNC标志,这个标志的作用类似于在每次调用write()函数后立即调用fdatasync()函数来进行数据同步。这意味着在使用O_DSYNC标志的情况下,系统会确保每次写入操作后都会将数据同步到磁盘,实现数据同步
    • O_SYNC 标志

      • 在调用 open()函数时,指定 O_SYNC 标志,使得每个 write()调用都会自动将文件内容数据和元数据刷新到磁盘设备中,其效果类似于在每个 write()调用之后调用 fsync()函数进行数据同步

7、直接I/O-绕过内核缓冲

O_DIRECT标志

  • 在调用 open()函数打开文件时,指定
    O_DIRECT 标志就可以实现直接I/O

  • 如:fd = open(filepath, O_WRONLY | O_DIRECT);

  • 需要在程序中自己指定 O_DIRECT 标志

    • 直接在源文件中定义:#define _GNU_SOURCE

    • gcc 编译时使用-D 选项定义_GNU_SOURCE 宏

      • gcc -D_GNU_SOURCE -o testApp testApp.c

      • 该宏定义在整个源码工程中都是生效的,是一个全局宏定义

直接I/O的对齐限制

  • 1、应用程序的缓冲区起始地址必须以块大小的整数倍进行对齐

    • 定义了一个静态字符数组

      • 其大小为块的整数倍字节

      • 指定数组在内存中的对齐方式为块大小个字节

        • 通过__attribute((aligned (块大小)))这个属性
      • 如:static char buf[8192] __attribute((aligned (4096)));

  • 2、写文件时,文件的位置偏移量必须是块大小的整数倍

    • 调用lseek()函数来移动读写指针
  • 3、写入到文件的数据大小必须是块大小的整数倍

    • 先判断写入字节
  • 不满足3 个对齐条件种的任何一个,然后编译程序进行测试,会发生 write()函数会报错,均是“Invalid argument”错误。

对比测试

  • 直接I/O方式的效率和性能较低,大多数应用程序不会使用直接I/O方式来进行文件I/O操作,通常只在一些特殊的应用场景下才会考虑使用。然而,我们可以利用直接I/O方式来测试磁盘设备的读写速率,因为这种测试方式相比普通I/O方式更加准确。

8、stdio缓冲

标准I/O的stdio缓冲

  • 标志I/O在应用层维护了自己的缓冲区,把这个缓冲区称为stdio缓冲

    • 标准 I/O(fopen、fread、fwrite、fclose、fseek 等)是 C 语言标准库函数

    • 文件 I/O(open、read、write、
      close、lseek 等)是系统调用

stdio缓冲的缓冲类型

  • 1、无缓冲_IONBF

    • 不对 I/O 进行缓冲(无缓冲)。意味着每个标准 I/O 函数将立即调用 write()或者 read(),并且忽略 buf 和 size 参数,可以分别指定两个参数为 NULL 和 0。标准错误 stderr 默认属于这一种类型,从而保证错误信息能够立即输出。
  • 2、行缓冲_IOLBF

    • 采用行缓冲 I/O。在这种情况下,当在输入或输出中遇到换行符"\n"时,标准 I/O 才会执
      行文件 I/O 操作。对于输出流,在输出一个换行符前将数据缓存(除非缓冲区已经被填满),当输出换行符时,再将这一行数据通过文件 I/O write()函数刷入到内核缓冲区中;对于输入流,每次读取一行数据。对于终端设备默认采用的就是行缓冲模式,譬如标准输入和标准输出。
  • 3、全缓冲_IOFBF

    • 采用全缓冲 I/O。在这种情况下,在填满 stdio 缓冲区后才进行文件 I/O 操作(read、write)。对于输出流,当 fwrite 写入文件的数据填满缓冲区时,才调用 write()将 stdio 缓冲区中的数据刷入 内核缓冲区;对于输入流,每次读取 stdio 缓冲区大小个字节数据。默认普通磁盘上的常规文件默认常用这种缓冲模式。

对stdio缓冲进行设置

  • setvbuf()函数

    • 对文件的 stdio 缓冲区进行设置,譬如缓冲区的缓冲模式、缓冲区的大小、起始地址等

    • #include <stdio.h>
      int setvbuf(FILE *stream, char *buf, int mode, size_t size);

      • stream:FILE 指针,用于指定对应的文件,每一个文件都可以设置它对应的 stdio 缓冲区

      • 如果参数buf不为NULL,则buf指向大小为size的内存区域将被用作该文件的stdio缓冲区。因为stdio库会使用buf指向的缓冲区,所以应该以动态方式(在堆内存中分配,比如使用malloc)或静态方式在堆中为该缓冲区分配空间,而不是在栈上分配函数内的自动变量(局部变量)。

      • 如果buf等于NULL,则stdio库会自动分配一块空间作为该文件的stdio缓冲区(除非参数mode配置为非缓冲模式)

      • mode:参数 mode 用于指定缓冲区的缓冲类型

      • size:指定缓冲区的大小

      • 返回值:成功返回 0,失败将返回一个非 0 值,并且会设置 errno

    • 当stdio缓冲区中的数据被刷新到内核缓冲区或被读取后,这些数据就不再存在于缓冲区中,而是被刷新到了内核缓冲区或被读取走了。

  • setbuf()函数

    • setbuf()函数构建与 setvbuf()之上,执行类似的任务

    • #include <stdio.h>
      void setbuf(FILE *stream, char *buf);

    • setbuf()调用除了不返回函数结果(void)外,就相当于:

      • setvbuf(stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ);

      • 要么 将 buf 设置为 NULL 以表示无缓冲 ,要么指向由调 用者分配 的 BUFSIZ 个字节大小的 缓冲区
        (BUFSIZ 定义于头文件<stdio.h>中,该值通常为 8192)

  • setbuffer()函数

    • setbuffer()函数类似于 setbuf(),但允许调用者指定 buf 缓冲区的大小

    • #include <stdio.h>
      void setbuffer(FILE *stream, char *buf, size_t size);

    • setbuffer()调用除了不返回函数结果(void)外,就相当于:

      • setvbuf(stream, buf, buf ? _IOFBF : _IONBF, size);

刷新stdio缓冲

  • 无论采取何种缓冲模式,在任何时候都可以使用库函数fflush()来强制刷新(将输出到stdio缓冲区中的数据写入到内核缓冲区,通过write()函数)stdio缓冲区。该函数会刷新指定文件的stdio输出缓冲区。

    • #include <stdio.h>
      int fflush(FILE *stream);

      • stream 指定需要进行强制刷新的文件,如果该参数设置为 NULL,则表示刷新所有的 stdio 缓冲区

      • 调用成功返回 0,否则将返回-1,并设置 errno

    • 调用fclose()函数也会去刷新stdio缓冲

  • 程序退出时刷新 stdio 缓冲区

9、I/O缓冲小结

10、文件描述符与FILE指针互转

fdopen()函数

  • 可以将文件 I/O 中所使用的文件描述符转换为标准 I/O 中使用的 FILE 指针

  • FILE *fdopen(int fd, const char *mode);

    • 返回值得到文件对应的 FILE 指针

    • mode参数与文件描述符 fd 的访问模式不一致,则会导致调用 fdopen()失败,返回NULL

fileno()函数

  • 可以将标准 I/O 中使用的 FILE 指针转换为文件 I/O 中所使用的文件描述符

  • int fileno(FILE *stream);

    • 返回值得到文件描述符

    • 如果转换错误将返回-1,并且会设置 errno

  • 30
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木木不迷茫(˵¯͒¯͒˵)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值