一、文件 IO(系统调用级 IO)
文件 IO 是 Linux 内核提供的系统调用接口 ,无用户层缓存,直接操作内核态文件描述符,相比标准 IO 更贴近底层,适合操作设备文件、管道、套接字等特殊文件,也可操作普通文件。
1. 文件 IO 与标准 IO 的核心区别
这是开发中选择接口的关键,两者对比如下:
表格
| 特性 | 标准 IO | 文件 IO |
|---|---|---|
| 接口类型 | 库函数(封装系统调用) | 系统调用(内核接口) |
| 缓存机制 | 有用户层缓存(全 / 行) | 无用户层缓存 |
| 操作句柄 | 文件流指针FILE * |
文件描述符int fd(非负整数) |
| 适用文件 | 普通文件(ASCII / 二进制) | 普通文件、设备文件、管道、套接字等 |
| 效率 | 普通文件读写效率高 | 特殊文件操作更高效 |
2. 核心系统调用接口
文件 IO 的核心接口为open/close/read/write/lseek,所有接口需包含头文件<unistd.h>、<fcntl.h>,文件描述符是其核心操作句柄(内核分配的非负整数,默认最小未使用)。
(1)open - 打开 / 创建文件,获取文件描述符
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
- 功能 :打开指定路径文件,若文件不存在且指定
O_CREAT则创建,返回唯一的文件描述符; - 参数 :
pathname:文件路径(绝对 / 相对);flags:打开模式(必选 + 可选组合),必选:O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(读写);可选:O_CREAT(创建)、O_TRUNC(清空)、O_APPEND(追加);mode:创建文件时的权限(如0664、0777),仅flags含O_CREAT时有效,最终权限受umask掩码影响;
- 返回值 :成功返回文件描述符(≥0);失败返回
-1,设置错误码。
常用模式组合:
O_RDONLY; // 只读打开
O_WRONLY | O_CREAT | O_TRUNC; // 只写,不存在创建,存在清空
O_RDWR | O_CREAT | O_APPEND; // 读写,不存在创建,存在追加
(2)close - 关闭文件描述符,释放资源
int close(int fd);
- 功能:关闭已打开的文件描述符,释放内核资源,避免资源泄漏;
- 参数 :
fd:要关闭的文件描述符; - 返回值 :成功返回
0;失败返回-1。
(3)read - 从文件描述符读取数据
ssize_t read(int fd, void *buf, size_t count);
- 功能 :从
fd指向的文件中读取count字节数据,存入buf缓冲区; - 参数 :
fd:文件描述符;buf:数据缓冲区;count:期望读取的字节数; - 返回值 :成功返回实际读取的字节数 ;读到文件末尾返回
0;失败返回-1。
(4)write - 向文件描述符写入数据
ssize_t write(int fd, const void *buf, size_t count);
- 功能 :将
buf中count字节数据写入fd指向的文件; - 参数 :与
read一致; - 返回值 :成功返回实际写入的字节数 ;失败返回
-1。
(5)lseek - 重定位文件描述符的偏移量
off_t lseek(int fd, off_t offset, int whence);
- 功能 :类似标准 IO 的
fseek,设置文件读写的偏移量,实现随机读写; - 参数 :
offset:偏移量(可正可负,正数向文件末尾,负数向文件开头);whence:偏移基准,SEEK_SET(文件开头)、SEEK_CUR(当前位置)、SEEK_END(文件末尾);
- 返回值 :成功返回新的偏移量 ;失败返回
-1。
3. 系统默认文件描述符
Linux 启动进程时,会默认打开 3 个文件描述符,供进程与终端交互:
0:标准输入stdin→ 对应终端输入;1:标准输出stdout→ 对应终端输出;2:标准错误stderr→ 对应终端错误输出。
4. 文件 IO 实用示例
示例 1:read+write 实现文件拷贝(支持任意文件,如图片 / 视频)
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#define BUF_SIZE 4096 // 缓冲区大小,与页大小一致,提升效率
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("Usage: %s src_file dest_file\n", argv[0]);
exit(1);
}
// 打开源文件(只读)、目标文件(只写,不存在创建,存在清空)
int fd_src = open(argv[1], O_RDONLY);
int fd_dst = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0664);
if (fd_src == -1 || fd_dst == -1) {
perror("open fail");
exit(1);
}
// 读写拷贝
char buf[BUF_SIZE] = {0};
ssize_t n = 0;
while ((n = read(fd_src, buf, BUF_SIZE)) > 0) {
write(fd_dst, buf, n); // 按实际读取的字节数写入
}
// 关闭文件描述符
close(fd_src);
close(fd_dst);
return 0;
}
示例 2:lseek 计算文件大小
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s filename\n", argv[0]);
return 1;
}
int fd = open(argv[1], O_RDONLY);
if (fd == -1) {
perror("open fail");
return 1;
}
// 偏移到文件末尾,返回值即为文件总字节数
off_t file_size = lseek(fd, 0, SEEK_END);
printf("File size: %ld bytes\n", file_size);
close(fd);
return 0;
}
二、目录 IO(专门处理目录文件)
Linux 中目录是特殊的文件 ,普通的文件 IO 无法直接遍历、读取目录内容,需使用专门的目录 IO 接口 (头文件<dirent.h>、<sys/stat.h>),核心实现目录的打开、关闭、遍历、创建、删除等操作。
1. 核心目录 IO 接口
目录 IO 的操作句柄是目录流指针DIR * ,核心接口分为目录操作 (opendir/closedir/readdir)和目录管理 (mkdir/rmdir/chdir/getcwd)两类。
(1)目录操作核心接口
opendir - 打开目录,获取目录流指针
DIR *opendir(const char *name);
- 功能:打开指定路径的目录文件,返回目录流指针;
- 参数 :
name:目录路径(绝对 / 相对); - 返回值 :成功返回
DIR *;失败返回NULL。
closedir - 关闭目录流指针
int closedir(DIR *dirp);
- 功能:关闭目录流指针,释放内核资源;
- 参数 :
dirp:已打开的目录流指针; - 返回值 :成功返回
0;失败返回-1。
readdir - 读取目录项信息
struct dirent *readdir(DIR *dirp);
- 功能 :逐行读取目录流中的目录项 (目录下的文件 / 子目录),每次调用返回下一个目录项,读到末尾返回
NULL; - 参数 :
dirp:目录流指针; - 返回值 :成功返回
struct dirent *(目录项结构体);失败 / 读到末尾返回NULL。
核心结构体struct dirent(存储目录项信息):
struct dirent {
ino_t d_ino; // 节点号
off_t d_off; // 目录项偏移量
unsigned short d_reclen; // 结构体长度
unsigned char d_type; // 文件类型(普通文件/目录/链接等)
char d_name[256];// 文件名/目录名(核心)
};
(2)目录管理核心接口
mkdir - 创建目录
int mkdir(const char *pathname, mode_t mode);
- 功能 :创建指定路径的目录,权限由
mode指定(如0777); - 参数 :
pathname:目录路径;mode:目录权限; - 返回值 :成功返回
0;失败返回-1。
rmdir - 删除空目录
int rmdir(const char *pathname);
- 功能 :删除指定的空目录(非空目录需先删除内部文件);
- 返回值 :成功返回
0;失败返回-1。
chdir - 切换当前进程的工作目录
int chdir(const char *path);
- 功能 :类似 Shell 命令
cd,切换进程的当前工作目录; - 返回值 :成功返回
0;失败返回-1。
getcwd - 获取当前工作目录的绝对路径
char *getcwd(char *buf, size_t size);
- 功能 :将当前进程的工作目录绝对路径存入
buf缓冲区; - 参数 :
buf:存储路径的缓冲区;size:缓冲区大小; - 返回值 :成功返回
buf首地址;失败返回NULL。
2. 目录 IO 实用示例
示例 1:遍历指定目录下的所有文件 / 子目录
#include <stdio.h>
#include <dirent.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s dirname\n", argv[0]);
exit(1);
}
// 打开目录
DIR *dp = opendir(argv[1]);
if (dp == NULL) {
perror("opendir fail");
exit(1);
}
// 遍历目录项
struct dirent *p = NULL;
while ((p = readdir(dp)) != NULL) {
// 过滤.和..(当前目录和上级目录)
if (strcmp(p->d_name, ".") == 0 || strcmp(p->d_name, "..") == 0) {
continue;
}
printf("文件名:%s\n", p->d_name);
}
// 关闭目录流
closedir(dp);
return 0;
}
示例 2:获取 / 切换当前工作目录
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define BUF_SIZE 256
int main() {
char buf[BUF_SIZE] = {0};
// 获取当前工作目录
getcwd(buf, BUF_SIZE);
printf("当前工作目录:%s\n", buf);
// 切换工作目录到/home
if (chdir("/home") == -1) {
perror("chdir fail");
exit(1);
}
// 再次获取
getcwd(buf, BUF_SIZE);
printf("切换后工作目录:%s\n", buf);
return 0;
}