继续学习 Linux 系统编程中的基础函数。这次我们介绍 close 函数,它是与 open 相对应的,用于关闭不再需要的文件描述符。
1. 函数介绍 close 是一个 Linux 系统调用,其主要功能是关闭一个由 open、pipe、socket 等系统调用打开的文件描述符 (file descriptor)。关闭文件描述符会释放与之关联的内核资源(如文件表项),并使其可以被进程重新使用(例如,后续的 open 调用可能会返回这个刚刚被关闭的文件描述符值)。
data-ad-format="fluid"
data-ad-layout-key="-7k+ex-4a-9w+4a">
你可以把它想象成离开房间时关上门,并交还钥匙(文件描述符),这样其他人(或你自己稍后)才能再使用这把钥匙(文件描述符号)进入别的房间(打开别的文件)。
2. 函数原型 1 2 3 4 #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. 返回值
重要提示: 必须检查 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,展示了它们的标准使用流程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #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:处理多个文件描述符和错误检查 这个例子展示了打开多个文件,并在程序结束前逐一正确关闭它们,同时进行错误检查。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 #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 程序至关重要。