好的,我们来深入学习 syncsyncfs 系统调用,从 Linux 编程小白的角度出发。

1. 函数介绍 Link to heading

在 Linux 系统中,为了提高文件操作的性能,内核广泛使用了缓存 (Caching) 机制。当你调用 write() 将数据写入文件时,数据通常首先被写入到内存中的高速缓存(页缓存 Page Cache)中,而不是直接写入物理磁盘。操作系统会在稍后(例如缓存满、定时刷新或系统关闭时)才将这些缓存中的“脏数据”(已修改但未写回磁盘的数据)真正写入到磁盘上。

这种机制虽然高效,但也带来了数据安全的风险:如果在数据从缓存写入磁盘之前,系统突然断电或崩溃,那么这部分未写入的数据就会丢失。

syncsyncfs 系统调用就是用来强制将缓存中的数据刷新到磁盘,以确保数据的持久化。

  • sync: 这是一个全局性的操作。它会要求内核将所有已挂载文件系统上的所有脏数据(包括文件数据和元数据,如目录结构、文件权限、修改时间等)都刷新到对应的物理存储设备上。这是一个重量级操作,可能需要一些时间。
  • syncfs: 这是一个针对性的操作。它只刷新与指定文件描述符相关联的那个文件系统上的脏数据。相比 sync,它的影响范围更小,通常也更快。

简单来说

  • sync 就像是对整个电脑说:“请把内存里所有还没存到硬盘上的东西,现在都给我存一遍!”。
  • syncfs 就像是对某个 U 盘(或硬盘分区)说:“请把你内存里还没存到你这个盘上的东西,现在都给我存一遍!”。

2. 函数原型 Link to heading

#include <unistd.h>  // 包含 sync 的声明

void sync(void);     // 注意:sync 没有返回值,也不会报告错误

#include <sys/syscall.h> // 对于 syncfs,有时需要 syscall.h
// #include <linux/fs.h> // 或者这个头文件
// 在某些较新的 glibc 版本中,可能直接在 unistd.h 中声明
long syscall(SYS_syncfs, int fd); // 底层调用方式

// 或者,如果 glibc 支持
// int syncfs(int fd); // 更简单的用户空间接口 (较新 glibc)

注意syncfs 相对较新,一些旧版本的 glibc 可能没有直接提供 syncfs() 的包装函数。在这种情况下,你需要使用 syscall() 来直接调用。较新版本的 glibc (2.20+) 通常直接提供了 int syncfs(int fd);

3. 功能 Link to heading

  • sync(): 将所有已挂载的文件系统的脏数据(文件数据和元数据)刷新到磁盘。
  • syncfs(fd): 将文件描述符 fd 所在的文件系统的脏数据刷新到磁盘。

4. 参数 Link to heading

  • sync(): 无参数。
  • syncfs(fd):
    • int fd: 一个已打开文件的有效文件描述符。函数会根据这个 fd 找到它所属的文件系统,并只对该文件系统执行同步操作。

5. 返回值 Link to heading

  • sync(): 无返回值。它不会告诉你操作是否成功或失败。这是一个设计上的特点,保证了即使在系统资源紧张时也能尽可能地执行同步。
  • syncfs(fd):
    • 成功: 返回 0。
    • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno) - 仅适用于 syncfs Link to heading

  • EBADF: fd 不是有效的文件描述符。
  • EIO: I/O 错误。
  • ENOSPC: 设备上没有足够的空间(虽然对于同步操作不太常见)。
  • EROFS: 文件系统是只读的。

7. 相似函数或关联函数 Link to heading

  • fsync: 同步单个文件的所有数据和元数据。它会等待操作完成。
  • fdatasync: 类似于 fsync,但只同步文件的数据和必要的元数据(不包括访问时间等),通常更快。
  • sync_file_range: 提供对文件特定字节范围的更精细同步控制。
  • msync: 用于同步 mmap 内存映射区域到文件。
  • sync 命令: 用户空间的命令行工具,功能与 sync() 系统调用相同。

8. 示例代码 Link to heading

下面的示例演示了如何使用 syncsyncfs

#define _GNU_SOURCE // 启用 GNU 扩展,可能需要用于 syncfs
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>    // 包含 open, O_* flags
#include <sys/stat.h> // 包含 open modes
#include <string.h>
#include <errno.h>
#include <time.h>     // 包含 clock_gettime
#include <sys/syscall.h> // 包含 SYS_syncfs
#include <linux/fs.h>    // 包含 SYNCFS 等定义 (如果需要)

// 计算时间差(毫秒)
double time_diff_ms(struct timespec start, struct timespec end) {
    return ((end.tv_sec - start.tv_sec) * 1000.0) + ((end.tv_nsec - start.tv_nsec) / 1000000.0);
}

// 封装 syncfs 调用,处理不同 glibc 版本的兼容性
int my_syncfs(int fd) {
    // 尝试直接调用 syncfs (如果 glibc 支持)
    // 如果编译报错,注释掉 #ifdef 部分,只保留 syscall 部分
#ifdef SYS_syncfs // 检查是否定义了系统调用号
    return syscall(SYS_syncfs, fd);
#else
    // 如果你的 glibc 版本较新,可能直接支持 syncfs 函数
    // return syncfs(fd);
    fprintf(stderr, "syncfs system call not available on this system.\n");
    return -1;
#endif
}

int main() {
    const char *file1_name = "/tmp/sync_test_file1.txt"; // /tmp 通常在根文件系统或 tmpfs
    const char *file2_name = "/home/user/sync_test_file2.txt"; // 假设 /home 是另一个分区
    int fd1, fd2;
    struct timespec start, end;
    double elapsed_time;

    printf("--- Demonstrating sync and syncfs ---\n");
    printf("PID: %d\n", getpid());

    // 1. 创建并写入两个测试文件
    printf("\n1. Creating and writing test files...\n");

    // --- 文件 1 ---
    fd1 = open(file1_name, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd1 == -1) {
        perror("open file1");
        // 如果 /home/user 不可写,跳过 file2
        printf("Skipping file2 due to open error.\n");
        fd2 = -1;
    } else {
        printf("Opened %s\n", file1_name);
        if (write(fd1, "Data for file 1 on root/tmp filesystem.\n", 40) != 40) {
            perror("write file1");
        }
        // 注意:不要 close,保持 fd1 打开用于 syncfs
    }

    // --- 文件 2 (可能在不同文件系统) ---
    fd2 = open(file2_name, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd2 == -1) {
        perror("open file2");
        printf("Skipping operations on file2.\n");
    } else {
        printf("Opened %s\n", file2_name);
        if (write(fd2, "Data for file 2 on home filesystem.\n", 37) != 37) {
            perror("write file2");
        }
        // 注意:不要 close,保持 fd2 打开用于 syncfs
    }

    printf("\nData written to files. It's likely in the page cache now.\n");

    // 2. 演示 syncfs: 同步特定文件系统
    printf("\n2. --- Demonstrating syncfs ---\n");
    if (fd1 != -1) {
        printf("Calling syncfs() on the filesystem containing %s...\n", file1_name);
        clock_gettime(CLOCK_MONOTONIC, &start);
        if (my_syncfs(fd1) == -1) {
            perror("syncfs fd1");
            printf("This might be because syncfs is not supported or fd is invalid.\n");
        } else {
            clock_gettime(CLOCK_MONOTONIC, &end);
            elapsed_time = time_diff_ms(start, end);
            printf("syncfs(fd1) completed in %.2f ms.\n", elapsed_time);
            printf("This synced only the filesystem containing %s.\n", file1_name);
        }
    }

    if (fd2 != -1) {
        printf("Calling syncfs() on the filesystem containing %s...\n", file2_name);
        clock_gettime(CLOCK_MONOTONIC, &start);
        if (my_syncfs(fd2) == -1) {
            perror("syncfs fd2");
        } else {
            clock_gettime(CLOCK_MONOTONIC, &end);
            elapsed_time = time_diff_ms(start, end);
            printf("syncfs(fd2) completed in %.2f ms.\n", elapsed_time);
            printf("This synced only the filesystem containing %s.\n", file2_name);
        }
    }

    // 3. 演示 sync: 同步所有文件系统
    printf("\n3. --- Demonstrating sync ---\n");
    printf("Calling sync() to flush ALL filesystems...\n");
    clock_gettime(CLOCK_MONOTONIC, &start);
    sync(); // sync() 没有返回值
    clock_gettime(CLOCK_MONOTONIC, &end);
    elapsed_time = time_diff_ms(start, end);
    printf("sync() completed in %.2f ms.\n", elapsed_time);
    printf("This synced data for ALL mounted filesystems on the system.\n");

    // 4. 关闭文件描述符
    if (fd1 != -1) close(fd1);
    if (fd2 != -1) close(fd2);

    // 5. 清理测试文件 (可选)
    printf("\n4. --- Cleaning up ---\n");
    if (fd1 != -1) {
        if (unlink(file1_name) == 0) {
            printf("Deleted %s\n", file1_name);
        } else {
            perror("unlink file1");
        }
    }
    if (fd2 != -1) {
        if (unlink(file2_name) == 0) {
            printf("Deleted %s\n", file2_name);
        } else {
            perror("unlink file2");
        }
    }

    printf("\n--- Summary ---\n");
    printf("1. sync(): Flushes dirty data for ALL mounted filesystems. No return value.\n");
    printf("2. syncfs(fd): Flushes dirty data for ONLY the filesystem containing the file referred to by 'fd'. Returns 0 on success.\n");
    printf("3. syncfs is more targeted and usually faster than sync.\n");
    printf("4. Use sync() before system shutdown. Use syncfs() or fsync() for specific data safety in applications.\n");

    return 0;
}

9. 编译和运行 Link to heading

# 假设代码保存在 sync_example.c 中
# _GNU_SOURCE 通常由 #define 定义,显式添加也无妨
gcc -D_GNU_SOURCE -o sync_example sync_example.c

# 运行程序 (可能需要权限来写入 /home/user)
# 如果 /home/user 不可写,请修改代码中的 file2_name
./sync_example

10. 预期输出 (片段,时间值会有所不同) Link to heading

--- Demonstrating sync and syncfs ---
PID: 12345

1. Creating and writing test files...
Opened /tmp/sync_test_file1.txt
Opened /home/user/sync_test_file2.txt

Data written to files. It's likely in the page cache now.

2. --- Demonstrating syncfs ---
Calling syncfs() on the filesystem containing /tmp/sync_test_file1.txt...
syncfs(fd1) completed in 15.23 ms.
This synced only the filesystem containing /tmp/sync_test_file1.txt.
Calling syncfs() on the filesystem containing /home/user/sync_test_file2.txt...
syncfs(fd2) completed in 8.45 ms.
This synced only the filesystem containing /home/user/sync_test_file2.txt.

3. --- Demonstrating sync ---
Calling sync() to flush ALL filesystems...
sync() completed in 45.67 ms.
This synced data for ALL mounted filesystems on the system.

4. --- Cleaning up ---
Deleted /tmp/sync_test_file1.txt
Deleted /home/user/sync_test_file2.txt

--- Summary ---
1. sync(): Flushes dirty data for ALL mounted filesystems. No return value.
2. syncfs(fd): Flushes dirty数据 for ONLY the filesystem containing the file referred to by 'fd'. Returns 0 on success.
3. syncfs is more targeted and usually faster than sync.
4. Use sync() before system shutdown. Use syncfs() or fsync() for specific data safety in applications.

11. 总结 Link to heading

syncsyncfs 是确保数据持久化的重要系统调用。

  • sync() 是一个全局操作,强制刷新系统中所有文件系统的缓存数据。它简单粗暴,但开销大。通常在系统关机脚本中调用。
  • syncfs(int fd) 是一个更精细的操作,只刷新与特定文件描述符 fd 相关联的文件系统的缓存数据。它更高效,适用于需要确保特定存储设备数据安全的场景。

fsync (同步单个文件) 和 sync_file_range (同步文件特定范围) 相比,syncsyncfs 的作用范围更大。选择哪个取决于你的具体需求:是保证一个文件的安全,还是保证一个分区或整个系统的数据安全。