Unix 系统调用 I/O 与标准 I/O 对比

本文档整理自学习笔记,涵盖概念定义、核心区别、详细接口列表及混用注意事项。

一、概念定义

系统调用 I/O(又称"无缓冲 I/O")

  • 由内核直接提供的接口:open、close、read、write、lseek、pread、pwrite 等
  • 操作对象是文件描述符(fd)
  • 由 POSIX.1 标准定义

标准 I/O

  • 建立在系统调用之上、由 C 库封装的一层:fopen、fclose、fread、fwrite、fgets、fputs、printf、scanf、fflush、fseek 等
  • 操作对象是 FILE 流(FILE *)
  • 由 ANSI C 标准定义

二、核心区别

对比项系统调用 I/O标准 I/O
缓冲无用户级缓冲,每次调用直接陷入内核(一次 read/write = 一次系统调用)有用户级缓冲(全缓冲、行缓冲、无缓冲三种模式),减少系统调用次数
操作对象文件描述符 fd流指针 FILE *
标准POSIX.1ANSI C(跨平台移植性好)
性能小数据量频繁读写时开销大利用缓冲,批量陷入内核,通常更快
能力底层、完整,支持 lseek、fcntl、select、O_NONBLOCK 等提供格式化、字符/行读写、复制等高层能力
并发访问无fork 后父子进程共享 FILE 结构时需注意

三、底层关系

// 系统调用:每次 write 都是一次内核陷阱
write(fd, buf, n);          // -> 内核

// 标准 I/O:先写入用户态缓冲区
fwrite(buf, 1, n, fp);      // -> 缓冲区 -> (满时) -> 内核

fread/fwrite 内部最终调用 read/write;标准 I/O 只是在内核之上加了一层用户态缓冲,并封装了格式化等功能。

四、系统调用 I/O 接口详解

定义在 <unistd.h>、<fcntl.h>、<sys/stat.h>、<sys/types.h> 等头文件中。

1. 打开 / 关闭

函数说明
open(path, flags[, mode])打开或创建文件,返回 fd;flags 如 O_RDONLY、O_WRONLY、O_RDWR、O_CREAT、O_APPEND、O_TRUNC、O_NONBLOCK
openat(dirfd, path, flags[, mode])相对目录 fd 打开(POSIX.1-2008),避免竞态条件
creat(path, mode)等价于 `open(path, O_CREAT\O_WRONLY\O_TRUNC, mode)`
close(fd)关闭 fd

2. 读 / 写

函数说明
read(fd, buf, nbytes)读 nbytes 到 buf,返回实际读到的字节数(可能 < nbytes)
write(fd, buf, nbytes)写 nbytes,返回实际写入字节数
pread(fd, buf, n, offset)定位 + 读的原子操作,不改变文件偏移量
pwrite(fd, buf, n, offset)定位 + 写的原子操作
readv / writev分散读 / 聚集写(scatter-gather)
recv / send / sendto / recvfrom套接字专用读写

3. 文件定位与截断

函数说明
lseek(fd, offset, whence)设置偏移量;whence 为 SEEK_SET/SEEK_CUR/SEEK_END
truncate(path, len) / ftruncate(fd, len)按路径 / 按 fd 截断文件

4. 描述符复制与重定向

函数说明
dup(fd)复制 fd,返回最小的可用 fd
dup2(fd, fd2) / dup3(fd, fd2, flags)复制到指定 fd(常用于 shell 重定向 >、2>)

5. 文件控制与状态

函数说明
fcntl(fd, cmd, ...)万能控制:复制 fd、设置 O_NONBLOCK、获取/设置记录锁(F_SETLK 等)
ioctl(fd, request, ...)设备控制(终端、网卡等)
stat / fstat / lstat获取文件元信息(inode、权限、大小等)
fstatat相对目录 fd 的 stat
access(path, mode) / faccessat检查访问权限
chmod / fchmod / fchmodat修改权限
chown / fchown / lchown修改属主/属组
umask(mask)设置默认权限掩码

6. 同步与缓存控制

函数说明
fsync(fd)将数据 + 元数据刷到磁盘
fdatasync(fd)只刷数据(不刷元数据,更快)
sync()刷所有内核缓冲
mmap / munmap / msync内存映射 I/O 及其同步

7. 目录与链接

函数说明
mkdir / rmdir建/删目录
opendir / readdir / closedir / rewinddir读目录项
chdir / fchdir / getcwd切换/获取工作目录
link / unlink / symlink / readlink / rename硬链接、删除、软链接、重命名

8. 多路复用(配合非阻塞 I/O)

函数说明
select / pselect跨平台的多路复用
poll / ppoll同上
epoll_create / epoll_ctl / epoll_waitLinux 高性能多路复用

9. 文件锁

函数说明
flock(fd, op)整文件咨询锁
fcntl 记录锁(F_RDLCK/F_WRLCK/F_UNLCK)字节范围锁,POSIX 标准

五、标准 I/O 接口详解

定义在 <stdio.h> 中,核心是 FILE * 流。

1. 打开 / 关闭 / 转换

函数说明
fopen(path, mode)打开流;mode 如 "r"、"w"、"a"、"r+"、"wb"
freopen(path, mode, stream)重定向已有流(常用于 freopen("/dev/null","w",stdout))
fdopen(fd, mode)将 fd 包装成流(系统调用 ↔ 标准 I/O 的桥梁)
fclose(fp)关闭流(自动刷新缓冲)
fileno(fp)从流取出底层 fd(反过来用)

2. 字符级读写(无缓冲调用,但经过流缓冲)

函数说明
getc(fp) / fgetc(fp) / getchar()读单个字符
putc(c, fp) / fputc(c, fp) / putchar(c)写单个字符

3. 行级读写

函数说明
fgets(buf, n, fp)读一行(含换行符,安全,推荐)
fputs(s, fp)写一行(不自动加换行)
gets(s)读一行(已废弃,无法防止缓冲区溢出)
puts(s)写一行(自动加换行)

4. 二进制 / 块读写

函数说明
fread(ptr, size, nmemb, fp)读 size*nmemb 字节,返回完整读入的元素数
fwrite(ptr, size, nmemb, fp)写二进制块

5. 格式化 I/O(标准 I/O 独有,系统调用没有)

函数说明
printf(fmt, ...)格式化到 stdout
fprintf(fp, fmt, ...)格式化到任意流
sprintf / snprintf(buf, n, fmt, ...)格式化到字符串(snprintf 安全)
dprintf(fd, fmt, ...)格式化到 fd(POSIX)
scanf(fmt, ...) / fscanf(fp, ...) / sscanf(str, ...)格式化输入
vfprintf / vprintf / vsnprintf可变参数版本(供自定义 printf 使用)

6. 定位

函数说明
fseek(fp, offset, whence) / ftell(fp)定位 / 获取位置
rewind(fp)回到文件头(等价 fseek(0, SEEK_SET))
fgetpos / fsetpos大文件安全定位

7. 缓冲控制

函数说明
fflush(fp)把缓冲内容刷给内核(或立即输出到终端)
setvbuf(fp, buf, mode, size)设置缓冲模式:_IOFBF 全缓冲、_IOLBF 行缓冲、_IONBF 无缓冲
setbuf(fp, buf)简化版 setvbuf

8. 错误与 EOF 检测

函数说明
feof(fp)是否到达文件尾
ferror(fp)是否有 I/O 错误
clearerr(fp)清除 EOF/错误标志
perror(s) / strerror(errno)输出/获取错误描述

9. 文件操作与临时文件

函数说明
remove(path)删除文件或空目录
rename(old, new)重命名
tmpfile()创建自动删除的临时文件
tmpnam / tempnam生成临时文件名(有竞态,不推荐)
mkstemp / mkdtemp安全创建临时文件/目录

六、混用的关键桥梁

int fd = open("f", O_RDWR);        // 系统调用
FILE *fp = fdopen(fd, "r+");       // 包装成流
fflush(fp);                        // 混用前先刷缓冲
转换方向函数
fd → FILE *fdopen(fd, mode)
FILE * → fdfileno(fp)

混用时的注意事项

  1. 同步问题:如果同文件同时用 fd 和 FILE 流操作,缓冲内容可能丢失。需用 fflush 刷空缓冲,或用 fdopen / dup 让两者共享偏移量。
  2. 必须用系统调用的场景:

    • 需要控制缓冲的设备行为(如套接字设 O_NONBLOCK、配合 select/poll)
    • read/write 可能被信号中断,需要自行处理 EINTR
    • 需要原子性保证(pread/pwrite、O_APPEND)

七、选择建议

  • 优先用标准 I/O:普通文本/二进制文件、需要格式化、追求可移植性
  • 必须用系统调用:非阻塞 I/O、多路复用(select/poll/epoll)、信号中断处理(EINTR)、精确控制缓冲、直接操作设备、需要原子定位读写(pread/pwrite)、文件锁

八、一句话总结

系统调用 I/O 是内核接口,灵活底层但每次调用开销大;标准 I/O 是 C 库封装,带用户态缓冲、方便安全,适合大多数普通文件读写场景。标准 I/O 解决的是效率与易用性,系统调用解决的是控制力。

标签: none

添加新评论