继续学习 Linux 系统编程中的基础函数。这次我们介绍 close
函数,它是与 open
相对应的,用于关闭不再需要的文件描述符。
1. 函数介绍
close
是一个 Linux 系统调用,其主要功能是关闭一个由 open
、pipe
、socket
等系统调用打开的文件描述符 (file descriptor)。关闭文件描述符会释放与之关联的内核资源(如文件表项),并使其可以被进程重新使用(例如,后续的 open
调用可能会返回这个刚刚被关闭的文件描述符值)。
你可以把它想象成离开房间时关上门,并交还钥匙(文件描述符),这样其他人(或你自己稍后)才能再使用这把钥匙(文件描述符号)进入别的房间(打开别的文件)。
2. 函数原型
#include <unistd.h>
int close(int fd);
3. 功能
- 释放资源: 释放与文件描述符
fd
相关的内核资源。 - 刷新缓冲: 对于某些类型的文件(如普通文件),内核可能会缓存写操作。调用
close
通常会触发将这些缓存的数据写入到实际的存储介质中(虽然不保证 100% 刷新,fsync
可以强制刷新)。 - 关闭连接: 对于套接字 (socket) 或管道 (pipe),
close
会关闭连接的一端。 - 回收描述符: 使文件描述符
fd
在当前进程中变为无效,该值可以被后续的文件操作(如open
、dup
)重新使用。
4. 参数
int fd
: 这是需要关闭的文件描述符。它应该是之前成功调用open
、creat
、pipe
、socket
、dup
等函数返回的有效文件描述符。
5. 返回值
- 成功时: 返回 0。
- 失败时: 返回 -1,并设置全局变量
errno
来指示具体的错误原因(例如EBADF
表示fd
不是一个有效的、已打开的文件描述符)。
重要提示: 必须检查 close
的返回值! 虽然很多人忽略,但 close
是可能失败的。如果 close
失败,可能意味着数据没有被正确写入(例如磁盘空间满、设备故障等)。忽略 close
的错误可能会导致数据丢失或不一致。
6. 相似函数,或关联函数
open
: 与close
相对应,用于打开文件并获取文件描述符。read
,write
: 在文件描述符被close
之后,不能再对它使用read
或write
。dup
,dup2
,fcntl(F_DUPFD)
: 这些函数可以复制文件描述符。需要注意的是,close
只关闭指定的那个文件描述符副本。只有当一个打开文件的最后一个引用(即最后一个指向该打开文件表项的文件描述符)被关闭时,相关的资源(如文件偏移量、状态标志)才会被真正释放,对于文件来说,数据也才会被刷新。fsync
: 在close
之前显式调用fsync(fd)
可以确保文件的所有修改都被写入到存储设备,提供更强的数据持久性保证。
7. 示例代码
示例 1:基本的打开、读取、关闭操作
这个例子结合了 open
、read
和 close
,展示了它们的标准使用流程。
#include <unistd.h> // read, write, close
#include <fcntl.h> // open, O_RDONLY
#include <stdio.h> // perror, printf
#include <stdlib.h> // exit
#include <errno.h> // errno
#define BUFFER_SIZE 512
int main() {
int fd; // 文件描述符
char buffer[BUFFER_SIZE]; // 读取缓冲区
ssize_t bytes_read; // 实际读取的字节数
int close_result; // close 的返回值
// 1. 打开文件
fd = open("sample.txt", O_RDONLY);
if (fd == -1) {
perror("Error opening file 'sample.txt'");
exit(EXIT_FAILURE);
}
printf("File 'sample.txt' opened successfully with fd: %d\n", fd);
// 2. 读取文件内容
printf("Reading file content:\n");
while ((bytes_read = read(fd, buffer, BUFFER_SIZE)) > 0) {
// 将读取的内容写到标准输出
if (write(STDOUT_FILENO, buffer, bytes_read) != bytes_read) {
perror("Error writing to stdout");
// 即使写 stdout 出错,也要尝试关闭原始文件
close(fd); // 忽略此处 close 的返回值,因为主要错误是 write
exit(EXIT_FAILURE);
}
}
if (bytes_read == -1) {
perror("Error reading file");
// 读取失败,关闭文件
close(fd); // 忽略此处 close 的返回值,因为主要错误是 read
exit(EXIT_FAILURE);
}
// bytes_read == 0, 表示到达文件末尾,正常流程
// 3. 关闭文件 - 这是关键步骤
close_result = close(fd);
if (close_result == -1) {
// close 失败!这是一个严重错误,需要处理
perror("CRITICAL ERROR: Failed to close file 'sample.txt'");
exit(EXIT_FAILURE); // 或者根据应用逻辑决定如何处理
}
printf("File 'sample.txt' closed successfully.\n");
return 0;
}
代码解释:
- 使用
open
打开文件。 - 使用
read
和write
循环读取并打印文件内容。 - 关键: 在所有可能的退出路径(正常结束、读/写错误)上都调用了
close(fd)
。 - 最重要: 检查了
close(fd)
的返回值。如果返回 -1,则打印严重错误信息并退出。这确保了我们能发现close
本身可能遇到的问题(如 I/O 错误导致缓冲区刷新失败)。
示例 2:处理多个文件描述符和错误检查
这个例子展示了打开多个文件,并在程序结束前逐一正确关闭它们,同时进行错误检查。
#include <unistd.h> // close
#include <fcntl.h> // open
#include <stdio.h> // perror, printf
#include <stdlib.h> // exit
#include <string.h> // strerror
int main() {
int fd1 = -1, fd2 = -1, fd3 = -1; // 初始化为无效值
int result;
// 打开几个不同的文件
fd1 = open("file1.txt", O_RDONLY);
if (fd1 == -1) {
perror("Failed to open 'file1.txt'");
// fd2, fd3 还未打开,无需关闭
exit(EXIT_FAILURE);
}
fd2 = open("file2.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd2 == -1) {
perror("Failed to open/create 'file2.txt'");
// 关闭之前成功打开的 fd1
if (close(fd1) == -1) {
fprintf(stderr, "Warning: Also failed to close 'file1.txt': %s\n", strerror(errno));
}
exit(EXIT_FAILURE);
}
fd3 = open("/etc/passwd", O_RDONLY); // 尝试打开一个系统文件
if (fd3 == -1) {
perror("Failed to open '/etc/passwd'");
// 关闭之前成功打开的 fd1 和 fd2
if (close(fd1) == -1) {
fprintf(stderr, "Warning: Also failed to close 'file1.txt': %s\n", strerror(errno));
}
if (close(fd2) == -1) {
fprintf(stderr, "Warning: Also failed to close 'file2.txt': %s\n", strerror(errno));
}
exit(EXIT_FAILURE);
}
printf("All files opened successfully: fd1=%d, fd2=%d, fd3=%d\n", fd1, fd2, fd3);
// ... 这里可以进行文件读写操作 ...
// 程序结束前,关闭所有打开的文件描述符
// 注意:关闭顺序通常不重要,但保持一致性是好习惯
// 关闭时检查每个的返回值
if (fd1 != -1) {
result = close(fd1);
if (result == -1) {
// 主要错误:记录日志或处理
fprintf(stderr, "ERROR: Failed to close 'file1.txt' (fd=%d): %s\n", fd1, strerror(errno));
// 根据应用策略决定是否 exit(EXIT_FAILURE)
} else {
printf("Successfully closed 'file1.txt' (fd=%d)\n", fd1);
}
fd1 = -1; // 关闭后设为无效值,防止重复关闭
}
if (fd2 != -1) {
result = close(fd2);
if (result == -1) {
fprintf(stderr, "ERROR: Failed to close 'file2.txt' (fd=%d): %s\n", fd2, strerror(errno));
} else {
printf("Successfully closed 'file2.txt' (fd=%d)\n", fd2);
}
fd2 = -1;
}
if (fd3 != -1) {
result = close(fd3);
if (result == -1) {
fprintf(stderr, "ERROR: Failed to close '/etc/passwd' (fd=%d): %s\n", fd3, strerror(errno));
} else {
printf("Successfully closed '/etc/passwd' (fd=%d)\n", fd3);
}
fd3 = -1;
}
printf("Program finished closing all files.\n");
return 0;
}
代码解释:
- 初始化文件描述符变量为 -1(无效值)。
- 依次尝试打开多个文件。
- 如果中间某个
open
失败,在退出前会关闭之前成功打开的文件描述符。 - 在程序正常结束前,有一个清理阶段,遍历所有可能有效的文件描述符(通过检查是否不等于 -1)并调用
close
。 - 关键: 对每一次
close
调用都检查了返回值。如果失败,会打印错误信息。注意这里使用了strerror(errno)
来获取errno
对应的可读错误信息。 - 关闭后,将文件描述符变量设置回 -1,这是一种防止重复关闭的好习惯(虽然重复关闭同一个 已经关闭的 文件描述符通常是安全的,会返回错误
EBADF
,但保持清晰的状态是好的实践)。
总结来说,close
是资源管理的关键环节。养成始终检查 close
返回值的习惯对于编写健壮的 Linux 程序至关重要。