14. fdatasync - 文件数据同步 Link to heading

函数介绍 Link to heading

fdatasync系统调用用于将文件的数据修改强制写入磁盘,与fsync类似,但只同步文件数据和必要的元数据(如文件大小),而不同步所有元数据。这使得fdatasyncfsync更快。

函数原型 Link to heading

#include <unistd.h>

int fdatasync(int fd);

功能 Link to heading

将文件描述符对应的文件数据和必要元数据强制写入磁盘,不包括访问时间等非必要元数据。

参数 Link to heading

  • int fd: 文件描述符

返回值 Link to heading

  • 成功时返回0
  • 失败时返回-1,并设置errno:
    • EBADF: 文件描述符无效
    • EIO: I/O错误
    • EINVAL: 文件描述符不支持同步
    • EROFS: 文件在只读文件系统上

相似函数 Link to heading

  • fsync(): 同步文件数据和所有元数据
  • sync(): 同步所有文件系统缓冲区

示例代码 Link to heading

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>

int main() {
    int fd;
    struct stat stat_before, stat_after;
    
    printf("=== Fdatasync函数示例 ===\n");
    
    // 示例1: 基本的文件数据同步操作
    printf("\n示例1: 基本的文件数据同步操作\n");
    
    // 创建测试文件
    fd = open("test_fdatasync.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        exit(EXIT_FAILURE);
    }
    printf("成功创建测试文件,文件描述符: %d\n", fd);
    
    // 写入数据
    const char *data1 = "First line of data for fdatasync test\n";
    if (write(fd, data1, strlen(data1)) == -1) {
        perror("写入数据失败");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("写入数据: %s", data1);
    
    // 使用fdatasync同步数据到磁盘
    printf("使用fdatasync同步数据...\n");
    if (fdatasync(fd) == -1) {
        perror("fdatasync失败");
    } else {
        printf("fdatasync成功完成\n");
    }
    
    // 再写入更多数据
    const char *data2 = "Second line of data\nThird line of data\n";
    if (write(fd, data2, strlen(data2)) == -1) {
        perror("写入更多数据失败");
    } else {
        printf("写入更多数据成功\n");
    }
    
    // 再次使用fdatasync同步
    if (fdatasync(fd) == -1) {
        perror("第二次fdatasync失败");
    } else {
        printf("第二次fdatasync成功完成\n");
    }
    
    // 示例2: fdatasync与fsync对比
    printf("\n示例2: fdatasync与fsync对比\n");
    
    // 获取同步前的文件状态
    if (fstat(fd, &stat_before) == -1) {
        perror("获取同步前文件状态失败");
    } else {
        printf("同步前文件状态:\n");
        printf("  文件大小: %ld 字节\n", stat_before.st_size);
        printf("  最后修改时间: %s", ctime(&stat_before.st_mtime));
        printf("  最后访问时间: %s", ctime(&stat_before.st_atime));
    }
    
    // 写入大量数据
    char large_data[1000];
    memset(large_data, 'A', sizeof(large_data) - 1);
    large_data[sizeof(large_data) - 1] = '\n';
    
    for (int i = 0; i < 10; i++) {
        if (write(fd, large_data, sizeof(large_data)) == -1) {
            perror("写入大量数据失败");
            break;
        }
    }
    printf("写入大量数据完成\n");
    
    // 使用fdatasync同步
    clock_t start = clock();
    if (fdatasync(fd) == -1) {
        perror("fdatasync失败");
    } else {
        clock_t fdatasync_time = clock() - start;
        printf("fdatasync完成,耗时: %f 秒\n", 
               ((double)fdatasync_time) / CLOCKS_PER_SEC);
    }
    
    // 获取fdatasync后的文件状态
    if (fstat(fd, &stat_after) == -1) {
        perror("获取fdatasync后文件状态失败");
    } else {
        printf("fdatasync后文件状态:\n");
        printf("  文件大小: %ld 字节\n", stat_after.st_size);
        printf("  最后修改时间: %s", ctime(&stat_after.st_mtime));
        printf("  最后访问时间: %s", ctime(&stat_after.st_atime));
    }
    
    // 使用fsync同步进行对比
    start = clock();
    if (fsync(fd) == -1) {
        perror("fsync失败");
    } else {
        clock_t fsync_time = clock() - start;
        printf("fsync完成,耗时: %f 秒\n", 
               ((double)fsync_time) / CLOCKS_PER_SEC);
    }
    
    printf("通常情况下,fdatasync比fsync更快\n");
    
    // 示例3: 性能测试对比
    printf("\n示例3: 性能测试对比\n");
    
    // 创建性能测试文件
    int perf_fd = open("perf_test.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (perf_fd != -1) {
        const char *test_data = "Performance test data line\n";
        const int iterations = 1000;
        
        // 测试fdatasync性能
        clock_t fdatasync_start = clock();
        for (int i = 0; i < iterations; i++) {
            char buffer[100];
            sprintf(buffer, "Line %d: %s", i, test_data);
            if (write(perf_fd, buffer, strlen(buffer)) == -1) {
                perror("性能测试写入失败");
                break;
            }
            if (fdatasync(perf_fd) == -1) {
                perror("fdatasync失败");
                break;
            }
        }
        clock_t fdatasync_total = clock() - fdatasync_start;
        
        // 重新创建文件测试fsync性能
        close(perf_fd);
        unlink("perf_test.txt");
        perf_fd = open("perf_test.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
        
        clock_t fsync_start = clock();
        for (int i = 0; i < iterations; i++) {
            char buffer[100];
            sprintf(buffer, "Line %d: %s", i, test_data);
            if (write(perf_fd, buffer, strlen(buffer)) == -1) {
                perror("性能测试写入失败");
                break;
            }
            if (fsync(perf_fd) == -1) {
                perror("fsync失败");
                break;
            }
        }
        clock_t fsync_total = clock() - fsync_start;
        
        printf("fdatasync %d次操作耗时: %f 秒\n", 
               iterations, ((double)fdatasync_total) / CLOCKS_PER_SEC);
        printf("fsync %d次操作耗时: %f 秒\n", 
               iterations, ((double)fsync_total) / CLOCKS_PER_SEC);
        printf("性能提升: %.2f%%\n", 
               ((double)(fsync_total - fdatasync_total) / fsync_total) * 100);
        
        close(perf_fd);
        unlink("perf_test.txt");
    }
    
    // 示例4: 适用场景演示
    printf("\n示例4: 适用场景演示\n");
    
    // 场景1: 日志文件 - 需要保证数据不丢失
    printf("场景1: 日志文件同步\n");
    int log_fd = open("app.log", O_CREAT | O_RDWR | O_APPEND, 0644);
    if (log_fd != -1) {
        const char *log_entry = "2023-01-01 12:00:00 INFO Application started\n";
        if (write(log_fd, log_entry, strlen(log_entry)) != -1) {
            // 对于日志文件,通常使用fdatasync,因为不需要同步访问时间
            if (fdatasync(log_fd) == -1) {
                perror("日志同步失败");
            } else {
                printf("日志条目已同步到磁盘\n");
            }
        }
        close(log_fd);
        unlink("app.log");
    }
    
    // 场景2: 数据库事务日志 - 需要高性能
    printf("场景2: 数据库事务日志\n");
    int db_fd = open("transaction.log", O_CREAT | O_RDWR | O_APPEND, 0644);
    if (db_fd != -1) {
        const char *transaction = "TXN: UPDATE users SET balance=1000 WHERE id=1\n";
        if (write(db_fd, transaction, strlen(transaction)) != -1) {
            // 数据库通常使用fdatasync来提高性能
            if (fdatasync(db_fd) == -1) {
                perror("事务日志同步失败");
            } else {
                printf("事务日志已高效同步\n");
            }
        }
        close(db_fd);
        unlink("transaction.log");
    }
    
    // 示例5: 错误处理演示
    printf("\n示例5: 错误处理演示\n");
    
    // 尝试对无效文件描述符同步
    if (fdatasync(999) == -1) {
        printf("对无效文件描述符同步: %s\n", strerror(errno));
    }
    
    // 尝试对只读文件描述符同步
    int readonly_fd = open("test_fdatasync.txt", O_RDONLY);
    if (readonly_fd != -1) {
        if (fdatasync(readonly_fd) == -1) {
            printf("对只读文件同步: %s\n", strerror(errno));
        } else {
            printf("对只读文件同步成功\n");
        }
        close(readonly_fd);
    }
    
    // 清理资源
    printf("\n清理资源...\n");
    if (close(fd) == -1) {
        perror("关闭文件失败");
    } else {
        printf("成功关闭文件描述符 %d\n", fd);
    }
    
    if (unlink("test_fdatasync.txt") == -1) {
        perror("删除测试文件失败");
    } else {
        printf("成功删除测试文件\n");
    }
    
    return 0;
}