Linux 文件 IO 详解:从系统调用到实际操作
在 Linux 系统中,文件 IO 是用户与系统交互的核心方式之一。无论是操作普通文件、设备文件,还是进行目录遍历,都离不开系统提供的文件 IO 接口。本文将深入解析 Linux 文件 IO 的核心概念、系统调用函数及实际应用场景,帮助你掌握从底层到应用的文件操作逻辑。
一、Linux 文件 IO 基础:系统调用与 C 库封装
Linux 操作系统通过系统调用向用户提供文件操作功能,这些系统调用是内核暴露的底层接口,直接与硬件或文件系统交互。而 C 标准库(如 glibc
)则对这些系统调用进行了封装,提供了更易用的函数(如 fopen
、fread
等),并增加了缓冲区机制提升效率。
值得注意的是:
- 系统调用的文件 IO(如
open
、read
)没有缓冲区,适合操作设备文件(如打印机、终端) - C 库 IO 函数(如
fopen
、fread
)有缓冲区,更适合普通文件操作
二、文件类型与权限:理解 ll
命令的输出
在 Linux 中,"一切皆文件",不同类型的文件通过权限字符串的第一个字符区分。使用 ll
(即 ls -l
)命令可以查看文件类型和权限,例如:
-rw-rw-r-- 1 user group 1024 Jul 10 16:00 test.txt
drwxrwxr-x 2 user group 4096 Jul 10 16:00 docs/
lrwxrwxrwx 1 user group 5 Jul 10 16:00 link -> test.txt
crw-rw-r-- 1 root root 1, 3 Jul 10 16:00 /dev/null
brw-rw-r-- 1 root root 8, 0 Jul 10 16:00 /dev/sda
1. 文件类型标识
权限字符串的第一个字符代表文件类型:
-
:普通文件(文本、二进制等)d
:目录l
:软链接(快捷方式)s
:socket 网络文件(用于进程间网络通信)p
:管道文件(用于进程间通信)c
:字符设备(如键盘、终端,按字符流处理)b
:块设备(如硬盘、U 盘,按块处理的存储设备)
2. 文件权限解析
权限字符串后 9 个字符分为 3 组,分别代表所有者权限、组用户权限、其他用户权限,每组包含 r
(读)、w
(写)、x
(执行)三个权限:
以 rw- rw- r--
为例:
- 所有者(第一组):
rw-
→ 可读、可写、不可执行 - 组用户(第二组):
rw-
→ 可读、可写、不可执行 - 其他用户(第三组):
r--
→ 可读、不可写、不可执行
权限可以用八进制数表示(每组 3 个二进制位对应一个八进制数):
r=4
、w=2
、x=1
- 例如
rwx
对应7
(4+2+1),rw-
对应6
(4+2),r--
对应4
3. 权限与 umask
新建文件的默认权限由umask(权限掩码)决定,计算方式为:
默认权限 = 0666(普通文件)/0777(可执行文件/目录) - umask
例如,当 umask 为 002
时:
- 普通文件默认权限:
0666 - 002 = 0664
(对应rw-rw-r--
) - 目录默认权限:
0777 - 002 = 0775
(对应rwxrwxr-x
)
三、文件描述符:Linux 文件 IO 的 "身份证"
在 Linux 中,所有打开的文件都通过文件描述符(File Descriptor)标识,这是一个非负整数(int
类型),范围通常是 0-1023
(默认最多打开 1024 个文件,可通过系统配置修改)。
系统默认打开三个文件描述符:
0
:标准输入(stdin
,通常对应键盘)1
:标准输出(stdout
,通常对应终端)2
:标准错误(stderr
,通常对应终端)
当我们打开新文件时,系统会从 3
开始分配未使用的最小描述符。
四、核心系统调用函数:直接操作文件的 "工具集"
Linux 提供了一组基础的文件 IO 系统调用,这些函数没有缓冲区,适合操作设备文件(如终端、打印机),也可用于普通文件操作。
1. open:打开文件
#include <fcntl.h>
int open(const char *pathname, int flags, mode_t mode);
- 功能:打开或创建文件
- 参数:
pathname
:文件路径flags
:打开方式(如O_RDONLY
只读、O_WRONLY
只写、O_RDWR
读写、O_CREAT
不存在则创建)mode
:创建文件时的权限(如0664
,仅O_CREAT
时有效)
- 返回值:成功返回文件描述符,失败返回
-1
(设置errno
)
2. read/write:读写文件
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
- 功能:从文件描述符
fd
读写数据 - 参数:
fd
:文件描述符buf
:数据缓冲区(read
是读出到缓冲区,write
是从缓冲区写入)count
:read
最多读取的字节数(可大于实际内容);write
要写入的有效字节数
- 返回值:成功返回实际读写的字节数,失败返回
-1
,read
读到文件尾返回0
3. close:关闭文件
#include <unistd.h>
int close(int fd);
- 功能:关闭文件描述符,释放资源
- 返回值:成功返回
0
,失败返回-1
4. lseek:移动文件指针
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
- 功能:调整文件读写指针的位置
- 参数:
offset
:偏移量whence
:基准位置(SEEK_SET
从文件头、SEEK_CUR
从当前位置、SEEK_END
从文件尾)
- 返回值:成功返回新指针位置(相对于文件头),失败返回
-1
5. fileno:获取 FILE 指针对应的文件描述符
#include <stdio.h>
int fileno(FILE *stream);
- 功能:将 C 库的
FILE*
指针(如stdin
、fopen
返回的指针)转换为文件描述符 - 返回值:成功返回文件描述符,失败返回
-1
五、目录操作:遍历文件系统的 "导航仪"
目录也是一种特殊的文件,Linux 提供了专门的目录操作函数:
1. opendir:打开目录
#include <dirent.h>
DIR *opendir(const char *name);
- 功能:打开目录并返回目录流指针
- 返回值:成功返回
DIR*
指针,失败返回NULL
2. readdir:读取目录项
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
- 功能:从目录流中读取一个目录项(文件 / 子目录)
- 返回值:成功返回
struct dirent*
指针(包含文件名、inode 等信息),读到末尾或失败返回NULL
3. closedir:关闭目录
#include <dirent.h>
int closedir(DIR *dirp);
- 功能:关闭目录流,释放资源
- 返回值:成功返回
0
,失败返回-1
六、C 库 IO 函数:带缓冲区的 "高级封装"
C 标准库对系统调用进行了封装,提供了带缓冲区的 IO 函数,更适合普通文件操作:
- 文本文件操作:
fgetc
(读字符)、fgets
(读行)、fputc
(写字符)、fputs
(写行)等 - 二进制文件操作:
fread
(读块)、fwrite
(写块)等
这些函数通过 FILE*
指针操作文件,内部维护缓冲区,减少系统调用次数,提高效率。
七、实践示例:用系统调用实现文件复制
下面是一个使用 open
、read
、write
实现文件复制的简单程序:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>#define BUF_SIZE 1024int main(int argc, char *argv[]) {if (argc != 3) {fprintf(stderr, "用法:%s <源文件> <目标文件>\n", argv[0]);return 1;}// 打开源文件(只读)int src_fd = open(argv[1], O_RDONLY);if (src_fd == -1) {perror("打开源文件失败");return 1;}// 打开目标文件(只写,不存在则创建,权限 0664)int dest_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0664);if (dest_fd == -1) {perror("打开目标文件失败");close(src_fd);return 1;}// 读写数据char buf[BUF_SIZE];ssize_t n;while ((n = read(src_fd, buf, BUF_SIZE)) > 0) {if (write(dest_fd, buf, n) != n) {perror("写入失败");close(src_fd);close(dest_fd);return 1;}}if (n == -1) {perror("读取失败");}// 关闭文件close(src_fd);close(dest_fd);return 0;
}
总结
Linux 文件 IO 系统为我们提供了灵活的操作方式:系统调用函数(open
、read
等)直接与内核交互,适合设备文件操作;C 库函数(fopen
、fread
等)带缓冲区,更适合普通文件。理解文件描述符、权限机制和目录操作逻辑,是掌握 Linux 系统编程的基础。
无论是开发命令行工具、操作硬件设备,还是实现文件管理功能,这些知识都必不可少。希望本文能帮助你打通从理论到实践的任督二脉,在 Linux 文件操作的世界里游刃有余!