学习主题:Linux 文件 IO 系统调用,open/read/write/close/lseek,文件 IO 与标准 IO 对比、缓冲区、格式化输出系列函数

前言

今天告别 C 库的标准 IO,深入学习 Linux 内核提供的文件 IO 系统调用。这是嵌入式 Linux 开发非常核心的知识点,硬件设备操作、底层文件读写都会用到这套 API。系统调用直接和内核打交道,没有用户层缓冲区,和之前学的FILE*标准库接口有很大区别。

一、文件 IO 基础概念

文件 IO 是 Linux 内核提供的文件操作接口,属于系统调用
核心学习接口分为四组:

  1. 打开文件:open()
  2. 读写文件:read()write()
  3. 关闭文件:close()
  4. 文件定位:lseek()

1.1 open 函数

头文件

c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

函数原型

c
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

功能:打开一个文件,成功后获取一个文件描述符。

参数

说明

pathname

要打开的文件路径 + 文件名

flags

打开方式,多个可以位或组合

mode

新建文件时,设置文件操作权限(mode & ~umask)

flags 常用宏:

  • O_RDONLY:只读
  • O_WRONLY:只写
  • O_RDWR:读写
  • O_CREAT:文件不存在则创建
  • O_TRUNC:清空原有文件内容
  • O_APPEND:追加写入

注意:使用O_CREAT的时候,必须带上 mode 参数;普通文件常用权限0664,权限满为0777

返回值

  • 成功:返回文件描述符(非负整数)
  • 失败:返回-1

1.2 文件描述符

打开文件时,操作系统会分配文件描述符用来标识该文件。

  • 文件描述符:小的、非负整型数字
  • 系统可分配总数量:0‑1023,一共 1024 个
  • 分配原则:选取最小未被使用的数字

系统默认预先打开 3 个文件描述符:

fd 数值

宏名

含义

0

STDIN_FILENO

标准输入 stdin

1

STDOUT_FILENO

标准输出 stdout

2

STDERR_FILENO

标准错误 stderr

1.3 close 关闭文件

头文件

c
#include <unistd.h>

原型

c
int close(int fd);

功能:关闭已经打开的文件。
参数:fd,要关闭的文件描述符。
返回:成功返回 0;失败返回‑1。

⚠️重要提醒:打开的文件一定要 close,不关闭会造成文件描述符泄露,耗尽可用 fd,后续无法打开任何文件。

1.4 write 写文件

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

功能:向打开的文件写入数据。

  • fd:目标文件描述符
  • buf:待写入数据的首地址
  • count:想要写入的字节数量

返回值:成功返回实际写入字节数;失败返回‑1。

1.5 read 读文件

c
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

功能:从文件读取数据。

  • fd:文件描述符
  • buf:存放读取内容的内存首地址
  • count:期望读取的字节数

返回值:成功返回读到的字节数;失败返回‑1。1.6 lseek 文件定位函数

头文件

c
#include <sys/types.h>
#include <unistd.h>

原型

c
off_t lseek(int fd, off_t offset, int whence);

功能:重新修改文件读写指针的位置。

参数

解释

fd

文件描述符

offset

偏移量

whence

偏移的起始基准位置

whence 三个可选宏:

  • SEEK_SET:文件开头
  • SEEK_CUR:当前读写位置
  • SEEK_END:文件末尾

返回值:成功返回当前位置距离文件开头的偏移字节;失败返回‑1。

二、文件 IO vs 标准 IO 对比

对比表格

对比项

标准 IO

文件 IO(系统调用)

操作对象

FILE* 文件流指针

fd 文件描述符

来源

C 库函数,可移植性强

Linux 系统调用,可移植弱

缓冲区

具备用户缓冲区,减少用户态内核态切换,提升效率

无用户缓冲区,实时性能好

主要场景

普通文件读写

普通文件、硬件相关设备文件

三种缓冲区详解

  1. 行缓冲:默认大小:2048 字节(1K),多用于人机交互终端。刷新触发条件:遇到\n换行;程序结束;调用fflush();缓冲区写满。
  2. 全缓冲:默认大小:4096 字节(4K),多用于普通文件交互。刷新触发条件:程序结束;文件关闭;缓冲区写满;手动fflush()
  3. 无缓冲:大小 0K,典型代表:stderr标准错误,错误信息直接输出,不缓存。

三、格式化输出系列函数

c
// 打印到标准输出
int printf(const char *format,...);

// 输出到文件流FILE*
int fprintf(FILE * stream, const char * format,...);

// 输出到文件描述符fd
int dprintf(int fd, const char * format,...);

// 输出到内存字符数组,字符串拼接格式化
int sprintf(char *str, const char * format,...);

核心作用:

  1. 将格式化后的字符串写入目标位置(终端、文件、内存数组)
  2. 可以把整型、浮点型数据,转换为字符串,完成字符串拼接格式化。

四、今日学习小结

  1. open/read/write/close/lseek 是 Linux 系统调用,直接陷入内核,没有用户缓冲区,嵌入式操作硬件设备经常使用。
  2. 文件描述符 0、1、2 程序启动默认打开,对应输入输出错误;用完 fd 必须 close,防止泄露。
  3. 标准 IO 带缓冲区,文件 IO 不带缓冲区;行缓冲、全缓冲、无缓冲三种刷新条件要分清。
  4. printf / fprintf / dprintf / sprintf 四个格式化函数,目标对象不一样:终端、FILE 流、fd、内存数组。

小练习提示:可以写一个简单文件复制 demo,使用 open read write close 完成文件拷贝,巩固今天系统调用 API。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐