嵌入式Linux C应用编程入门——文件IO

本文主要参考文献是正点原子的C应用编程指南,很多内容与正点原子文档中的一致。作为初学者,为了和大家一起学习所以准备写这一系列文章,也是作为自己学习的一个笔记,如果有错误欢迎大家提出来一起讨论。

基本概念

文件 I/O 指的是对文件的输入/输出操作,即对文件的读写操作。Linux 下一切皆文件,文件作为 Linux 系统设计思想的核心理念,在 Linux 系统下非常重要。

文件IO操作实例代码

ini 复制代码
#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h>

int main(void)

{

char buff[1024];

int fd1, fd2;

int ret;

/* 打开源文件 src_file(只读方式) */

fd1 = open("./src_file", O_RDONLY);

if (-1 == fd1)

return fd1;

/* 打开目标文件 dest_file(只写方式) */

fd2 = open("./dest_file", O_WRONLY);

if (-1 == fd2) {

ret = fd2;

goto out1;

}

/* 读取源文件 1KB 数据到 buff 中 */

ret = read(fd1, buff, sizeof(buff));

if (-1 == ret)

goto out2;

/* 将 buff 中的数据写入目标文件 */

ret = write(fd2, buff, sizeof(buff));

if (-1 == ret)

goto out2;

ret = 0;

out2:

/* 关闭目标文件 */
close(fd2);

out1:

/* 关闭源文件 */

close(fd1);

return ret;

}

文件描述符

  • open函数在函数执行成功的情况下,会返回一个非负整数,该返回值就是一个文件描述符(file descriptor)对于 Linux 内核而言,所有打开的文件都会通过文件描述符进行索引。因此fb1就是源文件src_file被打开时所对应的文件描述符。fb2就是目标文件 dest_file 被打开时所对应的文件描述符。文件描述符是从 0 开始分配的,譬如说进程中第一个被打开的文件对应的文件描述符是 0、第二个文件是 1、第三个文件是 2、第 4 个文件是 3......以此类推,每一个被打开的文件在同一个进程中都有一个唯一的文件描述符,不会重复。

值得注意的是,调用 open 函数打开文件的时候,分配的文件描述符一般都是从 3 开始0、1、2 这三个文件描述符默认被系统占用,分别分配给了系统标准输入(0)、标准输出(1)以及标准错误(2)。对于Linux系统来说,一切都是文件,包括各种硬件设备,每一个硬件设备都会对应于 Linux 系统下的某一个文件,把这类文件称为设备文件。一般0对应的是键盘,1,2是LCD显示器。

  • 在 Linux 系统中,一个进程可以打开的文件数是有限制,不可以无限制打开很多的文件。如果超过进程可打开的最大文件数限制,内核将会发送警告信号给对应的进程,然后结束进程。 通过ulimit -n可以查看此进程可打开的最大文件数,默认是1024,是可设置的。

打开文件命令:open

在Linux系统下可以通过man命令来查看帮助信息。

man 2 open
man命令后面跟着两个参数,数字 2 表示系统调用,man 命令除了可以查看系统调用的帮助信息外,还可以查看 Linux 命令(对应数字 1)以及标准 C 库函数(对应数字 3)所对应的帮助信息;最后一个参数 open 表示需要查看的系统调用函数名。

命令使用

arduino 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
  • 返回值:文件标识符。
  • pathname:字符串类型,用于标识需要打开或创建的文件,可以包含路径(绝对路径或相对路径)信息,譬如:"./src_file"(当前目录下的 src_file 文件)、"/home/dengtao/hello.c"等;如果 pathname 是一个符号链接,会对其进行解引用。
  • flags:调用 open 函数时需要提供的标志,包括文件访问模式标志以及其它文件相关标志,这些标志使用宏定义进行描述,都是常量,open 函数提供了非常多的标志,我们传入 flags 参数时既可以单独使用某一个标志,也可以通过位或运算将多个标志进行组合。常用的标志介绍如下:
flags 标志 用途说明 补充说明 / 注意事项
O_RDONLY 以只读方式打开文件 文件访问权限标志。这三个标志必须且只能指定其中一个,决定了文件的访问模式。
O_WRONLY 以只写方式打开文件 文件访问权限标志。(同上)
O_RDWR 以可读可写方式打开文件 文件访问权限标志。(同上)
O_CREAT 如果 pathname 指向的文件不存在,则创建此文件 使用此标志时,必须提供第 3 个参数 mode,用于指定新建文件的访问权限。
O_DIRECTORY 如果 pathname 指向的不是一个目录,则调用 open 失败 -
O_EXCL 一般结合 O_CREAT 标志一起使用,用于专门创建文件 如果文件已存在则返回错误。使得 "测试文件是否存在"和"创建文件"成为一个原子操作,避免竞态条件。
O_NOFOLLOW 如果 pathname 指向的是一个符号链接,将不对其进行解引用,直接返回错误 不加此标志时 ,如果 pathname 是符号链接,系统会默认对其进行解引用(指向所链接的实际文件)。

不同内核版本所支持的 flags 标志是存在差别的,譬如说新版本内核所支持的标志可能在老版本 是不支持的,亦或者老版本支持的标志在新版本已经被取消、替代,具体而言可以根据man指令查询。

  • mode:此参数用于指定新建文件的访问权限,只有当 flags 参数中包含 O_CREAT 或 O_TMPFILE 标志时才有效。在新建完文档后,可以通过chmod命令进行修改权限。在linux终端中用ls -l可以查看到当前目录下的文件权限,第一位表示文件的类型,后续的每三位各表示其它用户权限、同组用户权限、文件所属用户的权限 。 r-读,w-写,x-执行。常用宏:S_IRUSR (读)、 S_IWUSR (写)、 S_IROTH(执行)。

只有用户对该文件具有相应权限时,才可以使用对应的标志去打开文件,否则会打开失败。

写命令: write

命令使用

arduino 复制代码
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);
  • 返回值:如果成功将返回写入的字节数(0 表示未写入任何字节),如果此数字小于 count 参数,这不 是错误,譬如磁盘空间已满,可能会发生这种情况;如果写入出错,则返回-1。
  • fd:文件标识符。
  • buf:指定写入数据对应的缓冲区。
  • count:指定写入的字节数。

值得注意的是,write函数会直接覆盖当前位置偏移量的数据。默认的偏移量是0,前位置偏移量可以通过 lseek 系统调用进行设置。如果想在 "ABCDEFG"A 后面插入 "123",得到 "A123BCDEFG" 则需要读取 A 后面的所有数据("BCDEFG")存到一个临时缓冲区里。把文件指针移动到 A 后面的位置。先写入要插入的 "123"再把刚才保存的 "BCDEFG" 写回去。

读命令: read

命令使用

arduino 复制代码
#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
  • 返回值:如果读取成功将返回读取到的字节数,实际读取到的字节数可能会小于 count 参数指定的字节数,也有可能会为 0,譬如进行读操作时,当前文件位置偏移量已经到了文件末尾。实际读取到的字节数少于要求读取的字节数,譬如在到达文件末尾之前有 30 个字节数据,而要求读取 100 个字节,则 read 读取成功只能返回 30;而下一次再调用 read 读,它将返回 0(目前的"光标"在文件末尾)。
  • fd:文件标识符。
  • buf:指定写入数据对应的缓冲区。
  • count:指定写入的字节数。

关闭文件: close

命令使用

arduino 复制代码
#include <unistd.h>

int close(int fd);
  • 返回值:如果成功返回 0,如果失败则返回-1。
  • fd:文件标识符。

在一个进程终止的时候,内核会自动关闭所打开的所有文件。为了代码书写规范,以及文件描述符是有限资源,当不再需要时必须将其释放、归还于系统。

读写偏移量设置: lseek

每个已经打开的文件,系统会记录他的读写位置偏移,也就是每次对同一个文件进行读写后,它的偏移量会不断累积,而不会重置

命令使用

arduino 复制代码
#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);
  • fd:文件标识符。
  • offset:偏移量,以字节(8位)为单位。
  • whence:用于定义参数 offset 偏移量对应的参考值,该参数为下列其中一种(宏定义):
    • SEEK_SET:读写偏移量将指向 offset 字节位置处(从文件头部开始算);
    • SEEK_CUR:读写偏移量将指向当前位置偏移量 + offset 字节位置处,offset 可以为正、也可以为负,如果是正数表示往后偏移,如果是负数则表示往前偏移;
    • SEEK_END:读写偏移量将指向文件末尾 + offset 字节位置处,同样 offset 可以为正、也可以为负,如果是正数表示往后偏移、如果是负数则表示往前偏移。
  • 返回值:成功将返回从文件头部开始算起的位置偏移量(字节为单位),也就是当前的读写位置;发生错误将返回-1。
相关推荐
用户120487221612 天前
Linux驱动编译与加载
linux·嵌入式
用户805533698032 天前
Input 子系统架构:Core、Handler、Driver 三层是怎么协作的
linux·嵌入式
用户805533698032 天前
RK-Forge外设系列开篇 - 把板子从「能启动」变成「能用」:Ethernet/SPI/MMC 三个纯接线外设
linux·github·嵌入式
神奇啊龙3 天前
我的第一个 TinyGo 项目:ESP32-C3 + DHT11 + SSD1306
物联网·嵌入式
比老马还六4 天前
Bipes-Blockly项目二次开发/Coze智能体(十)
前端·嵌入式
ForTime6 天前
HAL库大雷预警!STM32 HAL库CAN启动超时解决办法
嵌入式
大辉狼_音频架构6 天前
Vol. NXP SOF Arch
嵌入式
用户805533698038 天前
主线 U-Boot 上 RK3506:和闭源 rkbin 拔河的三个隐性契约
linux·嵌入式
荣--11 天前
在 strip 二进制 + 基址随机化的栈里做崩溃去重 —— 三阶段算法与一行 Crash Flag
嵌入式·崩溃分析·栈指纹·去重算法