close系统调用及示例

继续学习 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. 返回值

  • 成功时: 返回 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,展示了它们的标准使用流程。

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&#91;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 程序至关重要。

data-ad-format="auto" data-full-width-responsive="true">