close系统调用及示例

继续学习 Linux 系统编程中的基础函数。这次我们介绍 close 函数,它是与 open 相对应的,用于关闭不再需要的文件描述符。


1. 函数介绍

close 是一个 Linux 系统调用,其主要功能是关闭一个由 openpipesocket 等系统调用打开的文件描述符 (file descriptor)。关闭文件描述符会释放与之关联的内核资源(如文件表项),并使其可以被进程重新使用(例如,后续的 open 调用可能会返回这个刚刚被关闭的文件描述符值)。

你可以把它想象成离开房间时关上门,并交还钥匙(文件描述符),这样其他人(或你自己稍后)才能再使用这把钥匙(文件描述符号)进入别的房间(打开别的文件)。


2. 函数原型

#include <unistd.h>

int close(int fd);

3. 功能

  • 释放资源: 释放与文件描述符 fd 相关的内核资源。
  • 刷新缓冲: 对于某些类型的文件(如普通文件),内核可能会缓存写操作。调用 close 通常会触发将这些缓存的数据写入到实际的存储介质中(虽然不保证 100% 刷新,fsync 可以强制刷新)。
  • 关闭连接: 对于套接字 (socket) 或管道 (pipe),close 会关闭连接的一端。
  • 回收描述符: 使文件描述符 fd 在当前进程中变为无效,该值可以被后续的文件操作(如 opendup)重新使用。

4. 参数

  • int fd: 这是需要关闭的文件描述符。它应该是之前成功调用 opencreatpipesocketdup 等函数返回的有效文件描述符。

5. 返回值

  • 成功时: 返回 0。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EBADF 表示 fd 不是一个有效的、已打开的文件描述符)。

重要提示必须检查 close 的返回值! 虽然很多人忽略,但 close 是可能失败的。如果 close 失败,可能意味着数据没有被正确写入(例如磁盘空间满、设备故障等)。忽略 close 的错误可能会导致数据丢失或不一致。


6. 相似函数,或关联函数

  • open: 与 close 相对应,用于打开文件并获取文件描述符。
  • readwrite: 在文件描述符被 close 之后,不能再对它使用 read 或 write
  • dupdup2fcntl(F_DUPFD): 这些函数可以复制文件描述符。需要注意的是,close 只关闭指定的那个文件描述符副本。只有当一个打开文件的最后一个引用(即最后一个指向该打开文件表项的文件描述符)被关闭时,相关的资源(如文件偏移量、状态标志)才会被真正释放,对于文件来说,数据也才会被刷新。
  • fsync: 在 close 之前显式调用 fsync(fd) 可以确保文件的所有修改都被写入到存储设备,提供更强的数据持久性保证。

7. 示例代码

示例 1:基本的打开、读取、关闭操作

这个例子结合了 openread 和 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;
}

代码解释:

  1. 使用 open 打开文件。
  2. 使用 read 和 write 循环读取并打印文件内容。
  3. 关键: 在所有可能的退出路径(正常结束、读/写错误)上都调用了 close(fd)
  4. 最重要: 检查了 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. 初始化文件描述符变量为 -1(无效值)。
  2. 依次尝试打开多个文件。
  3. 如果中间某个 open 失败,在退出前会关闭之前成功打开的文件描述符。
  4. 在程序正常结束前,有一个清理阶段,遍历所有可能有效的文件描述符(通过检查是否不等于 -1)并调用 close
  5. 关键: 对每一次 close 调用都检查了返回值。如果失败,会打印错误信息。注意这里使用了 strerror(errno) 来获取 errno 对应的可读错误信息。
  6. 关闭后,将文件描述符变量设置回 -1,这是一种防止重复关闭的好习惯(虽然重复关闭同一个 已经关闭的 文件描述符通常是安全的,会返回错误 EBADF,但保持清晰的状态是好的实践)。

总结来说,close 是资源管理的关键环节。养成始终检查 close 返回值的习惯对于编写健壮的 Linux 程序至关重要。

此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注