unlink系统调用及示例

26. unlink – 删除文件或硬链接

函数介绍

unlink系统调用用于删除文件或硬链接。当文件的所有硬链接都被删除且没有进程打开该文件时,文件数据才会被真正删除。

函数原型

#include <unistd.h>

int unlink(const char *pathname);

功能

删除指定路径的文件或硬链接。

参数

  • const char *pathname: 要删除的文件路径

返回值

  • 成功时返回0
  • 失败时返回-1,并设置errno

特殊限制

  • 只能删除文件,不能删除目录(使用rmdir)
  • 如果文件正被进程打开,文件数据不会立即删除

相似函数

  • rmdir(): 删除空目录
  • remove(): 删除文件或空目录
  • unlinkat(): 相对路径版本

示例代码

#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>

int main() {
    int fd;
    struct stat st;
    
    printf("=== Unlink函数示例 ===\n");
    
    // 示例1: 基本文件删除
    printf("示例1: 基本文件删除\n");
    
    // 创建测试文件
    fd = open("test_unlink.txt", O_CREAT | O_WRONLY, 0644);
    if (fd != -1) {
        write(fd, "Test content for unlink", 23);
        close(fd);
        printf("创建测试文件\n");
    }
    
    // 删除文件
    if (unlink("test_unlink.txt") == -1) {
        perror("删除文件失败");
    } else {
        printf("成功删除文件\n");
        
        // 验证文件已删除
        if (access("test_unlink.txt", F_OK) == -1) {
            printf("文件确实已删除\n");
        }
    }
    
    // 示例2: 硬链接删除演示
    printf("示例2: 硬链接删除演示\n");
    
    // 创建文件和硬链接
    fd = open("multi_link.txt", O_CREAT | O_WRONLY, 0644);
    if (fd != -1) {
        write(fd, "Content with multiple links", 27);
        close(fd);
    }
    
    if (link("multi_link.txt", "second_link.txt") == 0) {
        printf("创建硬链接\n");
        
        // 检查链接数
        if (stat("multi_link.txt", &st) == 0) {
            printf("当前链接数: %ld\n", st.st_nlink);
        }
        
        // 删除一个链接
        if (unlink("second_link.txt") == 0) {
            printf("删除一个链接\n");
            
            if (stat("multi_link.txt", &st) == 0) {
                printf("剩余链接数: %ld\n", st.st_nlink);
                printf("文件数据仍然存在\n");
            }
        }
    }
    
    // 示例3: 文件被打开时的删除
    printf("示例3: 文件被打开时的删除\n");
    
    fd = open("open_file.txt", O_CREAT | O_WRONLY, 0644);
    if (fd != -1) {
        write(fd, "Content of open file", 20);
        printf("创建并打开文件\n");
        
        // 删除已打开的文件
        if (unlink("open_file.txt") == 0) {
            printf("删除已打开的文件(文件仍可访问)\n");
            
            // 仍可以读写文件
            lseek(fd, 0, SEEK_SET);
            char buffer[50];
            int n = read(fd, buffer, sizeof(buffer));
            if (n > 0) {
                buffer[n] = '\0';
                printf("文件内容: %s\n", buffer);
            }
            
            // 关闭文件后,数据才真正删除
            close(fd);
            printf("关闭文件后,数据真正删除\n");
        }
    }
    
    // 清理剩余文件
    unlink("multi_link.txt");
    
    return 0;
}
发表在 linux文章 | 留下评论

 unshare 系统调用及示例

好的,我们来深入学习 unshare 系统调用

1. 函数介绍

在 Linux 系统中,命名空间 (Namespaces) 是内核提供的一种强大的隔离机制。它允许将一组进程及其资源(如网络接口、挂载点、进程 ID 等)与系统上的其他进程隔离开来,仿佛它们运行在独立的系统中一样。这是实现 容器 (Containers) 技术(如 Docker, LXC)的核心基础之一。

通常,当我们使用 clone() 系统调用创建新进程时,可以通过传递特定的 CLONE_NEW* 标志(如 CLONE_NEWNETCLONE_NEWNS),让新进程在全新的命名空间中启动。

但是,有时候我们希望当前正在运行的进程能够脱离它当前所处的某个命名空间,并进入一个新创建的、空的同类型命名空间。这正是 unshare 系统调用所做的事情。

简单来说,unshare 就是让一个正在运行的进程说:“我不想和别人共享我的 [网络/文件系统/用户ID空间] 了,给我一个全新的、只属于我自己的!”

想象一下,你在一个大办公室(原始命名空间)里工作,突然你想拥有一个完全私密的、只有你一个人的小房间(新命名空间)来处理一些敏感任务。unshare 就像是帮你瞬间建造并搬进这个小房间的过程。

2. 函数原型

#define _GNU_SOURCE // 启用 GNU 扩展以使用 unshare
#include <sched.h>  // 包含 unshare 函数声明和 CLONE_NEW* 常量

int unshare(int flags);

3. 功能

使调用线程(进程)脱离当前由 flags 参数指定的一个或多个命名空间,并使该线程进入新创建的、空的同类型命名空间。

4. 参数

  • flags:
    • int 类型。
    • 一个位掩码,指定了调用进程希望脱离并重新加入的命名空间类型。可以是以下一个或多个标志的按位或 (|) 组合:
      • CLONE_NEWCGROUP: 创建新的 Cgroup 命名空间。
      • CLONE_NEWIPC: 创建新的 IPC (Inter-Process Communication) 命名空间。
      • CLONE_NEWNET: 创建新的 Network 命名空间。
      • CLONE_NEWNS: 创建新的 Mount 命名空间。
      • CLONE_NEWPID: 创建新的 PID (Process ID) 命名空间。
      • CLONE_NEWUSER: 创建新的 User 命名空间。
      • CLONE_NEWUTS: 创建新的 UTS (UNIX Timesharing System) 命名空间。

重要提示

  • unshare 只影响调用线程本身。如果进程是多线程的,其他线程仍然留在原来的命名空间中。
  • 新创建的命名空间是空的。例如,新的 Network 命名空间只有 lo (回环) 接口;新的 PID 命名空间中调用进程将成为 PID 1。
  • 权限和限制:创建某些命名空间(尤其是 CLONE_NEWUSER)可能需要特殊权限或遵循复杂的规则。

5. 返回值

  • 成功: 返回 0。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

  • EFAULTflags 中指定的地址无效(不太常见)。
  • EINVALflags 包含无效或不支持的标志,或者尝试同时 unshare CLONE_NEWPID 和其他需要特权的命名空间。
  • ENOMEM: 内核内存不足。
  • EPERM: 调用者没有权限创建请求的命名空间。例如:
    • 创建 CLONE_NEWUSER 命名空间通常需要进程没有被其他进程跟踪(ptrace)。
    • 创建 CLONE_NEWPID 通常需要进程是多线程的,或者有其他限制。
  • EUSERS: (对于 CLONE_NEWUSER) 达到了每个用户命名空间的最大所有者数量限制。
  • ENOSPC: (对于 CLONE_NEWPID) 达到了系统范围内的最大嵌套 PID 命名空间层级限制。

7. 相似函数或关联函数

  • clone: 创建新进程时,可以通过 CLONE_NEW* 标志使其在新的命名空间中启动。
  • setns: 将调用进程加入一个已存在的命名空间。
  • /proc/[pid]/ns/: 这个目录包含了进程所处的各种命名空间的符号链接文件。通过打开这些文件可以获得命名空间的文件描述符。
  • unshare 命令: 一个命令行工具,可以在取消共享指定的命名空间后执行命令。它在底层使用了 unshare() 系统调用。
  • namespace 相关系统调用clonesetnsunshare 共同构成了 Linux 命名空间操作的基础。

8. 示例代码

下面的示例演示了如何使用 unshare 来隔离网络和挂载命名空间。

警告:此示例需要 root 权限来执行某些操作(如挂载文件系统)。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>    // 包含 unshare, CLONE_NEW* 常量
#include <sys/mount.h> // 包含 mount
#include <sys/stat.h>  // 包含 mkdir
#include <sys/wait.h>  // 包含 waitpid
#include <errno.h>
#include <string.h>
#include <fcntl.h>    // 包含 open, O_* flags

void print_current_namespaces() {
    char buffer[256];
    pid_t pid = getpid();

    printf("Current namespaces for PID %d:\n", pid);
    // 读取并打印网络命名空间 inode
    ssize_t len = readlink("/proc/self/ns/net", buffer, sizeof(buffer) - 1);
    if (len != -1) {
        buffer[len] = '\0';
        printf("  Network: %s\n", buffer);
    } else {
        perror("  readlink /proc/self/ns/net");
    }

    // 读取并打印挂载命名空间 inode
    len = readlink("/proc/self/ns/mnt", buffer, sizeof(buffer) - 1);
    if (len != -1) {
        buffer[len] = '\0';
        printf("  Mount:   %s\n", buffer);
    } else {
        perror("  readlink /proc/self/ns/mnt");
    }
    printf("\n");
}

int main() {
    printf("--- Demonstrating unshare ---\n");
    printf("Main process PID: %d\n", getpid());

    // 1. 显示初始命名空间
    printf("1. Initial namespaces:\n");
    print_current_namespaces();

    // 2. 使用 unshare 脱离当前的 Network 和 Mount 命名空间
    printf("2. Calling unshare(CLONE_NEWNET | CLONE_NEWNS)...\n");
    if (unshare(CLONE_NEWNET | CLONE_NEWNS) == -1) {
        perror("unshare");
        fprintf(stderr, "Do you have root privileges?\n");
        exit(EXIT_FAILURE);
    }
    printf("unshare() succeeded.\n");

    // 3. 再次显示命名空间,应该已经改变
    printf("3. Namespaces after unshare:\n");
    print_current_namespaces();

    // 4. 在新的 Network 命名空间中,网络接口视图是隔离的
    printf("4. --- Network Isolation ---\n");
    printf("Running 'ip link show' in new network namespace:\n");
    // 使用 system 调用执行命令来查看网络接口
    int ret = system("ip link show");
    if (ret == -1) {
        perror("system ip link");
    }
    printf("Note: You should only see the 'lo' (loopback) interface.\n");
    printf("\n");

    // 5. 在新的 Mount 命名空间中,挂载操作是隔离的
    printf("5. --- Mount Isolation ---\n");
    const char *mount_point = "/tmp/unshare_test_mount";
    if (mkdir(mount_point, 0755) == -1 && errno != EEXIST) {
        perror("mkdir mount point");
    } else {
        printf("Created directory %s\n", mount_point);
    }

    if (mount("tmpfs", mount_point, "tmpfs", 0, NULL) == -1) {
        perror("mount tmpfs");
    } else {
        printf("Mounted tmpfs on %s\n", mount_point);
        printf("This mount is only visible inside this process's mount namespace.\n");
    }

    // 6. 演示挂载隔离:在新的命名空间中创建一个文件
    char test_file_path[256];
    snprintf(test_file_path, sizeof(test_file_path), "%s/test_file.txt", mount_point);
    FILE *f = fopen(test_file_path, "w");
    if (f) {
        fprintf(f, "Hello from process in its own mount namespace!\n");
        fclose(f);
        printf("Created file %s\n", test_file_path);
    } else {
        perror("fopen test_file.txt");
    }

    printf("\n--- Summary ---\n");
    printf("1. The main process called unshare() to get new, isolated Network and Mount namespaces.\n");
    printf("2. Network namespace: 'ip link show' only displays the loopback interface.\n");
    printf("3. Mount namespace: A tmpfs mounted on %s is private to this process.\n", mount_point);
    printf("4. If you run 'mount' or 'ip link show' in another terminal (outside this process),\n");
    printf("   you will see the global network interfaces and mounts, not these isolated ones.\n");

    // 7. 清理 (可选,因为退出时会自动清理命名空间)
    // umount(mount_point);
    // rmdir(mount_point);

    printf("\nProgram finished. The isolated namespaces cease to exist when this process exits.\n");
    return 0;
}

9. 使用 unshare 命令行工具的对比示例

unshare 命令行工具是用户更常接触到的使用 unshare 系统调用的方式。

# 1. 在当前 shell 中取消共享网络命名空间,并运行一个命令
# 这会启动一个新的 shell,它在网络和挂载上都是隔离的
unshare -n -m /bin/bash

# (你现在在一个新的 shell 中,提示符可能略有不同)
# $ ip link show
# 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
#     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
# (只有 lo 接口)

# $ mount -t tmpfs tmpfs /tmp/isolated_tmp
# $ mount | grep isolated_tmp
# tmpfs on /tmp/isolated_tmp type tmpfs (rw,relatime)
# (这个挂载只在这个 unshare 的会话中可见)

# $ exit
# (退出隔离的 shell)

# 2. 回到原 shell,检查隔离效果
# $ ip link show
# (你会看到所有正常的网络接口,如 eth0, wlan0 等)
# $ mount | grep isolated_tmp
# (应该找不到这个挂载点)

10. 编译和运行

# 假设代码保存在 unshare_example.c 中
gcc -o unshare_example unshare_example.c

# 运行程序 (需要 root 权限)
sudo ./unshare_example

11. 预期输出

--- Demonstrating unshare ---
Main process PID: 12345
1. Initial namespaces:
Current namespaces for PID 12345:
  Network: net:[4026531992]
  Mount:   mnt:[4026531991]

2. Calling unshare(CLONE_NEWNET | CLONE_NEWNS)...
unshare() succeeded.
3. Namespaces after unshare:
Current namespaces for PID 12345:
  Network: net:[4026532222]  <-- Changed
  Mount:   mnt:[4026532223]  <-- Changed

4. --- Network Isolation ---
Running 'ip link show' in new network namespace:
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
Note: You should only see the 'lo' (loopback) interface.

5. --- Mount Isolation ---
Created directory /tmp/unshare_test_mount
Mounted tmpfs on /tmp/unshare_test_mount
This mount is only visible inside this process's mount namespace.
Created file /tmp/unshare_test_mount/test_file.txt

--- Summary ---
1. The main process called unshare() to get new, isolated Network and Mount namespaces.
2. Network namespace: 'ip link show' only displays the loopback interface.
3. Mount namespace: A tmpfs mounted on /tmp/unshare_test_mount is private to this process.
4. If you run 'mount' or 'ip link show' in another terminal (outside this process),
   you will see the global network interfaces and mounts, not these isolated ones.

Program finished. The isolated namespaces cease to exist when this process exits.

12. 总结

unshare 是 Linux 命名空间功能的关键系统调用之一。

  • 核心作用:让当前运行的进程脱离现有的命名空间,并加入新创建的、空的同类型命名空间。
  • 与 clone 和 setns 的区别
    • clone: 创建新进程时分配新命名空间。
    • setns: 加入一个已存在的命名空间。
    • unshare: 为当前进程创建并加入的命名空间。
  • 应用场景
    • 容器技术:容器运行时使用 unshare 来为容器进程提供隔离环境。
    • 系统管理脚本:在执行可能影响全局环境的操作前,先 unshare 进入隔离环境,避免影响宿主机。
    • 安全沙箱:为不受信任的程序创建隔离的运行环境。
  • 权限:通常需要 root 权限,特别是涉及挂载、网络等操作时。

理解 unshare 有助于深入理解 Linux 容器和进程隔离的原理。对于 Linux 编程新手来说,它是掌握现代 Linux 系统编程和容器化技术的重要一环。

发表在 linux文章 | 留下评论

uselib 系统调用及示例

uselib 函数详解

1. 函数介绍

uselib 是Linux系统调用,用于将指定的共享库加载到调用进程的地址空间中。它允许程序动态加载和使用共享库,而无需在编译时链接这些库。这个函数主要用于实现动态库加载和插件系统。

2. 函数原型

#include <unistd.h>
int uselib(const char *library);

3. 功能

uselib 将指定路径的共享库文件加载到当前进程的地址空间中,使得库中的符号可以在运行时被解析和使用。它主要用于动态加载共享库,支持构建灵活的插件架构和模块化应用程序。

4. 参数

  • *const char library: 指向共享库文件路径的字符串指针

5. 返回值

  • 成功: 返回0
  • 失败: 返回-1,并设置errno

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

  • dlopen/dlsym/dlclose: 更现代的动态库加载接口
  • mmap: 内存映射文件
  • ld.so: 动态链接器
  • RTLD_*: 动态加载标志

7. 示例代码

示例1:基础uselib使用

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <dlfcn.h>
#include <sys/stat.h>
#include <fcntl.h>

/**
 * 演示基础uselib使用方法
 */
int demo_uselib_basic() {
    const char *library_path = "/lib/x86_64-linux-gnu/libc.so.6";  // 系统C库
    int result;
    
    printf("=== 基础uselib使用示例 ===\n");
    
    // 显示当前系统信息
    printf("系统信息:\n");
    system("uname -a");
    printf("\n");
    
    // 检查库文件是否存在
    printf("1. 检查库文件:\n");
    printf("   库文件路径: %s\n", library_path);
    
    struct stat st;
    if (stat(library_path, &st) == 0) {
        printf("   ✓ 文件存在\n");
        printf("   文件大小: %ld 字节\n", st.st_size);
        printf("   文件权限: %o\n", st.st_mode & 0777);
    } else {
        printf("   ✗ 文件不存在: %s\n", strerror(errno));
        printf("   注意:不同系统的库路径可能不同\n");
        return 0;  // 不返回错误,因为这是演示
    }
    
    // 尝试使用uselib加载库
    printf("\n2. 使用uselib加载库:\n");
    printf("   调用: uselib(\"%s\")\n", library_path);
    
    result = uselib(library_path);
    if (result == 0) {
        printf("   ✓ 库加载成功\n");
        
        // 验证库是否真的被加载
        printf("   验证库加载状态:\n");
        
        // 尝试使用dlopen检查库是否可用
        void *handle = dlopen(library_path, RTLD_LAZY | RTLD_NOLOAD);
        if (handle) {
            printf("   ✓ 通过dlopen验证库已加载\n");
            dlclose(handle);
        } else {
            printf("   ℹ uselib可能只是标记库为已加载\n");
        }
        
    } else {
        printf("   ✗ 库加载失败: %s\n", strerror(errno));
        if (errno == ENOENT) {
            printf("   原因:文件不存在\n");
        } else if (errno == EACCES) {
            printf("   原因:权限不足\n");
        } else if (errno == ENOEXEC) {
            printf("   原因:文件不是有效的可执行文件\n");
        } else if (errno == EPERM) {
            printf("   原因:操作被禁止(可能已过时)\n");
        }
    }
    
    // 演示加载不存在的库
    printf("\n3. 演示加载不存在的库:\n");
    const char *nonexistent_lib = "/nonexistent/library.so";
    printf("   尝试加载: %s\n", nonexistent_lib);
    
    result = uselib(nonexistent_lib);
    if (result == -1) {
        printf("   ✓ 预期失败: %s\n", strerror(errno));
        if (errno == ENOENT) {
            printf("   正确:文件不存在\n");
        }
    } else {
        printf("   ✗ 意外成功\n");
    }
    
    // 显示uselib的历史和现状
    printf("\n4. uselib状态说明:\n");
    printf("   历史作用:早期Linux用于动态加载共享库\n");
    printf("   当前状态:在现代Linux中已被弃用\n");
    printf("   替代方案:使用dlopen/dlsym等现代接口\n");
    printf("   兼容性:某些系统可能仍支持,但不推荐使用\n");
    
    return 0;
}

int main() {
    return demo_uselib_basic();
}

示例2:现代动态库加载对比

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <dlfcn.h>
#include <sys/stat.h>
#include <unistd.h>

/**
 * 现代动态库加载演示
 */
int demo_modern_dl_loading() {
    const char *system_lib = "/lib/x86_64-linux-gnu/libc.so.6";
    const char *math_lib = "/lib/x86_64-linux-gnu/libm.so.6";
    
    printf("=== 现代动态库加载对比演示 ===\n");
    
    // 1. 使用dlopen加载系统库
    printf("1. 使用dlopen加载系统库:\n");
    printf("   加载目标: %s\n", system_lib);
    
    void *libc_handle = dlopen(system_lib, RTLD_LAZY);
    if (libc_handle) {
        printf("   ✓ dlopen加载成功\n");
        
        // 获取函数符号
        void *(*malloc_func)(size_t) = dlsym(libc_handle, "malloc");
        if (malloc_func) {
            printf("   ✓ 成功获取malloc函数地址: %p\n", malloc_func);
            
            // 测试函数调用
            void *ptr = malloc_func(1024);
            if (ptr) {
                printf("   ✓ malloc函数调用成功\n");
                void (*free_func)(void*) = dlsym(libc_handle, "free");
                if (free_func) {
                    free_func(ptr);
                    printf("   ✓ free函数调用成功\n");
                }
            }
        } else {
            printf("   ✗ 获取malloc函数失败: %s\n", dlerror());
        }
        
        // 不立即关闭,继续使用
    } else {
        printf("   ✗ dlopen加载失败: %s\n", dlerror());
    }
    
    // 2. 加载数学库
    printf("\n2. 加载数学库:\n");
    printf("   加载目标: %s\n", math_lib);
    
    void *libm_handle = dlopen(math_lib, RTLD_LAZY);
    if (libm_handle) {
        printf("   ✓ 数学库加载成功\n");
        
        // 获取sin函数
        double (*sin_func)(double) = dlsym(libm_handle, "sin");
        if (sin_func) {
            printf("   ✓ 成功获取sin函数地址: %p\n", sin_func);
            
            // 测试函数调用
            double result = sin_func(3.14159 / 2);  // sin(π/2) ≈ 1
            printf("   ✓ sin(π/2) = %.6f\n", result);
        } else {
            printf("   ✗ 获取sin函数失败: %s\n", dlerror());
        }
    } else {
        printf("   ✗ 数学库加载失败: %s\n", dlerror());
    }
    
    // 3. 错误处理演示
    printf("\n3. 错误处理演示:\n");
    printf("   尝试加载不存在的库:\n");
    
    void *bad_handle = dlopen("/nonexistent/badlib.so", RTLD_LAZY);
    if (bad_handle) {
        printf("   ✗ 意外成功加载不存在的库\n");
        dlclose(bad_handle);
    } else {
        printf("   ✓ 正确处理不存在的库: %s\n", dlerror());
    }
    
    // 4. 符号查找演示
    printf("\n4. 符号查找演示:\n");
    
    // 在libc中查找各种函数
    const char *functions[] = {"printf", "strlen", "memcpy", "memset", NULL};
    
    for (int i = 0; functions[i]; i++) {
        void *func_addr = dlsym(libc_handle, functions[i]);
        if (func_addr) {
            printf("   %s: %p\n", functions[i], func_addr);
        } else {
            printf("   %s: 未找到 (%s)\n", functions[i], dlerror());
        }
    }
    
    // 5. 库信息显示
    printf("\n5. 加载的库信息:\n");
    
    if (libc_handle) {
        // 获取库信息(注意:这不是标准的dlinfo)
        printf("   C库句柄: %p\n", libc_handle);
        
        // 显示库引用计数(伪代码,实际需要更复杂的实现)
        printf("   C库已加载\n");
    }
    
    if (libm_handle) {
        printf("   数学库句柄: %p\n", libm_handle);
        printf("   数学库已加载\n");
    }
    
    // 6. 清理资源
    printf("\n6. 清理资源:\n");
    
    if (libc_handle) {
        if (dlclose(libc_handle) == 0) {
            printf("   ✓ C库句柄关闭成功\n");
        } else {
            printf("   ✗ C库句柄关闭失败: %s\n", dlerror());
        }
    }
    
    if (libm_handle) {
        if (dlclose(libm_handle) == 0) {
            printf("   ✓ 数学库句柄关闭成功\n");
        } else {
            printf("   ✗ 数学库句柄关闭失败: %s\n", dlerror());
        }
    }
    
    // 7. 显示现代动态加载的优势
    printf("\n=== 现代动态加载优势 ===\n");
    printf("1. 功能完整:\n");
    printf("   ✓ 支持符号查找和调用\n");
    printf("   ✓ 支持引用计数管理\n");
    printf("   ✓ 支持错误处理和诊断\n");
    printf("   ✓ 支持多种加载模式\n");
    
    printf("\n2. 安全特性:\n");
    printf("   ✓ 支持版本检查\n");
    printf("   ✓ 支持依赖关系管理\n");
    printf("   ✓ 支持安全的库卸载\n");
    printf("   ✓ 支持插件隔离\n");
    
    printf("\n3. 灵活性:\n");
    printf("   ✓ 支持运行时库选择\n");
    printf("   ✓ 支持条件加载\n");
    printf("   ✓ 支持热插拔\n");
    printf("   ✓ 支持延迟加载\n");
    
    return 0;
}

int main() {
    return demo_modern_dl_loading();
}

示例3:插件系统架构演示

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <dlfcn.h>
#include <dirent.h>
#include <sys/stat.h>

/**
 * 插件接口定义
 */
typedef struct {
    const char *name;
    const char *version;
    const char *description;
    int (*initialize)(void);
    int (*execute)(const char *params);
    int (*finalize)(void);
} plugin_interface_t;

/**
 * 插件管理器
 */
typedef struct {
    plugin_interface_t *plugins[32];
    int plugin_count;
    char plugin_directory[256];
} plugin_manager_t;

/**
 * 初始化插件管理器
 */
int init_plugin_manager(plugin_manager_t *manager, const char *plugin_dir) {
    strncpy(manager->plugin_directory, plugin_dir, sizeof(manager->plugin_directory) - 1);
    manager->plugin_directory[sizeof(manager->plugin_directory) - 1] = '\0';
    manager->plugin_count = 0;
    
    printf("插件管理器初始化:\n");
    printf("  插件目录: %s\n", manager->plugin_directory);
    
    // 检查目录是否存在
    struct stat st;
    if (stat(manager->plugin_directory, &st) == 0) {
        if (S_ISDIR(st.st_mode)) {
            printf("  ✓ 插件目录存在\n");
        } else {
            printf("  ✗ 路径不是目录\n");
            return -1;
        }
    } else {
        printf("  ℹ 插件目录不存在,将创建空目录\n");
        // 在实际应用中可能会创建目录
    }
    
    return 0;
}

/**
 * 扫描并加载插件
 */
int scan_and_load_plugins(plugin_manager_t *manager) {
    printf("扫描插件目录: %s\n", manager->plugin_directory);
    
    DIR *dir = opendir(manager->plugin_directory);
    if (!dir) {
        printf("  无法打开插件目录: %s\n", strerror(errno));
        return -1;
    }
    
    struct dirent *entry;
    int loaded_count = 0;
    
    printf("  发现的插件文件:\n");
    
    while ((entry = readdir(dir)) != NULL) {
        // 检查是否为.so文件
        if (strstr(entry->d_name, ".so")) {
            printf("    %s\n", entry->d_name);
            loaded_count++;
            
            // 构造完整路径
            char full_path[512];
            snprintf(full_path, sizeof(full_path), "%s/%s", 
                     manager->plugin_directory, entry->d_name);
            
            // 演示加载过程(实际应用中会真正加载)
            printf("    模拟加载插件: %s\n", entry->d_name);
            
            // 在实际应用中,这里会使用dlopen加载库
            // 并获取插件接口函数
        }
    }
    
    closedir(dir);
    
    printf("  共发现 %d 个插件文件\n", loaded_count);
    manager->plugin_count = loaded_count;
    
    return 0;
}

/**
 * 插件调用演示
 */
void demonstrate_plugin_calls() {
    printf("=== 插件调用演示 ===\n");
    
    // 模拟插件接口
    printf("1. 插件接口定义:\n");
    printf("   typedef struct {\n");
    printf("       const char *name;\n");
    printf("       const char *version;\n");
    printf("       const char *description;\n");
    printf("       int (*initialize)(void);\n");
    printf("       int (*execute)(const char *params);\n");
    printf("       int (*finalize)(void);\n");
    printf("   } plugin_interface_t;\n");
    
    // 模拟插件实例
    printf("\n2. 插件实例演示:\n");
    
    struct {
        const char *name;
        const char *version;
        const char *description;
    } mock_plugins[] = {
        {"数据库插件", "1.0.0", "提供数据库访问功能"},
        {"网络插件", "2.1.3", "提供网络通信功能"},
        {"图形插件", "1.5.2", "提供图形渲染功能"},
        {"音频插件", "3.0.1", "提供音频处理功能"},
        {NULL, NULL, NULL}
    };
    
    for (int i = 0; mock_plugins[i].name; i++) {
        printf("  插件 %d:\n", i + 1);
        printf("    名称: %s\n", mock_plugins[i].name);
        printf("    版本: %s\n", mock_plugins[i].version);
        printf("    描述: %s\n", mock_plugins[i].description);
        printf("    状态: 模拟加载成功\n");
    }
    
    // 模拟插件调用
    printf("\n3. 插件调用演示:\n");
    
    for (int i = 0; mock_plugins[i].name; i++) {
        printf("  调用插件 %d (%s):\n", i + 1, mock_plugins[i].name);
        printf("    initialize() -> 成功\n");
        printf("    execute(\"参数%d\") -> 执行成功\n", i + 1);
        printf("    finalize() -> 成功\n");
    }
}

/**
 * 演示插件系统架构
 */
int demo_plugin_system_architecture() {
    plugin_manager_t manager;
    
    printf("=== 插件系统架构演示 ===\n");
    
    // 初始化插件管理器
    printf("1. 初始化插件管理器:\n");
    if (init_plugin_manager(&manager, "/usr/lib/plugins") != 0) {
        printf("插件管理器初始化失败\n");
        return -1;
    }
    
    // 扫描插件
    printf("\n2. 扫描插件:\n");
    scan_and_load_plugins(&manager);
    
    // 演示插件调用
    printf("\n3. 插件调用演示:\n");
    demonstrate_plugin_calls();
    
    // 显示插件系统架构
    printf("\n=== 插件系统架构说明 ===\n");
    printf("1. 核心组件:\n");
    printf("   ✓ 插件管理器: 负责插件的加载、卸载和管理\n");
    printf("   ✓ 插件接口: 定义插件的标准接口\n");
    printf("   ✓ 插件加载器: 使用dlopen动态加载插件\n");
    printf("   ✓ 符号解析器: 使用dlsym获取插件函数\n");
    
    printf("\n2. 加载流程:\n");
    printf("   1. 扫描插件目录\n");
    printf("   2. 验证插件文件\n");
    printf("   3. 动态加载库文件\n");
    printf("   4. 获取插件接口\n");
    printf("   5. 调用初始化函数\n");
    printf("   6. 注册插件实例\n");
    
    printf("\n3. 调用流程:\n");
    printf("   1. 查找目标插件\n");
    printf("   2. 验证插件状态\n");
    printf("   3. 调用插件函数\n");
    printf("   4. 处理返回结果\n");
    printf("   5. 错误处理\n");
    
    printf("\n4. 安全考虑:\n");
    printf("   ✓ 插件签名验证\n");
    printf("   ✓ 沙箱环境隔离\n");
    printf("   ✓ 权限控制\n");
    printf("   ✓ 资源限制\n");
    printf("   ✓ 异常处理\n");
    
    return 0;
}

int main() {
    return demo_plugin_system_architecture();
}

示例4:动态库版本管理

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <dlfcn.h>
#include <sys/utsname.h>

/**
 * 库版本信息结构
 */
typedef struct {
    char name[64];
    char version[32];
    char filepath[256];
    void *handle;
    int is_loaded;
    unsigned long load_time;
} library_version_t;

/**
 * 版本管理器
 */
typedef struct {
    library_version_t libraries[16];
    int library_count;
    int max_libraries;
} version_manager_t;

/**
 * 初始化版本管理器
 */
int init_version_manager(version_manager_t *manager) {
    memset(manager, 0, sizeof(version_manager_t));
    manager->max_libraries = 16;
    manager->library_count = 0;
    
    printf("版本管理器初始化完成\n");
    printf("最大库数量: %d\n", manager->max_libraries);
    
    return 0;
}

/**
 * 添加库版本信息
 */
int add_library_version(version_manager_t *manager, 
                       const char *name, const char *version, const char *filepath) {
    if (manager->library_count >= manager->max_libraries) {
        printf("库数量已达上限\n");
        return -1;
    }
    
    library_version_t *lib = &manager->libraries[manager->library_count];
    
    strncpy(lib->name, name, sizeof(lib->name) - 1);
    lib->name[sizeof(lib->name) - 1] = '\0';
    
    strncpy(lib->version, version, sizeof(lib->version) - 1);
    lib->version[sizeof(lib->version) - 1] = '\0';
    
    strncpy(lib->filepath, filepath, sizeof(lib->filepath) - 1);
    lib->filepath[sizeof(lib->filepath) - 1] = '\0';
    
    lib->handle = NULL;
    lib->is_loaded = 0;
    lib->load_time = 0;
    
    manager->library_count++;
    
    printf("添加库版本信息:\n");
    printf("  名称: %s\n", lib->name);
    printf("  版本: %s\n", lib->version);
    printf("  路径: %s\n", lib->filepath);
    
    return 0;
}

/**
 * 显示系统库信息
 */
void show_system_library_info() {
    printf("=== 系统库信息 ===\n");
    
    // 显示系统信息
    struct utsname sys_info;
    if (uname(&sys_info) == 0) {
        printf("系统名称: %s\n", sys_info.sysname);
        printf("节点名称: %s\n", sys_info.nodename);
        printf("发行版本: %s\n", sys_info.release);
        printf("系统版本: %s\n", sys_info.version);
        printf("硬件架构: %s\n", sys_info.machine);
    }
    
    // 显示常见系统库
    printf("\n常见系统库路径:\n");
    
    const char *common_lib_paths[] = {
        "/lib/x86_64-linux-gnu/libc.so.6",
        "/lib/x86_64-linux-gnu/libm.so.6",
        "/lib/x86_64-linux-gnu/libdl.so.2",
        "/lib/x86_64-linux-gnu/librt.so.1",
        "/lib/x86_64-linux-gnu/libpthread.so.0",
        NULL
    };
    
    for (int i = 0; common_lib_paths[i]; i++) {
        struct stat st;
        if (stat(common_lib_paths[i], &st) == 0) {
            printf("  %s (存在)\n", common_lib_paths[i]);
        } else {
            printf("  %s (不存在)\n", common_lib_paths[i]);
        }
    }
}

/**
 * 演示版本管理
 */
int demo_version_management() {
    version_manager_t manager;
    
    printf("=== 动态库版本管理演示 ===\n");
    
    // 初始化版本管理器
    printf("1. 初始化版本管理器:\n");
    if (init_version_manager(&manager) != 0) {
        return -1;
    }
    
    // 显示系统信息
    printf("\n2. 系统库信息:\n");
    show_system_library_info();
    
    // 添加库版本信息
    printf("\n3. 添加库版本信息:\n");
    
    add_library_version(&manager, "libc", "2.31", "/lib/x86_64-linux-gnu/libc.so.6");
    add_library_version(&manager, "libm", "2.31", "/lib/x86_64-linux-gnu/libm.so.6");
    add_library_version(&manager, "libdl", "2.31", "/lib/x86_64-linux-gnu/libdl.so.2");
    add_library_version(&manager, "librt", "2.31", "/lib/x86_64-linux-gnu/librt.so.1");
    add_library_version(&manager, "libpthread", "2.31", "/lib/x86_64-linux-gnu/libpthread.so.0");
    
    // 显示所有库信息
    printf("\n4. 所有库版本信息:\n");
    printf("%-15s %-10s %-40s %-10s\n", "名称", "版本", "路径", "状态");
    printf("%-15s %-10s %-40s %-10s\n", "----", "----", "----", "----");
    
    for (int i = 0; i < manager.library_count; i++) {
        library_version_t *lib = &manager.libraries[i];
        printf("%-15s %-10s %-40s %-10s\n", 
               lib->name, lib->version, lib->filepath,
               lib->is_loaded ? "已加载" : "未加载");
    }
    
    // 演示版本比较
    printf("\n5. 版本比较演示:\n");
    
    struct {
        const char *version1;
        const char *version2;
        const char *description;
    } version_pairs[] = {
        {"2.31", "2.30", "向前兼容"},
        {"2.29", "2.31", "向后兼容检查"},
        {"3.0.0", "2.9.9", "大版本升级"},
        {"2.31.1", "2.31.0", "补丁版本"},
        {NULL, NULL, NULL}
    };
    
    for (int i = 0; version_pairs[i].version1; i++) {
        printf("  %s vs %s: %s\n", 
               version_pairs[i].version1, version_pairs[i].version2,
               version_pairs[i].description);
    }
    
    // 显示版本管理策略
    printf("\n=== 版本管理策略 ===\n");
    printf("1. 版本兼容性:\n");
    printf("   ✓ 主版本号变化:可能不兼容\n");
    printf("   ✓ 次版本号变化:向后兼容\n");
    printf("   ✓ 修订版本号变化:完全兼容\n");
    
    printf("\n2. 加载策略:\n");
    printf("   ✓ 优先加载最新兼容版本\n");
    printf("   ✓ 支持版本回退\n");
    printf("   ✓ 支持并行版本加载\n");
    printf("   ✓ 支持版本冲突解决\n");
    
    printf("\n3. 安全策略:\n");
    printf("   ✓ 版本签名验证\n");
    printf("   ✓ 依赖关系检查\n");
    printf("   ✓ 兼容性测试\n");
    printf("   ✓ 回滚机制\n");
    
    return 0;
}

int main() {
    return demo_version_management();
}

示例5:现代动态加载最佳实践

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <dlfcn.h>
#include <sys/stat.h>
#include <time.h>

/**
 * 安全的动态加载器
 */
typedef struct {
    void *handle;
    char library_path[256];
    time_t load_time;
    int reference_count;
    int is_valid;
} secure_loader_t;

/**
 * 初始化安全加载器
 */
int init_secure_loader(secure_loader_t *loader, const char *library_path) {
    // 验证库文件
    struct stat st;
    if (stat(library_path, &st) != 0) {
        printf("库文件不存在: %s\n", library_path);
        return -1;
    }
    
    if (!S_ISREG(st.st_mode)) {
        printf("路径不是常规文件: %s\n", library_path);
        return -1;
    }
    
    // 检查文件权限
    if (!(st.st_mode & S_IRUSR)) {
        printf("没有读取权限: %s\n", library_path);
        return -1;
    }
    
    // 初始化加载器
    loader->handle = NULL;
    strncpy(loader->library_path, library_path, sizeof(loader->library_path) - 1);
    loader->library_path[sizeof(loader->library_path) - 1] = '\0';
    loader->load_time = 0;
    loader->reference_count = 0;
    loader->is_valid = 1;
    
    printf("安全加载器初始化:\n");
    printf("  库路径: %s\n", loader->library_path);
    printf("  文件大小: %ld 字节\n", st.st_size);
    printf("  修改时间: %s", ctime(&st.st_mtime));
    
    return 0;
}

/**
 * 安全加载库
 */
int secure_load_library(secure_loader_t *loader) {
    if (!loader->is_valid) {
        printf("加载器无效\n");
        return -1;
    }
    
    printf("安全加载库: %s\n", loader->library_path);
    
    // 使用RTLD_LAZY延迟加载
    loader->handle = dlopen(loader->library_path, RTLD_LAZY);
    if (!loader->handle) {
        printf("加载失败: %s\n", dlerror());
        return -1;
    }
    
    loader->load_time = time(NULL);
    loader->reference_count = 1;
    
    printf("  ✓ 加载成功\n");
    printf("  加载时间: %s", ctime(&loader->load_time));
    
    return 0;
}

/**
 * 获取符号地址
 */
void* secure_get_symbol(secure_loader_t *loader, const char *symbol_name) {
    if (!loader->handle) {
        printf("库未加载\n");
        return NULL;
    }
    
    void *symbol = dlsym(loader->handle, symbol_name);
    if (!symbol) {
        printf("符号未找到: %s (%s)\n", symbol_name, dlerror());
        return NULL;
    }
    
    printf("获取符号成功: %s -> %p\n", symbol_name, symbol);
    return symbol;
}

/**
 * 安全卸载库
 */
int secure_unload_library(secure_loader_t *loader) {
    if (!loader->handle) {
        printf("库未加载\n");
        return 0;
    }
    
    loader->reference_count--;
    if (loader->reference_count > 0) {
        printf("引用计数: %d,延迟卸载\n", loader->reference_count);
        return 0;
    }
    
    printf("卸载库: %s\n", loader->library_path);
    
    if (dlclose(loader->handle) != 0) {
        printf("卸载失败: %s\n", dlerror());
        return -1;
    }
    
    loader->handle = NULL;
    loader->load_time = 0;
    
    printf("  ✓ 卸载成功\n");
    return 0;
}

/**
 * 演示现代动态加载最佳实践
 */
int demo_modern_best_practices() {
    secure_loader_t loader;
    const char *system_lib = "/lib/x86_64-linux-gnu/libc.so.6";
    
    printf("=== 现代动态加载最佳实践演示 ===\n");
    
    // 1. 安全初始化
    printf("1. 安全初始化:\n");
    if (init_secure_loader(&loader, system_lib) != 0) {
        printf("安全加载器初始化失败\n");
        return -1;
    }
    
    // 2. 安全加载
    printf("\n2. 安全加载:\n");
    if (secure_load_library(&loader) != 0) {
        printf("安全加载失败\n");
        return -1;
    }
    
    // 3. 符号获取和使用
    printf("\n3. 符号获取和使用:\n");
    
    // 获取并使用malloc
    void* (*malloc_func)(size_t) = 
        (void* (*)(size_t))secure_get_symbol(&loader, "malloc");
    if (malloc_func) {
        printf("  测试malloc函数:\n");
        void *ptr = malloc_func(1024);
        if (ptr) {
            printf("    分配1024字节成功: %p\n", ptr);
            
            // 获取并使用free
            void (*free_func)(void*) = 
                (void (*)(void*))secure_get_symbol(&loader, "free");
            if (free_func) {
                free_func(ptr);
                printf("    释放内存成功\n");
            }
        }
    }
    
    // 获取并使用printf
    int (*printf_func)(const char*, ...) = 
        (int (*)(const char*, ...))secure_get_symbol(&loader, "printf");
    if (printf_func) {
        printf("  测试printf函数:\n");
        printf_func("    这是通过动态加载的printf输出\n");
    }
    
    // 4. 错误处理演示
    printf("\n4. 错误处理演示:\n");
    
    // 尝试获取不存在的符号
    void *nonexistent = secure_get_symbol(&loader, "nonexistent_function");
    if (!nonexistent) {
        printf("    ✓ 正确处理不存在的符号\n");
    }
    
    // 尝试加载不存在的库
    secure_loader_t bad_loader;
    if (init_secure_loader(&bad_loader, "/nonexistent/badlib.so") != 0) {
        printf("    ✓ 正确拒绝不存在的库文件\n");
    }
    
    // 5. 安全卸载
    printf("\n5. 安全卸载:\n");
    if (secure_unload_library(&loader) != 0) {
        printf("安全卸载失败\n");
        return -1;
    }
    
    printf("    ✓ 安全卸载完成\n");
    
    // 显示最佳实践总结
    printf("\n=== 现代动态加载最佳实践 ===\n");
    printf("1. 安全性最佳实践:\n");
    printf("   ✓ 文件完整性验证\n");
    printf("   ✓ 权限检查\n");
    printf("   ✓ 路径安全检查\n");
    printf("   ✓ 符号白名单\n");
    
    printf("\n2. 错误处理最佳实践:\n");
    printf("   ✓ 详细的错误信息\n");
    printf("   ✓ 优雅的降级处理\n");
    printf("   ✓ 资源清理\n");
    printf("   ✓ 日志记录\n");
    
    printf("\n3. 性能优化最佳实践:\n");
    printf("   ✓ 延迟加载\n");
    printf("   ✓ 引用计数\n");
    printf("   ✓ 缓存机制\n");
    printf("   ✓ 预加载策略\n");
    
    printf("\n4. 资源管理最佳实践:\n");
    printf("   ✓ 自动资源清理\n");
    printf("   ✓ 内存泄漏检测\n");
    printf("   ✓ 生命周期管理\n");
    printf("   ✓ 监控和统计\n");
    
    return 0;
}

int main() {
    return demo_modern_best_practices();
}

uselib 使用注意事项

历史背景:

  1. 早期Linux: 在早期Linux版本中用于动态加载共享库
  2. 现代状态: 在现代Linux系统中已被弃用
  3. 替代方案: 使用dlopen/dlsym等现代接口

系统要求:

  1. 内核版本: 早期支持uselib的Linux内核
  2. 权限要求: 通常需要适当的文件访问权限
  3. 架构支持: 历史上支持主流架构

安全考虑:

  1. 代码注入: 直接加载库文件可能存在安全风险
  2. 权限提升: 加载恶意库可能导致权限提升
  3. 内存安全: 直接内存操作可能影响系统稳定性

最佳实践:

  1. 使用现代接口: 优先使用dlopen/dlsym等现代动态加载接口
  2. 安全验证: 加载前验证库文件的完整性和来源
  3. 错误处理: 妥善处理加载和使用过程中的各种错误
  4. 资源管理: 及时释放加载的库资源

现代动态加载对比

uselib vs dlopen:

// uselib: 已过时的接口
uselib("/lib/libexample.so");

// dlopen: 现代标准接口
void *handle = dlopen("/lib/libexample.so", RTLD_LAZY);

功能对比:

特性uselibdlopen
符号获取不支持支持
错误处理有限完善
引用计数支持
资源管理手动自动
安全性较低较高

相关函数和工具

现代动态加载接口:

#include <dlfcn.h>

// 动态加载库
void *dlopen(const char *filename, int flag);

// 获取符号地址
void *dlsym(void *handle, const char *symbol);

// 卸载库
int dlclose(void *handle);

// 获取错误信息
char *dlerror(void);

编译选项:

# 链接动态加载库
gcc -o program program.c -ldl

# 位置无关代码
gcc -fPIC -shared -o libexample.so example.c

常见使用场景

1. 插件系统:

// 动态加载插件库
void *plugin = dlopen("plugin.so", RTLD_LAZY);
if (plugin) {
    // 获取插件接口
    plugin_interface_t *interface = dlsym(plugin, "plugin_interface");
    // 使用插件功能
}

2. 模块化应用:

// 根据配置动态加载不同模块
void *module = dlopen(config.module_path, RTLD_LAZY);
// 实现模块化架构

3. 热插拔功能:

// 运行时加载和卸载功能模块
void *feature = dlopen("feature.so", RTLD_LAZY);
// 使用完毕后卸载
dlclose(feature);

总结

uselib 是Linux历史上用于动态加载共享库的系统调用,但在现代Linux系统中已被弃用。开发者应该使用更现代、更安全的动态加载接口如 dlopen/dlsym/dlclose

现代动态加载提供了:

  1. 完整的功能: 支持符号查找、引用计数、错误处理
  2. 更好的安全性: 完善的安全检查和错误处理机制
  3. 标准兼容: 符合POSIX标准,跨平台支持
  4. 丰富的特性: 支持多种加载模式和灵活的配置选项

通过合理使用现代动态加载技术,可以构建灵活、安全、高效的模块化应用程序。

发表在 linux文章 | 留下评论

userfaultfd系统调用及示例

好的,我们继续按照您的要求学习 Linux 系统编程中的重要函数。这次我们介绍 userfaultfd


1. 函数介绍

userfaultfd (User Fault File Descriptor) 是一个 Linux 系统调用(内核版本 >= 4.3),它提供了一种机制,允许用户态程序处理发生在其他进程(或同一进程的其他线程)中的页面错误(Page Fault)。

简单来说,它让你可以:

  1. 监控一块内存区域。
  2. 当这块内存被访问(读/写)但尚未分配物理页被换出时,内核会暂停访问该内存的进程/线程。
  3. 内核通过一个特殊的文件描述符userfaultfd通知你的用户态程序。
  4. 你的程序可以读取这个通知,了解到哪个进程、哪个地址发生了缺页。
  5. 然后,你的程序可以自行决定如何处理这个缺页:
    • 从磁盘加载数据。
    • 从网络获取数据。
    • 生成所需的数据。
    • 映射另一块已存在的内存。
    • 返回错误。
  6. 最后,你的程序通过 ioctl 调用告诉内核:“我已经处理好了,让那个进程继续运行吧”。

你可以把它想象成一个智能的内存管家

  • 你告诉管家(userfaultfd):“监控客厅(某块内存)”。
  • 当孩子(另一个进程)跑进客厅玩耍(访问内存),但客厅还没收拾好(页面未分配/换出),孩子会被卡住。
  • 管家会立刻通知你:“孩子卡在客厅了!”。
  • 你收到通知后,赶紧去把客厅收拾好(准备数据)。
  • 然后你告诉管家:“客厅搞定了,让孩子进去玩吧”。
  • 孩子就能继续开心地玩耍了。

这使得实现惰性加载(Lazy Loading)、内存快照用户态垃圾回收分布式共享内存等高级功能成为可能。


2. 函数原型

#include <sys/syscall.h> // 因为 glibc 可能未包装,常需 syscall
#include <unistd.h>
#include <fcntl.h>       // O_CLOEXEC, O_NONBLOCK
#include <linux/userfaultfd.h> // 核心定义

// 注意:glibc 可能没有直接包装,需要 syscall 或使用 liburing 等库
// 系统调用号在 x86_64 上通常是 323 (SYS_userfaultfd)
int userfaultfd(int flags);

3. 功能

  • 创建 UFFD: 创建一个新的 userfaultfd 对象,并返回一个与之关联的文件描述符
  • 设置监听: 后续需要使用 ioctl (UFFDIO_APIUFFDIO_REGISTER) 来配置这个 userfaultfd,告诉它要监听哪些内存区域以及监听哪些类型的页面错误。
  • 接收通知: 其他进程访问被监听的内存区域时发生页面错误,内核会将错误信息通过这个文件描述符发送给用户态程序。
  • 处理错误: 用户态程序读取错误信息,进行处理,并通过 ioctl (UFFDIO_COPYUFFDIO_ZEROPAGEUFFDIO_WAKE) 等操作来解决页面错误,使被阻塞的进程恢复运行。

4. 参数

  • int flags: 控制 userfaultfd 行为的标志。可以是以下值的按位或组合:
    • 0: 默认行为。
    • O_CLOEXEC: 在调用 exec() 时自动关闭该文件描述符。这是个好习惯,防止意外传递给新程序。
    • O_NONBLOCK: 使 userfaultfd 文件描述符变为非阻塞模式。对 userfaultfd 本身的读写操作(读取事件)会受到影响。

5. 返回值

  • 成功时: 返回一个新的 userfaultfd 文件描述符(一个非负整数)。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EINVAL flags 无效,EMFILE 进程打开的文件描述符已达上限,ENOSYS 内核不支持 userfaultfd 等)。

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

  • mmap: 用于分配和映射内存区域,这些区域后续可以用 userfaultfd 来监控。
  • ioctl: 用于配置 userfaultfd(注册内存区域、处理页面错误)和与其交互的主要方式。
  • poll / select / epoll: 用于等待 userfaultfd 文件描述符变为可读(有事件到来)。
  • read: 用于从 userfaultfd 文件描述符读取页面错误事件。
  • fork / pthreaduserfaultfd 通常与多进程或多线程结合使用,一个线程/进程负责监控和处理错误,其他线程/进程进行内存访问。

7. 示例代码

重要提示userfaultfd 的使用非常复杂,涉及多线程/多进程、ioctl 操作、内存管理等。下面的示例是一个极度简化的概念演示,展示了核心流程。

示例 1:基本的 userfaultfd 设置和事件读取(概念性)

// userfaultfd_concept.c
#define _GNU_SOURCE
#include <sys/syscall.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <poll.h>
#include <linux/userfaultfd.h>
#include <sys/ioctl.h>

// 简化系统调用包装
static inline int sys_userfaultfd(int flags) {
    return syscall(__NR_userfaultfd, flags);
}

int main() {
    int uffd;
    struct uffdio_api api;
    struct uffdio_register reg;
    char *page;
    long page_size = sysconf(_SC_PAGESIZE);
    struct pollfd pollfd;
    struct uffd_msg msg;
    ssize_t nread;

    printf("Page size: %ld bytes\n", page_size);

    // 1. 检查内核支持
    printf("Checking for userfaultfd support...\n");
    uffd = sys_userfaultfd(O_CLOEXEC | O_NONBLOCK);
    if (uffd == -1) {
        if (errno == ENOSYS) {
            printf("userfaultfd is NOT supported on this kernel (need >= 4.3).\n");
        } else {
            perror("userfaultfd");
        }
        exit(EXIT_FAILURE);
    }
    printf("userfaultfd supported. File descriptor: %d\n", uffd);

    // 2. 启用 API (必须步骤)
    printf("\nEnabling UFFD API...\n");
    api.api = UFFD_API;
    api.features = 0; // 不请求任何额外特性
    if (ioctl(uffd, UFFDIO_API, &api) == -1) {
        perror("ioctl UFFDIO_API");
        close(uffd);
        exit(EXIT_FAILURE);
    }
    if (api.api != UFFD_API) {
        fprintf(stderr, "UFFD API version mismatch.\n");
        close(uffd);
        exit(EXIT_FAILURE);
    }
    printf("UFFD API enabled successfully.\n");

    // 3. 分配并映射内存
    printf("\nAllocating memory using mmap...\n");
    page = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (page == MAP_FAILED) {
        perror("mmap");
        close(uffd);
        exit(EXIT_FAILURE);
    }
    printf("Memory mapped at %p (size: %ld bytes)\n", (void*)page, page_size);

    // 4. 注册内存区域到 userfaultfd
    printf("\nRegistering memory region with userfaultfd...\n");
    reg.range.start = (unsigned long) page;
    reg.range.len = page_size;
    reg.mode = UFFDIO_REGISTER_MODE_MISSING; // 监听缺页错误
    if (ioctl(uffd, UFFDIO_REGISTER, &reg) == -1) {
        perror("ioctl UFFDIO_REGISTER");
        munmap(page, page_size);
        close(uffd);
        exit(EXIT_FAILURE);
    }
    printf("Memory region registered successfully.\n");

    // 5. 触发缺页错误 (在另一个线程/进程中通常发生)
    printf("\nTriggering a page fault by accessing the memory...\n");
    printf("  Accessing address %p...\n", (void*)page);
    *page = 'X'; // 这会触发缺页错误,并被 uffd 捕获
    printf("  Successfully wrote 'X' to %p.\n", (void*)page);

    // 6. 等待并读取事件 (通常在专用线程中)
    printf("\nPolling userfaultfd for events...\n");
    pollfd.fd = uffd;
    pollfd.events = POLLIN;
    int poll_res = poll(&pollfd, 1, 1000); // 1秒超时
    if (poll_res > 0 && (pollfd.revents & POLLIN)) {
        printf("Event detected on uffd.\n");
        nread = read(uffd, &msg, sizeof(msg));
        if (nread > 0) {
            if (msg.event == UFFD_EVENT_PAGEFAULT) {
                printf("  Received PAGEFAULT event:\n");
                printf("    Address: %p\n", (void*)msg.arg.pagefault.address);
                printf("    Flags: 0x%llx\n", (unsigned long long)msg.arg.pagefault.flags);
                if (msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WRITE) {
                    printf("    (Fault was caused by a WRITE access)\n");
                } else {
                    printf("    (Fault was caused by a READ access)\n");
                }

                // --- 关键: 处理缺页错误 ---
                printf("\n--- Resolving page fault ---\n");
                struct uffdio_zeropage zeropage;
                zeropage.range.start = msg.arg.pagefault.address & ~(page_size - 1); // 对齐到页
                zeropage.range.len = page_size;
                zeropage.mode = 0;
                if (ioctl(uffd, UFFDIO_ZEROPAGE, &zeropage) == -1) {
                    perror("ioctl UFFDIO_ZEROPAGE");
                } else {
                    printf("  Page zeroed and resolved successfully.\n");
                    printf("  The faulting process/thread should now resume.\n");
                }
            } else {
                printf("  Received unexpected event type: %d\n", msg.event);
            }
        } else if (nread == 0) {
            printf("  Read 0 bytes from uffd (unexpected).\n");
        } else {
            perror("read from uffd");
        }
    } else if (poll_res == 0) {
        printf("  Timeout: No event received on uffd within 1 second.\n");
        printf("  (This might be because the page was already handled by the kernel\n");
        printf("   or the access didn't trigger a monitored fault.)\n");
    } else {
        perror("poll uffd");
    }

    // 7. 清理
    printf("\nCleaning up...\n");
    if (munmap(page, page_size) == -1) {
        perror("munmap");
    }
    if (close(uffd) == -1) {
        perror("close uffd");
    }
    printf("Cleanup completed.\n");

    return 0;
}

**代码解释 **(概念性):

  1. 定义 sys_userfaultfd 包装 syscall,因为 glibc 可能未直接提供。
  2. 调用 sys_userfaultfd(O_CLOEXEC | O_NONBLOCK) 创建 userfaultfd
  3. 关键: 调用 ioctl(uffd, UFFDIO_API, &api) 启用 userfaultfd API。这是必需的初始化步骤。
  4. 使用 mmap 分配一个匿名私有内存页。
  5. 关键: 调用 ioctl(uffd, UFFDIO_REGISTER, &reg) 将 mmap 分配的内存区域注册到 userfaultfd 进行监控。UFFDIO_REGISTER_MODE_MISSING 表示监听缺页错误。
  6. 触发缺页: 程序直接访问 page 指向的内存。对于匿名私有映射,首次访问会触发内核分配一个“零页”并映射,这通常不会被 userfaultfd 捕获,因为内核自己处理了。为了让 userfaultfd 生效,通常需要更复杂的设置(如 mmap 一个未实际分配物理页的区域,或在另一个进程中访问)。
  7. 等待事件: 使用 poll 等待 userfaultfd 文件描述符变为可读。
  8. 读取事件: 如果 poll 检测到事件,调用 read(uffd, &msg, sizeof(msg)) 读取 struct uffd_msg
  9. 检查事件类型: 检查 msg.event 是否为 UFFD_EVENT_PAGEFAULT
  10. 处理事件: 打印缺页的地址和访问类型(读/写)。
  11. 关键: 调用 ioctl(uffd, UFFDIO_ZEROPAGE, &zeropage) 来解决缺页错误。这里使用 UFFDIO_ZEROPAGE 将缺页的地址范围映射为一个全零页。
  12. 清理资源。

注意: 这个例子为了简化,直接在主线程中触发并处理缺页,这在实际中可能不会按预期工作,因为内核通常会自己处理匿名页的首次分配。userfaultfd 的威力在于跨进程或更复杂的惰性加载场景。


重要提示与注意事项:

  1. 内核版本: 需要 Linux 内核 4.3 或更高版本。
  2. glibc 支持: 标准 glibc 可能没有包装 userfaultfd,需要使用 syscall
  3. 权限: 通常需要特殊权限(如 CAP_SYS_PTRACE)才能对其他进程使用 userfaultfd。同一进程内使用通常不需要。
  4. 复杂性userfaultfd 的完整使用非常复杂,涉及多线程、事件循环、复杂的 ioctl 操作(如 UFFDIO_COPY 从用户态缓冲区复制数据到缺页地址)。
  5. 性能: 虽然功能强大,但处理页面错误涉及用户态和内核态的多次交互,可能带来开销。
  6. 应用场景: 主要用于实现高级内存管理功能,如用户态分页、惰性加载文件、内存快照、分布式内存等。

总结:

userfaultfd 是一个强大而独特的 Linux 系统调用,它将传统的、由内核全权处理的页面错误处理机制,部分地移交给了用户态程序。这为实现创新的内存管理和虚拟化技术提供了前所未有的灵活性和控制力。虽然使用起来相当复杂,但它是现代 Linux 系统编程中一个极具价值的工具。

发表在 linux文章 | 留下评论

ustat 系统调用及示例

好的,我们来深入学习 ustat 系统调用

1. 函数介绍

ustat (Unix Status) 是一个非常古老的 Unix 系统调用,它的作用是获取已挂载文件系统的信息,特别是文件系统根节点(即设备)的一些基本统计信息,比如总空闲块数、总空闲 inode 数等。

你可以把它想象成一个远古版本的、功能非常有限的 statfs。在早期的 Unix 系统中,它被用来快速查看某个设备(代表一个文件系统)的健康状况和容量。

然而,对于现代的 Linux 系统(特别是基于 glibc 的系统),ustat 已经被弃用 (deprecated) 了。

简单来说,ustat 是一个过时的、不推荐在现代 Linux 编程中使用的系统调用。

为什么被弃用

  1. 功能有限:它提供的信息远不如现代的 statfs 或 statvfs 丰富和准确。
  2. 设计陈旧:它基于设备号 (dev_t) 工作,而现代文件系统和挂载方式(如网络文件系统 NFS、伪文件系统 tmpfs 等)使得仅通过设备号获取信息变得不准确或不可能。
  3. 更好的替代品statfs (或 POSIX 标准的 statvfs) 提供了更全面、更标准化的文件系统信息查询接口。

2. 函数原型

#include <ustat.h> // 包含 ustat 函数声明和 struct ustat

// 注意:在许多现代 Linux 系统上,这个头文件可能不存在或不推荐使用
int ustat(dev_t dev, struct ustat *ubuf);

3. 功能

获取指定设备号 (dev) 对应的文件系统的基本状态信息,并将结果存储在 ubuf 指向的 struct ustat 结构体中。

4. 参数

  • dev:
    • dev_t 类型。
    • 指定要查询的文件系统的设备号。设备号通常可以通过 stat() 系统调用获取文件或目录的 st_dev 字段来得到。
  • ubuf:
    • struct ustat * 类型。
    • 一个指向 struct ustat 结构体的指针。函数调用成功后,会将查询到的信息填充到这个结构体中。

5. struct ustat 结构体

// 这是 ustat.h 中定义的结构(概念性)
struct ustat {
    daddr_t f_tfree;  /* Total free blocks */
    ino_t   f_tinode; /* Number of free inodes */
    char    f_fname[6]; /* Filsys name, optional */
    char    f_fpack[6]; /* Filsys pack name, optional */
};
  • f_tfree: 文件系统中总的空闲块数。
  • f_tinode: 文件系统中总的空闲 inode 数。
  • f_fname 和 f_fpack: 可选的文件系统名称和卷标,但通常不被现代文件系统支持。

6. 返回值

  • 成功: 返回 0。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

7. 错误码 (errno)

  • EINVALdev 参数无效。
  • ENOSYS: 系统不支持 ustat 系统调用(在现代 Linux 上很常见)。

8. 相似函数或关联函数 (现代替代品)

  • statfs / fstatfs: 现代 Linux 中用于获取文件系统统计信息的标准系统调用。功能更强大,信息更丰富。
  • statvfs / fstatvfs: POSIX 标准定义的函数,与 statfs 功能类似,具有更好的可移植性。
  • stat: 获取文件或目录的状态信息,包括其所在的设备号 (st_dev)。

9. 为什么不推荐使用 ustat 以及现代替代方案

ustat 已经过时且不推荐使用。现代 Linux 编程应使用 statfs 或 statvfs

现代替代方案示例 (statfs)

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/vfs.h>    // 包含 statfs
#include <sys/stat.h>   // 包含 stat
#include <string.h>
#include <errno.h>

int main() {
    struct statfs sfs;
    struct stat statbuf;
    const char *path = "/"; // 查询根文件系统

    printf("--- Demonstrating modern alternative to ustat: statfs ---\n");

    // 方法 1: 使用 statfs 直接查询路径
    printf("1. Querying filesystem info for path '%s' using statfs():\n", path);
    if (statfs(path, &sfs) == 0) {
        printf("  Filesystem Type (Magic): 0x%lx\n", (unsigned long)sfs.f_type);
        printf("  Block Size:              %ld bytes\n", (long)sfs.f_bsize);
        printf("  Total Blocks:            %ld\n", (long)sfs.f_blocks);
        printf("  Free Blocks:             %ld\n", (long)sfs.f_bfree);
        printf("  Available Blocks:        %ld (for non-root users)\n", (long)sfs.f_bavail);
        printf("  Total Inodes:            %ld\n", (long)sfs.f_files);
        printf("  Free Inodes:             %ld\n", (long)sfs.f_ffree);
        printf("  Filesystem ID:           %d:%d\n", (int)sfs.f_fsid.__val[0], (int)sfs.f_fsid.__val[1]);
    } else {
        perror("statfs");
        exit(EXIT_FAILURE);
    }

    // 方法 2: 先用 stat 获取设备号,再用 statfs 查询 (模拟 ustat 的思路,但用 statfs)
    printf("\n2. Getting device number with stat() and querying with statfs():\n");
    if (stat(path, &statbuf) == 0) {
        printf("  Device ID for '%s': %ld\n", path, (long)statbuf.st_dev);
        // 注意:没有直接的 "statfs_by_dev" 函数。
        // 我们仍然需要一个路径。现代方法是直接用路径查询。
        // 如果真的需要基于设备号查询,需要遍历 /proc/mounts 等找到挂载点。
        // 这正是 ustat 设计上的局限性。
        printf("  Note: Modern statfs works with paths, not device IDs directly.\n");
        printf("        This is more robust than the old ustat approach.\n");
    } else {
        perror("stat");
    }

    printf("\n--- Summary ---\n");
    printf("1. ustat is obsolete and deprecated on modern Linux systems.\n");
    printf("2. Use statfs() or statvfs() instead for querying filesystem information.\n");
    printf("3. These modern functions provide more details and work correctly with all filesystem types.\n");

    return 0;
}

10. 编译和运行 (现代替代方案)

# 假设代码保存在 modern_statfs_example.c 中
gcc -o modern_statfs_example modern_statfs_example.c

# 运行程序
./modern_statfs_example

11. 预期输出 (现代替代方案)

--- Demonstrating modern alternative to ustat: statfs ---
1. Querying filesystem info for path '/' using statfs():
  Filesystem Type (Magic): 0xef53
  Block Size:              4096 bytes
  Total Blocks:            123456789
  Free Blocks:             56789012
  Available Blocks:        50000000 (for non-root users)
  Total Inodes:            30000000
  Free Inodes:             25000000
  Filesystem ID:           12345:67890

2. Getting device number with stat() and querying with statfs():
  Device ID for '/': 64768
  Note: Modern statfs works with paths, not device IDs directly.
        This is more robust than the old ustat approach.

--- Summary ---
1. ustat is obsolete and deprecated on modern Linux systems.
2. Use statfs() or statvfs() instead for querying filesystem information.
3. These modern functions provide more details and work correctly with all filesystem types.

12. 总结

ustat 是一个历史遗留的、已被弃用的系统调用。

  • 历史作用:在早期 Unix 系统中用于查询文件系统基本信息。
  • 为何弃用
    • 功能有限且不准确。
    • 基于设备号的设计不适用于现代复杂文件系统。
  • 现代替代品
    • statfs(path, &buf): 通过路径查询文件系统信息(推荐)。
    • statvfs(path, &buf): POSIX 标准接口,可移植性更好。
  • 给 Linux 编程小白的建议完全不需要学习 ustat。从一开始就应该学习和使用 statfs 或 statvfs 来获取文件系统信息。
发表在 linux文章 | 留下评论

utimensat 和 futimens 系统调用及示例

好的,我们来深入学习 utimensat 和 futimens 系统调用

1. 函数介绍

在 Linux 系统中,每个文件都关联着一些重要的时间属性:

  1. 访问时间 (atime): 文件上一次被读取的时间。
  2. 修改时间 (mtime): 文件内容上一次被修改的时间。
  3. 状态改变时间 (ctime): 文件的元数据(如权限、所有者、链接数等)上一次被改变的时间。

我们之前学过 utimes 可以用来修改 atime 和 mtimeutimensat 和 futimens 是更现代、更强大的系统调用,用于完成相同的任务。

utimensat 和 futimens 的核心优势

  1. 纳秒级精度:它们使用 struct timespec,可以精确到纳秒(虽然底层文件系统可能不支持这么高的精度,但接口提供了)。
  2. 更灵活的控制:它们引入了特殊的标记,允许你精确控制对每个时间戳的操作:
    • 设置为当前时间
    • 保持不变
    • 设置为指定时间
  3. 更灵活的路径解析utimensat 可以像 openat 一样,相对于一个目录文件描述符解析路径,并且可以选择是否跟随符号链接。

简单来说,utimensat 和 futimens 是 utimes 的“升级版”,提供了更高的精度和更灵活的操作方式。

2. 函数原型

#include <fcntl.h>      // 包含 AT_FDCWD 等常量
#include <sys/stat.h>   // 包含 utimensat, futimens 函数声明和 timespec 结构体

// 通过路径名设置时间戳 (更灵活)
int utimensat(int dirfd, const char *pathname, const struct timespec times[2], int flags);

// 通过文件描述符设置时间戳
int futimens(int fd, const struct timespec times[2]);

3. 功能

两者都用于设置文件的访问时间 (atime) 和修改时间 (mtime)。

  • utimensat: 通过路径名指定文件,并提供额外的灵活性(相对路径解析、符号链接处理)。
  • futimens: 通过已打开的文件描述符 (fd) 指定文件。

4. 参数详解

futimens(int fd, const struct timespec times[2])

  • fd:
    • int 类型。
    • 一个指向目标文件的已打开文件描述符
  • times:
    • const struct timespec times[2] 类型。
    • 一个包含两个 struct timespec 元素的数组。
    • times[0] 指定了新的访问时间 (atime)。
    • times[1] 指定了新的修改时间 (mtime)。
    • 特殊值:
      • 如果 times[0] 或 times[1] 的 tv_nsec 字段是 UTIME_NOW,则相应的时间戳会被设置为调用时的当前时间
      • 如果 times[0] 或 times[1] 的 tv_nsec 字段是 UTIME_OMIT,则相应的时间戳将保持不变
      • 否则,时间戳将被设置为 tv_sec 和 tv_nsec 指定的值。

utimensat(int dirfd, const char *pathname, const struct timespec times[2], int flags)

  • dirfd:
    • int 类型。
    • 一个目录文件描述符,用作 pathname 的基础路径。
    • 如果 pathname 是绝对路径,则 dirfd 被忽略。
    • 特殊值 AT_FDCWD 表示使用当前工作目录作为基础路径。
  • pathname:
    • const char * 类型。
    • 指向要修改时间戳的文件的路径名(可以是相对路径或绝对路径)。
  • times:
    • const struct timespec times[2] 类型。
    • 含义与 futimens 相同。
  • flags:
    • int 类型。
    • 用于修改 utimensat 行为的标志。目前主要支持一个标志:
      • AT_SYMLINK_NOFOLLOW: 如果 pathname 是一个符号链接,则修改符号链接本身的时间戳,而不是它指向的目标文件的时间戳。如果未设置此标志(默认),则会跟随符号链接。

struct timespec 结构体:

struct timespec {
    time_t tv_sec;  /* 秒数 (自 Unix 纪元以来) */
    long   tv_nsec; /* 纳秒数 (0-999,999,999) */
};

5. 返回值

两者返回值相同:

  • 成功: 返回 0。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

两者共享许多相同的错误码:

  • EACCES: 搜索 pathname 的路径组件时权限不足,或者没有写权限。
  • EBADF: (对于 futimensfd 不是有效的文件描述符。
  • EBADF: (对于 utimensatdirfd 不是有效的文件描述符,且不等于 AT_FDCWD
  • EFAULTpathname 或 times 指向了调用进程无法访问的内存地址。
  • EINVALtimes 数组中的时间值无效(例如,纳秒数超出范围或 flags 无效)。
  • EIO: I/O 错误。
  • ELOOP: 解析 pathname 时遇到符号链接循环。
  • ENAMETOOLONGpathname 太长。
  • ENOENTpathname 指定的文件或目录不存在。
  • ENOMEM: 内核内存不足。
  • ENOTDIR: (对于 utimensatpathname 的某个前缀不是目录,或者 dirfd 是一个文件描述符但不是目录。
  • EPERMtimes 中指定的时间早于文件的 ctime 和 mtime,且调用进程不拥有该文件(某些文件系统会阻止将时间戳设置得比 ctime/mtime 更早)。
  • EROFS: 文件系统是只读的。

7. 相似函数或关联函数

  • utimes: 旧版函数,使用 struct timeval(微秒精度)。
  • utime: 更老的函数,使用 struct utimbuf(秒精度)。
  • lutimes: 旧版函数,用于修改符号链接本身的时间戳。
  • stat / lstat: 用于获取文件的当前时间戳。

8. 示例代码

下面的示例演示了如何使用 utimensat 和 futimens 来修改文件时间戳,并展示它们的灵活性。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>      // 包含 open, O_* flags, AT_FDCWD, AT_SYMLINK_NOFOLLOW
#include <sys/stat.h>   // 包含 utimensat, futimens, timespec, stat
#include <string.h>
#include <errno.h>
#include <time.h>       // 包含 time, localtime, strftime

// 辅助函数:打印文件的时间信息
void print_file_times(const char *filename) {
    struct stat sb;
    if (stat(filename, &sb) == -1) {
        perror("stat");
        return;
    }

    printf("File: %s\n", filename);
    printf("  Last Status Change (ctime): %s", ctime(&sb.st_ctime)); // ctime 包含换行符
    printf("  Last Modification (mtime):  %s", ctime(&sb.st_mtime));
    printf("  Last Access (atime):        %s", ctime(&sb.st_atime));
    printf("\n");
}

// 辅助函数:创建一个测试文件
void create_test_file(const char *filename) {
    FILE *f = fopen(filename, "w");
    if (!f) {
        perror("fopen");
        exit(EXIT_FAILURE);
    }
    fprintf(f, "This is a test file for utimensat/futimens example.\n");
    fclose(f);
    printf("Created test file: %s\n\n", filename);
}

int main() {
    const char *test_file = "utimensat_test_file.txt";
    const char *test_symlink = "utimensat_test_symlink.txt";
    struct timespec new_times[2];
    time_t fixed_time_sec;
    struct tm tm_tmp;

    printf("--- Demonstrating utimensat and futimens ---\n");

    // 1. 创建一个测试文件
    create_test_file(test_file);

    // 2. 创建一个指向测试文件的符号链接
    if (symlink(test_file, test_symlink) == -1) {
        perror("symlink");
        unlink(test_file);
        exit(EXIT_FAILURE);
    }
    printf("Created symlink: %s -> %s\n\n", test_symlink, test_file);

    // 3. 显示初始时间
    printf("1. Initial timestamps:\n");
    print_file_times(test_file);
    print_file_times(test_symlink); // 符号链接的时间通常和目标文件一样(除非文件系统特殊支持)

    // 4. 准备一个固定的时间
    printf("2. Preparing fixed time...\n");
    memset(&tm_tmp, 0, sizeof(tm_tmp));
    tm_tmp.tm_year = 2023 - 1900; // tm_year is years since 1900
    tm_tmp.tm_mon = 10 - 1;       // tm_mon is 0-11
    tm_tmp.tm_mday = 27;
    tm_tmp.tm_hour = 10;
    tm_tmp.tm_min = 0;
    tm_tmp.tm_sec = 0;
    tm_tmp.tm_isdst = 0;
    fixed_time_sec = timegm(&tm_tmp);
    if (fixed_time_sec == -1) {
        perror("timegm");
        unlink(test_file);
        unlink(test_symlink);
        exit(EXIT_FAILURE);
    }

    // 5. 使用 futimens 设置时间
    printf("3. --- Using futimens() ---\n");
    int fd = open(test_file, O_RDONLY);
    if (fd == -1) {
        perror("open test_file");
        unlink(test_file);
        unlink(test_symlink);
        exit(EXIT_FAILURE);
    }

    new_times[0].tv_sec = fixed_time_sec; // atime
    new_times[0].tv_nsec = 123456789;     // atime 纳秒
    new_times[1].tv_sec = fixed_time_sec; // mtime
    new_times[1].tv_nsec = 987654321;     // mtime 纳秒

    printf("Setting timestamps using futimens()...\n");
    if (futimens(fd, new_times) == -1) {
        perror("futimens");
    } else {
        printf("futimens() succeeded.\n");
    }
    close(fd);
    printf("Timestamps after futimens:\n");
    print_file_times(test_file);

    // 6. 使用 utimensat 设置时间 (相对路径)
    printf("4. --- Using utimensat() with relative path ---\n");
    // 等待几秒,让时间不同
    sleep(2);

    // 将 atime 设置为当前时间,mtime 保持不变
    new_times[0].tv_sec = 0;        // 无关紧要
    new_times[0].tv_nsec = UTIME_NOW; // 设置为当前时间
    new_times[1].tv_sec = 0;        // 无关紧要
    new_times[1].tv_nsec = UTIME_OMIT; // 保持 mtime 不变

    printf("Setting atime to NOW and mtime to OMIT using utimensat(AT_FDCWD, ...)...\n");
    // AT_FDCWD 表示相对于当前工作目录解析路径
    if (utimensat(AT_FDCWD, test_file, new_times, 0) == -1) {
        perror("utimensat");
    } else {
        printf("utimensat() succeeded.\n");
    }
    printf("Timestamps after utimensat (atime updated, mtime unchanged):\n");
    print_file_times(test_file);

    // 7. 使用 utimensat 处理符号链接
    printf("5. --- Using utimensat() with symlinks ---\n");
    // 准备新的时间
    new_times[0].tv_sec = fixed_time_sec + 3600; // atime + 1 小时
    new_times[0].tv_nsec = 111111111;
    new_times[1].tv_sec = fixed_time_sec + 7200; // mtime + 2 小时
    new_times[1].tv_nsec = 222222222;

    // 默认情况下,utimensat 会跟随符号链接,修改目标文件
    printf("Calling utimensat() on symlink WITHOUT AT_SYMLINK_NOFOLLOW...\n");
    printf("  This will modify the TARGET file's timestamps.\n");
    if (utimensat(AT_FDCWD, test_symlink, new_times, 0) == -1) {
        perror("utimensat (follow symlink)");
    } else {
        printf("utimensat() succeeded (followed symlink).\n");
    }
    printf("Target file timestamps after utimensat (followed symlink):\n");
    print_file_times(test_file);
    printf("Symlink file timestamps (should be unchanged or linked):\n");
    print_file_times(test_symlink);

    // 现在使用 AT_SYMLINK_NOFOLLOW 来修改符号链接本身
    new_times[0].tv_sec = fixed_time_sec + 10800; // atime + 3 小时
    new_times[0].tv_nsec = 333333333;
    new_times[1].tv_sec = fixed_time_sec + 14400; // mtime + 4 小时
    new_times[1].tv_nsec = 444444444;

    printf("\nCalling utimensat() on symlink WITH AT_SYMLINK_NOFOLLOW...\n");
    printf("  This will modify the SYMLINK's timestamps (if filesystem supports it).\n");
    if (utimensat(AT_FDCWD, test_symlink, new_times, AT_SYMLINK_NOFOLLOW) == -1) {
        if (errno == EOPNOTSUPP) {
            printf("utimensat with AT_SYMLINK_NOFOLLOW failed: %s\n", strerror(errno));
            printf("  This is expected on many filesystems (e.g., ext4).\n");
        } else {
            perror("utimensat (nofollow symlink)");
        }
    } else {
        printf("utimensat() succeeded (modified symlink itself).\n");
        print_file_times(test_symlink);
    }

    // 8. 清理
    printf("\n6. --- Cleaning up ---\n");
    if (unlink(test_file) == 0) {
        printf("Deleted test file '%s'.\n", test_file);
    } else {
        perror("unlink test_file");
    }
    if (unlink(test_symlink) == 0) {
        printf("Deleted symlink '%s'.\n", test_symlink);
    } else {
        perror("unlink test_symlink");
    }

    printf("\n--- Summary ---\n");
    printf("1. futimens(fd, times[2]): Sets atime/mtime for a file via its file descriptor.\n");
    printf("2. utimensat(dirfd, pathname, times[2], flags): Sets atime/mtime via pathname, with more options.\n");
    printf("3. Both use struct timespec, providing nanosecond precision.\n");
    printf("4. Special timespec values:\n");
    printf("   - tv_nsec = UTIME_NOW:  Set timestamp to current time.\n");
    printf("   - tv_nsec = UTIME_OMIT: Leave timestamp unchanged.\n");
    printf("5. utimensat flags:\n");
    printf("   - 0 (default): Follow symlinks.\n");
    printf("   - AT_SYMLINK_NOFOLLOW: Modify symlink itself (filesystem support varies).\n");
    printf("   - dirfd allows relative path resolution (like openat).\n");
    printf("6. These are the modern, preferred functions for changing file timestamps.\n");

    return 0;
}

9. 编译和运行

# 假设代码保存在 utimensat_futimens_example.c 中
gcc -o utimensat_futimens_example utimensat_futimens_example.c

# 运行程序
./utimensat_futimens_example

10. 预期输出 (片段)

--- Demonstrating utimensat and futimens ---
Created test file: utimensat_test_file.txt

Created symlink: utimensat_test_symlink.txt -> utimensat_test_file.txt

1. Initial timestamps:
File: utimensat_test_file.txt
  Last Status Change (ctime): Fri Oct 27 11:00:00 2023
  Last Modification (mtime):  Fri Oct 27 11:00:00 2023
  Last Access (atime):        Fri Oct 27 11:00:00 2023

File: utimensat_test_symlink.txt
  Last Status Change (ctime): Fri Oct 27 11:00:00 2023
  Last Modification (mtime):  Fri Oct 27 11:00:00 2023
  Last Access (atime):        Fri Oct 27 11:00:00 2023

2. Preparing fixed time...
3. --- Using futimens() ---
Setting timestamps using futimens()...
futimens() succeeded.
Timestamps after futimens:
File: utimensat_test_file.txt
  Last Status Change (ctime): Fri Oct 27 11:00:00 2023
  Last Modification (mtime):  Fri Oct 27 10:00:00 2023
  Last Access (atime):        Fri Oct 27 10:00:00 2023

4. --- Using utimensat() with relative path ---
Setting atime to NOW and mtime to OMIT using utimensat(AT_FDCWD, ...)...
utimensat() succeeded.
Timestamps after utimensat (atime updated, mtime unchanged):
File: utimensat_test_file.txt
  Last Status Change (ctime): Fri Oct 27 11:00:02 2023
  Last Modification (mtime):  Fri Oct 27 10:00:00 2023
  Last Access (atime):        Fri Oct 27 11:00:02 2023

5. --- Using utimensat() with symlinks ---
Calling utimensat() on symlink WITHOUT AT_SYMLINK_NOFOLLOW...
  This will modify the TARGET file's timestamps.
utimensat() succeeded (followed symlink).
Target file timestamps after utimensat (followed symlink):
File: utimensat_test_file.txt
  Last Status Change (ctime): Fri Oct 27 11:00:02 2023
  Last Modification (mtime):  Fri Oct 27 12:00:00 2023
  Last Access (atime):        Fri Oct 27 11:00:00 2023
Symlink file timestamps (should be unchanged or linked):
File: utimensat_test_symlink.txt
  ... (same as target) ...

Calling utimensat() on symlink WITH AT_SYMLINK_NOFOLLOW...
  This will modify the SYMLINK's timestamps (if filesystem supports it).
utimensat with AT_SYMLINK_NOFOLLOW failed: Operation not supported
  This is expected on many filesystems (e.g., ext4).

6. --- Cleaning up ---
Deleted test file 'utimensat_test_file.txt'.
Deleted symlink 'utimensat_test_symlink.txt'.

--- Summary ---
1. futimens(fd, times[2]): Sets atime/mtime for a file via its file descriptor.
2. utimensat(dirfd, pathname, times[2], flags): Sets atime/mtime via pathname, with more options.
3. Both use struct timespec, providing nanosecond precision.
4. Special timespec values:
   - tv_nsec = UTIME_NOW:  Set timestamp to current time.
   - tv_nsec = UTIME_OMIT: Leave timestamp unchanged.
5. utimensat flags:
   - 0 (default): Follow symlinks.
   - AT_SYMLINK_NOFOLLOW: Modify symlink itself (filesystem support varies).
   - dirfd allows relative path resolution (like openat).
6. These are the modern, preferred functions for changing file timestamps.

11. 总结

utimensat 和 futimens 是 Linux 中用于修改文件时间戳的现代、强大的系统调用。

  • 核心优势
    • 高精度:纳秒级时间戳设置。
    • 灵活控制:通过 UTIME_NOW 和 UTIME_OMIT 精确控制每个时间戳的行为。
    • 路径灵活性utimensat 支持相对路径解析和符号链接处理。
  • futimens:通过已打开的文件描述符操作,简单直接。
  • utimensat:功能更全,可以处理相对路径、绝对路径,并控制符号链接行为。
  • 与旧函数对比
    • 比 utimes (微秒) 和 utime (秒) 精度更高。
    • 比 lutimes 功能更强大(lutimes 只是 utimensat 的一个特例)。
  • 给 Linux 编程小白的建议:在需要修改文件时间戳的新代码中,优先使用 utimensat 或 futimens。它们是当前的标准,功能强大且设计良好。
发表在 linux文章 | 留下评论

utimes系统调用及示例

好的,我们来深入学习 utimes 系统调用

1. 函数介绍

在 Linux 系统中,每个文件都关联着一些重要的时间属性:

  1. 访问时间 (atime): 文件上一次被读取的时间。
  2. 修改时间 (mtime): 文件内容上一次被修改的时间。
  3. 状态改变时间 (ctime): 文件的元数据(如权限、所有者、链接数等)上一次被改变的时间。

这些时间戳对于系统管理、备份策略、审计日志等非常重要。

utimes (Update Times) 系统调用的作用就是手动设置一个文件的访问时间 (atime) 和 修改时间 (mtime)

简单来说,utimes 就是让你用程序来“篡改”一个文件的“上次访问时间”和“上次修改时间”。

你可能会问,为什么要手动修改这些时间呢?常见的场景有:

  • 文件同步工具:在同步文件时,可能需要确保目标文件的时间戳与源文件完全一致。
  • 备份和归档:某些备份工具可能需要调整文件时间戳以匹配备份时的状态。
  • 测试:编写测试程序时,可能需要模拟文件在特定时间点被访问或修改。
  • 修复:如果因为某些原因文件的时间戳不正确,可以手动修正。

2. 函数原型

#include <sys/time.h> // 包含 utimes 函数声明和 timeval 结构体

int utimes(const char *filename, const struct timeval times[2]);

3. 功能

设置由 filename 指定的文件的访问时间 (atime) 和修改时间 (mtime)。

4. 参数

  • filename:
    • const char * 类型。
    • 指向一个以 null 结尾的字符串,表示要修改时间戳的文件的路径名。
  • times:
    • const struct timeval times[2] 类型。
    • 一个包含两个 struct timeval 元素的数组。
    • times[0] 指定了新的访问时间 (atime)。
    • times[1] 指定了新的修改时间 (mtime)。
    • 如果 times 指针为 NULL,则 utimes 会将 atime 和 mtime 都设置为当前时间

struct timeval 结构体:

struct timeval {
    time_t      tv_sec;     /* 秒数 (自 Unix 纪元以来) */
    suseconds_t tv_usec;    /* 微秒数 (0-999999) */
};

5. 返回值

  • 成功: 返回 0。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

  • EACCES: 搜索 filename 的路径组件时权限不足,或者没有写权限(因为修改时间戳通常需要写权限)。
  • EFAULTfilename 或 times 指向了调用进程无法访问的内存地址。
  • EINVALtimes 数组中的时间值无效(例如,微秒数超出范围)。
  • EIO: I/O 错误。
  • ELOOP: 解析 filename 时遇到符号链接循环。
  • ENAMETOOLONGfilename 太长。
  • ENOENTfilename 指定的文件或目录不存在。
  • ENOMEM: 内核内存不足。
  • ENOTDIRfilename 的某个前缀不是目录。
  • EPERM: 系统调用被阻止(例如,由 seccomp 或安全模块)。
  • EROFSfilename 所在的文件系统是只读的。

7. 相似函数或关联函数

  • utime: 一个更老的、功能类似的函数。它使用 struct utimbuf,精度只到秒。utimes 是 utime 的微秒精度版本。#include <utime.h> struct utimbuf { time_t actime; /* 访问时间 */ time_t modtime; /* 修改时间 */ }; int utime(const char *filename, const struct utimbuf *times);
  • lutimes: 与 utimes 类似,但如果 filename 是一个符号链接,它会修改符号链接本身的 atime 和 mtime,而不是它指向的目标文件。
  • futimes: 与 utimes 类似,但它通过已打开的文件描述符 (fd) 来指定文件,而不是文件路径。int futimes(int fd, const struct timeval tv[2]);
  • futimens / utimensat: 更现代的系统调用,使用 struct timespec,提供纳秒级精度,并且有更多选项(如 UTIME_OMITUTIME_NOW)。这些是推荐在新代码中使用的。#include <fcntl.h> // 包含 AT_FDCWD 等 #include <sys/stat.h> // 包含 timespec, UTIME_* 常量 int futimens(int fd, const struct timespec times[2]); int utimensat(int dirfd, const char *pathname, const struct timespec times[2], int flags);

8. 示例代码

下面的示例演示了如何使用 utimes 来修改文件的时间戳,并与其他相关函数进行比较。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>   // 包含 utimes, timeval
#include <sys/stat.h>   // 包含 stat, timespec
#include <utime.h>      // 包含 utime, utimbuf
#include <fcntl.h>      // 包含 open, O_* flags
#include <string.h>
#include <errno.h>
#include <time.h>       // 包含 time, localtime, strftime

// 辅助函数:打印文件的时间信息
void print_file_times(const char *filename) {
    struct stat sb;
    if (stat(filename, &sb) == -1) {
        perror("stat");
        return;
    }

    printf("File: %s\n", filename);
    // 使用 ctime 将 time_t 转换为可读字符串
    printf("  Last Status Change (ctime): %s", ctime(&sb.st_ctime)); // ctime 包含换行符
    printf("  Last Modification (mtime):  %s", ctime(&sb.st_mtime));
    printf("  Last Access (atime):        %s", ctime(&sb.st_atime));
    printf("\n");
}

// 辅助函数:创建一个测试文件
void create_test_file(const char *filename) {
    FILE *f = fopen(filename, "w");
    if (!f) {
        perror("fopen");
        exit(EXIT_FAILURE);
    }
    fprintf(f, "This is a test file for utimes example.\n");
    fclose(f);
    printf("Created test file: %s\n\n", filename);
}

int main() {
    const char *test_file = "utimes_test_file.txt";
    struct timeval new_times[2];
    time_t fixed_time_sec;
    struct tm tm_tmp;

    printf("--- Demonstrating utimes ---\n");

    // 1. 创建一个测试文件
    create_test_file(test_file);

    // 2. 显示初始时间
    printf("1. Initial timestamps:\n");
    print_file_times(test_file);

    // 3. 使用 utimes 将 atime 和 mtime 设置为固定时间
    printf("2. Setting timestamps to a fixed time using utimes()...\n");
    // 设置一个固定的日期时间:例如 2023-10-27 10:00:00 UTC
    memset(&tm_tmp, 0, sizeof(tm_tmp));
    tm_tmp.tm_year = 2023 - 1900; // tm_year is years since 1900
    tm_tmp.tm_mon = 10 - 1;       // tm_mon is 0-11
    tm_tmp.tm_mday = 27;
    tm_tmp.tm_hour = 10;
    tm_tmp.tm_min = 0;
    tm_tmp.tm_sec = 0;
    tm_tmp.tm_isdst = 0; // Not daylight saving time
    fixed_time_sec = timegm(&tm_tmp); // Convert to time_t (UTC)
    if (fixed_time_sec == -1) {
        perror("timegm");
        unlink(test_file);
        exit(EXIT_FAILURE);
    }

    new_times[0].tv_sec = fixed_time_sec; // atime
    new_times[0].tv_usec = 123456;        // atime 微秒
    new_times[1].tv_sec = fixed_time_sec; // mtime
    new_times[1].tv_usec = 654321;        // mtime 微秒

    if (utimes(test_file, new_times) == -1) {
        perror("utimes");
        unlink(test_file);
        exit(EXIT_FAILURE);
    }
    printf("Set atime to %s", ctime(&fixed_time_sec)); // ctime adds newline
    printf("Set mtime to %s", ctime(&fixed_time_sec));
    printf("Note: Microseconds are set but not displayed by ctime.\n\n");

    printf("3. Timestamps after utimes (fixed time):\n");
    print_file_times(test_file);

    // 4. 使用 utimes(NULL) 将时间设置为当前时间
    printf("4. Setting timestamps to CURRENT time using utimes(NULL)...\n");
    sleep(3); // 等待几秒,确保当前时间不同
    if (utimes(test_file, NULL) == -1) {
        perror("utimes(NULL)");
        unlink(test_file);
        exit(EXIT_FAILURE);
    }
    printf("Timestamps updated to current time.\n\n");

    printf("5. Timestamps after utimes(NULL):\n");
    print_file_times(test_file);

    // 6. 比较 utime (旧版,秒级精度)
    printf("6. --- Comparing with older utime() ---\n");
    struct utimbuf old_times;
    old_times.actime = fixed_time_sec;  // 访问时间
    old_times.modtime = fixed_time_sec; // 修改时间
    printf("Setting timestamps back to fixed time using utime() (second precision)...\n");
    if (utime(test_file, &old_times) == -1) {
        perror("utime");
    } else {
        printf("utime() succeeded.\n");
        print_file_times(test_file);
        printf("Note: atime and mtime are now at second precision.\n\n");
    }

    // 7. 比较 futimes (通过文件描述符)
    printf("7. --- Comparing with futimes() ---\n");
    int fd = open(test_file, O_RDONLY);
    if (fd == -1) {
        perror("open");
    } else {
        new_times[0].tv_sec = fixed_time_sec + 3600; // atime + 1 小时
        new_times[0].tv_usec = 0;
        new_times[1].tv_sec = fixed_time_sec + 7200; // mtime + 2 小时
        new_times[1].tv_usec = 0;
        printf("Setting timestamps using futimes() via file descriptor...\n");
        if (futimes(fd, new_times) == -1) {
            perror("futimes");
        } else {
            printf("futimes() succeeded.\n");
            print_file_times(test_file);
        }
        close(fd);
    }

    // 8. 清理
    printf("8. --- Cleaning up ---\n");
    if (unlink(test_file) == 0) {
        printf("Deleted test file '%s'.\n", test_file);
    } else {
        perror("unlink");
    }

    printf("\n--- Summary ---\n");
    printf("1. utimes(filename, times[2]): Sets atime and mtime for a file via its path.\n");
    printf("2. Precision is up to microseconds (tv_usec).\n");
    printf("3. If times is NULL, both atime and mtime are set to the current time.\n");
    printf("4. Related functions:\n");
    printf("   - utime(): Older, second-precision version.\n");
    printf("   - lutimes(): Modifies symlink itself, not target.\n");
    printf("   - futimes(): Modifies file via file descriptor.\n");
    printf("   - futimens() / utimensat(): Modern, nanosecond-precision, more flexible (recommended).\n");

    return 0;
}

9. 编译和运行

# 假设代码保存在 utimes_example.c 中
gcc -o utimes_example utimes_example.c

# 运行程序
./utimes_example

10. 预期输出

--- Demonstrating utimes ---
Created test file: utimes_test_file.txt

1. Initial timestamps:
File: utimes_test_file.txt
  Last Status Change (ctime): Fri Oct 27 11:00:00 2023
  Last Modification (mtime):  Fri Oct 27 11:00:00 2023
  Last Access (atime):        Fri Oct 27 11:00:00 2023

2. Setting timestamps to a fixed time using utimes()...
Set atime to Fri Oct 27 10:00:00 2023
Set mtime to Fri Oct 27 10:00:00 2023
Note: Microseconds are set but not displayed by ctime.

3. Timestamps after utimes (fixed time):
File: utimes_test_file.txt
  Last Status Change (ctime): Fri Oct 27 11:00:00 2023
  Last Modification (mtime):  Fri Oct 27 10:00:00 2023
  Last Access (atime):        Fri Oct 27 10:00:00 2023

4. Setting timestamps to CURRENT time using utimes(NULL)...
Timestamps updated to current time.

5. Timestamps after utimes(NULL):
File: utimes_test_file.txt
  Last Status Change (ctime): Fri Oct 27 11:00:03 2023
  Last Modification (mtime):  Fri Oct 27 11:00:03 2023
  Last Access (atime):        Fri Oct 27 11:00:03 2023

6. --- Comparing with older utime() ---
Setting timestamps back to fixed time using utime() (second precision)...
utime() succeeded.
File: utimes_test_file.txt
  Last Status Change (ctime): Fri Oct 27 11:00:03 2023
  Last Modification (mtime):  Fri Oct 27 10:00:00 2023
  Last Access (atime):        Fri Oct 27 10:00:00 2023
Note: atime and mtime are now at second precision.

7. --- Comparing with futimes() ---
Setting timestamps using futimes() via file descriptor...
futimes() succeeded.
File: utimes_test_file.txt
  Last Status Change (ctime): Fri Oct 27 11:00:03 2023
  Last Modification (mtime):  Fri Oct 27 12:00:00 2023
  Last Access (atime):        Fri Oct 27 11:00:00 2023

8. --- Cleaning up ---
Deleted test file 'utimes_test_file.txt'.

--- Summary ---
1. utimes(filename, times[2]): Sets atime and mtime for a file via its path.
2. Precision is up to microseconds (tv_usec).
3. If times is NULL, both atime and mtime are set to the current time.
4. Related functions:
   - utime(): Older, second-precision version.
   - lutimes(): Modifies symlink itself, not target.
   - futimes(): Modifies file via file descriptor.
   - futimens() / utimensat(): Modern, nanosecond-precision, more flexible (recommended).

11. 总结

utimes 是一个用于修改文件访问时间和修改时间的系统调用。

  • 核心功能:精确设置文件的 atime 和 mtime(微秒级)。
  • 参数:文件路径和包含两个 timeval 结构的数组。
  • 特殊用法:传入 NULL 可将时间设置为当前时间。
  • 相关函数
    • utime: 更老的秒级精度版本。
    • lutimes: 修改符号链接本身的时间。
    • futimes: 通过文件描述符修改时间。
    • futimens / utimensat现代推荐的函数,提供纳秒精度和更多控制选项。
  • 使用场景:文件同步、备份、测试、时间戳修复。
  • 给 Linux 编程小白的建议:虽然 utimes 很有用,但在编写新代码时,考虑使用更新、更强大的 utimensat 或 futimens,因为它们提供了更好的精度和灵活性。
发表在 linux文章 | 留下评论

vfork系统调用及示例

在 fork 之后,根据您提供的列表,下一个函数是 vfork


1. 函数介绍

vfork 是一个历史悠久的 Linux/Unix 系统调用,它的设计目的是为了优化 fork 在特定场景下的性能。vfork 的行为与 fork 非常相似,但也存在关键的区别

核心思想:

当一个进程调用 fork 后,最常见的操作是在子进程中立即调用 exec 系列函数来执行一个全新的程序。在标准的 fork 实现中,内核会完整地复制父进程的地址空间(页表、内存页等)给子进程。但是,如果子进程紧接着就调用 exec,这些刚复制的内存很快就会被新程序的内存镜像完全覆盖,那么这次复制操作就是浪费的。

vfork 就是为了解决这个“先 fork 再 exec”的常见模式下的性能浪费问题。

你可以把 vfork 想象成借用自己的身体来打电话

  • 你(父进程)需要让别人(子进程)去隔壁房间打一个重要的电话(exec)。
  • 用 fork 就像你复制了一个自己的身体(克隆人),然后让克隆人去隔壁打电话。但克隆人刚出门,你就把他的身体销毁了,因为用完就没了。
  • 用 vfork 就像你暂时把自己的身体借给那个人,让他去隔壁打电话。在打电话的这段时间(从 vfork 返回到 exec 或 _exit 被调用),你(父进程)必须一动不动地等着,因为你把身体借出去了。
  • 一旦那个人打完电话(调用 exec 或 _exit),你的身体就回来了,你可以继续做自己的事。

2. 函数原型

#include <unistd.h> // 必需

pid_t vfork(void);

3. 功能

  • 创建新进程: 与 fork 类似,vfork 也用于创建一个新的子进程。
  • 共享地址空间: 与 fork 不同,vfork 创建的子进程暂时与父进程共享相同的地址空间(内存、栈等)。这意味着子进程对内存的任何修改在父进程中都是可见的。
  • 挂起父进程: 调用 vfork 后,父进程会被挂起(暂停执行),直到子进程调用 exec 系列函数或 _exit 为止。
  • 子进程限制: 在子进程中,从 vfork 返回到调用 exec 或 _exit 之间,只能执行这两个操作或修改局部变量后直接返回。执行任何其他操作(如修改全局变量、调用可能修改内存的库函数、返回到 vfork 调用之前的函数栈帧)都可能导致未定义行为

4. 参数

  • voidvfork 函数不接受任何参数。

5. 返回值

vfork 的返回值语义与 fork 完全相同

  • 在父进程中:
    • 成功: 返回新创建**子进程的进程 ID **(PID)。
    • 失败: 返回 -1,并设置 errno
  • 在子进程中:
    • 成功: 返回 0。
  • **失败时 **(父进程)
    • 返回 -1,且没有子进程被创建。

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

  • forkvfork 的“兄弟”。vfork 是 fork 的一个变种,旨在优化“fork-then-exec”模式。
  • clone: Linux 特有的底层系统调用。vfork 在底层可以通过特定的 clone 标志 (CLONE_VFORK | CLONE_VM) 来实现。
  • exec 系列函数vfork 通常与 exec 系列函数结合使用。
  • _exit: 子进程在 vfork 后通常调用 _exit 来终止,而不是 exit

7. 示例代码

示例 1:基本的 vfork + exec 使用

这个例子演示了 vfork 最经典和推荐的用法:创建子进程并立即执行新程序。

// vfork_exec.c
#include <unistd.h>   // vfork, _exit
#include <sys/wait.h> // wait
#include <stdio.h>    // printf, perror
#include <stdlib.h>   // exit

int global_var = 100; // 全局变量,用于演示共享地址空间

int main() {
    pid_t pid;
    int local_var = 200; // 局部变量

    printf("Before vfork: Parent PID: %d\n", getpid());
    printf("  Global var: %d, Local var: %d\n", global_var, local_var);

    // --- 关键: 调用 vfork ---
    pid = vfork();

    if (pid == -1) {
        // vfork 失败
        perror("vfork failed");
        exit(EXIT_FAILURE);

    } else if (pid == 0) {
        // --- 子进程 ---
        printf("Child process (PID: %d) created by vfork.\n", getpid());

        // 在 vfork 的子进程中,修改局部变量通常是安全的
        // (只要不返回到 vfork 之前的栈帧)
        local_var = 250;
        printf("  Child modified local var to: %d\n", local_var);

        // 修改全局变量也是可以的,但这会影响父进程看到的值
        // 这只是为了演示共享内存,实际使用中要非常小心
        global_var = 150;
        printf("  Child modified global var to: %d\n", global_var);

        // --- 关键: 子进程必须立即调用 exec 或 _exit ---
        printf("  Child is about to exec 'echo'.\n");

        // 准备 execv 所需的参数
        char *args[] = { "echo", "Hello from exec'd process!", NULL };

        // 调用 execv 执行新的程序
        execv("/bin/echo", args);

        // --- 如果代码执行到这里,说明 execv 失败了 ---
        perror("execv failed in child");
        // 在 vfork 的子进程中,失败时必须使用 _exit,而不是 exit
        _exit(EXIT_FAILURE);

    } else {
        // --- 父进程 ---
        // vfork 会挂起父进程,直到子进程调用 exec 或 _exit
        printf("Parent process (PID: %d) resumed after child's exec.\n", getpid());

        // 父进程现在可以安全地访问自己的变量了
        // 注意:由于子进程修改了 global_var,在 exec 之前,
        // 父进程看到的 global_var 值可能已经被改变了
        // (但这依赖于具体实现和时机,不应依赖此行为)
        printf("  Parent sees global var as: %d (may be modified by child)\n", global_var);
        printf("  Parent sees local var as: %d (should be unchanged)\n", local_var);

        // 等待子进程结束 (子进程 exec 后变成了新的程序,最终会退出)
        int status;
        if (wait(&status) == -1) {
            perror("wait failed");
            exit(EXIT_FAILURE);
        }

        if (WIFEXITED(status)) {
            int exit_code = WEXITSTATUS(status);
            printf("Parent: Child (new process) exited with status %d.\n", exit_code);
        } else {
            printf("Parent: Child (new process) did not exit normally.\n");
        }

        printf("Parent process (PID: %d) finished.\n", getpid());
    }

    return 0;
}

代码解释:

  1. 定义了一个全局变量 global_var 和一个局部变量 local_var
  2. 调用 pid = vfork();
  3. 在子进程中 (pid == 0):
    • 打印信息。
    • 修改局部变量 local_var(这通常被认为是安全的)。
    • 修改全局变量 global_var(这是为了演示地址空间共享,但实际编程中非常危险且不推荐)。
    • 关键: 准备 execv 的参数并立即调用 execv("/bin/echo", args)
    • 如果 execv 失败,调用 _exit(EXIT_FAILURE) 退出。强调: 在 vfork 子进程中,失败时必须使用 _exit,而不是 exit
  4. 在父进程中 (pid > 0):
    • 程序执行到这里时,意味着子进程已经调用了 exec 或 _exit,父进程被恢复执行
    • 打印信息,并检查变量的值。
      • local_var 应该没有变化。
      • global_var 的值是不确定的,因为子进程可能修改了它。这展示了共享地址空间的风险。
    • 调用 wait 等待子进程(现在是 echo 程序)结束。
    • 打印子进程的退出状态。
    • 父进程结束。

示例 2:演示 vfork 子进程中的危险操作

这个例子(仅供演示,请勿模仿!)展示了在 vfork 子进程中执行不当操作可能导致的问题。

// vfork_danger.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int global_counter = 0;

// 一个简单的函数
void dangerous_function() {
    global_counter++; // 修改全局变量
    printf("Dangerous function called, global_counter: %d\n", global_counter);
    // 如果这个函数还调用了其他库函数,或者有复杂的返回路径,
    // 在 vfork 子进程中调用它会非常危险。
}

int main() {
    pid_t pid;

    printf("Parent PID: %d, Global counter: %d\n", getpid(), global_counter);

    pid = vfork();

    if (pid == -1) {
        perror("vfork failed");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // --- vfork 子进程 ---
        printf("Child PID: %d\n", getpid());

        // --- 危险操作 1: 调用非 async-signal-safe 的函数 ---
        // printf 通常被认为是安全的,但更复杂的函数可能不是。
        // dangerous_function(); // 取消注释这行可能会导致问题

        // --- 危险操作 2: 从 vfork 子进程中返回 ---
        // return 0; // 这是非常危险的!绝对不要这样做!
        // 从 vfork 子进程中返回会导致返回到父进程的栈帧,
        // 而父进程的栈可能已经被子进程修改或破坏。

        // --- 危险操作 3: 修改复杂的数据结构 ---
        // 任何涉及 malloc/free, stdio buffers, 等的操作都可能不安全。

        // 正确的做法:只调用 exec 或 _exit
        // 为了演示,我们在这里直接 _exit
        printf("Child is exiting via _exit().\n");
        _exit(EXIT_SUCCESS);

    } else {
        // --- 父进程 ---
        // 父进程在这里恢复
        printf("Parent PID: %d resumed. Global counter: %d\n", getpid(), global_counter);

        // 简单等待,实际程序中应使用 wait
        sleep(1);
        printf("Parent finished.\n");
    }

    return 0;
}

代码解释:

  1. 该示例旨在说明在 vfork 子进程中的限制。
  2. dangerous_function 是一个示例函数,它修改全局变量并调用 printf
  3. 代码注释中指出了几种在 vfork 子进程中不应该做的事情:
    • 调用复杂的库函数。
    • 从子进程函数中返回(return)。
    • 修改复杂的数据结构。
  4. 强调了在 vfork 子进程中只应执行 exec 或 _exit

重要提示与注意事项:

  1. 已过时/不推荐: 在现代 Linux 编程中,vfork通常被认为是过时的,并且不推荐使用。原因如下:
    • 复杂且易出错: 子进程的行为受到严格限制,很容易因违反规则而导致程序崩溃或数据损坏。
    • 优化不再显著: 现代操作系统的 fork 实现(利用写时复制 Copy-On-Write, COW 技术)已经非常高效。当 fork 后立即 exec 时,内核几乎不需要复制任何实际的物理内存页,因为 exec 会立即替换整个地址空间。因此,vfork 带来的性能提升非常有限。
    • 更安全的替代方案posix_spawn() 是一个更现代、更安全、更可移植的创建并执行新进程的方式,它旨在提供 fork + exec 的功能,同时避免 vfork 的陷阱。
  2. vfork vs fork + COW:
    • 传统的 fork 确实会复制页表。
    • 但是现代的 fork 实现使用写时复制(COW)。这意味着 fork 调用本身很快,因为它只复制页表,而物理内存页在父子进程之间是共享的。只有当任一进程尝试修改某页时,内核才会复制该页。如果子进程紧接着调用 exec,那么大部分(甚至全部)页面都无需复制。
    • 因此,vfork 的性能优势在现代系统上已经大大减弱。
  3. 严格的使用规则: 如果你必须使用 vfork(例如,为了兼容非常老的系统或特殊需求),必须严格遵守其规则:
    • 子进程只能调用 exec 或 _exit
    • 子进程不能修改除局部变量外的任何数据。
    • 子进程不能返回到 vfork 调用之前的任何函数栈帧。
    • 子进程不能调用任何非异步信号安全(async-signal-safe)的函数(除了 exec 和 _exit)。
  4. _exit vs exit: 与 fork 子进程一样,在 vfork 子进程中,如果需要终止,应使用 _exit() 而不是 exit()
  5. 可移植性vfork 不是 POSIX 标准的一部分,尽管在很多类 Unix 系统上都可用。fork 和 posix_spawn 具有更好的可移植性。

总结:

vfork 是一个为特定场景(fork 后立即 exec)优化的系统调用。它通过让父子进程共享地址空间并挂起父进程来避免内存复制的开销。然而,由于其使用规则极其严格且容易出错,加上现代 fork 实现(COW)已经非常高效,vfork 在现代编程实践中已基本被弃用。推荐使用 fork + exec 或更现代的 posix_spawn 来创建和执行新进程。理解 vfork 的原理和历史意义仍然重要,但应避免在新代码中使用它。

发表在 linux文章 | 留下评论

vmsplice系统调用及示例

vmsplice函数详解

1. 函数介绍

vmsplice函数是Linux系统中用于高效地将用户空间缓冲区数据传输到管道的函数。它是splice系列函数的一部分,专门用于从用户空间向管道传输数据。可以把vmsplice想象成一个”高速数据管道注入器”,它能够将内存中的数据块直接传输到管道中,而无需传统的数据拷贝操作。

vmsplice的主要优势在于它提供了零拷贝或最小拷贝的数据传输机制,通过将用户空间的内存页直接映射到内核空间,大大提高了数据传输的效率。这在需要大量数据传输的高性能应用中特别有用。

使用场景:

  • 高性能网络服务器的数据传输
  • 大数据处理和流处理应用
  • 实时系统的数据管道
  • 避免内存拷贝的高效数据传输
  • 管道和套接字之间的数据传输

2. 函数原型

#define _GNU_SOURCE
#include <fcntl.h>
#include <sys/uio.h>

ssize_t vmsplice(int fd, const struct iovec *iov, unsigned long nr_segs, unsigned int flags);

3. 功能

vmsplice函数的主要功能是将用户空间缓冲区的数据高效地传输到管道中。它支持多种传输模式和选项,可以优化不同场景下的数据传输性能。

4. 参数

  • fd: 管道文件描述符
    • 类型:int
    • 含义:目标管道的写端文件描述符
  • iov: 输入输出向量数组
    • 类型:const struct iovec*
    • 含义:描述要传输的数据缓冲区的向量数组
    • 结构体定义:struct iovec { void *iov_base; // 缓冲区起始地址 size_t iov_len; // 缓冲区长度 };
  • nr_segs: 向量数组元素个数
    • 类型:unsigned long
    • 含义:iov数组中有效元素的个数
  • flags: 操作标志
    • 类型:unsigned int
    • 含义:控制传输行为的标志位
    • 常用值:
      • SPLICE_F_MOVE:尽可能移动页面而不是复制
      • SPLICE_F_NONBLOCK:非阻塞操作
      • SPLICE_F_MORE:提示还有更多数据要写入
      • SPLICE_F_GIFT:页面是礼品(传输后内核拥有页面)

5. 返回值

  • 成功: 返回实际传输的字节数
  • 失败: 返回-1,并设置errno错误码
    • EAGAIN:非阻塞模式下无法立即完成
    • EBADF:无效的文件描述符
    • EINVAL:参数无效
    • ENOMEM:内存不足
    • EPIPE:管道已关闭读端

6. 相似函数或关联函数

  • splice(): 在文件描述符之间传输数据
  • tee(): 在管道之间复制数据
  • write(): 传统的数据写入函数
  • writev(): 向量写入函数
  • mmap(): 内存映射函数
  • sendfile(): 文件到套接字的高效传输

7. 示例代码

示例1:基础vmsplice使用 – 简单数据传输

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

// 创建管道
int create_pipe(int pipefd[2]) {
    if (pipe(pipefd) == -1) {
        perror("创建管道失败");
        return -1;
    }
    printf("创建管道: 读端=%d, 写端=%d\n", pipefd[0], pipefd[1]);
    return 0;
}

// 使用vmsplice传输数据
ssize_t send_data_with_vmsplice(int pipe_write_fd, const char* data, size_t data_len) {
    struct iovec iov;
    iov.iov_base = (void*)data;
    iov.iov_len = data_len;
    
    ssize_t result = vmsplice(pipe_write_fd, &iov, 1, SPLICE_F_MOVE);
    if (result == -1) {
        perror("vmsplice传输失败");
    } else {
        printf("vmsplice传输成功: %zd 字节\n", result);
    }
    
    return result;
}

// 从管道接收数据
ssize_t receive_data_from_pipe(int pipe_read_fd, char* buffer, size_t buffer_size) {
    ssize_t bytes_read = read(pipe_read_fd, buffer, buffer_size - 1);
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("从管道读取: %zd 字节\n", bytes_read);
    } else if (bytes_read == -1) {
        perror("从管道读取失败");
    }
    
    return bytes_read;
}

int main() {
    printf("=== 基础vmsplice使用示例 ===\n");
    
    int pipefd[2];
    
    // 创建管道
    if (create_pipe(pipefd) == -1) {
        exit(EXIT_FAILURE);
    }
    
    // 准备测试数据
    const char* test_data = "这是使用vmsplice传输的测试数据";
    size_t data_len = strlen(test_data);
    
    printf("1. 准备传输数据:\n");
    printf("   数据内容: %s\n", test_data);
    printf("   数据长度: %zu 字节\n", data_len);
    
    // 使用vmsplice传输数据
    printf("\n2. 使用vmsplice传输数据:\n");
    ssize_t bytes_sent = send_data_with_vmsplice(pipefd[1], test_data, data_len);
    
    if (bytes_sent > 0) {
        // 从管道接收数据
        printf("\n3. 从管道接收数据:\n");
        char receive_buffer[256];
        ssize_t bytes_received = receive_data_from_pipe(pipefd[0], receive_buffer, sizeof(receive_buffer));
        
        if (bytes_received > 0) {
            printf("   接收内容: %s\n", receive_buffer);
            printf("   验证结果: %s\n", 
                   strcmp(test_data, receive_buffer) == 0 ? "✓ 数据一致" : "✗ 数据不一致");
        }
    }
    
    // 演示向量传输
    printf("\n4. 演示向量传输:\n");
    
    // 准备多个数据段
    const char* segments[] = {
        "第一段数据",
        "第二段数据",
        "第三段数据"
    };
    
    size_t segment_count = sizeof(segments) / sizeof(segments[0]);
    struct iovec iov[segment_count];
    
    // 初始化向量数组
    for (size_t i = 0; i < segment_count; i++) {
        iov[i].iov_base = (void*)segments[i];
        iov[i].iov_len = strlen(segments[i]);
        printf("   段 %zu: %s (%zu 字节)\n", i + 1, segments[i], strlen(segments[i]));
    }
    
    // 使用vmsplice传输多个段
    ssize_t vector_result = vmsplice(pipefd[1], iov, segment_count, SPLICE_F_MOVE);
    if (vector_result == -1) {
        perror("向量传输失败");
    } else {
        printf("   向量传输成功: %zd 字节\n", vector_result);
        
        // 接收并验证数据
        char vector_buffer[256];
        ssize_t total_received = 0;
        printf("   接收数据:\n");
        
        while (total_received < vector_result) {
            ssize_t bytes_read = read(pipefd[0], vector_buffer + total_received, 
                                    sizeof(vector_buffer) - total_received - 1);
            if (bytes_read > 0) {
                total_received += bytes_read;
            } else if (bytes_read == 0) {
                break;  // 管道已关闭
            } else {
                perror("读取数据失败");
                break;
            }
        }
        
        if (total_received > 0) {
            vector_buffer[total_received] = '\0';
            printf("   接收内容: %s\n", vector_buffer);
        }
    }
    
    // 演示错误处理
    printf("\n5. 错误处理演示:\n");
    
    // 使用无效的文件描述符
    struct iovec invalid_iov = {(void*)"test", 4};
    ssize_t error_result = vmsplice(999, &invalid_iov, 1, 0);
    if (error_result == -1) {
        printf("   使用无效fd: %s (预期错误)\n", strerror(errno));
    }
    
    // 使用空向量
    error_result = vmsplice(pipefd[1], NULL, 0, 0);
    if (error_result == -1) {
        printf("   使用空向量: %s (预期错误)\n", strerror(errno));
    }
    
    // 清理资源
    printf("\n6. 清理资源:\n");
    close(pipefd[0]);
    close(pipefd[1]);
    printf("   管道已关闭\n");
    
    printf("\n=== 基础vmsplice演示完成 ===\n");
    
    return 0;
}

示例2:高性能数据管道应用

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

#define BUFFER_SIZE 4096
#define NUM_BUFFERS 100

// 生产者进程 - 使用vmsplice发送数据
void producer_process(int pipe_write_fd) {
    printf("生产者进程启动 (PID: %d)\n", getpid());
    
    // 准备数据缓冲区
    char** buffers = malloc(NUM_BUFFERS * sizeof(char*));
    if (!buffers) {
        perror("分配缓冲区失败");
        exit(EXIT_FAILURE);
    }
    
    // 初始化缓冲区
    for (int i = 0; i < NUM_BUFFERS; i++) {
        buffers[i] = malloc(BUFFER_SIZE);
        if (!buffers[i]) {
            perror("分配缓冲区失败");
            exit(EXIT_FAILURE);
        }
        
        // 填充缓冲区内容
        snprintf(buffers[i], BUFFER_SIZE, 
                 "生产者数据包 %d - 时间戳: %ld", i + 1, time(NULL));
    }
    
    struct iovec iov;
    int total_sent = 0;
    
    // 发送数据包
    for (int i = 0; i < NUM_BUFFERS; i++) {
        iov.iov_base = buffers[i];
        iov.iov_len = strlen(buffers[i]) + 1;  // 包括字符串结束符
        
        // 使用vmsplice发送数据
        ssize_t bytes_sent = vmsplice(pipe_write_fd, &iov, 1, 
                                     SPLICE_F_MOVE | SPLICE_F_MORE);
        
        if (bytes_sent == -1) {
            if (errno == EAGAIN) {
                printf("生产者: 管道满,等待...\n");
                usleep(1000);  // 等待1毫秒后重试
                i--;  // 重试当前数据包
                continue;
            } else {
                perror("生产者: vmsplice发送失败");
                break;
            }
        } else {
            total_sent += bytes_sent;
            if ((i + 1) % 20 == 0) {
                printf("生产者: 已发送 %d 个数据包 (%d 字节)\n", i + 1, total_sent);
            }
        }
        
        // 模拟处理时间
        if (i % 10 == 0) {
            usleep(10000);  // 10毫秒
        }
    }
    
    printf("生产者: 总共发送 %d 字节\n", total_sent);
    
    // 清理缓冲区
    for (int i = 0; i < NUM_BUFFERS; i++) {
        free(buffers[i]);
    }
    free(buffers);
    
    printf("生产者进程完成\n");
}

// 消费者进程 - 从管道接收数据
void consumer_process(int pipe_read_fd) {
    printf("消费者进程启动 (PID: %d)\n", getpid());
    
    char buffer[BUFFER_SIZE];
    int total_received = 0;
    int packet_count = 0;
    
    clock_t start_time = clock();
    
    // 接收数据
    while (packet_count < NUM_BUFFERS) {
        ssize_t bytes_received = read(pipe_read_fd, buffer, sizeof(buffer) - 1);
        
        if (bytes_received > 0) {
            buffer[bytes_received] = '\0';
            total_received += bytes_received;
            packet_count++;
            
            if (packet_count % 20 == 0) {
                printf("消费者: 已接收 %d 个数据包 (%d 字节)\n", 
                       packet_count, total_received);
            }
        } else if (bytes_received == 0) {
            printf("消费者: 管道已关闭\n");
            break;
        } else {
            if (errno == EAGAIN) {
                usleep(1000);  // 等待1毫秒后重试
                continue;
            } else {
                perror("消费者: 读取数据失败");
                break;
            }
        }
    }
    
    clock_t end_time = clock();
    double elapsed_time = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
    
    printf("消费者: 总共接收 %d 个数据包, %d 字节\n", packet_count, total_received);
    printf("消费者: 耗时 %.3f 秒, 吞吐量 %.2f KB/s\n", 
           elapsed_time, (total_received / 1024.0) / elapsed_time);
    
    printf("消费者进程完成\n");
}

// 性能测试函数
void performance_test() {
    printf("=== 性能测试 ===\n");
    
    int pipefd[2];
    if (pipe(pipefd) == -1) {
        perror("创建管道失败");
        return;
    }
    
    // 设置管道为非阻塞模式
    int flags = fcntl(pipefd[1], F_GETFL);
    fcntl(pipefd[1], F_SETFL, flags | O_NONBLOCK);
    flags = fcntl(pipefd[0], F_GETFL);
    fcntl(pipefd[0], F_SETFL, flags | O_NONBLOCK);
    
    // 准备大量测试数据
    char* large_data = malloc(1024 * 1024);  // 1MB数据
    if (!large_data) {
        perror("分配测试数据失败");
        close(pipefd[0]);
        close(pipefd[1]);
        return;
    }
    
    memset(large_data, 'A', 1024 * 1024);
    large_data[1024 * 1024 - 1] = '\0';
    
    // 测试vmsplice性能
    clock_t start_time = clock();
    
    struct iovec iov;
    iov.iov_base = large_data;
    iov.iov_len = 1024 * 1024;
    
    ssize_t bytes_sent = vmsplice(pipefd[1], &iov, 1, SPLICE_F_MOVE);
    
    clock_t end_time = clock();
    double elapsed_time = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
    
    if (bytes_sent > 0) {
        printf("vmsplice传输 %zd 字节\n", bytes_sent);
        printf("耗时: %.6f 秒\n", elapsed_time);
        printf("吞吐量: %.2f MB/s\n", (bytes_sent / (1024.0 * 1024.0)) / elapsed_time);
    } else {
        printf("vmsplice传输失败: %s\n", strerror(errno));
    }
    
    // 测试传统write性能(对比)
    lseek(pipefd[0], 0, SEEK_SET);  // 清空管道
    lseek(pipefd[1], 0, SEEK_SET);
    
    start_time = clock();
    ssize_t write_result = write(pipefd[1], large_data, 1024 * 1024);
    end_time = clock();
    elapsed_time = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
    
    if (write_result > 0) {
        printf("write传输 %zd 字节\n", write_result);
        printf("耗时: %.6f 秒\n", elapsed_time);
        printf("吞吐量: %.2f MB/s\n", (write_result / (1024.0 * 1024.0)) / elapsed_time);
    }
    
    free(large_data);
    close(pipefd[0]);
    close(pipefd[1]);
    
    printf("=== 性能测试完成 ===\n\n");
}

int main() {
    printf("=== 高性能数据管道应用示例 ===\n");
    
    // 执行性能测试
    performance_test();
    
    // 创建管道用于进程间通信
    int pipefd[2];
    if (pipe(pipefd) == -1) {
        perror("创建管道失败");
        exit(EXIT_FAILURE);
    }
    
    printf("创建数据管道: 读端=%d, 写端=%d\n", pipefd[0], pipefd[1]);
    
    // 启动生产者进程
    pid_t producer_pid = fork();
    if (producer_pid == 0) {
        // 生产者子进程
        close(pipefd[0]);  // 关闭读端
        producer_process(pipefd[1]);
        close(pipefd[1]);
        exit(EXIT_SUCCESS);
    } else if (producer_pid == -1) {
        perror("创建生产者进程失败");
        close(pipefd[0]);
        close(pipefd[1]);
        exit(EXIT_FAILURE);
    }
    
    // 启动消费者进程
    pid_t consumer_pid = fork();
    if (consumer_pid == 0) {
        // 消费者子进程
        close(pipefd[1]);  // 关闭写端
        consumer_process(pipefd[0]);
        close(pipefd[0]);
        exit(EXIT_SUCCESS);
    } else if (consumer_pid == -1) {
        perror("创建消费者进程失败");
        close(pipefd[0]);
        close(pipefd[1]);
        exit(EXIT_FAILURE);
    }
    
    // 父进程关闭两端并等待子进程完成
    close(pipefd[0]);
    close(pipefd[1]);
    
    printf("主进程等待子进程完成...\n");
    waitpid(producer_pid, NULL, 0);
    waitpid(consumer_pid, NULL, 0);
    
    printf("所有进程已完成\n");
    
    printf("\n=== 高性能数据管道应用演示完成 ===\n");
    
    return 0;
}

示例3:vmsplice与内存映射结合使用

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

#define SHARED_MEMORY_SIZE (1024 * 1024)  // 1MB
#define PACKET_SIZE 1024

// 共享内存结构
typedef struct {
    volatile int write_index;
    volatile int read_index;
    volatile int data_ready;
    char data[SHARED_MEMORY_SIZE - sizeof(int) * 3];
} shared_memory_t;

// 使用vmsplice发送内存映射数据
ssize_t send_mmap_data_with_vmsplice(int pipe_fd, const void* data, size_t data_size) {
    struct iovec iov;
    iov.iov_base = (void*)data;
    iov.iov_len = data_size;
    
    // 使用SPLICE_F_GIFT标志,表示传输后内核拥有页面
    ssize_t result = vmsplice(pipe_fd, &iov, 1, SPLICE_F_MOVE | SPLICE_F_GIFT);
    
    if (result == -1) {
        printf("vmsplice发送失败: %s\n", strerror(errno));
    } else {
        printf("vmsplice发送成功: %zd 字节\n", result);
    }
    
    return result;
}

// 创建测试数据文件
int create_test_data_file(const char* filename, size_t size) {
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    // 填充测试数据
    char* buffer = malloc(4096);
    if (!buffer) {
        close(fd);
        return -1;
    }
    
    for (size_t i = 0; i < size; i += 4096) {
        size_t write_size = (size - i > 4096) ? 4096 : (size - i);
        memset(buffer, 'A' + (i / 4096) % 26, write_size - 1);
        buffer[write_size - 1] = '\n';
        
        if (write(fd, buffer, write_size) != (ssize_t)write_size) {
            perror("写入测试数据失败");
            free(buffer);
            close(fd);
            return -1;
        }
    }
    
    free(buffer);
    printf("创建测试文件: %s (%zu 字节)\n", filename, size);
    return fd;
}

// 演示文件到管道的高效传输
void demonstrate_file_to_pipe_transfer() {
    printf("=== 文件到管道传输演示 ===\n");
    
    const char* test_file = "vmsplice_test_data.txt";
    const size_t file_size = 64 * 1024;  // 64KB
    
    // 创建测试数据文件
    int file_fd = create_test_data_file(test_file, file_size);
    if (file_fd == -1) {
        return;
    }
    
    // 创建管道
    int pipefd[2];
    if (pipe(pipefd) == -1) {
        perror("创建管道失败");
        close(file_fd);
        unlink(test_file);
        return;
    }
    
    // 内存映射文件
    char* mapped_data = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, file_fd, 0);
    if (mapped_data == MAP_FAILED) {
        perror("内存映射文件失败");
        close(file_fd);
        close(pipefd[0]);
        close(pipefd[1]);
        unlink(test_file);
        return;
    }
    
    printf("文件内存映射成功: %p (%zu 字节)\n", mapped_data, file_size);
    
    // 使用vmsplice传输映射的数据
    printf("使用vmsplice传输映射数据...\n");
    
    clock_t start_time = clock();
    ssize_t bytes_sent = send_mmap_data_with_vmsplice(pipefd[1], mapped_data, file_size);
    clock_t end_time = clock();
    
    if (bytes_sent > 0) {
        double elapsed_time = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
        printf("传输完成: %zd 字节\n", bytes_sent);
        printf("耗时: %.6f 秒\n", elapsed_time);
        printf("吞吐量: %.2f MB/s\n", (bytes_sent / (1024.0 * 1024.0)) / elapsed_time);
        
        // 验证数据传输
        printf("验证数据传输...\n");
        char* verify_buffer = malloc(file_size);
        if (verify_buffer) {
            ssize_t bytes_received = read(pipefd[0], verify_buffer, file_size);
            if (bytes_received > 0) {
                printf("验证接收: %zd 字节\n", bytes_received);
                if (bytes_received == bytes_sent) {
                    printf("✓ 数据传输验证通过\n");
                } else {
                    printf("✗ 数据传输验证失败\n");
                }
            }
            free(verify_buffer);
        }
    }
    
    // 清理资源
    munmap(mapped_data, file_size);
    close(file_fd);
    close(pipefd[0]);
    close(pipefd[1]);
    unlink(test_file);
    
    printf("=== 文件传输演示完成 ===\n\n");
}

// 高级vmsplice特性演示
void advanced_vmsplice_features() {
    printf("=== 高级vmsplice特性演示 ===\n");
    
    int pipefd[2];
    if (pipe(pipefd) == -1) {
        perror("创建管道失败");
        return;
    }
    
    // 演示不同标志的使用
    printf("1. 不同标志演示:\n");
    
    const char* test_message = "测试消息数据";
    struct iovec iov;
    iov.iov_base = (void*)test_message;
    iov.iov_len = strlen(test_message);
    
    // SPLICE_F_MOVE 标志
    printf("   使用 SPLICE_F_MOVE 标志:\n");
    ssize_t result = vmsplice(pipefd[1], &iov, 1, SPLICE_F_MOVE);
    if (result > 0) {
        printf("   ✓ 传输成功: %zd 字节\n", result);
        
        // 读取验证
        char buffer[256];
        ssize_t bytes_read = read(pipefd[0], buffer, sizeof(buffer) - 1);
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            printf("   接收数据: %s\n", buffer);
        }
    }
    
    // SPLICE_F_MORE 标志(提示还有更多数据)
    printf("   使用 SPLICE_F_MORE 标志:\n");
    const char* more_data = "更多数据";
    iov.iov_base = (void*)more_data;
    iov.iov_len = strlen(more_data);
    
    result = vmsplice(pipefd[1], &iov, 1, SPLICE_F_MORE);
    if (result > 0) {
        printf("   ✓ 传输成功: %zd 字节\n", result);
    }
    
    // SPLICE_F_NONBLOCK 标志(非阻塞模式)
    printf("   使用 SPLICE_F_NONBLOCK 标志:\n");
    
    // 设置管道为非阻塞模式
    int flags = fcntl(pipefd[1], F_GETFL);
    fcntl(pipefd[1], F_SETFL, flags | O_NONBLOCK);
    
    const char* nonblock_data = "非阻塞数据";
    iov.iov_base = (void*)nonblock_data;
    iov.iov_len = strlen(nonblock_data);
    
    result = vmsplice(pipefd[1], &iov, 1, SPLICE_F_NONBLOCK);
    if (result > 0) {
        printf("   ✓ 非阻塞传输成功: %zd 字节\n", result);
    } else if (result == -1) {
        if (errno == EAGAIN) {
            printf("   ✓ 非阻塞模式下暂时无法传输 (EAGAIN)\n");
        } else {
            printf("   ✗ 传输失败: %s\n", strerror(errno));
        }
    }
    
    // 演示大数据传输
    printf("\n2. 大数据传输演示:\n");
    
    // 重置管道为阻塞模式
    fcntl(pipefd[1], F_SETFL, flags);
    
    // 分配大块内存
    size_t large_data_size = 64 * 1024;  // 64KB
    char* large_data = malloc(large_data_size);
    if (large_data) {
        // 填充数据
        for (size_t i = 0; i < large_data_size; i++) {
            large_data[i] = 'A' + (i % 26);
        }
        
        iov.iov_base = large_data;
        iov.iov_len = large_data_size;
        
        clock_t start_time = clock();
        result = vmsplice(pipefd[1], &iov, 1, SPLICE_F_MOVE);
        clock_t end_time = clock();
        
        if (result > 0) {
            double elapsed_time = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
            printf("   大数据传输: %zd 字节\n", result);
            printf("   耗时: %.6f 秒\n", elapsed_time);
            printf("   吞吐量: %.2f MB/s\n", (result / (1024.0 * 1024.0)) / elapsed_time);
        }
        
        free(large_data);
    }
    
    // 清理资源
    close(pipefd[0]);
    close(pipefd[1]);
    
    printf("=== 高级特性演示完成 ===\n\n");
}

int main() {
    printf("=== vmsplice与内存映射结合使用示例 ===\n");
    
    // 演示文件到管道的高效传输
    demonstrate_file_to_pipe_transfer();
    
    // 演示高级vmsplice特性
    advanced_vmsplice_features();
    
    // 错误处理和边界情况演示
    printf("=== 错误处理演示 ===\n");
    
    // 使用无效参数
    printf("1. 无效参数测试:\n");
    
    ssize_t result = vmsplice(-1, NULL, 0, 0);
    if (result == -1) {
        printf("   无效文件描述符: %s (预期)\n", strerror(errno));
    }
    
    // 使用空向量
    int dummy_pipe[2];
    if (pipe(dummy_pipe) == 0) {
        result = vmsplice(dummy_pipe[1], NULL, 0, 0);
        if (result == -1) {
            printf("   空向量: %s (预期)\n", strerror(errno));
        }
        close(dummy_pipe[0]);
        close(dummy_pipe[1]);
    }
    
    // 使用无效标志
    if (pipe(dummy_pipe) == 0) {
        struct iovec iov = {(void*)"test", 4};
        result = vmsplice(dummy_pipe[1], &iov, 1, 0xFFFFFFFF);
        if (result == -1) {
            printf("   无效标志: %s (预期)\n", strerror(errno));
        }
        close(dummy_pipe[0]);
        close(dummy_pipe[1]);
    }
    
    printf("=== 错误处理演示完成 ===\n");
    
    printf("\n=== vmsplice综合演示完成 ===\n");
    
    return 0;
}

示例4:实际应用场景 – 网络数据转发

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <pthread.h>

#define BUFFER_SIZE 8192
#define MAX_PACKETS 1000

// 数据包结构
typedef struct {
    char data[BUFFER_SIZE];
    size_t length;
    int packet_id;
    time_t timestamp;
} data_packet_t;

// 转发器统计信息
typedef struct {
    volatile long long packets_forwarded;
    volatile long long bytes_forwarded;
    volatile long long packets_dropped;
    time_t start_time;
} forwarder_stats_t;

forwarder_stats_t stats = {0};

// 模拟网络接收缓冲区
typedef struct {
    char* buffer;
    size_t size;
    size_t offset;
} network_buffer_t;

// 创建模拟网络缓冲区
network_buffer_t* create_network_buffer(size_t size) {
    network_buffer_t* nb = malloc(sizeof(network_buffer_t));
    if (!nb) return NULL;
    
    nb->buffer = malloc(size);
    if (!nb->buffer) {
        free(nb);
        return NULL;
    }
    
    nb->size = size;
    nb->offset = 0;
    
    // 填充模拟数据
    for (size_t i = 0; i < size; i++) {
        nb->buffer[i] = 'A' + (i % 26);
    }
    
    return nb;
}

// 从网络缓冲区读取数据包
int read_packet_from_buffer(network_buffer_t* nb, char* packet_buffer, size_t max_size) {
    if (nb->offset >= nb->size) {
        return 0;  // 没有更多数据
    }
    
    // 模拟不同大小的数据包
    size_t packet_size = 100 + (rand() % 1000);
    if (packet_size > max_size) {
        packet_size = max_size;
    }
    
    if (nb->offset + packet_size > nb->size) {
        packet_size = nb->size - nb->offset;
    }
    
    if (packet_size > 0) {
        memcpy(packet_buffer, nb->buffer + nb->offset, packet_size);
        nb->offset += packet_size;
        return packet_size;
    }
    
    return 0;
}

// 使用vmsplice转发数据包
int forward_packet_with_vmsplice(int pipe_fd, const char* packet_data, size_t packet_size) {
    struct iovec iov;
    iov.iov_base = (void*)packet_data;
    iov.iov_len = packet_size;
    
    ssize_t result = vmsplice(pipe_fd, &iov, 1, SPLICE_F_MOVE | SPLICE_F_NONBLOCK);
    
    if (result > 0) {
        __atomic_fetch_add(&stats.packets_forwarded, 1, __ATOMIC_RELAXED);
        __atomic_fetch_add(&stats.bytes_forwarded, result, __ATOMIC_RELAXED);
        return 0;  // 成功
    } else if (result == -1) {
        if (errno == EAGAIN) {
            // 管道满,数据包被丢弃
            __atomic_fetch_add(&stats.packets_dropped, 1, __ATOMIC_RELAXED);
            return 1;  // 丢弃
        } else {
            perror("vmsplice转发失败");
            return -1;  // 错误
        }
    }
    
    return -1;
}

// 数据包生成器线程
void* packet_generator_thread(void* arg) {
    int pipe_write_fd = *(int*)arg;
    network_buffer_t* nb = create_network_buffer(1024 * 1024);  // 1MB缓冲区
    
    if (!nb) {
        printf("生成器: 创建网络缓冲区失败\n");
        return NULL;
    }
    
    printf("生成器线程启动\n");
    
    char packet_buffer[BUFFER_SIZE];
    int packet_count = 0;
    
    // 生成数据包
    while (packet_count < MAX_PACKETS) {
        int packet_size = read_packet_from_buffer(nb, packet_buffer, sizeof(packet_buffer));
        
        if (packet_size > 0) {
            // 使用vmsplice转发数据包
            int result = forward_packet_with_vmsplice(pipe_write_fd, packet_buffer, packet_size);
            
            if (result == 0) {
                packet_count++;
                if (packet_count % 100 == 0) {
                    printf("生成器: 已生成 %d 个数据包\n", packet_count);
                }
            } else if (result == 1) {
                printf("生成器: 数据包被丢弃 (管道满)\n");
            } else {
                printf("生成器: 转发错误\n");
                break;
            }
            
            // 模拟网络延迟
            usleep(1000);  // 1毫秒
        } else {
            break;  // 没有更多数据
        }
    }
    
    free(nb->buffer);
    free(nb);
    
    printf("生成器线程完成,共生成 %d 个数据包\n", packet_count);
    return NULL;
}

// 数据包处理器线程
void* packet_processor_thread(void* arg) {
    int pipe_read_fd = *(int*)arg;
    
    printf("处理器线程启动\n");
    
    char buffer[BUFFER_SIZE];
    int processed_packets = 0;
    
    // 处理数据包
    while (processed_packets < MAX_PACKETS) {
        ssize_t bytes_received = read(pipe_read_fd, buffer, sizeof(buffer));
        
        if (bytes_received > 0) {
            // 模拟数据包处理
            processed_packets++;
            
            if (processed_packets % 100 == 0) {
                printf("处理器: 已处理 %d 个数据包\n", processed_packets);
            }
            
            // 模拟处理时间
            usleep(500);  // 0.5毫秒
        } else if (bytes_received == 0) {
            printf("处理器: 管道已关闭\n");
            break;
        } else {
            if (errno == EAGAIN) {
                usleep(1000);  // 等待1毫秒后重试
                continue;
            } else {
                perror("处理器: 读取数据失败");
                break;
            }
        }
    }
    
    printf("处理器线程完成,共处理 %d 个数据包\n", processed_packets);
    return NULL;
}

// 显示转发统计
void show_forwarding_statistics() {
    time_t current_time = time(NULL);
    double elapsed_time = difftime(current_time, stats.start_time);
    
    long long packets_forwarded = __atomic_load_n(&stats.packets_forwarded, __ATOMIC_RELAXED);
    long long bytes_forwarded = __atomic_load_n(&stats.bytes_forwarded, __ATOMIC_RELAXED);
    long long packets_dropped = __atomic_load_n(&stats.packets_dropped, __ATOMIC_RELAXED);
    
    printf("\n=== 转发统计 ===\n");
    printf("转发数据包: %lld\n", packets_forwarded);
    printf("转发字节数: %lld (%.2f MB)\n", bytes_forwarded, bytes_forwarded / (1024.0 * 1024.0));
    printf("丢弃数据包: %lld\n", packets_dropped);
    printf("运行时间: %.2f 秒\n", elapsed_time);
    
    if (elapsed_time > 0) {
        printf("平均转发速率: %.2f 包/秒\n", packets_forwarded / elapsed_time);
        printf("平均吞吐量: %.2f MB/s\n", (bytes_forwarded / (1024.0 * 1024.0)) / elapsed_time);
    }
    
    if (packets_forwarded + packets_dropped > 0) {
        double drop_rate = (double)packets_dropped / (packets_forwarded + packets_dropped) * 100;
        printf("丢包率: %.2f%%\n", drop_rate);
    }
    printf("================\n\n");
}

// 性能基准测试
void performance_benchmark() {
    printf("=== vmsplice性能基准测试 ===\n");
    
    int pipefd[2];
    if (pipe(pipefd) == -1) {
        perror("创建管道失败");
        return;
    }
    
    // 设置非阻塞模式
    int flags = fcntl(pipefd[1], F_GETFL);
    fcntl(pipefd[1], F_SETFL, flags | O_NONBLOCK);
    flags = fcntl(pipefd[0], F_GETFL);
    fcntl(pipefd[0], F_SETFL, flags | O_NONBLOCK);
    
    // 准备测试数据
    size_t test_sizes[] = {1024, 4096, 16384, 65536, 262144};  // 1KB到256KB
    int num_sizes = sizeof(test_sizes) / sizeof(test_sizes[0]);
    
    printf("%-10s %-15s %-15s %-15s\n", "大小", "vmsplice", "write", "性能提升");
    printf("%-10s %-15s %-15s %-15s\n", "----", "--------", "-----", "--------");
    
    for (int i = 0; i < num_sizes; i++) {
        size_t size = test_sizes[i];
        char* test_data = malloc(size);
        if (!test_data) continue;
        
        // 填充测试数据
        memset(test_data, 'X', size);
        
        struct iovec iov;
        iov.iov_base = test_data;
        iov.iov_len = size;
        
        // 测试vmsplice
        clock_t start = clock();
        ssize_t vmsplice_result = vmsplice(pipefd[1], &iov, 1, SPLICE_F_MOVE | SPLICE_F_NONBLOCK);
        clock_t end = clock();
        double vmsplice_time = ((double)(end - start)) / CLOCKS_PER_SEC;
        
        // 清空管道
        char dummy_buffer[1024 * 1024];
        while (read(pipefd[0], dummy_buffer, sizeof(dummy_buffer)) > 0);
        
        // 测试传统write
        start = clock();
        ssize_t write_result = write(pipefd[1], test_data, size);
        end = clock();
        double write_time = ((double)(end - start)) / CLOCKS_PER_SEC;
        
        // 清空管道
        while (read(pipefd[0], dummy_buffer, sizeof(dummy_buffer)) > 0);
        
        double speedup = (write_time > 0) ? (write_time / vmsplice_time) : 0;
        
        printf("%-10zu %-15.6f %-15.6f %-15.2fx\n", 
               size, vmsplice_time, write_time, speedup);
        
        free(test_data);
    }
    
    close(pipefd[0]);
    close(pipefd[1]);
    
    printf("=== 性能基准测试完成 ===\n\n");
}

int main() {
    printf("=== 网络数据转发应用示例 ===\n");
    
    // 执行性能基准测试
    performance_benchmark();
    
    // 初始化统计
    stats.start_time = time(NULL);
    
    // 创建管道用于线程间通信
    int pipefd[2];
    if (pipe(pipefd) == -1) {
        perror("创建管道失败");
        exit(EXIT_FAILURE);
    }
    
    // 设置管道为非阻塞模式
    int flags = fcntl(pipefd[1], F_GETFL);
    fcntl(pipefd[1], F_SETFL, flags | O_NONBLOCK);
    flags = fcntl(pipefd[0], F_GETFL);
    fcntl(pipefd[0], F_SETFL, flags | O_NONBLOCK);
    
    printf("创建转发管道: 读端=%d, 写端=%d\n", pipefd[0], pipefd[1]);
    
    // 创建线程
    pthread_t generator_thread, processor_thread;
    
    // 启动数据包生成器线程
    if (pthread_create(&generator_thread, NULL, packet_generator_thread, &pipefd[1]) != 0) {
        perror("创建生成器线程失败");
        close(pipefd[0]);
        close(pipefd[1]);
        exit(EXIT_FAILURE);
    }
    
    // 启动数据包处理器线程
    if (pthread_create(&processor_thread, NULL, packet_processor_thread, &pipefd[0]) != 0) {
        perror("创建处理器线程失败");
        close(pipefd[0]);
        close(pipefd[1]);
        exit(EXIT_FAILURE);
    }
    
    // 主线程定期显示统计信息
    for (int i = 0; i < 30; i++) {  // 运行30秒
        sleep(2);
        show_forwarding_statistics();
    }
    
    // 等待线程完成
    pthread_join(generator_thread, NULL);
    pthread_join(processor_thread, NULL);
    
    // 显示最终统计
    show_forwarding_statistics();
    
    // 清理资源
    close(pipefd[0]);
    close(pipefd[1]);
    
    printf("=== 网络数据转发应用演示完成 ===\n");
    
    return 0;
}

编译和运行

# 编译示例(需要定义_GNU_SOURCE)
gcc -D_GNU_SOURCE -o vmsplice_example1 vmsplice_example1.c
gcc -D_GNU_SOURCE -o vmsplice_example2 vmsplice_example2.c
gcc -D_GNU_SOURCE -o vmsplice_example3 vmsplice_example3.c
gcc -D_GNU_SOURCE -o vmsplice_example4 vmsplice_example4.c -lpthread

# 运行示例
./vmsplice_example1
./vmsplice_example2
./vmsplice_example3
./vmsplice_example4

重要注意事项

  1. 内核支持: vmsplice需要Linux 2.6.17或更高版本内核支持
  2. 权限要求: 需要适当的权限来创建和访问管道
  3. 内存管理: 使用SPLICE_F_GIFT标志时要注意内存所有权转移
  4. 非阻塞模式: 建议在生产环境中使用非阻塞模式避免阻塞
  5. 错误处理: 必须检查返回值并处理EAGAIN等错误
  6. 性能考虑: vmsplice在大数据传输时优势明显
  7. 线程安全: 管道操作是线程安全的

最佳实践

  1. 使用非阻塞模式: 避免无限期阻塞
  2. 合理设置标志: 根据应用场景选择合适的标志
  3. 内存管理: 正确处理内存所有权和生命周期
  4. 错误处理: 完善的错误处理和恢复机制
  5. 性能监控: 监控传输性能和系统资源使用
  6. 批量传输: 使用向量传输提高效率
  7. 资源清理: 及时关闭文件描述符和释放内存

通过这些示例,你可以理解vmsplice在高效数据传输方面的强大功能,它为Linux系统提供了零拷贝或最小拷贝的数据传输机制,特别适用于高性能网络应用、大数据处理和实时系统。

发表在 linux文章 | 留下评论

wait4系统调用及示例

1. 函数介绍

wait4 是一个 Linux 系统调用,它是 waitpid 的一个扩展版本。它的主要功能是等待子进程的状态发生变化(通常是子进程终止或停止),并获取该子进程的退出状态信息

你可以把 wait4 想象成一位家长在等待孩子(子进程)完成任务后回来汇报情况:

  • 家长(父进程)给孩子(子进程)布置了一个任务(比如运行一个程序)。
  • 孩子出去执行任务。
  • 家长调用 wait4,表示“我在家等你回来,告诉我任务完成得怎么样”。
  • 孩子完成任务回家(子进程终止)。
  • wait4 返回,告诉家长孩子的 PID 和他是如何完成任务的(成功、失败、被中断等)。

wait4 比 wait 和 waitpid 更强大,因为它不仅可以获取子进程的 PID 和状态,还可以同时获取子进程使用的资源统计信息(如用户 CPU 时间、系统 CPU 时间、最大内存使用量等)。


2. 函数原型

#include <sys/wait.h> // 必需
#include <sys/resource.h> // 包含 struct rusage

pid_t wait4(pid_t pid, int *wstatus, int options, struct rusage *rusage);

3. 功能

  • 等待子进程: 挂起调用进程(父进程),直到由 pid 参数指定的一个或多个子进程的状态发生变化。
  • 获取状态: 当子进程状态变化被检测到时,wait4 会返回该子进程的进程 ID (PID),并将其退出状态(exit status)存储到 wstatus 指向的整型变量中。
  • 获取资源使用信息(可选): 如果 rusage 参数非空,wait4 还会将子进程的资源使用统计信息(Resource Usage)填充到 rusage 指向的 struct rusage 结构体中。

4. 参数

  • pid_t pid: 指定要等待的子进程的 ID。其行为与 waitpid 的 pid 参数完全相同:
    • < -1: 等待进程组 ID 等于 pid 绝对值的任意子进程。
    • -1: 等待任意子进程(这是最常用的情况)。
    • 0: 等待调用进程组 ID 与调用进程相同的任意子进程。
    • > 0: 等待进程 ID 等于 pid 的特定子进程。
  • int *wstatus: 这是一个指向 int 类型变量的指针,用于接收子进程的退出状态。
    • 如果不需要获取状态,可以传入 NULL(但在实践中很少这么做)。
    • 子进程的退出状态包含了它是如何结束的(正常退出、被信号终止等)以及具体的退出码或信号编号。
    • 通常使用 <sys/wait.h> 中定义的来检查和解析 wstatus 的值:
      • WIFEXITED(wstatus): 如果子进程是正常退出(通过 exit() 或从 main 返回),返回真(非 0)。
      • WEXITSTATUS(wstatus): 如果 WIFEXITED(wstatus) 为真,返回子进程的退出码(0-255)。
      • WIFSIGNALED(wstatus): 如果子进程是被信号终止的,返回真。
      • WTERMSIG(wstatus): 如果 WIFSIGNALED(wstatus) 为真,返回终止子进程的信号编号
      • WIFSTOPPED(wstatus): 如果子进程当前是停止状态(通常由 SIGSTOPSIGTSTPSIGTTINSIGTTOU 信号导致),返回真。
      • WSTOPSIG(wstatus): 如果 WIFSTOPPED(wstatus) 为真,返回导致子进程停止的信号编号
  • int options: 这是一个位掩码,用于修改 wait4 的行为。它可以是以下零个或多个标志的按位或(OR)组合:
    • WNOHANG: 如果没有子进程状态立即可用,则 wait4 不阻塞,立即返回 0。
    • WUNTRACED: 报告因信号而停止的子进程状态(即使没有追踪它们)。
    • WCONTINUED: 报告先前因信号停止、现已收到 SIGCONT 信号并继续执行的子进程。
  • struct rusage *rusage: 这是一个指向 struct rusage 结构体的指针。
    • 如果非 NULLwait4 会将子进程的资源使用统计信息填充到该结构体中。
    • 如果为 NULL,则不收集资源信息。
      struct rusage 包含了很多关于子进程执行期间资源消耗的详细信息,例如:
    struct rusage { struct timeval ru_utime; // 用户 CPU 时间 struct timeval ru_stime; // 系统 CPU 时间 long ru_maxrss; // 最大常驻集大小 (KB) long ru_ixrss; // 共享内存大小积分 (未维护) long ru_idrss; // 未共享数据大小积分 (未维护) long ru_isrss; // 未共享栈大小积分 (未维护) long ru_minflt; // 缺页中断次数 (无需从磁盘加载页面) long ru_majflt; // 主缺页中断次数 (需要从磁盘加载页面) long ru_nswap; // 内存交换次数 (未维护) long ru_inblock; // 文件系统读入操作次数 long ru_oublock; // 文件系统写入操作次数 long ru_msgsnd; // IPC 消息发送次数 (未维护) long ru_msgrcv; // IPC 消息接收次数 (未维护) long ru_nsignals; // 信号接收次数 long ru_nvcsw; // 自愿上下文切换次数 long ru_nivcsw; // 非自愿上下文切换次数 };
    • 注意: 并非所有字段在所有系统上都得到维护或精确计算。

5. 返回值

  • 成功时:
    • 返回已更改状态的子进程的 PID
  • 失败时:
    • 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 ECHILD 没有符合条件的子进程,EINTR 调用被信号中断,EINVAL options 参数无效)。
  • WNOHANG 且无子进程状态改变时:
    • 返回 0。

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

  • waitwait(&status) 等价于 wait4(-1, &status, 0, NULL)。它是最简单的等待任意子进程结束的函数。
  • waitpidwaitpid(pid, &status, options) 等价于 wait4(pid, &status, options, NULL)。它比 wait 更灵活,允许指定等待哪个子进程以及设置选项。
  • waitid: POSIX.1-2001 标准引入的更现代的等待函数,提供了更细粒度的控制和信息。
  • getrusage: 用于获取调用进程自身或其已终止子进程的资源使用信息。wait4 的 rusage 参数提供了类似的功能,但针对特定的已终止子进程。

7. 示例代码

示例 1:基本的 wait4 使用

这个例子演示了 wait4 最基本的用法,等待子进程结束并获取其退出状态。

// wait4_basic.c
#include <sys/wait.h>   // wait4, WIFEXITED, WEXITSTATUS, WIFSIGNALED, WTERMSIG
#include <sys/resource.h> // struct rusage
#include <unistd.h>     // fork, getpid
#include <stdio.h>      // printf, perror
#include <stdlib.h>     // exit

int main() {
    pid_t pid;
    int status;
    struct rusage usage;

    printf("Parent process (PID: %d) starting.\n", getpid());

    pid = fork();
    if (pid == -1) {
        perror("fork failed");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        // --- 子进程 ---
        printf("Child process (PID: %d) started.\n", getpid());

        // 模拟一些工作
        for (int i = 0; i < 3; ++i) {
            printf("  Child working... %d\n", i + 1);
            sleep(1);
        }

        // 子进程以特定状态码退出
        int exit_code = 42;
        printf("Child process (PID: %d) finished. Exiting with code %d.\n", getpid(), exit_code);
        exit(exit_code); // 正常退出

    } else {
        // --- 父进程 ---
        printf("Parent process (PID: %d) created child (PID: %d).\n", getpid(), pid);

        // --- 关键: 调用 wait4 等待子进程结束 ---
        // pid: 等待特定子进程 pid
        // &status: 接收子进程退出状态
        // 0: 默认选项 (阻塞等待)
        // &usage: 接收资源使用信息
        pid_t waited_pid = wait4(pid, &status, 0, &usage);

        if (waited_pid == -1) {
            perror("wait4 failed");
            exit(EXIT_FAILURE);
        }

        printf("Parent: wait4 returned. Waited for child PID %d.\n", (int)waited_pid);

        // --- 检查和解析子进程退出状态 ---
        if (WIFEXITED(status)) {
            int exit_status = WEXITSTATUS(status);
            printf("Parent: Child exited normally with status code %d.\n", exit_status);
        } else if (WIFSIGNALED(status)) {
            int signal_num = WTERMSIG(status);
            printf("Parent: Child was killed by signal %d.\n", signal_num);
        } else {
            printf("Parent: Child did not exit normally or by signal.\n");
        }

        // --- 打印资源使用信息 ---
        printf("\n--- Resource Usage of Child (PID: %d) ---\n", (int)waited_pid);
        printf("User CPU time used: %ld.%06ld seconds\n",
               (long)usage.ru_utime.tv_sec, (long)usage.ru_utime.tv_usec);
        printf("System CPU time used: %ld.%06ld seconds\n",
               (long)usage.ru_stime.tv_sec, (long)usage.ru_stime.tv_usec);
        printf("Maximum resident set size: %ld KB\n", usage.ru_maxrss);
        printf("Page reclaims (soft page faults): %ld\n", usage.ru_minflt);
        printf("Page faults (hard page faults): %ld\n", usage.ru_majflt);
        printf("Voluntary context switches: %ld\n", usage.ru_nvcsw);
        printf("Involuntary context switches: %ld\n", usage.ru_nivcsw);
        printf("------------------------------------------\n");

        printf("Parent process (PID: %d) finished.\n", getpid());
    }

    return 0;
}

代码解释:

  1. 父进程调用 fork() 创建子进程。
  2. 子进程:
    • 执行一些模拟工作(循环打印并 sleep)。
    • 以状态码 42 调用 exit(42) 正常退出。
  3. 父进程:
    • 调用 wait4(pid, &status, 0, &usage)
      • pid: 等待之前创建的特定子进程。
      • &status: 指向 int 变量,用于接收退出状态。
      • 0: 使用默认选项,即阻塞等待。
      • &usage: 指向 struct rusage 变量,用于接收资源使用信息。
    • 检查 wait4 的返回值。如果成功,返回值是子进程的 PID。
    • 使用 WIFEXITED 和 WEXITSTATUS 宏检查子进程是否正常退出,并获取其退出码(42)。
    • 打印从 rusage 结构体中获取的资源使用统计信息,如用户 CPU 时间、系统 CPU 时间、最大内存使用量等。

示例 2:使用 wait4 等待任意子进程 (pid = -1)

这个例子演示了如何使用 wait4 等待任意一个子进程结束。

// wait4_any.c
#include <sys/wait.h>
#include <sys/resource.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#define NUM_CHILDREN 3

int main() {
    pid_t pid;
    int i;

    printf("Parent process (PID: %d) creating %d children.\n", getpid(), NUM_CHILDREN);

    // 1. 创建多个子进程
    for (i = 0; i < NUM_CHILDREN; i++) {
        pid = fork();
        if (pid == -1) {
            perror("fork failed");
            exit(EXIT_FAILURE);
        } else if (pid == 0) {
            // --- 子进程 ---
            printf("Child %d (PID: %d) started.\n", i, getpid());
            // 每个子进程睡眠不同的时间
            sleep(i + 2);
            printf("Child %d (PID: %d) finished. Exiting with code %d.\n", i, getpid(), i);
            exit(i); // 以 i 作为退出码
        }
        // --- 父进程继续循环 ---
    }

    printf("Parent (PID: %d) created all children. Now waiting for them to finish.\n", getpid());

    // 2. 循环等待所有子进程结束
    // 使用 wait4(pid=-1, ...) 等待任意子进程
    for (i = 0; i < NUM_CHILDREN; i++) {
        int status;
        struct rusage usage;
        pid_t waited_pid;

        // --- 关键: 使用 pid = -1 等待任意子进程 ---
        waited_pid = wait4(-1, &status, 0, &usage);

        if (waited_pid == -1) {
            perror("wait4 failed");
            // 可能需要更复杂的错误处理
            continue;
        }

        printf("\nParent: wait4 returned. Waited for child PID %d.\n", (int)waited_pid);

        if (WIFEXITED(status)) {
            int exit_code = WEXITSTATUS(status);
            printf("Parent: Child (PID: %d) exited normally with code %d.\n", (int)waited_pid, exit_code);
        } else {
            printf("Parent: Child (PID: %d) did not exit normally.\n", (int)waited_pid);
        }

        printf("Resource usage summary for child (PID: %d):\n", (int)waited_pid);
        printf("  User CPU time: %ld.%06lds\n", (long)usage.ru_utime.tv_sec, (long)usage.ru_utime.tv_usec);
        printf("  Sys CPU time: %ld.%06lds\n", (long)usage.ru_stime.tv_sec, (long)usage.ru_stime.tv_usec);
        printf("  Max RSS: %ld KB\n", usage.ru_maxrss);
    }

    printf("\nParent (PID: %d) finished. All children have been reaped.\n", getpid());
    return 0;
}

代码解释:

  1. 父进程通过循环调用 fork() 创建 3 个子进程。
  2. 每个子进程执行不同的睡眠时间(2秒、3秒、4秒),然后以自己的索引 i 作为退出码退出。
  3. 父进程进入另一个循环,调用 NUM_CHILDREN 次 wait4
  4. 关键: 每次调用 wait4(-1, &status, 0, &usage)
    • pid = -1: 表示等待任意一个子进程结束。
  5. 每次 wait4 返回,就处理一个子进程的退出信息和资源使用情况。
  6. 循环结束后,所有子进程都被回收。

示例 3:使用 wait4 的 WNOHANG 选项

这个例子演示了如何使用 WNOHANG 选项让 wait4 非阻塞地检查是否有子进程已经结束。

// wait4_nonblock.c
#include <sys/wait.h>
#include <sys/resource.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h> // errno, ECHILD

int main() {
    pid_t pid;
    int i;

    printf("Parent process (PID: %d) creating 2 children.\n", getpid());

    // 1. 创建两个子进程
    for (i = 0; i < 2; i++) {
        pid = fork();
        if (pid == -1) {
            perror("fork failed");
            exit(EXIT_FAILURE);
        } else if (pid == 0) {
            // --- 子进程 ---
            printf("Child %d (PID: %d) started, will sleep for %d seconds.\n", i, getpid(), 5 + i * 2);
            sleep(5 + i * 2); // 第一个睡 5 秒,第二个睡 7 秒
            printf("Child %d (PID: %d) finished. Exiting.\n", i, getpid());
            exit(0);
        }
    }

    printf("Parent (PID: %d) created children. Now polling with WNOHANG.\n", getpid());

    int children_finished = 0;
    int loop_count = 0;

    // 2. 循环,使用 WNOHANG 非阻塞地检查子进程状态
    while (children_finished < 2) {
        loop_count++;
        int status;
        struct rusage usage;
        pid_t waited_pid;

        // --- 关键: 使用 WNOHANG 选项 ---
        waited_pid = wait4(-1, &status, WNOHANG, &usage);

        if (waited_pid == -1) {
            if (errno == ECHILD) {
                // 没有子进程了,但这在循环中不太可能发生
                // 因为我们知道有两个子进程
                printf("No children exist (ECHILD). This is unexpected in this loop.\n");
                break;
            } else {
                perror("wait4 failed");
                break;
            }
        } else if (waited_pid == 0) {
            // 没有子进程状态改变
            printf("Loop %d: No child has finished yet. Parent doing other work...\n", loop_count);
            // 模拟父进程在等待期间做其他事情
            sleep(1); // 实际应用中可能是处理其他任务
        } else {
            // 有一个子进程结束了
            children_finished++;
            printf("\nLoop %d: Parent: wait4 returned. Child PID %d has finished.\n", loop_count, (int)waited_pid);
            if (WIFEXITED(status)) {
                printf("  Child (PID: %d) exited normally with code %d.\n", (int)waited_pid, WEXITSTATUS(status));
            }
            printf("  Parent did other work for %d loop(s) while waiting.\n", loop_count);
        }
    }

    printf("\nParent (PID: %d) finished. Both children have been reaped after %d loops.\n", getpid(), loop_count);
    return 0;
}

代码解释:

  1. 父进程创建两个子进程,它们分别睡眠 5 秒和 7 秒。
  2. 父进程进入一个 while 循环。
  3. 关键: 在循环内部调用 wait4(-1, &status, WNOHANG, &usage)
    • WNOHANG: 使 wait4 成为非阻塞调用。
  4. 检查 wait4 的返回值:
    • waited_pid == -1: 调用失败。检查 errno 是否为 ECHILD
    • waited_pid == 0没有子进程状态改变。父进程可以在此时执行其他任务(这里用 sleep(1) 模拟)。
    • waited_pid > 0: 一个子进程结束了。处理其状态,并增加计数器 children_finished
  5. 循环直到两个子进程都结束。
  6. 打印父进程在等待期间执行了多少次循环(模拟做了多少其他工作)。

重要提示与注意事项:

  1. 僵尸进程: 当子进程结束而父进程尚未调用 wait4(或 waitwaitpid)来获取其状态时,子进程会变成僵尸进程(Zombie Process)。这会浪费一个进程表项。因此,父进程必须等待其子进程。
  2. ECHILD 错误: 如果没有符合条件的子进程(例如,所有子进程都已结束且状态已被回收),wait4 会返回 -1 并设置 errno 为 ECHILD
  3. EINTR 错误: 如果 wait4(阻塞模式)在等待期间被信号中断,它会返回 -1 并设置 errno 为 EINTR。可以使用 sigaction 设置 SA_RESTART 标志或在循环中处理此错误。
  4. rusage 的准确性rusage 提供的资源信息的准确性和完整性可能因系统和内核版本而异。某些字段可能未被维护。
  5. WNOHANG 的用途WNOHANG 对于实现非阻塞的服务器或需要在等待子进程的同时处理其他任务的程序非常有用。
  6. 与 wait/waitpid 的关系:
    • wait(&status) 等价于 wait4(-1, &status, 0, NULL)
    • waitpid(pid, &status, options) 等价于 wait4(pid, &status, options, NULL)

总结:

wait4 是一个功能强大的系统调用,用于等待子进程状态变化并获取其退出状态和资源使用信息。它通过 pid 参数提供了灵活的等待目标选择,通过 options 参数(特别是 WNOHANG)提供了阻塞/非阻塞行为控制,并通过 rusage 参数提供了宝贵的性能分析数据。理解其参数和返回值对于编写健壮、高效的多进程程序至关重要。

发表在 linux文章 | 留下评论