#Linux #嵌入式 #文件IO #系统调用 #C语言
📖前言
在Linux嵌入式开发中,文件IO是非常核心的基础知识点。Linux秉承一切皆文件的设计思想,普通文件、终端、设备、管道、套接字都可以通过文件接口操作 。
文件IO属于系统调用,由内核直接提供接口;而我们平时用的 fopen、fread 属于标准IO库函数,标准IO底层本质还是封装文件IO系统调用,并且增加用户态缓冲区提升效率 。
面试高频考点:文件描述符、open标志位、缓冲区机制、文件IO与标准IO区别、常见坑点(文件描述符泄漏、读写返回值处理)。本文结合手写笔记,梳理全套接口与易错点。
一、文件IO基本概念
文件IO是Linux内核为用户空间提供的文件操作接口,属于系统调用。
四大核心系统调用: open() 、 read() 、 write() 、 close() ,辅助接口: lseek() 。
文件描述符 fd
-
进程打开一个文件,内核分配一个非负小整数作为标识,就是文件描述符fd。
-
进程默认占用3个文件描述符:
-
0 :标准输入 stdin
-
1 :标准输出 stdout
-
2 :标准错误 stderr
-
分配规则:最小未被使用的文件描述符。
-
每个进程有最大可打开文件数,打开不关闭会造成文件描述符泄漏。
二、系统调用API详解
1.open 打开/创建文件
#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 :文件路径
-
flags :打开方式标志位,多个标志位用 | 或运算拼接
-
mode :创建文件时才生效,设置文件权限;实际权限 = mode & ~umask
flags常用标志
标志 含义 等价fopen模式
O_RDONLY 只读 "r"
O_WRONLY 只写
O_RDWR 读写 "r+"
O_CREAT 文件不存在则创建
O_TRUNC 打开清空文件原有内容 "w"
O_APPEND 追加写,光标指向文件末尾 "a"
模式等价关系
-
"w" : O_WRONLY | O_CREAT | O_TRUNC
-
"w+" : O_RDWR | O_CREAT | O_TRUNC
-
"a" : O_WRONLY | O_CREAT | O_APPEND
-
"a+" : O_RDWR | O_CREAT | O_APPEND
返回值:成功返回fd;失败返回 ‑1 。
2.close 关闭文件
#include <unistd.h>
int close(int fd);
-
功能:关闭文件,释放文件描述符资源
-
参数:open返回的文件描述符fd
-
返回:成功返回0;失败返回‑1
⚠面试坑:文件打开完成必须close;不关闭会发生文件描述符泄漏,耗尽进程可用fd资源。
3.write 向文件写入数据
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
-
fd:目标文件描述符
-
buf:要写入数据的首地址
-
count:希望写入的字节数
-
返回:成功返回实际写入字节数;失败返回‑1。
注意:实际写入字节数 ≤ count,不一定全部写完,代码需要处理返回值。
4.read 从文件读取数据
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
-
fd:文件描述符
-
buf:存放读取数据的缓冲区地址
-
count:希望读取的字节数
-
返回:成功返回实际读到字节;读到文件末尾返回 0 ;失败返回‑1。
⚠高频易错:判断文件结束条件是返回值等于0,不能靠buf内容判断。
5.lseek 文件读写位置偏移
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
功能:修改文件读写指针位置,可实现文件空洞。
-
offset:偏移量,正数向后偏移,负数向前偏移
-
whence:偏移基准位置
-
SEEK_SET :从文件开头偏移
-
SEEK_CUR :从当前读写位置偏移
-
SEEK_END :从文件末尾偏移
-
返回:成功返回当前读写位置距离文件开头的偏移字节;失败返回‑1。
三、文件IO vs 标准IO(C库)
对比维度 文件IO(系统调用 open/read) 标准IO库(fopen/fprintf)
操作对象 int 文件描述符 fd FILE* 文件流指针
实现来源 内核系统调用 C库函数,底层封装系统调用
缓冲区 无用户态缓冲区,内核存在页缓存 自带用户态缓冲区,减少系统调用次数
跨平台 Linux/Unix专用,可移植差 ANSI C标准,Windows/Linux通用
适用场景 嵌入式底层、设备文件、管道、套接字 普通文本文件、上层业务开发(CSDN博...)
四、标准IO缓冲区分类
标准IO有三类缓冲区,缓冲区刷新是面试高频考点:
-
行缓冲(默认1KB):终端交互使用;遇到 \n 换行、程序结束、fflush刷新缓冲区。
-
全缓冲(默认4KB):普通文件;缓冲区写满、程序结束、文件关闭、fflush才刷新。
-
无缓冲:stderr标准错误,数据直接输出,不做缓冲。
fflush(FILE* stream) :强制刷新用户态缓冲区,把缓冲区数据下发到底层系统调用。
五、格式化字符串函数
//输出到文件流
int fprintf(FILE *stream, const char *format, ...);
//输出到文件描述符(标准IO扩展)
int dprintf(int fd, const char *format, ...);
//输出到字符数组缓冲区
int sprintf(char *str, const char *format, ...);
- sprintf :把数字、结构体数据格式化为字符串存入字符数组,用于字符串拼接。
安全问题:sprintf不检查数组越界,工程优先使用 snprintf 。
strtok字符串分割
char *strtok(char *str, const char *delim);
功能:字符串分割函数,按照分隔符截取子串。
坑点:strtok会修改原字符串,不可传入字符串常量。
🎯小结
-
文件IO是Linux内核提供系统调用,核心 open/read/write/close/lseek ,操作对象是文件描述符fd。
-
文件描述符遵循最小未使用分配原则,0/1/2默认对应标准输入输出错误;打开文件必须close,防止fd泄漏。
-
open的flags标志位使用或运算 | 组合;mode权限仅O_CREAT创建文件时生效。
-
read返回0代表读到文件末尾,write返回实际写入字节,不要忽略返回值。
-
文件IO无用户缓冲区;标准IO库自带缓冲区,分行缓冲、全缓冲、无缓冲三类,理解缓冲区刷新时机。
-
标准IO底层调用文件IO;嵌入式底层开发经常直接使用文件IO系统调用。