sync/syncfs系统调用及示例

好的,我们来深入学习 sync 和 syncfs 系统调用

1. 函数介绍

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

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

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

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

简单来说

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

2. 函数原型

#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. 功能

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

4. 参数

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

5. 返回值

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

6. 错误码 (errno) – 仅适用于 syncfs

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

7. 相似函数或关联函数

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

8. 示例代码

下面的示例演示了如何使用 sync 和 syncfs

#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. 编译和运行

# 假设代码保存在 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. 预期输出 (片段,时间值会有所不同)

--- 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. 总结

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

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

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

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

发表回复

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