文件 I/O
一、基础 API
1. open / close
#include <fcntl.h>
#include <unistd.h>
int fd = open("/dev/i2c-1", O_RDWR);
if (fd < 0) { perror("open"); exit(1); }
close(fd);
// flags:
// O_RDONLY / O_WRONLY / O_RDWR
// O_CREAT / O_EXCL / O_TRUNC / O_APPEND
// O_NONBLOCK 非阻塞
// O_CLOEXEC exec 时自动关闭
// O_SYNC / O_DSYNC 同步写
// O_DIRECT 绕过页缓存
2. read / write
char buf[1024];
ssize_t n = read(fd, buf, sizeof(buf)); // 返回 0=EOF, -1=error
ssize_t w = write(fd, buf, n); // 不一定一次写完
短读写处理:
ssize_t read_full(int fd, void *buf, size_t n) {
char *p = buf;
size_t left = n;
while (left) {
ssize_t r = read(fd, p, left);
if (r < 0) { if (errno == EINTR) continue; return -1; }
if (r == 0) break;
p += r; left -= r;
}
return n - left;
}
3. lseek
off_t pos = lseek(fd, 0, SEEK_SET); // 绝对
lseek(fd, 100, SEEK_CUR); // 相对
lseek(fd, 0, SEEK_END); // 到末尾
4. fcntl / ioctl
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
ioctl(fd, FIONREAD, &n); // 看缓冲字节数
// 设备特定命令:ioctl(fd, BLKFLSBUF, 0); etc.
二、标准 I/O 库(stdio)
FILE *f = fopen("file", "r+");
fprintf(f, "x=%d\n", 42);
fscanf(f, "%d", &x);
fgets(buf, sizeof(buf), f);
size_t n = fread(buf, 1, 100, f);
fseek(f, 0, SEEK_END);
long pos = ftell(f);
fflush(f);
fclose(f);
| FILE* | fd |
|---|---|
| 用户态有缓冲 | 内核无缓冲 |
| fread/fwrite | read/write |
| fopen | open |
| 标准 IO 效率高 | raw IO 灵活 |
模式:r r+ w w+ a a+ b(b 在 Linux 无效)
三、缓冲设置
setvbuf(stdout, NULL, _IONBF, 0); // 无缓冲(嵌入式常用)
setvbuf(fp, buf, _IOLBF, BUFSIZ); // 行缓冲
四、内存映射(mmap)
#include <sys/mman.h>
int fd = open("data.bin", O_RDONLY);
void *p = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
if (p == MAP_FAILED) { ... }
// 直接读写 *p 即可(无需 read/write)
munmap(p, length);
close(fd);
// 共享内存
int shm_fd = shm_open("/myregion", O_CREAT|O_RDWR, 0666);
ftruncate(shm_fd, size);
void *p = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, shm_fd, 0);
优点:
- 减少内核/用户态拷贝
- 多进程共享简单
- 大文件访问性能好
五、高级 I/O
1. 多路复用(I/O 多路复用)
详见 08-socket.md。
2. AIO(异步 IO)
- POSIX AIO:
aio_read/aio_write - Linux 原生 AIO:
io_submit/io_getevents - 现在主流是
io_uring
3. io_uring(推荐)
- 2019 年 Linux 5.1 引入
- 用户/内核共享环形队列
- 提交和完成各一个 SQ/CQ
- 极高性能,替代 epoll + AIO
struct io_uring ring;
io_uring_queue_init(32, &ring, 0);
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, buf, len, 0);
io_uring_submit(&ring);
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);
// 处理 cqe->res
io_uring_cqe_seen(&ring, cqe);
4. splice / sendfile(零拷贝)
splice(fd_in, off_in, fd_out, off_out, len, flags);
sendfile(out_fd, in_fd, off, count); // 把 in_fd 直接 send 到 out_fd
六、目录操作
#include <dirent.h>
DIR *d = opendir("/usr/lib");
struct dirent *e;
while ((e = readdir(d)) != NULL) {
printf("%s\n", e->d_name);
}
closedir(d);
七、文件锁
1. flock(劝告锁)
flock(fd, LOCK_SH); // 共享
flock(fd, LOCK_EX); // 排他
flock(fd, LOCK_UN); // 解锁
2. fcntl POSIX 锁
struct flock fl = {
.l_type = F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 0, // 0 = 整个文件
};
fcntl(fd, F_SETLK, &fl);
八、嵌入式建议
- 优先用 mmap(操作 framebuffer / 共享内存)
- 关键路径避免 fopen(用 raw fd)
- 重要数据定期
fsync(fd)落盘 - 大块读写才走 DMA,小块用 read/write 即可
- 日志用
O_APPEND+ 周期 fsync,避免崩溃丢日志