getitimer系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 setitimer 函数的“伙伴”——getitimer 函数。虽然我们在介绍 setitimer 时已经涉及了 getitimer,但为了完整性和强调其重要性,我们再单独详细讲解一下。


1. 函数介绍

getitimer 是一个 Linux 系统调用,专门用于查询当前进程的间隔计时器(interval timers)的当前状态。它允许程序了解指定类型计时器的剩余时间(距离下次超时还有多久)和重载时间(超时后会自动重启的周期)。

你可以把它想象成查看你设置的闹钟或定时器上还剩多少时间。


2. 函数原型

#include <sys/time.h> // 必需

int getitimer(int which, struct itimerval *curr_value);

3. 功能

  • 查询状态: 获取由 which 参数指定的计时器类型的当前设置。
  • 填充结构体: 将获取到的计时器状态信息填充到调用者提供的 struct itimerval 结构体指针 curr_value 所指向的内存中。

4. 参数

  • int which: 指定要查询的计时器类型。与 setitimer 相同,主要有以下三种:
    • ITIMER_REAL: 实时计时器。时钟源是系统实时时间,超时发送 SIGALRM
    • ITIMER_VIRTUAL: 虚拟计时器。时钟源是进程在用户态的 CPU 时间,超时发送 SIGVTALRM
    • ITIMER_PROF: 性能计时器。时钟源是进程在用户态和内核态的总 CPU 时间,超时发送 SIGPROF
  • struct itimerval *curr_value: 这是一个指向 struct itimerval 结构体的指针。getitimer 调用成功后,会将查询到的计时器当前状态信息存储到这个结构体中。

5. struct itimerval 结构体

这个结构体在 setitimer 和 getitimer 中都使用,用于表示计时器的时间设置:

struct itimerval {
    struct timeval it_interval; // 重载时间 (Interval)
    struct timeval it_value;    // 当前值/剩余时间 (Value)
};

其中 struct timeval 定义了秒和微秒:

struct timeval {
    time_t      tv_sec;         // 秒
    suseconds_t tv_usec;        // 微秒 (0 - 999,999)
};
  • 通过 getitimer 返回的 curr_value:
    • curr_value->it_value: 表示该计时器当前还剩多少时间就会超时。如果计时器未启动或已超时,这个值通常是 0。
    • curr_value->it_interval: 表示该计时器被设置的重载时间(周期)。这个值是在调用 setitimer 时设置的,getitimer 只是将其读取出来。

6. 返回值

  • 成功时: 返回 0。同时,curr_value 指向的结构体被成功填充。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EINVAL which 参数无效)。

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

  • setitimer: 用于设置间隔计时器。
  • getitimer 与 setitimer: 通常成对出现,用于查询和设置计时器。
  • alarm / setitimeralarm(seconds) 大致等价于 setitimer(ITIMER_REAL, ...)
  • timer_gettime: POSIX 定时器 API 中用于获取定时器时间的函数,功能更强大。
  • clock_gettime: 用于获取各种系统时钟的当前时间。

8. 示例代码

示例 1:监控 ITIMER_REAL 的倒计时

这个例子演示了如何在启动一个 ITIMER_REAL 计时器后,使用 getitimer 在循环中监控其剩余时间。

#include <sys/time.h> // getitimer, setitimer
#include <signal.h>   // signal
#include <unistd.h>   // pause
#include <stdio.h>    // printf, perror
#include <stdlib.h>   // exit
#include <string.h>   // memset

volatile sig_atomic_t alarm_fired = 0;

void alarm_handler(int sig) {
    write(STDERR_FILENO, "Timer expired! SIGALRM received.\n", 32);
    alarm_fired = 1;
}

int main() {
    struct itimerval timer, current_timer;
    int seconds_to_wait = 5;

    if (signal(SIGALRM, alarm_handler) == SIG_ERR) {
        perror("signal SIGALRM");
        exit(EXIT_FAILURE);
    }

    // 1. 设置 ITIMER_REAL 计时器
    memset(&timer, 0, sizeof(timer)); // 清零结构体是个好习惯
    timer.it_value.tv_sec = seconds_to_wait; // 5 秒后超时
    timer.it_interval.tv_sec = 0;            // 一次性,不重复

    printf("Setting ITIMER_REAL to expire in %d seconds.\n", seconds_to_wait);

    if (setitimer(ITIMER_REAL, &timer, NULL) == -1) {
        perror("setitimer");
        exit(EXIT_FAILURE);
    }

    printf("Monitoring timer countdown:\n");

    // 2. 循环监控计时器剩余时间
    while (!alarm_fired) {
        if (getitimer(ITIMER_REAL, &current_timer) == -1) {
            perror("getitimer");
            // 即使 getitimer 失败,也继续循环或退出
            break;
        }

        // 打印剩余时间
        printf("  Time remaining: %ld.%06ld seconds\n",
               (long)current_timer.it_value.tv_sec,
               (long)current_timer.it_value.tv_usec);

        // 简单延时 0.5 秒再检查 (可以使用 nanosleep 实现更精确的延时)
        usleep(500000); // 500,000 微秒 = 0.5 秒
    }

    if (alarm_fired) {
        printf("Timer has fired. Program exiting.\n");
    } else {
        printf("Loop exited before timer fired.\n");
    }

    return 0;
}

代码解释:

  1. 设置 SIGALRM 信号处理函数。
  2. 使用 setitimer 启动一个 5 秒后超时的一次性 ITIMER_REAL 计时器。
  3. 进入一个 while 循环,循环条件是 alarm_fired 标志为假。
  4. 在循环内部:
    • 调用 getitimer(ITIMER_REAL, &current_timer) 获取计时器的当前状态。
    • 检查返回值,处理可能的错误。
    • 打印 current_timer.it_value 中的剩余秒数和微秒数。
    • 调用 usleep(500000) 延时 0.5 秒,然后继续下一次循环。
  5. 当 SIGALRM 信号到达,alarm_handler 被调用,设置 alarm_fired 为真,主循环退出。

示例 2:检查周期性计时器的状态

这个例子演示了如何查询一个周期性计时器的剩余时间和重载时间。

#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdatomic.h>
#include <string.h>

volatile atomic_int prof_count = 0;

void prof_handler(int sig) {
    atomic_fetch_add(&prof_count, 1);
    // 简单打印,实际信号处理应更谨慎
    write(STDERR_FILENO, "SIGPROF\n", 8);
}

int main() {
    struct itimerval timer, status;
    const int period_sec = 2;
    const int num_intervals = 3;

    if (signal(SIGPROF, prof_handler) == SIG_ERR) {
        perror("signal SIGPROF");
        exit(EXIT_FAILURE);
    }

    // 1. 设置 ITIMER_PROF 周期性计时器
    memset(&timer, 0, sizeof(timer));
    timer.it_value.tv_sec = period_sec;     // 第一次 2 秒后超时
    timer.it_interval.tv_sec = period_sec;  // 之后每 2 秒超时一次

    printf("Setting periodic ITIMER_PROF timer (every %d seconds).\n", period_sec);

    if (setitimer(ITIMER_PROF, &timer, NULL) == -1) {
        perror("setitimer ITIMER_PROF");
        exit(EXIT_FAILURE);
    }

    printf("Checking timer status immediately after setting:\n");
    if (getitimer(ITIMER_PROF, &status) == -1) {
        perror("getitimer ITIMER_PROF");
        exit(EXIT_FAILURE);
    }
    printf("  Current value (remaining time): %ld.%06ld seconds\n",
           (long)status.it_value.tv_sec, (long)status.it_value.tv_usec);
    printf("  Interval (reload time): %ld.%06ld seconds\n",
           (long)status.it_interval.tv_sec, (long)status.it_interval.tv_usec);

    // 2. 等待几个周期
    printf("Waiting for %d intervals...\n", num_intervals);
    int last_count = 0;
    while (atomic_load(&prof_count) < num_intervals) {
        // 可以在这里做些消耗 CPU 的工作,让 ITIMER_PROF 计时
        // 为了简单,我们用 sleep 模拟时间流逝
        // 注意:sleep 是墙钟时间,而 ITIMER_PROF 是 CPU 时间
        // 如果进程大部分时间在 sleep,ITIMER_PROF 可能不会按预期计时
        // 这里只是演示 getitimer,实际使用中要考虑时钟源
        sleep(1);

        // 检查计时器状态
        if (getitimer(ITIMER_PROF, &status) == -1) {
            perror("getitimer during loop");
        } else {
            // 只在计数变化时打印,减少输出
            int current_count = atomic_load(&prof_count);
            if (current_count != last_count) {
                printf("  After %d SIGPROF signals:\n", current_count);
                printf("    Current value: %ld.%06ld seconds\n",
                       (long)status.it_value.tv_sec, (long)status.it_value.tv_usec);
                printf("    Interval: %ld.%06ld seconds (unchanged)\n",
                       (long)status.it_interval.tv_sec, (long)status.it_interval.tv_usec);
                last_count = current_count;
            }
        }
    }

    printf("Received %d SIGPROF signals. Stopping timer.\n", num_intervals);

    // 3. 停止计时器
    struct itimerval stop_timer = {{0, 0}, {0, 0}};
    if (setitimer(ITIMER_PROF, &stop_timer, NULL) == -1) {
        perror("setitimer stop ITIMER_PROF");
    }

    // 4. 再次检查状态 (应该都是 0)
    printf("Checking timer status after stopping:\n");
    if (getitimer(ITIMER_PROF, &status) == -1) {
        perror("getitimer after stop");
    } else {
        printf("  Current value: %ld.%06ld seconds\n",
               (long)status.it_value.tv_sec, (long)status.it_value.tv_usec);
        printf("  Interval: %ld.%06ld seconds\n",
               (long)status.it_interval.tv_sec, (long)status.it_interval.tv_usec);
    }

    return 0;
}

代码解释:

  1. 设置 SIGPROF 信号处理函数,并使用原子变量 prof_count 来计数。
  2. 使用 setitimer 启动一个每 2 秒触发一次的周期性 ITIMER_PROF 计时器。
  3. 立即调用 getitimer 检查并打印计时器的初始状态,可以看到 it_value 和 it_interval 都被正确设置。
  4. 进入循环等待 SIGPROF 信号。为了简化,循环中使用 sleep(1),但这并不会增加 ITIMER_PROF 的计时,因为 sleep 是墙钟时间,而 ITIMER_PROF 计算的是 CPU 时间。在实际性能分析中,这里应该是消耗 CPU 的工作。
  5. 在循环中定期调用 getitimer,并打印剩余时间。注意 it_interval 始终保持不变,因为它表示的是设置的周期。
  6. 接收到指定数量的信号后,通过设置 it_value 和 it_interval 都为 0 来停止计时器。
  7. 最后再次调用 getitimer,确认计时器已停止(值为 0)。

示例 3:结合 setitimer 的 old_value 和 getitimer

这个例子演示了如何结合使用 setitimer 的 old_value 参数和 getitimer 来管理计时器状态。

#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void alarm_handler(int sig) {
    write(STDERR_FILENO, "SIGALRM\n", 8);
}

void print_timer_status(const char *msg, const struct itimerval *timer) {
    printf("%s:\n", msg);
    printf("  Current value (remaining): %ld.%06ld seconds\n",
           (long)timer->it_value.tv_sec, (long)timer->it_value.tv_usec);
    printf("  Interval (reload): %ld.%06ld seconds\n",
           (long)timer->it_interval.tv_sec, (long)timer->it_interval.tv_usec);
}

int main() {
    struct itimerval initial_timer, old_timer, current_timer;

    if (signal(SIGALRM, alarm_handler) == SIG_ERR) {
        perror("signal SIGALRM");
        exit(EXIT_FAILURE);
    }

    // 1. 设置一个初始计时器 (10秒后超时,不重复)
    memset(&initial_timer, 0, sizeof(initial_timer));
    initial_timer.it_value.tv_sec = 10;
    printf("Setting initial timer for 10 seconds.\n");
    if (setitimer(ITIMER_REAL, &initial_timer, NULL) == -1) {
        perror("setitimer initial");
        exit(EXIT_FAILURE);
    }

    sleep(3); // 等待 3 秒

    // 2. 使用 setitimer 的 old_value 参数获取并保存旧设置
    struct itimerval temp_timer = {{0, 0}, {0, 0}}; // 新的临时设置 (这里设置为 0)
    printf("\nCalling setitimer to get old value (without changing timer).\n");
    // 通过设置一个 '0' 计时器并获取 old_value,可以读取当前状态
    // 但这会取消当前计时器,不太符合 '只读' 的目的
    // 更标准的 '只读' 方式是使用 getitimer

    // 让我们用更清晰的方式:先 getitimer,再用 setitimer 的 old_value
    printf("--- Correct way to get timer status ---\n");
    if (getitimer(ITIMER_REAL, &current_timer) == -1) {
        perror("getitimer");
        exit(EXIT_FAILURE);
    }
    print_timer_status("Status after 3 seconds (using getitimer)", &current_timer);

    // 3. 现在,设置一个新计时器 (5秒),并保存旧设置
    struct itimerval new_timer;
    memset(&new_timer, 0, sizeof(new_timer));
    new_timer.it_value.tv_sec = 5; // 5 秒后超时

    printf("\n--- Setting new timer (5s) and saving old setting ---\n");
    if (setitimer(ITIMER_REAL, &new_timer, &old_timer) == -1) {
        perror("setitimer with old_value");
        exit(EXIT_FAILURE);
    }
    print_timer_status("Saved old timer setting", &old_timer);

    sleep(2); // 再等待 2 秒

    // 4. 检查当前计时器状态
    if (getitimer(ITIMER_REAL, &current_timer) == -1) {
        perror("getitimer current");
    } else {
        print_timer_status("Current timer status (after 2s of new timer)", &current_timer);
    }

    printf("Waiting for new 5-second timer to expire...\n");
    pause(); // 等待 SIGALRM

    // 5. 恢复旧的计时器设置
    printf("\n--- Restoring old timer setting ---\n");
    if (setitimer(ITIMER_REAL, &old_timer, NULL) == -1) {
        perror("setitimer restore old");
        exit(EXIT_FAILURE);
    }

    if (getitimer(ITIMER_REAL, &current_timer) == -1) {
        perror("getitimer after restore");
    } else {
        print_timer_status("Timer status after restore", &current_timer);
    }

    printf("Waiting for restored timer to expire...\n");
    pause(); // 等待恢复的计时器 SIGALRM

    printf("Restored timer expired. Program finished.\n");
    return 0;
}

代码解释:

  1. 设置一个 10 秒后超时的初始计时器。
  2. 等待 3 秒后,调用 getitimer 获取并打印当前计时器状态(剩余约 7 秒)。
  3. 调用 setitimer(ITIMER_REAL, &new_timer, &old_timer)
    • 设置一个新的 5 秒计时器。
    • 旧计时器的设置(剩余约 7 秒)通过 old_value 参数保存在 old_timer 变量中。
  4. 等待 2 秒后,再次调用 getitimer 检查当前(新)计时器的状态(剩余约 3 秒)。
  5. 等待新计时器超时。
  6. 调用 setitimer(ITIMER_REAL, &old_timer, NULL) 将计时器恢复为之前保存的设置(约 7 秒)。
  7. 调用 getitimer 验证恢复是否成功。
  8. 等待恢复的计时器超时。

重要提示与注意事项:

  1. “只读”查询getitimer 是查询计时器状态的标准且推荐方式。虽然可以通过 setitimer 的 old_value 参数间接获取信息(例如设置一个 0 秒的定时器),但这通常会改变计时器状态(取消它),不符合“只读”查询的初衷。
  2. 精度getitimer 返回的时间精度是微秒(tv_usec)。
  3. 时钟源: 返回的 it_value(剩余时间)是基于对应计时器的时钟源的。ITIMER_REAL 是墙钟时间,ITIMER_VIRTUAL 和 ITIMER_PROF 是 CPU 时间。
  4. 状态检查getitimer 是检查计时器是否仍在运行以及还剩多少时间的有效方法。
  5. 与 setitimer 配合getitimer 和 setitimer 经常一起使用,用于保存、恢复或动态调整计时器设置。

总结:

getitimer 是一个专门用于查询进程间隔计时器当前状态的系统调用。它通过填充 struct itimerval 结构体,返回指定计时器的剩余时间和重载时间。理解其与 setitimer 的配合使用对于精确控制和监控基于信号的定时任务至关重要。虽然 setitimer 的 old_value 参数也能提供一些信息,但 getitimer 是进行“只读”状态查询的标准和清晰的方法。

getitimer系统调用及示例-CSDN博客

发表在 linux文章 | 标签为 | 留下评论

linux命名空间系统调用及示例

我们来通过一个完整、易懂的示例来演示 Linux 命名空间相关的四个核心系统调用:cloneunsharesetns 和 ioctl_ns (通过 ioctl)。

这个示例将模拟一个简单的容器环境创建和管理过程,包含以下步骤:

  1. 使用 clone 创建一个带有隔离环境的新进程(容器的“init”进程)
  2. 在新进程中使用 unshare 进一步隔离其网络命名空间
  3. 父进程使用 setns 加入子进程的 Mount 命名空间
  4. 父进程使用 ioctl 查询子进程命名空间的信息

linux命名空间系统调用及示例

getgroups系统调用及示例

linux命名空间系统调用及示例-CSDN博客

为了简化,我们将重点放在 Mount (mnt) 和 Network (net) 命名空间上。

重要提示

  • 需要 root 权限:操作命名空间,特别是挂载文件系统,通常需要 root 权限。
  • 环境要求:你的 Linux 内核需要支持这些命名空间(现代 Linux 发行版默认支持)。
  • 安全:此示例涉及系统级操作,请在测试环境中运行。

完整示例代码

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>    // 包含 clone 标志和 unshare
#include <sys/syscall.h> // 包含 syscall 和系统调用号
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h> // 包含 waitpid
#include <fcntl.h>    // 包含 open, O_RDONLY 等
#include <errno.h>
#include <string.h>
#include <sys/mount.h> // 包含 mount
#include <linux/nsfs.h> // 包含 ioctl_ns 的常量 (NS_GET_nstype 等)
#include <sys/ioctl.h>  // 包含 ioctl

// 定义子进程栈大小
#define STACK_SIZE (1024 * 1024) // 1MB

// 子进程1:由 clone 创建,拥有自己的 Mount 和 UTS 命名空间
// 然后它会调用 unshare 来获得独立的 Network 命名空间
int container_init(void *arg) {
    printf("\n--- Inside Container Init Process (PID: %d) ---\n", getpid());

    // 1. 更改容器内的主机名 (在 UTS 命名空间内)
    // 这不会影响宿主机的主机名
    if (sethostname("my-container", strlen("my-container")) == -1) {
        perror("sethostname (in container)");
    } else {
        printf("Container: Set hostname to 'my-container'.\n");
    }

    // 2. 创建一个挂载点并挂载 tmpfs (在 Mount 命名空间内)
    // 这个挂载在宿主机上不可见
    const char* mount_point = "/tmp/container_root";
    if (mkdir(mount_point, 0755) == -1 && errno != EEXIST) {
        perror("mkdir (in container)");
        return 1;
    }

    if (mount("tmpfs", mount_point, "tmpfs", 0, NULL) == -1) {
        perror("mount tmpfs (in container)");
        return 1;
    }
    printf("Container: Mounted tmpfs on %s\n", mount_point);

    // 3. 在挂载点内创建一个文件
    char file_path[256];
    snprintf(file_path, sizeof(file_path), "%s/container_file.txt", mount_point);
    FILE *f = fopen(file_path, "w");
    if (f) {
        fprintf(f, "This file exists only inside the container's mount namespace.\n");
        fclose(f);
        printf("Container: Created file %s\n", file_path);
    } else {
        perror("fopen (in container)");
    }

    // 4. 使用 unshare 脱离当前的 Network 命名空间,进入一个新的空的 Network 命名空间
    // 这使得容器拥有完全隔离的网络视图
    printf("Container: Calling unshare(CLONE_NEWNET) to get isolated network...\n");
    if (unshare(CLONE_NEWNET) == -1) {
        perror("unshare CLONE_NEWNET");
        umount(mount_point); // 清理
        return 1;
    }
    printf("Container: Successfully unshared network namespace.\n");
    printf("Container: Network is now isolated. 'ip link' should show only loopback.\n");

    // 5. 容器主循环:等待信号或执行任务
    // 这里我们简单地睡眠,以便我们可以从外部观察
    printf("Container: Sleeping for 60 seconds. Explore from host and container.\n");
    printf("Container: From host terminal, run:\n");
    printf("  - 'ls /tmp/container_root' (should NOT see the file)\n");
    printf("  - 'sudo nsenter -t %d -n ip link' (should see only loopback)\n", getpid());
    printf("Container: From another terminal (as root), run this program's second part:\n");
    printf("  - './namespace_demo join %d'\n", getpid());

    sleep(60); // 睡眠 60 秒

    // 6. 清理 (退出时内核通常会自动清理命名空间和挂载)
    printf("Container: Cleaning up and exiting.\n");
    umount(mount_point);
    rmdir(mount_point);
    return 0;
}

// 辅助函数:打开并返回指定进程的指定类型命名空间的文件描述符
int open_namespace_fd(pid_t pid, const char* ns_type) {
    char path[256];
    snprintf(path, sizeof(path), "/proc/%d/ns/%s", pid, ns_type);
    int fd = open(path, O_RDONLY);
    if (fd == -1) {
        perror("open_namespace_fd");
        fprintf(stderr, "Failed to open %s namespace for PID %d\n", ns_type, pid);
    }
    return fd;
}

// 辅助函数:使用 ioctl_ns 获取命名空间类型
void query_namespace_type(int ns_fd) {
    // NS_GET_NSTYPE 是一个 ioctl 命令,用于获取命名空间类型
    int ns_type = ioctl(ns_fd, NS_GET_NSTYPE);
    if (ns_type == -1) {
        perror("ioctl NS_GET_NSTYPE");
        return;
    }

    const char* type_str;
    switch (ns_type) {
        case CLONE_NEWNS: type_str = "Mount (CLONE_NEWNS)"; break;
        case CLONE_NEWCGROUP: type_str = "Cgroup (CLONE_NEWCGROUP)"; break;
        case CLONE_NEWUTS: type_str = "UTS (CLONE_NEWUTS)"; break;
        case CLONE_NEWIPC: type_str = "IPC (CLONE_NEWIPC)"; break;
        case CLONE_NEWUSER: type_str = "User (CLONE_NEWUSER)"; break;
        case CLONE_NEWPID: type_str = "PID (CLONE_NEWPID)"; break;
        case CLONE_NEWNET: type_str = "Network (CLONE_NEWNET)"; break;
        default: type_str = "Unknown";
    }
    printf("  Namespace fd %d type is: %s\n", ns_fd, type_str);
}

// 主函数:演示创建和加入命名空间
int main(int argc, char *argv[]) {
    // --- 场景 1: 加入已存在的命名空间 ---
    if (argc == 3 && strcmp(argv[1], "join") == 0) {
        pid_t target_pid = atoi(argv[2]);
        if (target_pid <= 0) {
            fprintf(stderr, "Invalid PID provided.\n");
            exit(EXIT_FAILURE);
        }

        printf("--- Joining Existing Namespace (as separate process) ---\n");
        printf("This process (PID: %d) will join the mount namespace of PID: %d\n", getpid(), target_pid);

        // 1. 打开目标进程的 Mount 命名空间文件描述符
        int target_mnt_ns_fd = open_namespace_fd(target_pid, "mnt");
        if (target_mnt_ns_fd == -1) exit(EXIT_FAILURE);

        // 2. 查询并打印命名空间类型 (使用 ioctl)
        printf("Querying namespace type using ioctl...\n");
        query_namespace_type(target_mnt_ns_fd);

        // 3. 使用 setns 加入目标的 Mount 命名空间
        printf("Calling setns() to join the mount namespace...\n");
        if (syscall(SYS_setns, target_mnt_ns_fd, CLONE_NEWNS) == -1) {
            perror("setns");
            close(target_mnt_ns_fd);
            exit(EXIT_FAILURE);
        }
        printf("Successfully joined the mount namespace of PID %d.\n", target_pid);
        close(target_mnt_ns_fd);

        // 4. 现在,当前进程的文件系统视图与目标进程相同
        printf("Checking for file that exists in the target namespace...\n");
        if (access("/tmp/container_root/container_file.txt", F_OK) == 0) {
            printf("SUCCESS: Found '/tmp/container_root/container_file.txt'. We are inside the container's mount namespace!\n");
        } else {
            printf("FAIL: Could not find the file. setns might have failed or file doesn't exist.\n");
        }

        printf("Exiting join process.\n");
        exit(EXIT_SUCCESS);
    }

    // --- 场景 2: 创建新的隔离进程 (容器) ---
    printf("--- Creating Isolated Process (Container) ---\n");
    printf("Main process PID: %d\n", getpid());

    // 1. 分配子进程栈
    char *child_stack = malloc(STACK_SIZE);
    if (!child_stack) {
        perror("malloc");
        exit(EXIT_FAILURE);
    }

    // 2. 使用 clone 创建子进程,并让它在新的 Mount 和 UTS 命名空间中启动
    // SIGCHLD: 子进程退出时发送 SIGCHLD 信号给父进程
    pid_t container_pid = syscall(SYS_clone,
                                 CLONE_NEWNS | CLONE_NEWUTS | SIGCHLD,
                                 child_stack + STACK_SIZE, // 栈是向下增长的
                                 NULL, NULL);

    if (container_pid == -1) {
        perror("clone");
        free(child_stack);
        exit(EXIT_FAILURE);
    }

    if (container_pid == 0) {
        // --- 在子进程中 ---
        free(child_stack); // 子进程不需要父进程的栈指针
        container_init(NULL); // 运行容器初始化函数
        exit(EXIT_SUCCESS);
    }

    // --- 回到父进程 ---
    printf("\n--- Back in Parent Process ---\n");
    printf("Parent: Created container process with PID: %d\n", container_pid);

    // 等待几秒,让子进程完成初始化 (挂载等)
    sleep(3);

    // 3. 父进程演示:获取子进程的命名空间文件描述符并查询信息
    printf("\n--- Parent: Inspecting Container's Namespaces ---\n");
    int child_mnt_ns_fd = open_namespace_fd(container_pid, "mnt");
    int child_net_ns_fd = open_namespace_fd(container_pid, "net");

    if (child_mnt_ns_fd != -1) {
        printf("Parent: Opened child's Mount namespace fd: %d\n", child_mnt_ns_fd);
        query_namespace_type(child_mnt_ns_fd);
        // 注意:此时父进程还未加入,所以访问 /tmp/container_root 应该失败
        if (access("/tmp/container_root/container_file.txt", F_OK) != 0) {
             printf("Parent: As expected, '/tmp/container_root/container_file.txt' is NOT accessible from parent namespace.\n");
        }
        // close(child_mnt_ns_fd); // 暂时不关闭,后面 setns 还要用
    }

    if (child_net_ns_fd != -1) {
        printf("Parent: Opened child's Network namespace fd: %d\n", child_net_ns_fd);
        query_namespace_type(child_net_ns_fd);
        close(child_net_ns_fd);
    }

    // 4. 父进程演示:使用 setns 加入子进程的 Mount 命名空间
    // (注意:实际应用中,你可能不会在父进程中这样做,这里仅为演示)
    /*
    printf("\n--- Parent: Attempting to join child's Mount Namespace ---\n");
    if (child_mnt_ns_fd != -1) {
        if (syscall(SYS_setns, child_mnt_ns_fd, CLONE_NEWNS) == 0) {
            printf("Parent: Successfully joined child's mount namespace.\n");
            // 现在父进程可以访问容器内的文件了
            if (access("/tmp/container_root/container_file.txt", F_OK) == 0) {
                printf("Parent: Now I can access '/tmp/container_root/container_file.txt'.\n");
            }
        } else {
            perror("Parent: setns failed");
        }
        close(child_mnt_ns_fd);
    }
    */

    // 5. 等待子进程结束
    printf("\n--- Parent: Waiting for container process (PID: %d) to finish ---\n", container_pid);
    printf("Parent: The container will exit automatically after its sleep.\n");
    printf("Parent: Or, you can manually stop it with 'kill %d'\n", container_pid);
    int status;
    waitpid(container_pid, &status, 0);

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

    free(child_stack);
    printf("Parent: Main process finished.\n");

    return 0;
}

如何编译和运行

# 1. 保存代码为 namespace_demo.c

# 2. 编译 (需要 root 权限来运行,但编译不需要)
gcc -o namespace_demo namespace_demo.c

# 3. 打开两个终端 (Terminal 1 和 Terminal 2)

# --- Terminal 1: 创建容器 ---
# 运行程序的第一部分,创建一个隔离的容器进程
sudo ./namespace_demo

# 你会看到类似输出:
# --- Creating Isolated Process (Container) ---
# Main process PID: 12345
# Parent: Created container process with PID: 12346
#
# --- Inside Container Init Process (PID: 12346) ---
# Container: Set hostname to 'my-container'.
# Container: Mounted tmpfs on /tmp/container_root
# Container: Created file /tmp/container_root/container_file.txt
# Container: Calling unshare(CLONE_NEWNET) to get isolated network...
# Container: Successfully unshared network namespace.
# Container: Network is now isolated. 'ip link' should show only loopback.
# Container: Sleeping for 60 seconds. Explore from host and container.
# ...

# --- Terminal 2: 加入容器 ---
# 在程序输出中找到容器的 PID (例如 12346),然后运行程序的第二部分
# 加入容器的 Mount 命名空间
sudo ./namespace_demo join 12346

# 你会看到类似输出:
# --- Joining Existing Namespace (as separate process) ---
# This process (PID: 12347) will join the mount namespace of PID: 12346
# Querying namespace type using ioctl...
#   Namespace fd 3 type is: Mount (CLONE_NEWNS)
# Calling setns() to join the mount namespace...
# Successfully joined the mount namespace of PID 12346.
# Checking for file that exists in the target namespace...
# SUCCESS: Found '/tmp/container_root/container_file.txt'. We are inside the container's mount namespace!
# Exiting join process.

详细说明

场景 1: 使用 clone 创建隔离进程

  1. main 函数:作为父进程启动。
  2. malloc(STACK_SIZE):为子进程分配独立的栈空间。
  3. syscall(SYS_clone, ...):调用 clone 系统调用。
    • CLONE_NEWNS:告诉内核为新进程创建一个新的 Mount 命名空间
    • CLONE_NEWUTS:告诉内核为新进程创建一个新的 UTS 命名空间(用于隔离主机名)。
    • child_stack + STACK_SIZE:传递子进程栈的顶部指针(栈向下增长)。
  4. if (container_pid == 0):在 clone 返回后,执行流分叉。在子进程中,clone 返回 0。
  5. container_init:子进程执行此函数,它现在运行在一个隔离的 Mount 和 UTS 命名空间中。它设置了主机名、挂载了文件系统并创建了文件。然后它调用 unshare

场景 2: 在进程中使用 unshare 增加隔离

  1. container_init 函数内部:在子进程完成初步设置后。
  2. unshare(CLONE_NEWNET):调用 unshare 系统调用。
    • CLONE_NEWNET:告诉内核让当前进程脱离当前的 Network 命名空间,并加入一个新创建的、空的 Network 命名空间。
  3. 效果:现在这个子进程拥有完全独立的网络视图(例如,只有 lo 回环接口)。

场景 3: 使用 setns 加入已存在的命名空间

  1. main 函数 (第二个实例):我们运行 ./namespace_demo join <PID> 来启动一个新的、独立的进程,专门用于加入命名空间。
  2. open("/proc/<PID>/ns/mnt", O_RDONLY):打开目标进程(容器)的 Mount 命名空间文件。这返回一个文件描述符。
  3. syscall(SYS_setns, fd, CLONE_NEWNS):调用 setns 系统调用。
    • fd:之前打开的命名空间文件描述符。
    • CLONE_NEWNS:指定要加入的命名空间类型(Mount)。
  4. 效果:调用 setns 的这个新进程,其文件系统视图现在与目标容器进程完全相同。它可以访问容器内挂载的 /tmp/container_root 及其内部的文件。

场景 4: 使用 ioctl 查询命名空间信息

  1. open_namespace_fd:一个辅助函数,用于获取命名空间文件描述符。
  2. ioctl(ns_fd, NS_GET_NSTYPE):对命名空间文件描述符调用 ioctl 系统调用。
    • NS_GET_NSTYPE:这是一个特定的 ioctl 命令,请求内核返回该文件描述符对应的命名空间类型。
  3. 效果:程序可以动态地确定一个未知的命名空间文件描述符代表的是哪种类型的命名空间(Mount, Network, PID 等)。

总结

这个示例完整地演示了:

  • clone 如何在创建新进程时就为其分配隔离的环境(Mount, UTS)。
  • unshare 如何让一个已经在运行的进程(容器内)进一步获得新的隔离(Network)。
  • setns 如何让一个外部进程(或另一个独立的工具进程)加入到已存在的隔离环境(Mount)中。
  • ioctl (with NS_GET_NSTYPE) 如何查询命名空间文件描述符的类型。

通过这种方式,Linux 命名空间系统调用共同协作,提供了强大的资源隔离能力,构成了现代容器技术的基础。

2

好的,我们来构建一个高度简化但原理正确的“现代容器环境”示例。这个示例将展示创建一个容器所需的核心步骤,并对每一部分进行详细解释。

我们将模拟一个像 docker run 那样的命令,它会:

  1. 创建一个隔离的环境(使用命名空间)。
  2. 设置资源限制(使用 cgroups)。
  3. 准备一个文件系统根目录(使用 chroot 和 mount)。
  4. 在这个隔离的环境中运行一个指定的命令。

重要提示

  • 需要 root 权限:操作命名空间、cgroups、挂载文件系统都需要 root 权限。
  • 简化:真实的容器运行时(如 runc)极其复杂,涉及大量细节、安全加固、兼容性处理等。此示例仅展示核心原理。
  • 环境要求:你的 Linux 内核需要支持所需的特性(现代发行版默认支持)。
  • 安全性:此示例代码不安全,仅用于学习,请勿在生产环境使用。

完整示例代码:my_simple_container.c

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>      // 命名空间相关
#include <sys/syscall.h> // syscall
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>   // waitpid
#include <fcntl.h>      // open, O_* flags
#include <errno.h>
#include <string.h>
#include <sys/mount.h>  // mount, umount
#include <limits.h>     // PATH_MAX
#include <ftw.h>        // nftw (用于递归删除目录)
#include <signal.h>     // kill

// --- 配置部分 ---
#define STACK_SIZE (1024 * 1024) // 1MB 子进程栈
#define CGROUP_NAME "my_simple_container"
#define MEMORY_LIMIT "50M" // 限制内存为 50MB

// --- 全局变量 ---
char child_stack[STACK_SIZE]; // 子进程栈
char *container_root = "/tmp/my_container_root"; // 容器的根文件系统目录
char *host_fs_path = "/tmp/host_fs_for_container"; // 容器内挂载宿主机目录的位置

// --- 辅助函数 ---

// 递归删除目录的回调函数 (用于 nftw)
int remove_callback(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) {
    (void)sb; (void)typeflag; (void)ftwbuf; // 忽略未使用的参数
    int res = remove(fpath);
    if (res == -1) {
        perror(fpath);
    }
    return res;
}

// 递归删除整个目录树
int remove_directory(const char *path) {
    return nftw(path, remove_callback, 64, FTW_DEPTH | FTW_PHYS);
}

// 安全地写入文件内容
int write_file(const char *path, const char *content) {
    int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) {
        perror("open (write_file)");
        return -1;
    }
    if (write(fd, content, strlen(content)) != (ssize_t)strlen(content)) {
        perror("write (write_file)");
        close(fd);
        return -1;
    }
    close(fd);
    return 0;
}

// --- 核心功能函数 ---

// 1. 准备容器的根文件系统
// 这是容器运行的基础,它需要包含运行命令所需的所有文件 (如 /bin, /lib, /etc 等)
int prepare_rootfs() {
    printf("[*] Preparing root filesystem at %s\n", container_root);

    // 创建容器根目录
    if (mkdir(container_root, 0755) == -1 && errno != EEXIST) {
        perror("mkdir container_root");
        return -1;
    }

    // 挂载 tmpfs 作为容器的根文件系统
    // tmpfs 是内存中的临时文件系统,非常适合做实验性的 rootfs
    if (mount("tmpfs", container_root, "tmpfs", 0, NULL) == -1) {
        perror("mount tmpfs root");
        return -1;
    }
    printf("    Mounted tmpfs on %s\n", container_root);

    // 在容器根目录下创建基本的目录结构
    const char *dirs[] = {"/bin", "/lib", "/lib64", "/etc", "/proc", "/sys", "/dev", "/tmp", host_fs_path+strlen(container_root)};
    for (int i = 0; i < sizeof(dirs)/sizeof(dirs[0]); i++) {
        char path[PATH_MAX];
        snprintf(path, sizeof(path), "%s%s", container_root, dirs[i]);
        if (mkdir(path, 0755) == -1) {
            perror("mkdir (dirs)");
            return -1;
        }
    }
    printf("    Created basic directory structure\n");

    // 复制或绑定挂载一些必要的二进制文件和库
    // 这里我们只复制一个简单的命令: /bin/sh (shell)
    // 注意:实际的容器镜像会包含一个完整的文件系统
    const char *bins[] = {"/bin/sh"};
    for (int i = 0; i < sizeof(bins)/sizeof(bins[0]); i++) {
        char dst_path[PATH_MAX];
        snprintf(dst_path, sizeof(dst_path), "%s%s", container_root, bins[i]);
        // 使用硬链接或复制。这里简单使用系统调用 `cp`
        char cmd[PATH_MAX * 2];
        snprintf(cmd, sizeof(cmd), "cp -p %s %s", bins[i], dst_path);
        printf("    Copying %s...\n", bins[i]);
        if (system(cmd) != 0) {
             fprintf(stderr, "Failed to copy %s\n", bins[i]);
             return -1;
        }
    }

    // 创建一个简单的 /etc/passwd 和 /etc/group 文件,这样 `whoami` 等命令能工作
    char passwd_path[PATH_MAX], group_path[PATH_MAX];
    snprintf(passwd_path, sizeof(passwd_path), "%s/etc/passwd", container_root);
    snprintf(group_path, sizeof(group_path), "%s/etc/group", container_root);
    write_file(passwd_path, "root:x:0:0:root:/root:/bin/sh\nnobody:x:65534:65534:nobody:/:/bin/false\n");
    write_file(group_path, "root:x:0:\nnobody:x:65534:\n");
    printf("    Created minimal /etc/passwd and /etc/group\n");

    // 绑定挂载宿主机的 /lib, /lib64 目录到容器内,这样 /bin/sh 能找到它需要的共享库
    // 注意:这会暴露宿主机的库,实际容器会自带库或使用更精细的复制
    char lib_dst[PATH_MAX], lib64_dst[PATH_MAX];
    snprintf(lib_dst, sizeof(lib_dst), "%s/lib", container_root);
    snprintf(lib64_dst, sizeof(lib64_dst), "%s/lib64", container_root);
    if (mount("/lib", lib_dst, NULL, MS_BIND | MS_REC, NULL) == -1 ||
        mount("/lib64", lib64_dst, NULL, MS_BIND | MS_REC, NULL) == -1) {
        perror("mount /lib or /lib64");
        return -1;
    }
    printf("    Bind-mounted /lib and /lib64\n");

    // 在容器内创建一个挂载点,用于挂载宿主机的一个目录,演示数据共享
    // 这类似于 `docker run -v /host/path:/container/path`
    if (mkdir(host_fs_path, 0755) == -1 && errno != EEXIST) {
        perror("mkdir host_fs_path (host)");
        return -1;
    }
    char container_host_fs_path[PATH_MAX];
    snprintf(container_host_fs_path, sizeof(container_host_fs_path), "%s%s", container_root, host_fs_path + strlen(container_root));
    if (mount(host_fs_path, container_host_fs_path, NULL, MS_BIND, NULL) == -1) {
        perror("mount host_fs_path");
        return -1;
    }
    printf("    Bind-mounted host directory %s to container directory %s\n", host_fs_path, container_host_fs_path);

    printf("[+] Root filesystem prepared successfully.\n");
    return 0;
}

// 2. 设置 cgroups 以限制资源
// 这里我们只演示内存限制
int setup_cgroups(pid_t pid) {
    printf("[*] Setting up cgroups (memory limit: %s)\n", MEMORY_LIMIT);
    char cgroup_path[256];
    char tasks_path[256];
    char limit_path[256];

    // 创建我们自己的 cgroup 子目录
    snprintf(cgroup_path, sizeof(cgroup_path), "/sys/fs/cgroup/memory/%s", CGROUP_NAME);
    if (mkdir(cgroup_path, 0755) == -1 && errno != EEXIST) {
        perror("mkdir cgroup");
        // 如果 cgroup v2 或其他问题,可能需要更复杂的处理,这里简化
        fprintf(stderr, "Warning: Failed to create cgroup, skipping resource limits.\n");
        return 0; // 不算致命错误
    }

    // 设置内存限制
    snprintf(limit_path, sizeof(limit_path), "%s/memory.limit_in_bytes", cgroup_path);
    if (write_file(limit_path, MEMORY_LIMIT) == -1) {
        fprintf(stderr, "Warning: Failed to set memory limit, skipping.\n");
        return 0;
    }

    // 将子进程 PID 写入 tasks 文件,使其受此 cgroup 控制
    snprintf(tasks_path, sizeof(tasks_path), "%s/tasks", cgroup_path);
    char pid_str[32];
    snprintf(pid_str, sizeof(pid_str), "%d", pid);
    if (write_file(tasks_path, pid_str) == -1) {
        fprintf(stderr, "Warning: Failed to add process to cgroup, skipping.\n");
        return 0;
    }

    printf("[+] Cgroups configured.\n");
    return 0;
}

// 3. 容器初始化函数 (在子进程中运行)
// 这是容器内第一个执行的用户态进程 (PID 1)
int container_main(void *arg) {
    char **args = (char **)arg;
    printf("\n[*** INSIDE CONTAINER ***]\n");
    printf("Container PID 1: %d\n", getpid());

    // --- 关键步骤 1: 切换根文件系统 (chroot) ---
    // 将容器的根目录设置为我们准备好的目录
    if (chdir(container_root) == -1) {
        perror("chdir to container root");
        return 1;
    }
    // chroot 系统调用将当前进程看到的文件系统根目录 '/' 改变
    if (chroot(".") == -1) {
        perror("chroot");
        return 1;
    }
    printf("    Changed root to %s using chroot\n", container_root);

    // --- 关键步骤 2: 挂载必要的虚拟文件系统 ---
    // 容器内进程需要 /proc, /sys, /dev 等来获取系统信息和设备访问
    if (mount("proc", "/proc", "proc", 0, NULL) == -1 ||
        mount("sysfs", "/sys", "sysfs", 0, NULL) == -1 ||
        mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "size=65536k,mode=755") == -1) {
        perror("mount essential filesystems");
        return 1;
    }
    // 创建基本的设备节点 (简化)
    if (mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)) == -1 && errno != EEXIST) {
        perror("mknod /dev/null");
    }
    printf("    Mounted /proc, /sys, /dev\n");

    // --- 关键步骤 3: 运行用户指定的命令 ---
    printf("    Executing command: ");
    for (int i = 0; args[i] != NULL; i++) {
        printf("%s ", args[i]);
    }
    printf("\n");
    printf("[*** END OF CONTAINER SETUP ***]\n\n");

    // execvp 会用新程序替换当前进程的镜像
    execvp(args[0], args);
    // 如果 execvp 返回,说明执行失败
    perror("execvp");
    return 1;
}

// --- 主函数 ---
int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <command> [args...]\n", argv[0]);
        fprintf(stderr, "Example: sudo %s /bin/sh\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    printf("=== My Simple Container Runtime ===\n");

    // 1. 准备 rootfs
    if (prepare_rootfs() == -1) {
        fprintf(stderr, "Failed to prepare root filesystem\n");
        exit(EXIT_FAILURE);
    }

    // 2. 使用 clone 创建子进程,并设置命名空间隔离
    // CLONE_NEWPID: 新的 PID 命名空间 (容器内 PID 从 1 开始)
    // CLONE_NEWNS: 新的 Mount 命名空间 (隔离文件系统挂载点)
    // CLONE_NEWUTS: 新的 UTS 命名空间 (隔离主机名)
    // CLONE_NEWIPC: 新的 IPC 命名空间 (隔离 IPC 资源)
    // CLONE_NEWNET: 新的 Network 命名空间 (隔离网络)
    // SIGCHLD: 子进程结束时通知父进程
    int flags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWNET | SIGCHLD;

    pid_t container_pid = syscall(SYS_clone, flags, child_stack + STACK_SIZE, NULL, NULL);
    if (container_pid == -1) {
        perror("clone");
        // 清理
        umount(container_root);
        remove_directory(container_root);
        exit(EXIT_FAILURE);
    }

    if (container_pid == 0) {
        // --- 在子进程中 ---
        // 设置主机名
        sethostname("my-container", strlen("my-container"));

        // 调用容器主函数
        container_main(argv + 1); // 传递命令行参数 (跳过 argv[0])
        exit(EXIT_FAILURE); // 如果 container_main 返回,说明 exec 失败
    }

    // --- 回到父进程 ---
    printf("[*] Started container process with PID %d\n", container_pid);

    // 3. 设置 cgroups (在子进程启动后)
    if (setup_cgroups(container_pid) == -1) {
        fprintf(stderr, "Warning: Cgroup setup failed\n");
    }

    // 4. 等待容器进程结束
    printf("[*] Waiting for container (PID %d) to finish...\n", container_pid);
    int status;
    if (waitpid(container_pid, &status, 0) == -1) {
        perror("waitpid");
    }

    if (WIFEXITED(status)) {
        printf("[*] Container exited with status %d\n", WEXITSTATUS(status));
    } else if (WIFSIGNALED(status)) {
        printf("[*] Container killed by signal %d\n", WTERMSIG(status));
    }

    // 5. 清理资源
    printf("[*] Cleaning up...\n");
    // 卸载挂载点
    char container_host_fs_path[PATH_MAX];
    snprintf(container_host_fs_path, sizeof(container_host_fs_path), "%s%s", container_root, host_fs_path + strlen(container_root));
    umount(container_host_fs_path);
    umount("/lib64");
    umount("/lib");
    // 注意:chroot 后的卸载需要在 chroot 环境内或特殊处理,这里简化,依赖系统重启或手动清理
    // 通常容器运行时会更仔细地管理这些挂载
    umount(container_root);
    // 删除临时目录
    remove_directory(container_root);
    remove_directory(host_fs_path);
    printf("[+] Cleanup finished.\n");

    printf("=== Container Runtime Finished ===\n");
    return 0;
}

代码详细解释

1. 配置和辅助部分

  • #define 和全局变量:定义了栈大小、cgroup 名称、容器根目录路径等常量和全局变量,方便修改和使用。
  • remove_directory:使用 nftw 递归遍历并删除整个目录树,用于清理工作。
  • write_file:一个安全的小函数,用于向文件写入内容,避免重复代码。

2. prepare_rootfs – 准备文件系统

这是容器技术中最复杂的部分之一,因为容器需要一个完整的、自包含的文件系统。

  • mkdir 和 mount("tmpfs", ...):创建容器根目录,并挂载一个 tmpfstmpfs 是基于内存的文件系统,非常适合做实验,因为它启动快且隔离性好。
  • 创建基本目录/bin/lib/etc/proc/sys/dev 是 Linux 系统运行程序所必需的目录。
  • 复制/绑定挂载二进制文件:这里简化地只复制了 /bin/sh。真实的容器镜像(如 Docker 镜像)会包含一个完整的根文件系统(/bin/usr/lib 等所有内容)。
  • 绑定挂载库文件:为了让 /bin/sh 能运行,它需要依赖宿主机的共享库(.so 文件)。我们通过 mount --bind 将宿主机的 /lib 和 /lib64 挂载到容器内对应位置。注意:这在实际生产中是不安全的,因为容器会使用宿主机的库,可能导致版本不兼容或安全问题。真正的容器会自带所需的库。
  • 创建 /etc/passwd 等:为了让一些基础命令(如 whoami)能正常工作,需要创建这些用户/组信息文件。
  • 绑定挂载宿主机目录:模拟 docker run -v 功能,展示容器与宿主机之间的数据共享。

3. setup_cgroups – 设置资源限制

cgroups (Control Groups) 用于限制、记录和隔离进程组的资源使用(CPU、内存、磁盘 I/O 等)。

  • mkdir:在 /sys/fs/cgroup/memory/ 下创建一个子目录,作为我们容器专用的 cgroup。
  • write_file(limit_path, MEMORY_LIMIT):向 memory.limit_in_bytes 文件写入限制值(如 “50M”),内核会自动应用这个限制。
  • write_file(tasks_path, pid_str):将容器进程的 PID 写入 cgroup 的 tasks 文件。这一步是关键,它告诉内核:“请把这个 PID 的进程放进这个 cgroup 里,让它受到资源限制。”

4. container_main – 容器内的初始化

这是容器内第一个运行的用户态进程(通常 PID 为 1)。

  • chdir 和 chroot
    • chdir(container_root):先切换当前工作目录到我们准备好的容器根目录。
    • chroot("."):这是核心操作chroot 系统调用会将调用进程及其子进程的根目录/)永久性地更改为当前工作目录(.,即 container_root)。执行此操作后,进程将无法访问原宿主机根目录之外的任何文件。对它来说,container_root 就是世界的尽头,里面的 / 就是真正的 /
  • 挂载虚拟文件系统
    • mount("proc", "/proc", "proc", ...):挂载 proc 文件系统。进程需要通过 /proc 来读取自身信息(如 /proc/self/status)、查看子进程、获取 CPU 信息等。
    • mount("sysfs", "/sys", "sysfs", ...):挂载 sysfs,用于访问内核和硬件设备信息。
    • mount("tmpfs", "/dev", "tmpfs", ...):挂载一个 tmpfs 到 /dev,容器内的程序可能需要在这里创建设备节点或临时文件。
    • mknod:创建基本的设备文件,如 /dev/null
  • execvp(args[0], args):这是最后一步,也是至关重要的一步。execvp 系列函数会用磁盘上的一个新程序(由 args[0] 指定)完全替换当前进程的内存镜像(代码、数据、堆栈等)。执行成功后,容器内运行的就是用户指定的命令了,而不再是我们的 container_main 函数。这个新程序的 PID 仍然是 1(因为在新的 PID 命名空间中)。

5. main – 主流程控制

  • 参数检查:确保用户提供了要运行的命令。
  • 调用 prepare_rootfs:准备隔离环境。
  • 调用 clone
    • 这是启动隔离进程的核心。我们传递了多个 CLONE_NEW* 标志:
      • CLONE_NEWPID:创建新的 PID 命名空间。这使得容器内的第一个进程 PID 为 1,并且它无法看到或操作宿主机上 PID 命名空间中的进程。
      • CLONE_NEWNS:创建新的 Mount 命名空间。这使得容器内的 mount 和 umount 操作不会影响宿主机的文件系统视图。
      • CLONE_NEWUTS:创建新的 UTS 命名空间。这使得容器可以拥有独立的主机名 (hostname) 和 NIS 域名。
      • CLONE_NEWIPC:创建新的 IPC 命名空间。这隔离了 System V IPC 和 POSIX 消息队列。
      • CLONE_NEWNET:创建新的 Network 命名空间。这使得容器拥有独立的网络设备、IP 地址、路由表、端口等。示例中未配置网络,所以容器内网络功能受限。
    • child_stack + STACK_SIZE:传递子进程栈的顶部指针。
  • 子进程分支 (if (container_pid == 0))
    • 在子进程中,设置主机名,然后调用 container_main 进行初始化。
  • 父进程分支
    • 调用 setup_cgroups 来限制子进程的资源。
    • 使用 waitpid 等待容器进程结束。
    • 容器结束后,执行清理工作:卸载文件系统、删除临时目录。

如何编译和运行

# 1. 将代码保存为 my_simple_container.c

# 2. 编译 (需要 root 权限来运行,但编译不需要)
gcc -o my_simple_container my_simple_container.c

# 3. 运行容器,执行 /bin/sh
# 必须使用 sudo,因为涉及命名空间、cgroups、挂载等特权操作
sudo ./my_simple_container /bin/sh

# 你会看到类似输出:
# === My Simple Container Runtime ===
# [*] Preparing root filesystem at /tmp/my_container_root
#     Mounted tmpfs on /tmp/my_container_root
#     Created basic directory structure
#     Copying /bin/sh...
#     Created minimal /etc/passwd and /etc/group
#     Bind-mounted /lib and /lib64
#     Bind-mounted host directory /tmp/host_fs_for_container to container directory /tmp/host_fs_for_container
# [+] Root filesystem prepared successfully.
# [*] Started container process with PID 12345
# [*] Setting up cgroups (memory limit: 50M)
# [+] Cgroups configured.
# [*] Waiting for container (PID 12345) to finish...
#
# [*** INSIDE CONTAINER ***]
# Container PID 1: 1
#     Changed root to /tmp/my_container_root using chroot
#     Mounted /proc, /sys, /dev
#     Executing command: /bin/sh
# [*** END OF CONTAINER SETUP ***]
#
# # <--- 你现在在容器的 shell 里面了,PID 为 1 ---
# # ps aux
# USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
# root           1  0.0  0.0   2284  1536 ?        Ss   10:30   0:00 /bin/sh
# root          12  0.0  0.0   3864  3168 ?        R+   10:31   0:00 ps aux
# #
# # hostname
# my-container
# #
# # df -h
# Filesystem      Size  Used Avail Use% Mounted on
# tmpfs           1.9G     0  1.9G   0% /
# proc            1.9G     0  1.9G   0% /proc
# sysfs           1.9G     0  1.9G   0% /sys
# tmpfs            64M     0   64M   0% /dev
# tmpfs           1.9G     0  1.9G   0% /tmp
# /dev/sda1        20G  5.0G   15G  26% /tmp/host_fs_for_container
# #
# # cd /tmp/host_fs_for_container
# # touch file_created_in_container
# # exit
# # <--- 退出容器 ---
#
# [*] Container exited with status 0
# [*] Cleaning up...
# [+] Cleanup finished.
# === Container Runtime Finished ===
#
# # <--- 回到宿主机 ---
# # ls /tmp/host_fs_for_container/
# file_created_in_container  # 你在容器里创建的文件在宿主机上也能看到

总结原理

这个示例通过组合 Linux 内核的几个关键特性,模拟了现代容器的运行原理:

  1. 隔离 (Isolation) – 命名空间 (Namespaces):
    • clone 系统调用配合 CLONE_NEW* 标志,在创建新进程时就为其分配了独立的视图,包括进程 ID (CLONE_NEWPID)、文件系统挂载点 (CLONE_NEWNS)、主机名 (CLONE_NEWUTS)、IPC 资源 (CLONE_NEWIPC) 和网络 (CLONE_NEWNET)。
    • chroot 系统调用进一步将进程的文件系统根目录 (/) 切换到一个预先准备好的、与宿主机隔离的目录,实现了文件系统的彻底隔离。
  2. 资源限制 (Resource Limiting) – Control Groups (Cgroups):
    • 通过在 /sys/fs/cgroup 下创建子目录并配置参数(如 memory.limit_in_bytes),然后将容器进程的 PID 添加到该 cgroup 的 tasks 列表中,实现了对该进程资源使用的限制(此处为内存)。
  3. 文件系统 (Filesystem) – Rootfs:
    • 准备一个包含运行所需程序和库的目录 (/tmp/my_container_root)。
    • 使用 tmpfsbind mount 等技术构建这个目录。
    • 通过 chroot 使其成为容器进程的根目录。
  4. 执行 (Execution):
    • 在完成所有隔离和设置后,使用 execvp 系统调用,用用户指定的命令(如 /bin/sh)替换容器初始化进程(PID 1)的镜像,从而在隔离环境中运行该命令。

通过以上步骤,一个与宿主机环境隔离、资源受限、拥有独立文件系统和进程/网络视图的“沙盒”环境就被创建出来了,这就是容器的核心工作原理。

发表在 linux文章 | 标签为 | 留下评论

 setns 系统调用及示例

我们来深入学习 setns 系统调用

1. 函数介绍

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

Linux 支持多种类型的命名空间,每种隔离不同类型的系统资源:

  • Mount (mnt): 隔离文件系统挂载点。
  • PID (pid): 隔离进程 ID 空间。
  • Network (net): 隔离网络设备、IP 地址、端口等。
  • Interprocess Communication (ipc): 隔离 System V IPC 和 POSIX 消息队列。
  • UTS (uts): 隔离主机名和域名 (nodename/domainname)。
  • User (user): 隔离用户和组 ID。
  • Cgroup (cgroup): 隔离控制组 (cgroups) 的视图。

setns (Set Namespace) 系统调用的作用是:将调用它的进程,加入到一个已经存在的命名空间中

想象一下,你手里有一把钥匙,这把钥匙可以打开一扇通往某个“隔离房间”的门。setns 就像是你使用这把钥匙(文件描述符)进入那个特定的“隔离房间”(命名空间)的过程。一旦进入,你就能看到并使用那个房间里的东西(资源),就像你属于那个房间一样。

简单来说,setns 让一个正在运行的进程可以“穿越”到另一个隔离的环境(命名空间)中去。

重要提示

  1. 需要权限:加入某些类型的命名空间(尤其是 User 命名空间)可能需要特殊权限或遵循复杂的规则。
  2. 文件描述符setns 不是直接通过命名空间的名字或 ID 来操作,而是通过一个指向该命名空间的文件描述符。这个文件描述符通常是通过打开 /proc/[pid]/ns/ 目录下的特殊符号链接文件获得的。
  3. 部分加入:一个进程可以同时属于多个不同类型的命名空间。setns 每次只加入一个指定类型的命名空间。

2. 函数原型

// 标准 C 库通常不提供直接包装,需要通过 syscall 调用
#include <sched.h>       // 包含 CLONE_* 常量,定义了命名空间类型
#include <sys/syscall.h> // 包含系统调用号 SYS_setns
#include <unistd.h>      // 包含 syscall 函数

long syscall(SYS_setns, int fd, int nstype);

3. 功能

将调用进程加入由文件描述符 fd 指向的命名空间。如果 nstype 不为 0,还会检查该命名空间的类型是否与 nstype 指定的类型匹配。

4. 参数

  • fd:
    • int 类型。
    • 一个指向命名空间的文件描述符。这个文件描述符通常是通过打开 /proc/[pid]/ns/[namespace_name] 文件(例如 /proc/self/ns/net)获得的。[pid] 可以是目标进程的 PID,也可以是 self(代表调用进程自身)。
  • nstype:
    • int 类型。
    • 指定要加入的命名空间的类型。这是一个检查机制。有效的值是 <sched.h> 中定义的 CLONE_NEW* 常量,例如:
      • CLONE_NEWIPC
      • CLONE_NEWNET
      • CLONE_NEWNS (Mount)
      • CLONE_NEWPID
      • CLONE_NEWUSER
      • CLONE_NEWUTS
      • CLONE_NEWCGROUP
    • 如果 nstype 设置为 0,则不进行类型检查。

5. 返回值

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

6. 错误码 (errno)

  • EBADFfd 不是一个有效的文件描述符。
  • EINVALfd 是有效的,但它不指向一个命名空间文件,或者 nstype 指定的类型与文件描述符指向的命名空间类型不匹配。
  • ENOMEM: 内核内存不足,无法完成操作。
  • EPERM: 调用者没有权限加入该命名空间。例如,尝试加入一个 User 命名空间可能受到严格限制。

7. 相似函数或关联函数

  • unshare: 允许调用进程脱离当前的某个命名空间,并加入一个新创建的、空的同类型命名空间。
  • clone: 创建新进程时,可以通过传递 CLONE_NEW* 标志,使新进程在新的命名空间中启动。
  • /proc/[pid]/ns/: 这个目录包含了进程所处的各种命名空间的符号链接文件。通过打开这些文件可以获得命名空间的文件描述符。
  • nsenter: 命令行工具,可以在指定的命名空间中执行命令,它在底层使用了 setns

8. 示例代码

由于命名空间涉及系统级隔离,创建和操作它们通常需要 root 权限或对 /proc 文件系统的访问。下面的示例将展示如何使用 setns 加入一个 Mount 命名空间

警告:此示例需要 root 权限,并且会创建挂载点。请在测试环境中运行。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/mount.h> // 包含 mount 函数
#include <sys/wait.h>  // 包含 waitpid

// 简单的子进程函数,用于在新命名空间中执行
int child_func(void* arg) {
    printf("Child process (PID: %d) started.\n", getpid());

    // 在子进程中挂载一个 tmpfs 到 /tmp/my_test_mount
    // 这个挂载操作只在子进程的 Mount Namespace 中可见
    const char* mount_point = "/tmp/my_test_mount";
    if (mkdir(mount_point, 0755) == -1 && errno != EEXIST) {
        perror("mkdir (child)");
        return 1;
    }

    if (mount("tmpfs", mount_point, "tmpfs", 0, NULL) == -1) {
        perror("mount (child)");
        return 1;
    }

    printf("Child process: Mounted tmpfs on %s\n", mount_point);

    // 在挂载点创建一个文件
    char file_path[256];
    snprintf(file_path, sizeof(file_path), "%s/test_file.txt", mount_point);
    FILE *f = fopen(file_path, "w");
    if (f) {
        fprintf(f, "Hello from child process in its own mount namespace!\n");
        fclose(f);
        printf("Child process: Created file %s\n", file_path);
    } else {
        perror("fopen (child)");
    }

    printf("Child process: Sleeping for 30 seconds. Check /tmp/my_test_mount from parent and child.\n");
    printf("Child process: You can run 'ls /tmp/my_test_mount' in another terminal as root.\n");
    sleep(30); // 睡眠,让我们有时间从外部观察

    // 清理 (可选,因为退出时会自动清理)
    // umount(mount_point);
    // rmdir(mount_point);

    printf("Child process exiting.\n");
    return 0;
}

int main() {
    pid_t child_pid;
    int parent_ns_fd, child_ns_fd;
    char ns_path[256];
    const int STACK_SIZE = 1024 * 1024; // 1MB 栈
    char *child_stack = malloc(STACK_SIZE);
    if (!child_stack) {
        perror("malloc");
        exit(EXIT_FAILURE);
    }

    printf("--- Demonstrating setns with Mount Namespace ---\n");
    printf("Main process PID: %d\n", getpid());

    // 1. 获取父进程当前的 Mount Namespace 文件描述符
    snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/mnt");
    parent_ns_fd = open(ns_path, O_RDONLY);
    if (parent_ns_fd == -1) {
        perror("open parent namespace");
        free(child_stack);
        exit(EXIT_FAILURE);
    }
    printf("Opened parent's mount namespace fd: %d\n", parent_ns_fd);

    // 2. 使用 clone 创建一个新进程,并让它拥有自己的 Mount Namespace
    // CLONE_NEWNS: 创建新的 Mount Namespace
    child_pid = clone(child_func, child_stack + STACK_SIZE,
                      CLONE_NEWNS | SIGCHLD, NULL);
    if (child_pid == -1) {
        perror("clone");
        close(parent_ns_fd);
        free(child_stack);
        exit(EXIT_FAILURE);
    }
    printf("Created child process with new mount namespace. PID: %d\n", child_pid);

    // 等待一小会儿,让子进程完成挂载
    sleep(2);

    // 3. 获取子进程的 Mount Namespace 文件描述符
    snprintf(ns_path, sizeof(ns_path), "/proc/%d/ns/mnt", child_pid);
    child_ns_fd = open(ns_path, O_RDONLY);
    if (child_ns_fd == -1) {
        perror("open child namespace");
        close(parent_ns_fd);
        // 杀死子进程
        kill(child_pid, SIGKILL);
        waitpid(child_pid, NULL, 0);
        free(child_stack);
        exit(EXIT_FAILURE);
    }
    printf("Opened child's mount namespace fd: %d\n", child_ns_fd);

    // 4. 在父进程中,检查 /tmp/my_test_mount 是否存在
    // (它应该不存在,因为父进程在不同的 Mount Namespace)
    if (access("/tmp/my_test_mount", F_OK) == 0) {
         printf("Parent process: /tmp/my_test_mount EXISTS in parent namespace (unexpected!).\n");
    } else {
         printf("Parent process: /tmp/my_test_mount does NOT exist in parent namespace (as expected).\n");
    }

    // 5. 现在,使用 setns 将父进程加入到子进程的 Mount Namespace
    printf("\n--- Calling setns to join child's mount namespace ---\n");
    if (syscall(SYS_setns, child_ns_fd, CLONE_NEWNS) == -1) {
        perror("setns");
        printf("Failed to join child's namespace. Do you have root privileges?\n");
        close(parent_ns_fd);
        close(child_ns_fd);
        kill(child_pid, SIGKILL);
        waitpid(child_pid, NULL, 0);
        free(child_stack);
        exit(EXIT_FAILURE);
    }
    printf("Parent process successfully joined child's mount namespace.\n");

    // 6. 再次检查 /tmp/my_test_mount
    // (现在它应该存在了,因为父进程已经加入了子进程的命名空间)
    if (access("/tmp/my_test_mount", F_OK) == 0) {
         printf("Parent process (after setns): /tmp/my_test_mount NOW EXISTS in current namespace.\n");
         printf("Parent process (after setns): You can now see the file created by the child.\n");
         // 尝试读取子进程创建的文件
         char file_path[256];
         snprintf(file_path, sizeof(file_path), "%s/test_file.txt", "/tmp/my_test_mount");
         FILE *f = fopen(file_path, "r");
         if (f) {
             char buffer[256];
             if (fgets(buffer, sizeof(buffer), f)) {
                 printf("Parent process (after setns): Read from file: %s", buffer);
             }
             fclose(f);
         } else {
             perror("fopen (parent after setns)");
         }
    } else {
         printf("Parent process (after setns): /tmp/my_test_mount STILL does not exist (unexpected!).\n");
    }

    // 7. 清理和等待
    printf("\n--- Cleaning up ---\n");
    close(parent_ns_fd);
    close(child_ns_fd);

    // 等待子进程结束
    int status;
    waitpid(child_pid, &status, 0);
    if (WIFEXITED(status)) {
        printf("Child process exited with status %d.\n", WEXITSTATUS(status));
    } else {
        printf("Child process did not exit normally.\n");
    }

    free(child_stack);
    printf("Main process finished.\n");

    return 0;
}

使用 nsenter 命令行工具的对比示例:

nsenter 是一个非常方便的命令行工具,它封装了 setns 的功能,允许你在指定的命名空间中运行命令。

# 1. 启动一个长时间运行的进程 (例如 sleep) 并让它进入新的 PID 命名空间
# (通常与 unshare 一起使用)
unshare -p -f --mount-proc sleep 300 &
UNSHARE_PID=$!

# 等待一下让 unshare 启动
sleep 1

# 2. 查看这个新进程的 PID 命名空间 inode
ls -li /proc/1/ns/pid /proc/$UNSHARE_PID/ns/pid

# 3. 使用 nsenter 进入这个进程的 PID 和 Mount 命名空间,并运行 ps
# 这会显示在那个命名空间内部看到的进程
nsenter -t $UNSHARE_PID -p -m ps aux

# 4. 清理
kill $UNSHARE_PID

编译和运行:

# 假设代码保存在 setns_example.c 中
# 需要 root 权限来运行涉及 mount 和 setns 的操作

# 编译
gcc -o setns_example setns_example.c

# 运行 (必须使用 sudo)
sudo ./setns_example

预期输出 (片段):

--- Demonstrating setns with Mount Namespace ---
Main process PID: 12345
Opened parent's mount namespace fd: 3
Created child process with new mount namespace. PID: 12346
Child process (PID: 12346) started.
Child process: Mounted tmpfs on /tmp/my_test_mount
Child process: Created file /tmp/my_test_mount/test_file.txt
Child process: Sleeping for 30 seconds. Check /tmp/my_test_mount from parent and child.
Opened child's mount namespace fd: 4
Parent process: /tmp/my_test_mount does NOT exist in parent namespace (as expected).

--- Calling setns to join child's mount namespace ---
Parent process successfully joined child's mount namespace.
Parent process (after setns): /tmp/my_test_mount NOW EXISTS in current namespace.
Parent process (after setns): You can now see the file created by the child.
Parent process (after setns): Read from file: Hello from child process in its own mount namespace!

--- Cleaning up ---
Child process exited with status 0.
Main process finished.

总结:
setns 是一个强大的系统调用,是 Linux 容器技术的基石之一。它允许进程动态地加入到已存在的命名空间中,从而获得该命名空间的视图和资源访问权限。对于 Linux 编程新手来说,理解命名空间的概念是第一步,setns 则是实现命名空间操作的关键工具。直接使用它进行编程比较复杂,通常在容器运行时(如 runc)或高级系统管理脚本中会用到。日常开发中,使用 nsenter 或容器管理工具(如 docker exec)是更常见的与命名空间交互的方式。

 setns 系统调用及示例 – LinuxGuide setns setns,setns-系统调用及示例LinuxGuide

setns系统调用及示例-CSDN博客

发表在 linux文章 | 留下评论

awk分析Nginx日志

awk分析Nginx日志

以下是一个基于 Nginx 访问日志的 AWK 逻辑组合实战示例,模拟实际工作中“多条件筛选异常请求并统计”的场景,包含基础逻辑组合、高级统计及输出格式化。

场景背景

假设我们有一份 Nginx 访问日志 access.log,格式如下(简化版,字段含义对应):

$1: 客户端IP  $4: 访问时间  $6: 请求方法  $7: 请求路径  $9: 状态码  $10: 响应大小(字节)  

示例日志内容:

192.168.1.1 - - [10/Oct/2023:10:00:00 +0800] "GET /index.html HTTP/1.1" 200 1500  
10.0.0.2 - - [10/Oct/2023:10:01:30 +0800] "POST /login HTTP/1.1" 500 0  
192.168.1.3 - - [10/Oct/2023:10:02:15 +0800] "GET /api/data HTTP/1.1" 404 100  
10.0.0.2 - - [10/Oct/2023:10:03:00 +0800] "POST /login HTTP/1.1" 500 0  
192.168.1.1 - - [10/Oct/2023:10:05:00 +0800] "GET /largefile.zip HTTP/1.1" 200 10485760  

需求目标

用 AWK 逻辑组合实现:

  1. 筛选两类“异常请求”:
    • 条件A:状态码为 500(服务器错误)且请求方法为 POST 的请求(可能是接口故障)。
    • 条件B:状态码为 200(正常)但响应大小 > 10MB(10485760 字节)的 GET 请求(可能是大文件滥用)。
  2. 分别统计两类异常的出现次数,并按 IP 分组统计。
  3. 输出格式化报告,包含总次数和 Top IP。

实现代码

#!/usr/bin/awk -f  
# 处理 Nginx 日志,筛选并统计异常请求  

# 字段对应(根据实际日志格式调整)  
# $1: IP  $6: 请求方法(如"GET")  $9: 状态码  $10: 响应大小  

# 逻辑组合筛选异常请求,并统计  
{  
  # 条件A:500错误 + POST请求(提取方法时去除引号,如"POST→POST)  
  if ($9 == 500 && substr($6, 2, length($6)-2) == "POST") {  
    typeA_total++                     # 总次数+1  
    typeA_ip[$1]++                    # 按IP统计  
  }  
  # 条件B:200正常 + GET请求 + 响应大小>10MB(10485760字节)  
  else if ($9 == 200 && substr($6, 2, length($6)-2) == "GET" && $10 > 10485760) {  
    typeB_total++                     # 总次数+1  
    typeB_ip[$1]++                    # 按IP统计  
  }  
}  

# 输出报告(END块汇总结果)  
END {  
  print "===== 异常请求统计报告 ====="  
  # 输出类型A统计  
  print "\n1. 服务器错误(500+POST):"  
  print "   总次数:", typeA_total  
  print "   主要IP分布:"  
  for (ip in typeA_ip) {  
    printf "      %-15s 出现 %d 次\n", ip, typeA_ip[ip]  
  }  

  # 输出类型B统计  
  print "\n2. 大文件请求(200+GET+>10MB):"  
  print "   总次数:", typeB_total  
  print "   主要IP分布:"  
  for (ip in typeB_ip) {  
    printf "      %-15s 出现 %d 次\n", ip, typeB_ip[ip]  
  }  
}  

执行与输出

  1. 保存脚本为 log_analysis.awk,添加执行权限:chmod +x log_analysis.awk
  2. 执行:./log_analysis.awk access.log
  3. 输出结果:
===== 异常请求统计报告 =====  

1. 服务器错误(500+POST):  
   总次数: 2  
   主要IP分布:  
      10.0.0.2        出现 2 次  

2. 大文件请求(200+GET+>10MB):  
   总次数: 1  
   主要IP分布:  
      192.168.1.1     出现 1 次  

逻辑组合解析

  1. 多条件“与”组合
    • 条件A用 $9 == 500 && 方法==POST,确保同时满足“状态码500”和“POST请求”。
    • 条件B用 $9 == 200 && 方法==GET && 大小>10MB,三重条件筛选大文件请求。
  2. 字符串处理与逻辑结合
    • 用 substr($6, 2, length($6)-2) 提取请求方法(去除日志中方法外的引号,如 "POSTPOST),避免因引号导致匹配失败。
  3. 分支逻辑(if-else)
    • 用 else if 区分两类异常,避免重复统计(同一行不会同时属于A和B)。
  4. 数组统计
    • 用关联数组 typeA_ip[$1]++ 按IP分组,实现“多维度统计”(既算总数,又看IP分布)。

实际价值

此示例可直接用于生产环境的日志监控:

  • 快速定位接口故障(500+POST)的频发IP,排查是否为恶意请求;
  • 识别大文件滥用(200+GET+大尺寸)的IP,限制其带宽或访问权限;
  • 通过逻辑组合灵活扩展规则(如添加时间范围 $4 ~ /10:00:00/ 筛选特定时段异常)。
发表在 linux文章 | 留下评论

AWK逻辑组合专题:从基础到实战

awk逻辑组合专题:从基础到实战

一、专题提纲

AWK 逻辑组合专题:从基础到实战

一、专题提纲

1. 什么是 AWK 逻辑组合?
2. 基础逻辑运算符与优先级
3. 模式(Pattern)的逻辑组合方式
4. 高级逻辑组合技巧
5. 典型工作场景实战

二、核心知识点(基础篇)

1. 逻辑组合的本质

AWK 的逻辑组合是通过运算符将多个条件(模式)关联,实现“多条件筛选”或“分支处理”。核心目标:精准定位需要处理的行,并按规则执行对应动作

2. 基础逻辑运算符

运算符含义示例(条件)说明
&&逻辑与$2 > 100 && $3 == "ok"两个条件同时满足才成立
``逻辑或
!逻辑非!($1 == "admin")对条件取反(不满足时成立)

3. 模式组合的 3 种基础方式

(1)单个模式块内的多条件组合(同一动作)

用逻辑运算符将多个条件写在同一个模式中,匹配后执行同一动作。

# 示例:筛选“年龄>20 且 性别为male”的行,打印姓名  
awk '$2 > 20 && $3 == "male" {print $1}' student.txt  

(2)多个模式块(不同条件对应不同动作)

对不同条件设置独立的 模式 {动作} 块,各自匹配并执行对应操作(类似 if-else if)。

# 示例:按分数区间输出评价  
awk '
  $2 >= 90 {print $1, "→ 优秀"}      # 条件1:90分以上  
  $2 >= 60 && $2 < 90 {print $1, "→ 及格"}  # 条件2:60-89分  
  $2 < 60 {print $1, "→ 不及格"}     # 条件3:60分以下  
' scores.txt  

(3)范围模式(连续区间匹配)

用 , 分隔两个模式,表示“从第一个模式成立的行开始,到第二个模式成立的行结束”的所有行(包含边界)。

# 示例:提取日志中“[START]”到“[END]”之间的所有行  
awk '/\[START\]/, /\[END\]/ {print}' app.log  

4. 优先级与括号的重要性

  • 优先级顺序:!(非) > &&(与) > ||(或)。
  • 复杂逻辑必须用 () 明确分组,避免歧义。# 错误写法(&& 优先级高于 ||,实际等价于 $1=="a" || ($2=="b" && $3>100)) awk '$1 == "a" || $2 == "b" && $3 > 100 {print}' data.txt # 正确写法(用括号明确“a或b”的组合) awk '($1 == "a" || $2 == "b") && $3 > 100 {print}' data.txt

三、高级应用技巧

1. 逻辑与正则的混合组合

将逻辑运算符与正则表达式结合,实现更灵活的匹配。

# 示例:筛选“包含error且来自192.168网段,或包含warn且状态码为500”的日志行  
awk '(/error/ && $1 ~ /^192\.168\./) || (/warn/ && $9 == 500) {print}' access.log  

~ 表示“匹配正则”,$1 ~ /^192\.168\./ 即第1字段为192.168网段IP)

2. 条件嵌套(动作块内的逻辑组合)

在动作块({})中用 if-else 嵌套逻辑,实现“先筛选行,再细分处理”。

# 示例:先筛选分数>80的行,再按科目细分统计  
awk '$3 > 80 {  # 外层:只处理分数>80的行  
  if ($2 == "math") math++;    # 内层:按科目累加  
  else if ($2 == "english") english++;  
}' scores.txt  
END {print "数学优秀:", math; print "英语优秀:", english}  

3. 短路求值与效率优化

AWK 支持逻辑运算的“短路特性”:

  • A && B:若 A 为假,直接跳过 B(无需计算)。
  • A || B:若 A 为真,直接跳过 B(无需计算)。
    可利用此特性优化复杂逻辑的执行效率。
# 示例:先判断行是否包含“POST”请求(快速筛选),再判断状态码(复杂判断)  
awk '/POST/ && $9 == 500 {print "POST请求500错误:", $7}' access.log  

4. 结合数组的多条件统计

用逻辑组合定义数组的键,实现按“多维度条件”统计。

# 示例:统计“不同IP+不同状态码”的出现次数  
awk '{  
  # 键为“IP:状态码”,仅统计GET请求且状态码非200的记录  
  if ($6 == "\"GET" && $9 != 200) {  
    key = $1 ":" $9;  
    count[key]++  
  }  
} END {for (k in count) print k, count[k]}' access.log  

四、典型工作场景实战

1. 日志分析:错误分类与统计

场景:从 Nginx 日志中统计“404错误(来自移动端)”和“500错误(来自PC端)”的数量。

# Nginx日志格式(简化):$1=IP,$12=设备类型,$9=状态码  
awk '  
  # 条件1:404错误且设备为移动端  
  $9 == 404 && $12 ~ /Mobile/ {count_404_mobile++}  
  # 条件2:500错误且设备为PC端  
  $9 == 500 && $12 ~ /PC/ {count_500_pc++}  
END {  
  print "移动端404次数:", count_404_mobile;  
  print "PC端500次数:", count_500_pc  
}' access.log  

2. 数据清洗:多条件过滤无效数据

场景:处理用户数据(user.txt,格式:姓名 年龄 注册时间),筛选“年龄>18、注册时间在2023年之后”的有效用户。

awk '  
  # 年龄>18 且 注册时间($3)以2023/2024开头(假设格式YYYY-MM-DD)  
  $2 > 18 && ($3 ~ /^2023/ || $3 ~ /^2024/) {print}  
' user.txt > valid_users.txt  

3. 系统配置分析:用户权限筛选

场景:从 /etc/passwd 中筛选“可登录(shell非/sbin/nologin)且家目录在/home下”的用户。

awk -F ':' '  
  # 分隔符为:,$7=shell,$6=家目录  
  $7 != "/sbin/nologin" && $6 ~ /^\/home/ {print $1, $6}  
' /etc/passwd  

4. 报表生成:多维度数据汇总

场景:从销售数据(sales.csv,格式:日期,产品,销售额)中,统计“产品A在2023年10月销售额>1000”的日期。

awk -F ',' '  
  NR > 1 && $2 == "A" && $1 ~ /^2023-10/ && $3 > 1000 {  
    print $1, "销售额:", $3  
  }  
' sales.csv > report.txt  

五、总结

  • 基础核心:掌握 &&/||/! 的用法,明确优先级,善用括号。
  • 高级技巧:结合正则、数组、条件嵌套,利用短路特性优化效率。
  • 实战关键:根据场景选择“单模式多条件”或“多模式块”,优先用逻辑组合减少代码量。

通过多练习日志分析、数据清洗等场景,可快速掌握逻辑组合的精髓,提升 AWK 处理复杂文本的效率。

发表在 linux文章 | 留下评论

awk的逻辑组合

AWK的逻辑组合(多条件判断、分支处理等)凭借其轻量、高效的文本处理能力,在除日志分析外的多个领域都有广泛应用。以下是几个典型典型领域及具体场景,结合逻辑组合的用法说明:

一、数据清洗与格式转换

核心需求:从非结构化/半结构化数据中提取有效信息,按规则过滤、转换格式。
逻辑组合应用:用多条件判断筛选符合要求的记录,或按不同格式规则分支处理。

示例:处理 CSV 格式的用户数据

假设有 users.csv(格式:姓名,年龄,性别,注册日期),需:

  1. 保留“年龄>18 且 注册日期在2023年后”的有效用户;
  2. 对“男性”用户添加标签 [M],对“女性”用户添加标签 [F]
# 用逻辑组合筛选+分支处理
awk -F ',' '
  NR == 1 {print "姓名,标签,年龄,注册日期"}  # 保留表头
  NR > 1 {
    # 条件1:年龄>18 且 注册日期以2023/2024开头(多条件与)
    if ($2 > 18 && ($4 ~ /^2023/ || $4 ~ /^2024/)) {
      # 条件2:按性别分支处理(逻辑或+分支)
      if ($3 == "男") tag = "[M]";
      else if ($3 == "女") tag = "[F]";
      else tag = "[未知]";
      print $1 "," tag "," $2 "," $4;  # 输出转换后的数据
    }
  }
' users.csv > cleaned_users.csv

二、系统配置与监控

核心需求:分析系统配置文件、监控系统状态,提取关键信息或判断异常。
逻辑组合应用:结合正则匹配与条件判断,筛选特定配置项或异常状态。

示例:分析 /etc/passwd 识别特殊用户

需求:找出“可登录(shell 非 /sbin/nologin)且 家目录不在 /home 下”的特权用户。

# 多条件组合筛选系统用户
awk -F ':' '
  # 条件:shell不是nologin,且家目录不以/home开头(逻辑与+非)
  $7 != "/sbin/nologin" && !($6 ~ /^\/home/) {
    print "特权用户:", $1, "家目录:", $6, "shell:", $7
  }
' /etc/passwd

三、报表生成与统计分析

核心需求:从原始数据中按多维度统计(如分类计数、求和、平均值),生成结构化报表。
逻辑组合应用:用条件分组统计,结合数组存储多维度结果。

示例:销售数据多维度统计

假设有 sales.txt(格式:地区,产品,销售额),需统计:

  1. “华东地区”的“手机”和“电脑”销售额总和;
  2. 其他地区“电脑”销售额超 10000 的记录数。
# 多条件分组统计
awk -F ',' '
  NR > 1 {
    # 条件1:华东地区的手机或电脑(逻辑与+或)
    if ($1 == "华东" && ($2 == "手机" || $2 == "电脑")) {
      east_total += $3;
    }
    # 条件2:非华东地区且电脑销售额>10000(逻辑与+非)
    else if ($1 != "华东" && $2 == "电脑" && $3 > 10000) {
      other_high_count++;
    }
  }
END {
  print "华东地区手机+电脑总销售额:", east_total;
  print "其他地区电脑销售额超10000的记录数:", other_high_count;
}
' sales.txt

四、文本内容提取与过滤

核心需求:从文档、代码、配置文件中提取符合特定规则的内容(如提取URL、过滤敏感词)。
逻辑组合应用:用正则+条件判断精准定位目标内容。

示例:从HTML中提取有效链接

需求:从网页源码中提取“以 https 开头且 域名包含 example”的链接。

# 正则+逻辑组合提取链接
awk '
  # 匹配<a>标签中的href,且链接符合https+example条件(正则+逻辑与)
  /<a[^>]+href="[^"]+"/ {
    # 提取href值(简化处理)
    if (match($0, /href="([^"]+)"/, arr)) {
      url = arr[1];
      # 条件:以https开头且包含example(逻辑与)
      if (url ~ /^https/ && url ~ /example/) {
        print url;
      }
    }
  }
' page.html

五、数据库与数据交互辅助

核心需求:处理数据库导出的文本(如SQL结果、CSV备份),进行格式转换或校验。
逻辑组合应用:用多条件校验数据合法性,或按数据库格式规则转换。

示例:校验MySQL导出的用户表数据

假设有 users.sql(导出格式:INSERT INTO ... (id,name,email) VALUES (1,'a','a@x.com');),需校验:

  1. id 为数字且 >0;
  2. email 包含 @ 且 域名不为 test.com
# 多条件校验数据合法性
awk '
  /INSERT INTO.*users/ {  # 匹配用户表插入语句
    # 提取id、email(简化正则)
    if (match($0, /VALUES \(([0-9]+),[^,]+,'\''([^'\'']+)'\'')/, arr)) {
      id = arr[1];
      email = arr[2];
      # 条件1:id>0 且 email含@ 且 域名不是test.com(多条件与)
      if (id > 0 && email ~ /@/ && !(email ~ /@test\.com$/)) {
        valid++;
      } else {
        invalid++;
        print "无效记录:", $0;
      }
    }
  }
END {
  print "有效记录数:", valid, "无效记录数:", invalid;
}
' users.sql

总结

AWK 逻辑组合的核心价值在于**“用简洁的语法实现多条件驱动的文本处理”**,其应用场景覆盖:

  • 数据处理(清洗、转换、统计);
  • 系统管理(配置分析、状态监控);
  • 内容提取(文档、代码、网页);
  • 数据校验(数据库、文件格式)。

只要涉及“按规则筛选/处理文本”的场景,AWK 的逻辑组合都能大幅提升效率,尤其适合中小规模数据的快速处理(无需编写复杂脚本或依赖数据库)。

发表在 linux文章 | 留下评论

AWK 的主流版本

AWK 的主流版本是 GNU AWK(gawk),它是 AWK 语言的最主要实现,也是目前更新最活跃的版本。以下是其最新版本及主要升级内容:

AWK 的主流版本是 GNU AWK(gawk),它是 AWK 语言的最主要实现,也是目前更新最活跃的版本。以下是其最新版本及主要升级内容:

一、最新稳定版本

截至 2024 年,GNU AWK 的最新稳定版本是 gawk 5.3.0(2023 年 10 月发布)。

二、主要版本升级内容(5.x 系列)

1. gawk 5.3.0(2023 年 10 月)

  • 正则表达式增强:支持 Unicode 15.0 标准,改进了对多字节字符的匹配精度。
  • 性能优化:提升了大型数组遍历和字符串操作的效率,尤其在处理 GB 级文本时速度提升约 10%。
  • 新增函数strtonum() 函数支持更多进制转换(如二进制 0b 前缀、十六进制 0x 前缀)。
  • 兼容性修复:解决了与最新 Linux 内核版本的兼容性问题,修复了部分边缘场景的内存泄漏。

2. gawk 5.2.0(2022 年 12 月)

  • 安全增强:新增 --sandbox 模式,限制脚本对文件系统的写入和命令执行,防止恶意脚本攻击。
  • 调试功能:改进了 --debug 选项,支持更详细的语法解析日志,便于复杂脚本的调试。
  • 数组功能:支持对数组使用 delete 命令时返回删除的元素数量(n = delete arr)。
  • 国际化:增强了对非英语 locales 的支持,strftime() 函数可正确处理多语言日期格式。

3. gawk 5.1.0(2021 年 5 月)

  • 主要特性更新
    • 引入 命名捕获组(正则表达式中支持 (?<name>...) 语法,可通过 arr["name"] 访问捕获内容)。
    • 新增 @load 指令,支持动态加载扩展模块(如 JSON 解析、数学函数扩展)。
    • 改进 FPAT 变量(字段模式),更灵活地定义非分隔符的字段格式(适合处理不规则 CSV)。
  • 性能优化:优化了正则表达式引擎,复杂模式匹配速度提升约 15%。

三、5.x 系列与旧版本(4.x)的核心差异

1. 正则功能:5.x 支持命名捕获组、Unicode 更高版本标准,正则表达式能力更接近现代编程语言。
2. 安全性:新增沙箱模式,适合在多用户环境中运行不可信的 awk 脚本。
3. 扩展性:支持动态加载模块,可扩展功能(如处理 JSON、XML 等格式)。
4. 性能:数组操作和字符串处理效率显著提升,更适合大数据量场景。

四、升级方法

  1. 源码编译(推荐,获取最新版本):wget https://ftp.gnu.org/gnu/gawk/gawk-5.3.0.tar.gz tar -zxvf gawk-5.3.0.tar.gz cd gawk-5.3.0 ./configure && make && sudo make install
  2. 包管理器安装(适用于主流 Linux 发行版,版本可能滞后):
    • Ubuntu/Debian:sudo apt update && sudo apt install gawk
    • CentOS/RHEL:sudo yum install gawk
    • macOS(Homebrew):brew install gawk
  3. 验证版本gawk --version # 输出 "GNU Awk 5.3.0" 即表示升级成功

五、使用建议

  • 对于日常文本处理(如日志分析、数据清洗),gawk 5.x 的新特性(如命名捕获组、沙箱模式)能显著提升效率和安全性。
  • 若脚本需要兼容旧系统(如 CentOS 7 自带 gawk 4.0.2),需注意避免使用 5.x 独有的特性(如命名捕获组)。
  • 官方文档(info gawk 或在线手册)是学习新特性的最佳资源,尤其推荐关注正则增强和扩展模块部分。

通过升级到最新版本,可充分利用 AWK 在文本处理中的现代特性,适应更复杂的场景需求。

截至 2024 年,GNU AWK 的最新稳定版本是 gawk 5.3.0(2023 年 10 月发布)。

二、主要版本升级内容(5.x 系列)

1. gawk 5.3.0(2023 年 10 月)

  • 正则表达式增强:支持 Unicode 15.0 标准,改进了对多字节字符的匹配精度。
  • 性能优化:提升了大型数组遍历和字符串操作的效率,尤其在处理 GB 级文本时速度提升约 10%。
  • 新增函数strtonum() 函数支持更多进制转换(如二进制 0b 前缀、十六进制 0x 前缀)。
  • 兼容性修复:解决了与最新 Linux 内核版本的兼容性问题,修复了部分边缘场景的内存泄漏。

2. gawk 5.2.0(2022 年 12 月)

  • 安全增强:新增 --sandbox 模式,限制脚本对文件系统的写入和命令执行,防止恶意脚本攻击。
  • 调试功能:改进了 --debug 选项,支持更详细的语法解析日志,便于复杂脚本的调试。
  • 数组功能:支持对数组使用 delete 命令时返回删除的元素数量(n = delete arr)。
  • 国际化:增强了对非英语 locales 的支持,strftime() 函数可正确处理多语言日期格式。

3. gawk 5.1.0(2021 年 5 月)

  • 主要特性更新
    • 引入 命名捕获组(正则表达式中支持 (?<name>...) 语法,可通过 arr["name"] 访问捕获内容)。
    • 新增 @load 指令,支持动态加载扩展模块(如 JSON 解析、数学函数扩展)。
    • 改进 FPAT 变量(字段模式),更灵活地定义非分隔符的字段格式(适合处理不规则 CSV)。
  • 性能优化:优化了正则表达式引擎,复杂模式匹配速度提升约 15%。

三、5.x 系列与旧版本(4.x)的核心差异

1. 正则功能:5.x 支持命名捕获组、Unicode 更高版本标准,正则表达式能力更接近现代编程语言。
2. 安全性:新增沙箱模式,适合在多用户环境中运行不可信的 awk 脚本。
3. 扩展性:支持动态加载模块,可扩展功能(如处理 JSON、XML 等格式)。
4. 性能:数组操作和字符串处理效率显著提升,更适合大数据量场景。

四、升级方法

  1. 源码编译(推荐,获取最新版本):wget https://ftp.gnu.org/gnu/gawk/gawk-5.3.0.tar.gz tar -zxvf gawk-5.3.0.tar.gz cd gawk-5.3.0 ./configure && make && sudo make install
  2. 包管理器安装(适用于主流 Linux 发行版,版本可能滞后):
    • Ubuntu/Debian:sudo apt update && sudo apt install gawk
    • CentOS/RHEL:sudo yum install gawk
    • macOS(Homebrew):brew install gawk
  3. 验证版本gawk --version # 输出 "GNU Awk 5.3.0" 即表示升级成功

五、使用建议

  • 对于日常文本处理(如日志分析、数据清洗),gawk 5.x 的新特性(如命名捕获组、沙箱模式)能显著提升效率和安全性。
  • 若脚本需要兼容旧系统(如 CentOS 7 自带 gawk 4.0.2),需注意避免使用 5.x 独有的特性(如命名捕获组)。
  • 官方文档(info gawk 或在线手册)是学习新特性的最佳资源,尤其推荐关注正则增强和扩展模块部分。

通过升级到最新版本,可充分利用 AWK 在文本处理中的现代特性,适应更复杂的场景需求。

发表在 linux文章 | 留下评论

ls与du统计占用空间大小的区别

ls与du统计占用空间大小的区别

ls 命令显示的文件大小是通过读取文件系统的元数据(metadata)获取的,具体来说是读取文件的 inode 信息 中的大小字段。以下是详细解释:

一、获取文件大小的底层原理

  1. 文件系统存储元数据
    每个文件在 Linux 系统中都对应一个 inode(索引节点),其中记录了文件的关键属性:
    • 文件大小(字节数)
    • 创建/修改/访问时间
    • 权限、所有者
    • 数据块在磁盘上的存储位置等

ls 命令通过系统调用(如 stat() 或 lstat())读取这些元数据,从而获取文件大小。
(https://www.calcguide.tech/2025/08/20/ls%e4%b8%8edu%e7%bb%9f%e8%ae%a1%e5%8d%a0%e7%94%a8%e7%a9%ba%e9%97%b4%e5%a4%a7%e5%b0%8f%e7%9a%84%e5%8c%ba%e5%88%ab/)

  1. ls 命令的实现逻辑
    当执行 ls -l 或 ls -s 时,ls 会:
    • 遍历目标目录下的所有文件/目录
    • 对每个文件调用 stat() 系统调用,获取 inode 中的 st_size 字段(文件大小,单位字节)
    • 根据用户指定的格式(如 -h 显示人类可读单位,-k 显示 KB 等)转换并展示大小

二、不同 ls 选项的文件大小显示

ls 提供多种选项控制文件大小的显示方式,本质都是对 st_size 字段的转换:

选项含义示例(1024字节文件)
默认(无)显示字节数(st_size 原始值)1024
-h人类可读格式(自动转换为 K/M/G)1.0K
-k以 KB(1024字节)为单位1
-l长格式中显示字节数(同默认)1024
-s显示磁盘占用块数(每个块默认512字节)2(1024/512=2块)

三、特殊情况说明

  1. 目录的大小
    ls -l 显示的目录大小通常是固定值(如 4096 字节),这是目录本身的 inode 元数据占用的磁盘块大小,而非目录内所有文件的总大小。
    若要查看目录内所有文件的总大小,需用 du -sh 目录名
  2. 符号链接(软链接)
    ls -l 显示的是软链接本身的大小(通常是链接目标路径的字符串长度),而非目标文件的大小。
    若要查看目标文件大小,需用 ls -lL 链接名-L 跟随链接)。
  3. 稀疏文件(Sparse File)
    稀疏文件的 ls 显示大小(st_size)可能远大于实际磁盘占用(如虚拟机镜像文件),因为其未写入数据的区域不占用磁盘空间。

四、验证:手动查看文件大小元数据

可以通过 stat 命令直接查看文件的元数据,对比 ls 的结果:

# 查看文件的元数据
stat example.txt

# 输出中与大小相关的字段:
# Size: 1024        (文件逻辑大小,同 ls 显示的字节数)
# Blocks: 8         (实际占用磁盘块数,同 ls -s 显示的值)

总结:ls 的文件大小来自文件系统的 inode 元数据,通过系统调用读取后转换为用户指定的格式,不同选项仅影响显示单位,不改变原始大小值。

发表在 linux文章 | 留下评论

sigaltstack系统调用及示例

sigaltstack 函数详解

1. 函数介绍

sigaltstack 系统调用及示例,sigaltstack是Linux系统调用,用于设置和获取信号处理程序的备用栈(alternate signal stack)。当进程收到信号时,内核通常在当前栈上执行信号处理程序。使用 sigaltstack 可以为信号处理程序指定一个独立的栈空间,这对于处理栈溢出等异常情况特别有用。

2. 函数原型

#include <signal.h>
int sigaltstack(const stack_t *ss, stack_t *oss);

3. 功能

sigaltstack 允许进程为信号处理程序设置一个备用的栈空间。当信号被递送到使用备用栈的信号处理程序时,内核会切换到备用栈执行信号处理程序,执行完毕后再切换回原来的栈。

4. 参数

  • *const stack_t ss: 指向新栈设置的指针(NULL表示不改变当前设置)
  • *stack_t oss: 指向存储旧栈设置的指针(NULL表示不获取旧设置)

5. 返回值

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

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

  • signal/sigaction: 设置信号处理程序
  • sigprocmask: 设置信号屏蔽字
  • setjmp/longjmp: 非局部跳转
  • getcontext/setcontext: 上下文操作

7. 示例代码

示例1:基础sigaltstack使用

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

/**
 * 信号栈结构体
 */
typedef struct {
    void *stack_ptr;
    size_t stack_size;
    int is_active;
} signal_stack_t;

/**
 * 显示当前信号栈信息
 */
void show_signal_stack_info() {
    stack_t current_stack;
    
    if (sigaltstack(NULL, &current_stack) == 0) {
        printf("=== 当前信号栈信息 ===\n");
        printf("栈指针: %p\n", current_stack.ss_sp);
        printf("栈大小: %zu 字节\n", current_stack.ss_size);
        printf("栈标志: ");
        if (current_stack.ss_flags & SS_ONSTACK) {
            printf("SS_ONSTACK (正在使用中)\n");
        } else if (current_stack.ss_flags & SS_DISABLE) {
            printf("SS_DISABLE (已禁用)\n");
        } else {
            printf("SS_ENABLED (已启用)\n");
        }
        printf("\n");
    } else {
        printf("获取信号栈信息失败: %s\n", strerror(errno));
    }
}

/**
 * 信号处理程序
 */
void signal_handler(int sig) {
    stack_t current_stack;
    
    printf("信号处理程序执行中 (信号: %d)\n", sig);
    
    // 检查当前是否在备用栈上
    if (sigaltstack(NULL, &current_stack) == 0) {
        if (current_stack.ss_flags & SS_ONSTACK) {
            printf("  ✓ 正在备用栈上执行\n");
        } else {
            printf("  ✗ 在主栈上执行\n");
        }
    }
    
    printf("  当前栈指针: %p\n", &sig);
    printf("  信号处理完成\n\n");
}

/**
 * 演示基础sigaltstack使用方法
 */
int demo_sigaltstack_basic() {
    stack_t new_stack, old_stack;
    char *stack_buffer;
    struct sigaction sa;
    
    printf("=== 基础sigaltstack使用示例 ===\n");
    
    // 显示原始信号栈信息
    printf("1. 原始信号栈信息:\n");
    show_signal_stack_info();
    
    // 分配备用栈空间
    size_t stack_size = SIGSTKSZ;  // 系统推荐的栈大小
    stack_buffer = malloc(stack_size);
    if (!stack_buffer) {
        perror("分配栈空间失败");
        return -1;
    }
    
    printf("2. 分配备用栈空间:\n");
    printf("   栈大小: %zu 字节\n", stack_size);
    printf("   栈地址: %p\n", (void*)stack_buffer);
    
    // 设置备用栈
    new_stack.ss_sp = stack_buffer;
    new_stack.ss_size = stack_size;
    new_stack.ss_flags = 0;  // 启用栈
    
    printf("3. 设置备用信号栈:\n");
    if (sigaltstack(&new_stack, &old_stack) == 0) {
        printf("   ✓ 备用栈设置成功\n");
        show_signal_stack_info();
    } else {
        printf("   ✗ 备用栈设置失败: %s\n", strerror(errno));
        free(stack_buffer);
        return -1;
    }
    
    // 设置使用备用栈的信号处理程序
    printf("4. 设置信号处理程序:\n");
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_ONSTACK;  // 使用备用栈
    
    if (sigaction(SIGUSR1, &sa, NULL) == 0) {
        printf("   ✓ 信号处理程序设置成功 (使用备用栈)\n");
    } else {
        printf("   ✗ 信号处理程序设置失败: %s\n", strerror(errno));
        free(stack_buffer);
        return -1;
    }
    
    // 发送信号测试
    printf("5. 发送测试信号:\n");
    if (kill(getpid(), SIGUSR1) == 0) {
        printf("   ✓ 信号发送成功\n");
        sleep(1);  // 等待信号处理完成
    } else {
        printf("   ✗ 信号发送失败: %s\n", strerror(errno));
    }
    
    // 禁用备用栈
    printf("6. 禁用备用栈:\n");
    stack_t disable_stack;
    disable_stack.ss_flags = SS_DISABLE;
    
    if (sigaltstack(&disable_stack, NULL) == 0) {
        printf("   ✓ 备用栈禁用成功\n");
        show_signal_stack_info();
    } else {
        printf("   ✗ 备用栈禁用失败: %s\n", strerror(errno));
    }
    
    // 清理资源
    free(stack_buffer);
    
    return 0;
}

int main() {
    return demo_sigaltstack_basic();
}

示例2:栈溢出保护演示

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <setjmp.h>

/**
 * 栈溢出保护结构
 */
typedef struct {
    stack_t alt_stack;
    char *stack_buffer;
    sigjmp_buf jump_buffer;
    int overflow_detected;
} stack_protection_t;

/**
 * 栈溢出信号处理程序
 */
void stack_overflow_handler(int sig) {
    printf("⚠ 检测到栈溢出异常 (信号: %d)\n", sig);
    
    // 检查是否在备用栈上
    stack_t current_stack;
    if (sigaltstack(NULL, &current_stack) == 0) {
        if (current_stack.ss_flags & SS_ONSTACK) {
            printf("  ✓ 在备用栈上安全处理异常\n");
        } else {
            printf("  ✗ 不在备用栈上 (异常情况)\n");
        }
    }
    
    // 恢复到安全状态
    printf("  跳转到安全恢复点...\n");
    siglongjmp(((stack_protection_t*)NULL)->jump_buffer, 1);
}

/**
 * 递归函数(可能导致栈溢出)
 */
void recursive_function(int depth) {
    char buffer[1024];  // 消耗栈空间
    
    // 填充缓冲区以确保栈使用
    memset(buffer, depth & 0xFF, sizeof(buffer));
    
    printf("递归深度: %d, 栈地址: %p\n", depth, (void*)&buffer);
    
    // 深度递归可能导致栈溢出
    if (depth < 1000) {  // 限制递归深度
        recursive_function(depth + 1);
    }
}

/**
 * 安全的递归执行
 */
int safe_recursive_execution(stack_protection_t *protection) {
    struct sigaction sa;
    int result;
    
    printf("=== 栈溢出保护演示 ===\n");
    
    // 设置信号处理程序
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = stack_overflow_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_ONSTACK;
    
    if (sigaction(SIGSEGV, &sa, NULL) != 0) {
        printf("设置SIGSEGV处理程序失败: %s\n", strerror(errno));
        return -1;
    }
    
    if (sigaction(SIGBUS, &sa, NULL) != 0) {
        printf("设置SIGBUS处理程序失败: %s\n", strerror(errno));
        return -1;
    }
    
    // 设置跳转点
    result = sigsetjmp(protection->jump_buffer, 1);
    if (result == 0) {
        printf("开始安全递归执行...\n");
        
        // 执行可能引起栈溢出的操作
        recursive_function(0);
        
        printf("递归执行正常完成\n");
        return 0;
    } else {
        printf("✓ 从栈溢出异常中成功恢复\n");
        return 1;
    }
}

/**
 * 演示栈溢出保护
 */
int demo_stack_overflow_protection() {
    stack_protection_t protection = {0};
    size_t stack_size = SIGSTKSZ * 2;  // 更大的备用栈
    
    printf("=== 栈溢出保护演示 ===\n");
    
    // 分配备用栈
    protection.stack_buffer = malloc(stack_size);
    if (!protection.stack_buffer) {
        perror("分配备用栈失败");
        return -1;
    }
    
    printf("分配备用栈: %zu 字节\n", stack_size);
    
    // 设置备用栈
    protection.alt_stack.ss_sp = protection.stack_buffer;
    protection.alt_stack.ss_size = stack_size;
    protection.alt_stack.ss_flags = 0;
    
    if (sigaltstack(&protection.alt_stack, NULL) != 0) {
        printf("设置备用栈失败: %s\n", strerror(errno));
        free(protection.stack_buffer);
        return -1;
    }
    
    printf("备用栈设置成功\n");
    show_signal_stack_info();
    
    // 执行安全递归
    int result = safe_recursive_execution(&protection);
    
    // 清理资源
    free(protection.stack_buffer);
    
    return result;
}

// 辅助函数声明
void show_signal_stack_info();

int main() {
    return demo_stack_overflow_protection();
}

示例3:多信号处理程序管理

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>

/**
 * 多栈管理器
 */
typedef struct {
    stack_t stack1;
    stack_t stack2;
    char *buffer1;
    char *buffer2;
    int current_stack;
} multi_stack_manager_t;

/**
 * 信号处理程序1(使用栈1)
 */
void signal_handler_1(int sig) {
    printf("信号处理程序1执行 (信号: %d)\n", sig);
    
    stack_t current_stack;
    if (sigaltstack(NULL, &current_stack) == 0) {
        if (current_stack.ss_flags & SS_ONSTACK) {
            printf("  使用备用栈1执行\n");
        }
    }
    
    printf("  处理程序1完成\n");
}

/**
 * 信号处理程序2(使用栈2)
 */
void signal_handler_2(int sig) {
    printf("信号处理程序2执行 (信号: %d)\n", sig);
    
    stack_t current_stack;
    if (sigaltstack(NULL, &current_stack) == 0) {
        if (current_stack.ss_flags & SS_ONSTACK) {
            printf("  使用备用栈2执行\n");
        }
    }
    
    printf("  处理程序2完成\n");
}

/**
 * 初始化多栈管理器
 */
int init_multi_stack_manager(multi_stack_manager_t *manager) {
    size_t stack_size = SIGSTKSZ;
    
    printf("=== 多栈管理器初始化 ===\n");
    
    // 分配两个栈空间
    manager->buffer1 = malloc(stack_size);
    manager->buffer2 = malloc(stack_size);
    
    if (!manager->buffer1 || !manager->buffer2) {
        printf("分配栈空间失败\n");
        if (manager->buffer1) free(manager->buffer1);
        if (manager->buffer2) free(manager->buffer2);
        return -1;
    }
    
    printf("栈1地址: %p, 大小: %zu\n", (void*)manager->buffer1, stack_size);
    printf("栈2地址: %p, 大小: %zu\n", (void*)manager->buffer2, stack_size);
    
    // 初始化栈结构
    manager->stack1.ss_sp = manager->buffer1;
    manager->stack1.ss_size = stack_size;
    manager->stack1.ss_flags = 0;
    
    manager->stack2.ss_sp = manager->buffer2;
    manager->stack2.ss_size = stack_size;
    manager->stack2.ss_flags = 0;
    
    manager->current_stack = 0;
    
    return 0;
}

/**
 * 切换备用栈
 */
int switch_alternate_stack(multi_stack_manager_t *manager, int stack_id) {
    stack_t *target_stack = (stack_id == 1) ? &manager->stack1 : &manager->stack2;
    
    printf("切换到备用栈 %d\n", stack_id);
    
    if (sigaltstack(target_stack, NULL) == 0) {
        printf("✓ 成功切换到备用栈 %d\n", stack_id);
        manager->current_stack = stack_id;
        return 0;
    } else {
        printf("✗ 切换备用栈 %d 失败: %s\n", stack_id, strerror(errno));
        return -1;
    }
}

/**
 * 演示多信号处理程序
 */
int demo_multi_signal_handlers() {
    multi_stack_manager_t manager = {0};
    struct sigaction sa1, sa2;
    
    printf("=== 多信号处理程序演示 ===\n");
    
    // 初始化多栈管理器
    if (init_multi_stack_manager(&manager) != 0) {
        printf("初始化多栈管理器失败\n");
        return -1;
    }
    
    // 设置信号处理程序1(使用栈1)
    printf("\n设置信号处理程序1:\n");
    switch_alternate_stack(&manager, 1);
    
    memset(&sa1, 0, sizeof(sa1));
    sa1.sa_handler = signal_handler_1;
    sigemptyset(&sa1.sa_mask);
    sa1.sa_flags = SA_ONSTACK;
    
    if (sigaction(SIGUSR1, &sa1, NULL) == 0) {
        printf("✓ 信号处理程序1设置成功\n");
    } else {
        printf("✗ 信号处理程序1设置失败: %s\n", strerror(errno));
    }
    
    // 设置信号处理程序2(使用栈2)
    printf("\n设置信号处理程序2:\n");
    switch_alternate_stack(&manager, 2);
    
    memset(&sa2, 0, sizeof(sa2));
    sa2.sa_handler = signal_handler_2;
    sigemptyset(&sa2.sa_mask);
    sa2.sa_flags = SA_ONSTACK;
    
    if (sigaction(SIGUSR2, &sa2, NULL) == 0) {
        printf("✓ 信号处理程序2设置成功\n");
    } else {
        printf("✗ 信号处理程序2设置失败: %s\n", strerror(errno));
    }
    
    // 测试信号处理
    printf("\n测试信号处理:\n");
    
    // 发送SIGUSR1
    printf("发送SIGUSR1信号:\n");
    switch_alternate_stack(&manager, 1);
    if (kill(getpid(), SIGUSR1) == 0) {
        printf("✓ SIGUSR1发送成功\n");
        sleep(1);
    }
    
    // 发送SIGUSR2
    printf("发送SIGUSR2信号:\n");
    switch_alternate_stack(&manager, 2);
    if (kill(getpid(), SIGUSR2) == 0) {
        printf("✓ SIGUSR2发送成功\n");
        sleep(1);
    }
    
    // 清理资源
    free(manager.buffer1);
    free(manager.buffer2);
    
    return 0;
}

int main() {
    return demo_multi_signal_handlers();
}

示例4:线程安全的信号栈管理

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>

/**
 * 线程信号栈管理器
 */
typedef struct {
    stack_t signal_stack;
    char *stack_buffer;
    pthread_t thread_id;
    int thread_num;
} thread_stack_manager_t;

/**
 * 线程信号处理程序
 */
void thread_signal_handler(int sig) {
    pthread_t current_thread = pthread_self();
    
    printf("线程 %lu 收到信号 %d\n", (unsigned long)current_thread, sig);
    
    stack_t current_stack;
    if (sigaltstack(NULL, &current_stack) == 0) {
        if (current_stack.ss_flags & SS_ONSTACK) {
            printf("  线程 %lu 在备用栈上处理信号\n", (unsigned long)current_thread);
        } else {
            printf("  线程 %lu 在主栈上处理信号\n", (unsigned long)current_thread);
        }
    }
    
    printf("  线程 %lu 信号处理完成\n", (unsigned long)current_thread);
}

/**
 * 初始化线程栈管理器
 */
int init_thread_stack_manager(thread_stack_manager_t *manager, int thread_num) {
    size_t stack_size = SIGSTKSZ;
    
    manager->thread_num = thread_num;
    manager->thread_id = pthread_self();
    
    // 分配栈空间
    manager->stack_buffer = malloc(stack_size);
    if (!manager->stack_buffer) {
        printf("线程 %d: 分配栈空间失败\n", thread_num);
        return -1;
    }
    
    // 初始化栈结构
    manager->signal_stack.ss_sp = manager->stack_buffer;
    manager->signal_stack.ss_size = stack_size;
    manager->signal_stack.ss_flags = 0;
    
    printf("线程 %d: 分配备用栈 %p, 大小 %zu\n", 
           thread_num, (void*)manager->stack_buffer, stack_size);
    
    return 0;
}

/**
 * 线程工作函数
 */
void* thread_worker(void *arg) {
    thread_stack_manager_t *manager = (thread_stack_manager_t*)arg;
    struct sigaction sa;
    sigset_t set;
    
    printf("工作线程 %d 启动 (ID: %lu)\n", 
           manager->thread_num, (unsigned long)manager->thread_id);
    
    // 设置备用栈
    if (sigaltstack(&manager->signal_stack, NULL) != 0) {
        printf("线程 %d: 设置备用栈失败: %s\n", 
               manager->thread_num, strerror(errno));
        return NULL;
    }
    
    printf("线程 %d: 备用栈设置成功\n", manager->thread_num);
    
    // 设置信号处理程序
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = thread_signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_ONSTACK;
    
    if (sigaction(SIGUSR1, &sa, NULL) != 0) {
        printf("线程 %d: 设置信号处理程序失败: %s\n", 
               manager->thread_num, strerror(errno));
        return NULL;
    }
    
    printf("线程 %d: 信号处理程序设置成功\n", manager->thread_num);
    
    // 阻塞SIGUSR1以便测试
    sigemptyset(&set);
    sigaddset(&set, SIGUSR1);
    pthread_sigmask(SIG_BLOCK, &set, NULL);
    
    // 执行工作
    for (int i = 0; i < 5; i++) {
        printf("线程 %d: 工作中... (%d/5)\n", manager->thread_num, i + 1);
        sleep(2);
    }
    
    // 解除阻塞并等待信号
    printf("线程 %d: 等待信号...\n", manager->thread_num);
    pthread_sigmask(SIG_UNBLOCK, &set, NULL);
    
    // 等待一段时间让信号处理完成
    sleep(2);
    
    printf("线程 %d: 工作完成\n", manager->thread_num);
    return NULL;
}

/**
 * 演示线程安全的信号栈管理
 */
int demo_thread_safe_signal_stacks() {
    const int num_threads = 3;
    pthread_t threads[num_threads];
    thread_stack_manager_t managers[num_threads];
    sigset_t set;
    
    printf("=== 线程安全的信号栈管理演示 ===\n");
    
    // 阻塞SIGUSR1信号
    sigemptyset(&set);
    sigaddset(&set, SIGUSR1);
    pthread_sigmask(SIG_BLOCK, &set, NULL);
    
    // 创建工作线程
    printf("创建 %d 个工作线程:\n", num_threads);
    
    for (int i = 0; i < num_threads; i++) {
        if (init_thread_stack_manager(&managers[i], i + 1) != 0) {
            printf("初始化线程 %d 失败\n", i + 1);
            // 清理已分配的资源
            for (int j = 0; j < i; j++) {
                free(managers[j].stack_buffer);
            }
            return -1;
        }
        
        if (pthread_create(&threads[i], NULL, thread_worker, &managers[i]) != 0) {
            printf("创建线程 %d 失败\n", i + 1);
            free(managers[i].stack_buffer);
            // 清理已分配的资源
            for (int j = 0; j < i; j++) {
                free(managers[j].stack_buffer);
            }
            return -1;
        }
        
        managers[i].thread_id = threads[i];
        printf("创建线程 %d: ID=%lu\n", i + 1, (unsigned long)threads[i]);
    }
    
    // 等待线程启动
    sleep(1);
    
    // 向所有线程发送信号
    printf("\n向所有线程发送SIGUSR1信号:\n");
    pthread_sigmask(SIG_UNBLOCK, &set, NULL);
    
    for (int i = 0; i < num_threads; i++) {
        // 注意:实际应用中需要更精确的线程信号发送方法
        if (kill(getpid(), SIGUSR1) == 0) {
            printf("向线程 %d 发送信号成功\n", i + 1);
        } else {
            printf("向线程 %d 发送信号失败: %s\n", i + 1, strerror(errno));
        }
        sleep(1);  // 间隔发送
    }
    
    // 等待所有线程完成
    printf("\n等待所有线程完成:\n");
    for (int i = 0; i < num_threads; i++) {
        void *result;
        pthread_join(threads[i], &result);
        printf("线程 %d 已完成\n", i + 1);
        
        // 清理资源
        free(managers[i].stack_buffer);
    }
    
    return 0;
}

int main() {
    return demo_thread_safe_signal_stacks();
}

示例5:信号栈监控和调试

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/resource.h>

/**
 * 信号栈监控器
 */
typedef struct {
    stack_t original_stack;
    stack_t current_stack;
    int stack_switch_count;
    size_t total_stack_size;
    time_t last_switch_time;
} stack_monitor_t;

/**
 * 详细的栈信息显示
 */
void show_detailed_stack_info(const char *context) {
    stack_t stack_info;
    
    printf("=== %s ===\n", context);
    
    if (sigaltstack(NULL, &stack_info) == 0) {
        printf("栈指针: %p\n", stack_info.ss_sp);
        printf("栈大小: %zu 字节\n", stack_info.ss_size);
        printf("栈标志: 0x%x\n", stack_info.ss_flags);
        
        if (stack_info.ss_flags & SS_ONSTACK) {
            printf("状态: 正在使用备用栈\n");
        } else if (stack_info.ss_flags & SS_DISABLE) {
            printf("状态: 备用栈已禁用\n");
        } else {
            printf("状态: 备用栈已启用但未使用\n");
        }
        
        // 计算栈使用情况(简化版)
        void *current_sp;
        asm volatile("mov %%rsp, %0" : "=r"(current_sp));
        printf("当前栈指针: %p\n", current_sp);
        
        if (stack_info.ss_sp) {
            ptrdiff_t distance = (char*)current_sp - (char*)stack_info.ss_sp;
            printf("距离栈底: %td 字节\n", distance);
            
            if (distance > 0 && (size_t)distance < stack_info.ss_size) {
                double usage = (double)distance / stack_info.ss_size * 100;
                printf("栈使用率: %.1f%%\n", usage);
            }
        }
    } else {
        printf("获取栈信息失败: %s\n", strerror(errno));
    }
    
    printf("\n");
}

/**
 * 信号处理程序(带监控)
 */
void monitored_signal_handler(int sig) {
    static int call_count = 0;
    call_count++;
    
    printf("监控信号处理程序执行 (第 %d 次, 信号: %d)\n", call_count, sig);
    
    show_detailed_stack_info("信号处理程序中的栈状态");
    
    // 模拟一些栈使用
    char local_buffer[512];
    memset(local_buffer, call_count & 0xFF, sizeof(local_buffer));
    
    printf("  处理程序使用了 %zu 字节本地缓冲区\n", sizeof(local_buffer));
    printf("  处理程序执行完成\n\n");
}

/**
 * 栈使用压力测试
 */
void stack_pressure_test(int depth) {
    char buffer[1024];  // 每层消耗1KB栈空间
    
    // 填充缓冲区
    memset(buffer, depth & 0xFF, sizeof(buffer));
    
    if (depth < 50) {  // 限制递归深度
        printf("递归深度: %d, 使用栈空间: %d KB\n", depth, depth);
        stack_pressure_test(depth + 1);
    } else {
        printf("达到最大递归深度: %d\n", depth);
    }
}

/**
 * 演示信号栈监控和调试
 */
int demo_stack_monitoring() {
    stack_t alt_stack, old_stack;
    char *stack_buffer;
    struct sigaction sa;
    struct rlimit rl;
    
    printf("=== 信号栈监控和调试演示 ===\n");
    
    // 显示系统栈限制
    printf("1. 系统栈限制信息:\n");
    if (getrlimit(RLIMIT_STACK, &rl) == 0) {
        printf("   主栈大小限制: %ld 字节", rl.rlim_cur);
        if (rl.rlim_cur == RLIM_INFINITY) {
            printf(" (无限制)");
        }
        printf("\n");
        printf("   最大栈大小: %ld 字节", rl.rlim_max);
        if (rl.rlim_max == RLIM_INFINITY) {
            printf(" (无限制)");
        }
        printf("\n");
    }
    
    // 显示初始栈状态
    show_detailed_stack_info("初始栈状态");
    
    // 分配备用栈
    size_t stack_size = SIGSTKSZ * 4;  // 4倍标准大小
    stack_buffer = malloc(stack_size);
    if (!stack_buffer) {
        perror("分配备用栈失败");
        return -1;
    }
    
    printf("2. 分配备用栈:\n");
    printf("   请求大小: %zu 字节 (%.1f KB)\n", stack_size, stack_size / 1024.0);
    printf("   分配地址: %p\n", (void*)stack_buffer);
    
    // 设置备用栈
    alt_stack.ss_sp = stack_buffer;
    alt_stack.ss_size = stack_size;
    alt_stack.ss_flags = 0;
    
    printf("3. 设置备用栈:\n");
    if (sigaltstack(&alt_stack, &old_stack) == 0) {
        printf("   ✓ 备用栈设置成功\n");
        show_detailed_stack_info("设置备用栈后");
    } else {
        printf("   ✗ 备用栈设置失败: %s\n", strerror(errno));
        free(stack_buffer);
        return -1;
    }
    
    // 设置监控信号处理程序
    printf("4. 设置监控信号处理程序:\n");
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = monitored_signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_ONSTACK;
    
    if (sigaction(SIGUSR1, &sa, NULL) == 0) {
        printf("   ✓ 监控信号处理程序设置成功\n");
    } else {
        printf("   ✗ 监控信号处理程序设置失败: %s\n", strerror(errno));
        free(stack_buffer);
        return -1;
    }
    
    // 发送多个信号进行测试
    printf("5. 发送测试信号:\n");
    for (int i = 1; i <= 3; i++) {
        printf("   发送第 %d 个信号:\n", i);
        if (kill(getpid(), SIGUSR1) == 0) {
            printf("     ✓ 信号发送成功\n");
            sleep(1);  // 等待处理完成
        } else {
            printf("     ✗ 信号发送失败: %s\n", strerror(errno));
        }
    }
    
    // 栈压力测试
    printf("6. 栈压力测试:\n");
    printf("   开始递归栈使用测试...\n");
    stack_pressure_test(0);
    printf("   栈压力测试完成\n");
    
    show_detailed_stack_info("压力测试后栈状态");
    
    // 禁用备用栈
    printf("7. 禁用备用栈:\n");
    stack_t disable_stack;
    disable_stack.ss_flags = SS_DISABLE;
    
    if (sigaltstack(&disable_stack, NULL) == 0) {
        printf("   ✓ 备用栈禁用成功\n");
        show_detailed_stack_info("禁用备用栈后");
    } else {
        printf("   ✗ 备用栈禁用失败: %s\n", strerror(errno));
    }
    
    // 清理资源
    free(stack_buffer);
    
    printf("=== 监控演示完成 ===\n");
    return 0;
}

int main() {
    return demo_stack_monitoring();
}

sigaltstack 使用注意事项

系统要求:

  1. 内核版本: 支持信号备用栈的Linux内核
  2. 权限要求: 通常不需要特殊权限
  3. 架构支持: 支持所有主流架构

栈大小考虑:

  1. 最小大小: 至少MINSIGSTKSZ字节
  2. 推荐大小: 使用SIGSTKSZ或更大
  3. 动态分配: 建议在堆上分配栈空间

错误处理:

  1. ENOMEM: 内存不足
  2. EINVAL: 参数无效
  3. EPERM: 权限不足

安全考虑:

  1. 栈溢出保护: 备用栈可以防止主栈溢出
  2. 信号安全: 确保信号处理程序的安全执行
  3. 资源管理: 及时释放分配的栈空间

最佳实践:

  1. 适当大小: 根据信号处理程序的需求分配栈大小
  2. 错误检查: 始终检查sigaltstack的返回值
  3. 资源清理: 程序结束时释放栈空间
  4. 线程安全: 多线程环境中每个线程需要独立的栈
  5. 监控调试: 监控栈使用情况以便调试

信号栈标志说明

SS_ONSTACK:

  • 含义: 当前正在使用备用栈执行信号处理程序
  • 用途: 检查信号处理程序是否在备用栈上执行

SS_DISABLE:

  • 含义: 备用栈被禁用
  • 用途: 禁用备用栈功能

SS_ENABLED:

  • 含义: 备用栈已启用但未使用
  • 用途: 正常状态,可以使用备用栈

相关常量

SIGSTKSZ:

  • 含义: 系统推荐的信号栈大小
  • 典型值: 8KB或更大

MINSIGSTKSZ:

  • 含义: 信号栈的最小大小
  • 典型值: 2KB

常见使用场景

1. 栈溢出保护:

// 为可能引起栈溢出的程序设置备用栈
stack_t alt_stack;
alt_stack.ss_sp = malloc(SIGSTKSZ);
alt_stack.ss_size = SIGSTKSZ;
alt_stack.ss_flags = 0;
sigaltstack(&alt_stack, NULL);

2. 信号处理程序:

// 为信号处理程序设置独立的执行环境
struct sigaction sa;
sa.sa_handler = signal_handler;
sa.sa_flags = SA_ONSTACK;
sigaction(SIGSEGV, &sa, NULL);

3. 多线程应用:

// 每个线程设置独立的信号栈
void* thread_function(void *arg) {
    stack_t thread_stack;
    thread_stack.ss_sp = malloc(SIGSTKSZ);
    thread_stack.ss_size = SIGSTKSZ;
    thread_stack.ss_flags = 0;
    sigaltstack(&thread_stack, NULL);
    // 线程工作...
}

总结

sigaltstack 是Linux系统中重要的信号处理机制,提供了:

  1. 栈隔离: 为信号处理程序提供独立的执行环境
  2. 异常处理: 防止栈溢出等异常情况
  3. 安全执行: 确保信号处理程序的安全执行
  4. 灵活配置: 支持动态栈管理和配置

通过合理使用 sigaltstack,可以构建更加健壮和安全的信号处理系统。在实际应用中,需要注意栈大小、错误处理和资源管理等关键问题。

发表在 linux文章 | 留下评论

sethostname系统调用及示例

我们来深入学习 sethostname 系统调用及示例

我们来深入学习 sethostname 系统调用及示例

1. 函数介绍

在 Linux 系统(以及大多数 Unix-like 系统)中,每台计算机都有一个唯一的标识符,叫做 主机名 (hostname)。这个主机名用于在网络中识别这台机器。例如,当你在命令行输入 hostname 时,它会显示当前机器的主机名。

sethostname 系统调用的作用就是设置这台运行着 Linux 内核的计算机的 主机名。这是一个系统级别的设置,会影响整个机器,而不仅仅是调用它的那个进程。

简单来说,sethostname 就是让你用程序来给你的 Linux 电脑“改名字”。

重要提示
1. 需要权限:修改主机名是一个特权操作,通常只有 root 用户(超级用户)才有权限执行 sethostname。普通用户尝试调用它会失败。
2. 影响范围:主机名是系统全局的属性。一旦通过 sethostname 修改,系统中所有查询主机名的地方(如 gethostnameuname 命令)都会返回新的名字。
3. 持久性:通过 sethostname 设置的主机名是临时的。它只在当前的系统运行会话(直到关机或重启)中有效。系统重启后,主机会从配置文件(如 /etc/hostname)中读取并恢复原来的主机名。

2. 函数原型

#include <unistd.h>      // 包含系统调用声明
#include <sys/utsname.h> // 有时也需要,包含主机名长度常量

int sethostname(const char *name, size_t len);

3. 功能

设置内核维护的主机名。这个主机名可以通过 gethostname 系统调用或 uname 系统调用来查询。

4. 参数

  • name:
    • const char * 类型。
    • 一个指向以 null 结尾的字符串的指针,该字符串包含了新的主机名。
  • len:
    • size_t 类型。
    • 指定 name 字符串中实际包含的字符数(不包括末尾的 null 终止符 \0)。通常使用 strlen(name) 来获取这个长度。

5. 返回值

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

6. 错误码 (errno)

  • EFAULTname 指向了调用进程无法访问的内存地址。
  • EINVALlen 超过了系统允许的最大主机名长度(通常是 MAXHOSTNAMELEN,在 <sys/utsname.h> 或 <limits.h> 中定义,常见值是 64 或 256)。
  • EPERM: 调用进程没有权限(不是 root 用户)来更改主机名。

7. 相似函数或关联函数

  • gethostname: 用于获取当前的主机名。
  • uname: 系统调用,可以获取包括主机名在内的系统信息(系统名、版本、机器类型等)。对应的命令行工具也叫 uname
  • hostname: 命令行工具,用于显示或设置系统的主机名。它在底层就是调用 sethostname 和 gethostname
  • /etc/hostname: 在许多 Linux 发行版中,系统启动时会从这个文件读取主机名并使用 sethostname 设置。

8. 示例代码

下面的示例演示了如何使用 sethostname 来修改主机名,以及如何使用 gethostname 来查询它。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <unistd.h>
#include <sys/utsname.h> // 包含 gethostname, uname, MAXHOSTNAMELEN
#include <string.h>
#include <errno.h>
#include <stdlib.h>

#define HOST_NAME_BUFFER_SIZE MAXHOSTNAMELEN // 通常定义在 <sys/utsname.h> 或 <limits.h>

void print_current_hostname(const char* context) {
    char hostname[HOST_NAME_BUFFER_SIZE];
    if (gethostname(hostname, sizeof(hostname)) == 0) {
        printf("[%s] Current hostname is: '%s'\n", context, hostname);
    } else {
        perror("gethostname");
    }
}

int main(int argc, char *argv[]) {
    char new_hostname[HOST_NAME_BUFFER_SIZE];
    struct utsname uname_info; // 用于 uname 系统调用

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

    // 1. 显示当前主机名
    print_current_hostname("Initial");

    // 2. 使用 uname 系统调用获取更详细的系统信息
    if (uname(&uname_info) == 0) {
        printf("[uname] System name: %s\n", uname_info.sysname);
        printf("[uname] Node name (hostname): %s\n", uname_info.nodename);
        printf("[uname] Release: %s\n", uname_info.release);
        printf("[uname] Version: %s\n", uname_info.version);
        printf("[uname] Machine: %s\n", uname_info.machine);
    } else {
        perror("uname");
    }

    // 3. 检查命令行参数
    if (argc != 2) {
        printf("Usage: %s <new_hostname>\n", argv[0]);
        printf("Note: You need to run this program as root to change the hostname.\n");
        exit(EXIT_FAILURE);
    }

    strncpy(new_hostname, argv[1], sizeof(new_hostname) - 1);
    new_hostname[sizeof(new_hostname) - 1] = '\0'; // 确保 null 终止

    printf("\nAttempting to change hostname to: '%s'\n", new_hostname);

    // 4. 调用 sethostname
    // 注意:这需要 root 权限
    if (sethostname(new_hostname, strlen(new_hostname)) == -1) {
        perror("sethostname");
        if (errno == EPERM) {
            printf("Error: Permission denied. You must run this program as root (e.g., using sudo).\n");
        }
        exit(EXIT_FAILURE);
    }

    printf("sethostname('%s') succeeded.\n", new_hostname);

    // 5. 再次显示主机名以验证更改
    print_current_hostname("After sethostname");

    // 6. 再次使用 uname 验证
    if (uname(&uname_info) == 0) {
        printf("[uname after change] Node name (hostname): %s\n", uname_info.nodename);
    }

    printf("\n--- Important Notes ---\n");
    printf("1. The hostname change is TEMPORARY and only lasts until the system is rebooted.\n");
    printf("2. To make the change persistent, you need to update configuration files like /etc/hostname.\n");
    printf("3. You need ROOT privileges to call sethostname.\n");

    return 0;
}

编译和运行:

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

# 1. 不带参数运行 (会报错)
./sethostname_example

# 2. 带一个参数运行,但没有 root 权限 (会失败)
./sethostname_example MyNewTempHostname

# 3. 带一个参数运行,并使用 sudo 获取 root 权限 (应该成功)
# 注意:请将 MyNewTempHostname 替换为你想要的主机名
sudo ./sethostname_example MyNewTempHostname

# 4. 验证更改
hostname
uname -n

预期输出 (使用 sudo 运行):

# 假设原始主机名是 'old-hostname'
$ sudo ./sethostname_example NewTempName
--- Demonstrating sethostname ---
[Initial] Current hostname is: 'old-hostname'
[uname] System name: Linux
[uname] Node name (hostname): old-hostname
[uname] Release: 5.4.0-XX-generic
[uname] Version: #XX-Ubuntu SMP ...
[uname] Machine: x86_64

Attempting to change hostname to: 'NewTempName'
sethostname('NewTempName') succeeded.
[After sethostname] Current hostname is: 'NewTempName'
[uname after change] Node name (hostname): NewTempName

--- Important Notes ---
1. The hostname change is TEMPORARY and only lasts until the system is rebooted.
2. To make the change persistent, you need to update configuration files like /etc/hostname.
3. You need ROOT privileges to call sethostname.

# 验证命令
$ hostname
NewTempName
$ uname -n
NewTempName

重启后:
如果你重启系统,主机会名会恢复到 /etc/hostname 文件中配置的名称。

总结:
sethostname 是一个用于修改系统全局主机名的系统调用。它需要 root 权限,并且修改是临时的。理解它有助于你编写需要动态管理主机名的系统管理工具。在日常使用中,hostname 命令是更常见的设置主机名的方式。

发表在 linux文章 | 留下评论

setfsuid系统调用及示例

我们来深入学习 setfsgid 和 setfsuid 这两个系统调用。
(https://www.calcguide.tech/2025/08/19/setfsuid%e7%b3%bb%e7%bb%9f%e8%b0%83%e7%94%a8%e5%8f%8a%e7%a4%ba%e4%be%8b/)

1. 函数介绍

在 Linux 系统中,每个进程都有一套与之相关的用户 ID (UID) 和组 ID (GID),比如:

  • 真实用户 ID (Real UID): 登录系统的用户 ID。
  • 有效用户 ID (Effective UID): 决定进程当前拥有哪些权限,用于权限检查。
  • 保存的设置用户 ID (Saved Set-UID): 用于在有效 UID 和真实 UID 之间切换。

通常,文件访问权限检查是基于进程的 有效 UID 和 有效 GID 进行的。

但是,Linux 提供了两个特殊的系统调用:setfsuid 和 setfsgid

  • setfsuid (Set File System UID): 用来设置或修改进程的 File System UID (文件系统用户 ID)
  • setfsgid (Set File System GID): 用来设置或修改进程的 File System GID (文件系统组 ID)

关键点
这两个 ID 只用于文件系统的权限检查!它们不影响其他任何权限检查(比如信号发送权限、进程调度优先级等)。当内核执行文件访问权限检查时,它会使用 文件系统 UID/GID 而不是 有效 UID/GID

默认情况下
当一个进程启动时,它的 文件系统 UID/GID 会被自动设置为与 有效 UID/GID 相同。

为什么需要它们?(主要场景)
一个经典的例子是 NFS (Network File System) 服务器
1. NFS 服务器进程通常以 root 权限运行,因此它的 有效 UID 是 0 (root)。
2. 但是,当它代表一个非 root 用户(比如 UID 1000)访问 NFS 文件系统上的文件时,它需要进行权限检查,仿佛是 UID 1000 在访问。
3. 如果只使用 setuid/seteuid 来切换有效 UID,会影响服务器进程的其他权限(比如绑定到特权端口)。
4. 所以,NFS 服务器可以调用 setfsuid(1000),这样在进行文件权限检查时,内核会看到 UID 是 1000,但进程的其他权限(如有效 UID 仍然是 root)保持不变。

简单来说
setfsuid 和 setfsgid 允许进程在进行文件访问权限检查时,“伪装”成另一个用户或组,而不会影响进程的其他特权。

2. 函数原型

#include <sys/fsuid.h> // 包含函数声明 (在某些系统上可能在 unistd.h 或其他地方)

// 设置文件系统用户 ID
int setfsuid(uid_t fsuid);

// 设置文件系统组 ID
int setfsgid(gid_t fsgid);

3. 功能

分别设置调用进程的文件系统用户 ID (FS-UID) 和文件系统组 ID (FS-GID),仅用于文件系统的访问权限检查。

4. 参数

  • fsuid:
    • uid_t 类型。
    • 指定要设置的新文件系统用户 ID。
  • fsgid:
    • gid_t 类型。
    • 指定要设置的新文件系统组 ID。

5. 返回值

这两个函数的返回值比较特殊:

  • 总是返回调用者之前的 文件系统 UID (setfsuid) 或 文件系统 GID (setfsgid)
  • 无论调用成功与否

这与其他大多数系统调用不同(它们通常成功返回 0 或文件描述符,失败返回 -1)。这种设计意味着你无法仅通过返回值判断调用是否成功。

那么如何判断是否成功呢
通常的做法是:
1. 先调用 setfsuid/setfsgid
2. 然后立即再次调用 setfsuid/setfsgid,传入同一个 ID。
3. 如果两次返回的旧 ID 相同,说明第一次调用成功了;如果不同,则说明第一次调用失败。

调用失败的情况
调用通常会成功,但在某些安全模型下或传入无效 ID 时可能会失败。不过,失败的具体条件比较复杂,实践中很少遇到。

6. 相似函数或关联函数

  • setuid / seteuid / setreuid / setresuid: 用于设置进程的真实、有效、保存的用户 ID。这些会影响所有权限检查,而不仅仅是文件系统权限。
  • setgid / setegid / setregid / setresgid: 用于设置进程的组 ID。
  • getuid / geteuid / getgid / getegid: 获取进程的真实和有效 UID/GID。
  • capset / capget: 用于设置和获取进程的能力 (capabilities),这是 Linux 中更细粒度的权限控制机制,可以作为 setuid/setfsuid 的现代替代方案。
  • 文件系统权限检查: 内核在 openreadwritestat 等系统调用时执行的检查。

7. 示例代码

由于 setfsuid/setfsgid 的使用场景比较特殊(主要是像 NFS 这样的服务器程序),编写一个完全展示其效果的用户态程序比较困难,因为它需要特定的文件系统环境和权限设置。下面的示例将演示如何调用它们,并尝试解释其行为。

#define _GNU_SOURCE // 启用 GNU 扩展以使用 getresuid 等
#include <stdio.h>
#include <unistd.h>
#include <sys/fsuid.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

int main() {
    uid_t ruid, euid, suid;
    gid_t rgid, egid, sgid;
    uid_t old_fsuid, new_fsuid;
    gid_t old_fsgid, new_fsgid;

    printf("--- Demonstrating setfsuid and setfsgid ---\n");

    // 1. 获取当前进程的各种 ID
    if (getresuid(&ruid, &euid, &suid) == -1 ||
        getresgid(&rgid, &egid, &sgid) == -1) {
        perror("getresuid/getresgid");
        exit(EXIT_FAILURE);
    }

    printf("Initial IDs:\n");
    printf("  Real UID:      %d\n", ruid);
    printf("  Effective UID: %d\n", euid);
    printf("  Saved UID:     %d\n", suid);
    printf("  Real GID:      %d\n", rgid);
    printf("  Effective GID: %d\n", egid);
    printf("  Saved GID:     %d\n", sgid);
    printf("  (By default, FS-UID and FS-GID equal Effective UID/GID)\n");

    // 2. 演示 setfsuid
    printf("\n--- Testing setfsuid ---\n");
    // 获取当前 FS-UID (通过调用 setfsuid 并传入当前 EUID)
    old_fsuid = setfsuid(euid);
    printf("  Current FS-UID (queried): %d\n", old_fsuid);

    // 尝试将 FS-UID 设置为一个不同的值,例如 euid + 1 (如果存在)
    // 注意:这只是演示,实际是否有效取决于系统中是否存在该 UID
    // 并且权限检查仍然基于文件系统权限
    new_fsuid = (euid == 0) ? 1 : euid - 1; // 简单地选择一个不同的 UID
    printf("  Attempting to set FS-UID to: %d\n", new_fsuid);

    uid_t result1 = setfsuid(new_fsuid);
    printf("  setfsuid(%d) returned: %d (should be the old FS-UID: %d)\n", new_fsuid, result1, old_fsuid);

    // 验证是否设置成功:再次调用 setfsuid 获取当前值
    uid_t result2 = setfsuid(new_fsuid);
    printf("  Verifying: setfsuid(%d) again returned: %d\n", new_fsuid, result2);
    if (result1 == result2) {
        printf("  -> FS-UID was successfully set to %d.\n", new_fsuid);
    } else {
        printf("  -> FS-UID setting might have failed.\n");
    }

    // 3. 演示 setfsgid (逻辑同上)
    printf("\n--- Testing setfsgid ---\n");
    old_fsgid = setfsgid(egid);
    printf("  Current FS-GID (queried): %d\n", old_fsgid);

    new_fsgid = (egid == 0) ? 1 : egid - 1; // 选择一个不同的 GID
    printf("  Attempting to set FS-GID to: %d\n", new_fsgid);

    gid_t result3 = setfsgid(new_fsgid);
    printf("  setfsgid(%d) returned: %d (should be the old FS-GID: %d)\n", new_fsgid, result3, old_fsgid);

    gid_t result4 = setfsgid(new_fsgid);
    printf("  Verifying: setfsgid(%d) again returned: %d\n", new_fsgid, result4);
    if (result3 == result4) {
        printf("  -> FS-GID was successfully set to %d.\n", new_fsgid);
    } else {
        printf("  -> FS-GID setting might have failed.\n");
    }

    // 4. 尝试一个实际的文件操作来“感受” FS-UID/GID 的影响
    // 注意:这个例子的权限检查结果可能不会明显变化,
    // 因为我们没有真正切换到一个权限不同的用户上下文,
    // 也没有访问一个严格基于 FS-UID/GID 权限的特殊文件系统。
    printf("\n--- Attempting file operation to observe behavior ---\n");
    const char *test_file = "test_fsuid_file.txt";
    int fd;

    // 创建一个测试文件
    fd = open(test_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd != -1) {
        write(fd, "test", 4);
        close(fd);
        printf("  Created test file '%s'.\n", test_file);
    } else {
        perror("  Creating test file");
    }

    // 尝试读取文件 (应该成功,因为我们没有真正改变有效权限)
    fd = open(test_file, O_RDONLY);
    if (fd != -1) {
        printf("  Successfully opened '%s' for reading (as expected).\n", test_file);
        close(fd);
    } else {
        perror("  Opening test file for reading");
    }

    // 清理测试文件
    if (unlink(test_file) == -1) {
        perror("  Deleting test file");
    } else {
        printf("  Deleted test file '%s'.\n", test_file);
    }

    printf("\n--- Important Notes ---\n");
    printf("1. setfsuid/setfsgid primarily affect filesystem permission checks.\n");
    printf("2. They do not change effective UID/GID for other operations.\n");
    printf("3. Their main use is in system daemons like NFS servers.\n");
    printf("4. The return value is the OLD fsid, success is verified by calling again.\n");

    return 0;
}

编译和运行:

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

# 运行程序 (普通用户权限即可)
./fsuid_fsgid_example

# 你也可以用 sudo 运行,看看 root 权限下的行为
# sudo ./fsuid_fsgid_example

预期输出 (作为普通用户运行):

--- Demonstrating setfsuid and setfsgid ---
Initial IDs:
  Real UID:      1000
  Effective UID: 1000
  Saved UID:     1000
  Real GID:      1000
  Effective GID: 1000
  Saved GID:     1000
  (By default, FS-UID and FS-GID equal Effective UID/GID)

--- Testing setfsuid ---
  Current FS-UID (queried): 1000
  Attempting to set FS-UID to: 999
  setfsuid(999) returned: 1000 (should be the old FS-UID: 1000)
  Verifying: setfsuid(999) again returned: 999
  -> FS-UID was successfully set to 999.

--- Testing setfsgid ---
  Current FS-GID (queried): 1000
  Attempting to set FS-GID to: 999
  setfsgid(999) returned: 1000 (should be the old FS-GID: 1000)
  Verifying: setfsgid(999) again returned: 999
  -> FS-GID was successfully set to 999.

--- Attempting file operation to observe behavior ---
  Created test file 'test_fsuid_file.txt'.
  Successfully opened 'test_fsuid_file.txt' for reading (as expected).
  Deleted test file 'test_fsuid_file.txt'.

--- Important Notes ---
1. setfsuid/setfsgid primarily affect filesystem permission checks.
2. They do not change effective UID/GID for other operations.
3. Their main use is in system daemons like NFS servers.
4. The return value is the OLD fsid, success is verified by calling again.

总结:
对于 Linux 编程新手,setfsuid 和 setfsgid 是比较特殊的系统调用。它们不是日常编程中会频繁使用的。理解它们有助于理解 Linux 权限模型的细节,特别是文件系统权限检查是如何与进程的其他权限分离的。在编写需要代表不同用户执行文件操作的系统服务(如文件服务器)时,它们会很有用。在常规应用程序开发中,使用标准的 setuid/seteuid 或现代的 capabilities 通常更合适。

发表在 linux文章 | 留下评论

setfsgid系统调用及示例

我们来深入学习 setfsgid 和 setfsuid 这两个系统调用

1. 函数介绍

在 Linux 系统中,每个进程都有一套与之相关的用户 ID (UID) 和组 ID (GID),比如:

  • 真实用户 ID (Real UID): 登录系统的用户 ID。
  • 有效用户 ID (Effective UID): 决定进程当前拥有哪些权限,用于权限检查。
  • 保存的设置用户 ID (Saved Set-UID): 用于在有效 UID 和真实 UID 之间切换。

通常,文件访问权限检查是基于进程的 有效 UID 和 有效 GID 进行的。

但是,Linux 提供了两个特殊的系统调用:setfsuid 和 setfsgid

  • setfsuid (Set File System UID): 用来设置或修改进程的 File System UID (文件系统用户 ID)
  • setfsgid (Set File System GID): 用来设置或修改进程的 File System GID (文件系统组 ID)

关键点
这两个 ID 只用于文件系统的权限检查!它们不影响其他任何权限检查(比如信号发送权限、进程调度优先级等)。当内核执行文件访问权限检查时,它会使用 文件系统 UID/GID 而不是 有效 UID/GID

默认情况下
当一个进程启动时,它的 文件系统 UID/GID 会被自动设置为与 有效 UID/GID 相同。

为什么需要它们?(主要场景)
一个经典的例子是 NFS (Network File System) 服务器

  1. NFS 服务器进程通常以 root 权限运行,因此它的 有效 UID 是 0 (root)。
  2. 但是,当它代表一个非 root 用户(比如 UID 1000)访问 NFS 文件系统上的文件时,它需要进行权限检查,仿佛是 UID 1000 在访问。
  3. 如果只使用 setuid/seteuid 来切换有效 UID,会影响服务器进程的其他权限(比如绑定到特权端口)。
  4. 所以,NFS 服务器可以调用 setfsuid(1000),这样在进行文件权限检查时,内核会看到 UID 是 1000,但进程的其他权限(如有效 UID 仍然是 root)保持不变。

简单来说
setfsuid 和 setfsgid 允许进程在进行文件访问权限检查时,“伪装”成另一个用户或组,而不会影响进程的其他特权。

2. 函数原型

#include <sys/fsuid.h> // 包含函数声明 (在某些系统上可能在 unistd.h 或其他地方)

// 设置文件系统用户 ID
int setfsuid(uid_t fsuid);

// 设置文件系统组 ID
int setfsgid(gid_t fsgid);

3. 功能

分别设置调用进程的文件系统用户 ID (FS-UID) 和文件系统组 ID (FS-GID),仅用于文件系统的访问权限检查。

4. 参数

  • fsuid:
    • uid_t 类型。
    • 指定要设置的新文件系统用户 ID。
  • fsgid:
    • gid_t 类型。
    • 指定要设置的新文件系统组 ID。

5. 返回值

这两个函数的返回值比较特殊:

  • 总是返回调用者之前的 文件系统 UID (setfsuid) 或 文件系统 GID (setfsgid)
  • 无论调用成功与否

这与其他大多数系统调用不同(它们通常成功返回 0 或文件描述符,失败返回 -1)。这种设计意味着你无法仅通过返回值判断调用是否成功。

那么如何判断是否成功呢
通常的做法是:

  1. 先调用 setfsuid/setfsgid
  2. 然后立即再次调用 setfsuid/setfsgid,传入同一个 ID。
  3. 如果两次返回的旧 ID 相同,说明第一次调用成功了;如果不同,则说明第一次调用失败。

调用失败的情况
调用通常会成功,但在某些安全模型下或传入无效 ID 时可能会失败。不过,失败的具体条件比较复杂,实践中很少遇到。

6. 相似函数或关联函数

  • setuid / seteuid / setreuid / setresuid: 用于设置进程的真实、有效、保存的用户 ID。这些会影响所有权限检查,而不仅仅是文件系统权限。
  • setgid / setegid / setregid / setresgid: 用于设置进程的组 ID。
  • getuid / geteuid / getgid / getegid: 获取进程的真实和有效 UID/GID。
  • capset / capget: 用于设置和获取进程的能力 (capabilities),这是 Linux 中更细粒度的权限控制机制,可以作为 setuid/setfsuid 的现代替代方案。
  • 文件系统权限检查: 内核在 openreadwritestat 等系统调用时执行的检查。

7. 示例代码

由于 setfsuid/setfsgid 的使用场景比较特殊(主要是像 NFS 这样的服务器程序),编写一个完全展示其效果的用户态程序比较困难,因为它需要特定的文件系统环境和权限设置。下面的示例将演示如何调用它们,并尝试解释其行为。

#define _GNU_SOURCE // 启用 GNU 扩展以使用 getresuid 等
#include <stdio.h>
#include <unistd.h>
#include <sys/fsuid.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

int main() {
    uid_t ruid, euid, suid;
    gid_t rgid, egid, sgid;
    uid_t old_fsuid, new_fsuid;
    gid_t old_fsgid, new_fsgid;

    printf("--- Demonstrating setfsuid and setfsgid ---\n");

    // 1. 获取当前进程的各种 ID
    if (getresuid(&ruid, &euid, &suid) == -1 ||
        getresgid(&rgid, &egid, &sgid) == -1) {
        perror("getresuid/getresgid");
        exit(EXIT_FAILURE);
    }

    printf("Initial IDs:\n");
    printf("  Real UID:      %d\n", ruid);
    printf("  Effective UID: %d\n", euid);
    printf("  Saved UID:     %d\n", suid);
    printf("  Real GID:      %d\n", rgid);
    printf("  Effective GID: %d\n", egid);
    printf("  Saved GID:     %d\n", sgid);
    printf("  (By default, FS-UID and FS-GID equal Effective UID/GID)\n");

    // 2. 演示 setfsuid
    printf("\n--- Testing setfsuid ---\n");
    // 获取当前 FS-UID (通过调用 setfsuid 并传入当前 EUID)
    old_fsuid = setfsuid(euid);
    printf("  Current FS-UID (queried): %d\n", old_fsuid);

    // 尝试将 FS-UID 设置为一个不同的值,例如 euid + 1 (如果存在)
    // 注意:这只是演示,实际是否有效取决于系统中是否存在该 UID
    // 并且权限检查仍然基于文件系统权限
    new_fsuid = (euid == 0) ? 1 : euid - 1; // 简单地选择一个不同的 UID
    printf("  Attempting to set FS-UID to: %d\n", new_fsuid);

    uid_t result1 = setfsuid(new_fsuid);
    printf("  setfsuid(%d) returned: %d (should be the old FS-UID: %d)\n", new_fsuid, result1, old_fsuid);

    // 验证是否设置成功:再次调用 setfsuid 获取当前值
    uid_t result2 = setfsuid(new_fsuid);
    printf("  Verifying: setfsuid(%d) again returned: %d\n", new_fsuid, result2);
    if (result1 == result2) {
        printf("  -> FS-UID was successfully set to %d.\n", new_fsuid);
    } else {
        printf("  -> FS-UID setting might have failed.\n");
    }

    // 3. 演示 setfsgid (逻辑同上)
    printf("\n--- Testing setfsgid ---\n");
    old_fsgid = setfsgid(egid);
    printf("  Current FS-GID (queried): %d\n", old_fsgid);

    new_fsgid = (egid == 0) ? 1 : egid - 1; // 选择一个不同的 GID
    printf("  Attempting to set FS-GID to: %d\n", new_fsgid);

    gid_t result3 = setfsgid(new_fsgid);
    printf("  setfsgid(%d) returned: %d (should be the old FS-GID: %d)\n", new_fsgid, result3, old_fsgid);

    gid_t result4 = setfsgid(new_fsgid);
    printf("  Verifying: setfsgid(%d) again returned: %d\n", new_fsgid, result4);
    if (result3 == result4) {
        printf("  -> FS-GID was successfully set to %d.\n", new_fsgid);
    } else {
        printf("  -> FS-GID setting might have failed.\n");
    }

    // 4. 尝试一个实际的文件操作来“感受” FS-UID/GID 的影响
    // 注意:这个例子的权限检查结果可能不会明显变化,
    // 因为我们没有真正切换到一个权限不同的用户上下文,
    // 也没有访问一个严格基于 FS-UID/GID 权限的特殊文件系统。
    printf("\n--- Attempting file operation to observe behavior ---\n");
    const char *test_file = "test_fsuid_file.txt";
    int fd;

    // 创建一个测试文件
    fd = open(test_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd != -1) {
        write(fd, "test", 4);
        close(fd);
        printf("  Created test file '%s'.\n", test_file);
    } else {
        perror("  Creating test file");
    }

    // 尝试读取文件 (应该成功,因为我们没有真正改变有效权限)
    fd = open(test_file, O_RDONLY);
    if (fd != -1) {
        printf("  Successfully opened '%s' for reading (as expected).\n", test_file);
        close(fd);
    } else {
        perror("  Opening test file for reading");
    }

    // 清理测试文件
    if (unlink(test_file) == -1) {
        perror("  Deleting test file");
    } else {
        printf("  Deleted test file '%s'.\n", test_file);
    }

    printf("\n--- Important Notes ---\n");
    printf("1. setfsuid/setfsgid primarily affect filesystem permission checks.\n");
    printf("2. They do not change effective UID/GID for other operations.\n");
    printf("3. Their main use is in system daemons like NFS servers.\n");
    printf("4. The return value is the OLD fsid, success is verified by calling again.\n");

    return 0;
}

编译和运行:

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

# 运行程序 (普通用户权限即可)
./fsuid_fsgid_example

# 你也可以用 sudo 运行,看看 root 权限下的行为
# sudo ./fsuid_fsgid_example

预期输出 (作为普通用户运行):

--- Demonstrating setfsuid and setfsgid ---
Initial IDs:
  Real UID:      1000
  Effective UID: 1000
  Saved UID:     1000
  Real GID:      1000
  Effective GID: 1000
  Saved GID:     1000
  (By default, FS-UID and FS-GID equal Effective UID/GID)

--- Testing setfsuid ---
  Current FS-UID (queried): 1000
  Attempting to set FS-UID to: 999
  setfsuid(999) returned: 1000 (should be the old FS-UID: 1000)
  Verifying: setfsuid(999) again returned: 999
  -> FS-UID was successfully set to 999.

--- Testing setfsgid ---
  Current FS-GID (queried): 1000
  Attempting to set FS-GID to: 999
  setfsgid(999) returned: 1000 (should be the old FS-GID: 1000)
  Verifying: setfsgid(999) again returned: 999
  -> FS-GID was successfully set to 999.

--- Attempting file operation to observe behavior ---
  Created test file 'test_fsuid_file.txt'.
  Successfully opened 'test_fsuid_file.txt' for reading (as expected).
  Deleted test file 'test_fsuid_file.txt'.

--- Important Notes ---
1. setfsuid/setfsgid primarily affect filesystem permission checks.
2. They do not change effective UID/GID for other operations.
3. Their main use is in system daemons like NFS servers.
4. The return value is the OLD fsid, success is verified by calling again.

总结:
对于 Linux 编程新手,setfsuid 和 setfsgid 是比较特殊的系统调用。它们不是日常编程中会频繁使用的。理解它们有助于理解 Linux 权限模型的细节,特别是文件系统权限检查是如何与进程的其他权限分离的。在编写需要代表不同用户执行文件操作的系统服务(如文件服务器)时,它们会很有用。在常规应用程序开发中,使用标准的 setuid/seteuid 或现代的 capabilities 通常更合适。

发表在 linux文章 | 留下评论

sendmmsg系统调用及示例

sendmmsg 函数详解

1. 函数介绍

sendmmsg 是Linux 2.6.39引入的高效批量发送消息系统调用。它是 sendmsg 的批量版本,允许应用程序在单次系统调用中发送多个消息,显著减少了系统调用开销,特别适用于高吞吐量的网络服务器和实时应用。

2. 函数原型

#define _GNU_SOURCE
#include <sys/socket.h>
int sendmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
             int flags);

3. 功能

sendmmsg 允许向套接字批量发送多个消息,每个消息可以包含数据和控制信息。它支持分散缓冲区发送、地址信息指定、控制数据发送等功能,是构建高性能网络应用的关键工具。

4. 参数

  • int sockfd: 套接字文件描述符
  • *struct mmsghdr msgvec: 消息向量数组,描述多个发送消息
  • unsigned int vlen: 消息向量数组的长度(最大可发送的消息数)
  • int flags: 发送标志,与sendmsg相同

5. 返回值

  • 成功: 返回实际发送的消息数量
  • 失败: 返回-1,并设置errno

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

  • sendmsg: 单消息发送函数
  • send: 基本发送函数
  • recvmmsg: 对应的批量接收函数
  • writev: 分散缓冲区写入函数

7. 示例代码

示例1:基础sendmmsg使用

#define _GNU_SOURCE
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

/**
 * 演示sendmmsg的基础使用方法
 */
int demo_sendmmsg_basic() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    struct mmsghdr msgvec[3];
    struct iovec iov[3][1];
    char *messages[3] = {
        "First message from sendmmsg",
        "Second message from sendmmsg", 
        "Third message from sendmmsg"
    };
    int messages_sent;
    
    printf("=== sendmmsg 基础使用示例 ===\n");
    
    // 创建TCP服务器套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("创建服务器套接字失败");
        return -1;
    }
    
    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(8080);
    
    // 绑定套接字
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("绑定套接字失败");
        close(server_fd);
        return -1;
    }
    
    // 监听连接
    if (listen(server_fd, 1) == -1) {
        perror("监听失败");
        close(server_fd);
        return -1;
    }
    
    printf("服务器监听在端口 8080\n");
    
    // 启动客户端连接
    if (fork() == 0) {
        // 客户端代码
        sleep(1);  // 等待服务器启动
        client_fd = socket(AF_INET, SOCK_STREAM, 0);
        struct sockaddr_in serv_addr;
        
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(8080);
        serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        
        if (connect(client_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == 0) {
            printf("客户端连接成功\n");
            
            // 接收服务器发送的消息
            char buffer[256];
            for (int i = 0; i < 3; i++) {
                ssize_t bytes = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
                if (bytes > 0) {
                    buffer[bytes] = '\0';
                    printf("客户端接收到消息 %d: %s\n", i + 1, buffer);
                }
            }
        }
        
        close(client_fd);
        exit(0);
    }
    
    // 接受客户端连接
    client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
    if (client_fd == -1) {
        perror("接受连接失败");
        close(server_fd);
        return -1;
    }
    
    printf("客户端连接来自: %s:%d\n", 
           inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
    
    // 准备批量发送消息结构
    memset(msgvec, 0, sizeof(msgvec));
    
    for (int i = 0; i < 3; i++) {
        // 设置每个消息的缓冲区
        iov[i][0].iov_base = messages[i];
        iov[i][0].iov_len = strlen(messages[i]);
        
        // 设置消息头
        msgvec[i].msg_hdr.msg_iov = iov[i];
        msgvec[i].msg_hdr.msg_iovlen = 1;
        msgvec[i].msg_hdr.msg_name = NULL;  // TCP不需要目标地址
        msgvec[i].msg_hdr.msg_namelen = 0;
    }
    
    printf("准备批量发送3个消息...\n");
    
    // 批量发送消息
    messages_sent = sendmmsg(client_fd, msgvec, 3, 0);
    
    if (messages_sent == -1) {
        perror("sendmmsg 失败");
        close(client_fd);
        close(server_fd);
        return -1;
    }
    
    printf("成功发送 %d 个消息:\n", messages_sent);
    
    // 显示发送结果
    for (int i = 0; i < messages_sent; i++) {
        printf("  消息 %d: 发送了 %u 字节\n", i + 1, msgvec[i].msg_len);
    }
    
    close(client_fd);
    close(server_fd);
    
    // 等待客户端结束
    int status;
    wait(&status);
    
    return 0;
}

int main() {
    return demo_sendmmsg_basic();
}

示例2:UDP批量发送

#define _GNU_SOURCE
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>

/**
 * 演示UDP批量发送消息
 */
int demo_udp_batch_send() {
    int client_fd;
    struct sockaddr_in server_addr;
    struct mmsghdr msgvec[5];
    struct iovec iov[5][2];  // 每个消息使用2个缓冲区
    char *message_parts[5][2] = {
        {"UDP ", "Message 1"},
        {"UDP ", "Message 2"},
        {"UDP ", "Message 3"},
        {"UDP ", "Message 4"},
        {"UDP ", "Message 5"}
    };
    int messages_sent;
    
    printf("=== UDP批量发送示例 ===\n");
    
    // 创建UDP客户端套接字
    client_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (client_fd == -1) {
        perror("创建UDP套接字失败");
        return -1;
    }
    
    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8081);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    printf("UDP客户端准备向 127.0.0.1:8081 发送消息\n");
    
    // 准备批量发送结构(使用分散缓冲区)
    memset(msgvec, 0, sizeof(msgvec));
    
    for (int i = 0; i < 5; i++) {
        // 设置分散缓冲区
        iov[i][0].iov_base = message_parts[i][0];
        iov[i][0].iov_len = strlen(message_parts[i][0]);
        iov[i][1].iov_base = message_parts[i][1];
        iov[i][1].iov_len = strlen(message_parts[i][1]);
        
        // 设置消息头
        msgvec[i].msg_hdr.msg_iov = iov[i];
        msgvec[i].msg_hdr.msg_iovlen = 2;
        msgvec[i].msg_hdr.msg_name = &server_addr;
        msgvec[i].msg_hdr.msg_namelen = sizeof(server_addr);
    }
    
    printf("准备发送5个UDP消息(每个消息使用分散缓冲区)...\n");
    
    // 批量发送UDP消息
    messages_sent = sendmmsg(client_fd, msgvec, 5, 0);
    
    if (messages_sent == -1) {
        perror("sendmmsg 失败");
        close(client_fd);
        return -1;
    }
    
    printf("成功发送 %d 个UDP消息:\n", messages_sent);
    
    // 显示发送统计
    size_t total_bytes = 0;
    for (int i = 0; i < messages_sent; i++) {
        printf("  消息 %d: %u 字节\n", i + 1, msgvec[i].msg_len);
        total_bytes += msgvec[i].msg_len;
    }
    
    printf("总发送字节数: %zu\n", total_bytes);
    
    close(client_fd);
    
    return 0;
}

/**
 * UDP服务器用于接收批量消息
 */
int udp_server_receive() {
    int server_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buffer[256];
    ssize_t bytes_received;
    int message_count = 0;
    
    server_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (server_fd == -1) {
        perror("创建UDP服务器套接字失败");
        return -1;
    }
    
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(8081);
    
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("绑定UDP套接字失败");
        close(server_fd);
        return -1;
    }
    
    printf("UDP服务器监听在端口 8081\n");
    printf("等待接收消息(10秒超时)...\n");
    
    // 设置接收超时
    struct timeval timeout;
    timeout.tv_sec = 10;
    timeout.tv_usec = 0;
    setsockopt(server_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
    
    // 接收消息
    while (message_count < 10) {
        bytes_received = recvfrom(server_fd, buffer, sizeof(buffer) - 1, 0,
                                 (struct sockaddr*)&client_addr, &client_len);
        if (bytes_received == -1) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                printf("接收超时\n");
                break;
            }
            perror("接收消息失败");
            break;
        }
        
        buffer[bytes_received] = '\0';
        printf("接收到消息 %d: %s (来自 %s:%d)\n", 
               ++message_count, buffer,
               inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
    }
    
    close(server_fd);
    return 0;
}

int main() {
    // 启动UDP服务器
    if (fork() == 0) {
        sleep(1);  // 等待客户端准备
        return udp_server_receive();
    }
    
    // 执行UDP批量发送
    sleep(1);
    int result = demo_udp_batch_send();
    
    // 等待服务器结束
    int status;
    wait(&status);
    
    return result;
}

示例3:高性能网络服务器

#define _GNU_SOURCE
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#include <time.h>

/**
 * 高性能服务器结构
 */
typedef struct {
    int server_fd;
    int port;
    struct pollfd *clients;
    int max_clients;
    int client_count;
    unsigned long messages_sent;
    unsigned long bytes_sent;
} high_perf_server_t;

/**
 * 初始化高性能服务器
 */
int server_init(high_perf_server_t *server, int port, int max_clients) {
    struct sockaddr_in server_addr;
    
    memset(server, 0, sizeof(high_perf_server_t));
    server->port = port;
    server->max_clients = max_clients;
    server->client_count = 0;
    server->messages_sent = 0;
    server->bytes_sent = 0;
    
    // 分配客户端数组
    server->clients = calloc(max_clients + 1, sizeof(struct pollfd));
    if (!server->clients) {
        perror("分配客户端数组失败");
        return -1;
    }
    
    // 创建服务器套接字
    server->server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server->server_fd == -1) {
        perror("创建服务器套接字失败");
        free(server->clients);
        return -1;
    }
    
    // 设置套接字选项
    int opt = 1;
    if (setsockopt(server->server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
        perror("设置套接字选项失败");
        close(server->server_fd);
        free(server->clients);
        return -1;
    }
    
    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(port);
    
    // 绑定套接字
    if (bind(server->server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("绑定套接字失败");
        close(server->server_fd);
        free(server->clients);
        return -1;
    }
    
    // 监听连接
    if (listen(server->server_fd, 10) == -1) {
        perror("监听失败");
        close(server->server_fd);
        free(server->clients);
        return -1;
    }
    
    // 设置服务器套接字为poll监听
    server->clients[0].fd = server->server_fd;
    server->clients[0].events = POLLIN;
    
    printf("高性能服务器初始化完成,监听端口 %d\n", port);
    return 0;
}

/**
 * 接受新客户端连接
 */
int server_accept_client(high_perf_server_t *server) {
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    int client_fd;
    
    client_fd = accept(server->server_fd, (struct sockaddr*)&client_addr, &client_len);
    if (client_fd == -1) {
        perror("接受连接失败");
        return -1;
    }
    
    if (server->client_count >= server->max_clients) {
        printf("客户端数量已达上限,拒绝连接\n");
        close(client_fd);
        return -1;
    }
    
    // 添加到客户端数组
    int index = server->client_count + 1;
    server->clients[index].fd = client_fd;
    server->clients[index].events = POLLOUT;  // 准备发送数据
    server->client_count++;
    
    printf("新客户端连接: %s:%d (fd=%d)\n", 
           inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), client_fd);
    
    return 0;
}

/**
 * 使用sendmmsg向客户端批量发送消息
 */
int server_send_batch_messages(high_perf_server_t *server, int client_index) {
    int client_fd = server->clients[client_index].fd;
    const int BATCH_SIZE = 8;  // 每次批量发送8个消息
    struct mmsghdr msgvec[BATCH_SIZE];
    struct iovec iov[BATCH_SIZE][1];
    char messages[BATCH_SIZE][128];
    int messages_to_send = 0;
    
    // 准备批量发送消息
    memset(msgvec, 0, sizeof(msgvec));
    
    for (int i = 0; i < BATCH_SIZE; i++) {
        // 构造消息内容
        snprintf(messages[i], sizeof(messages[i]), 
                "Server Message %lu: Batch %d, Index %d", 
                server->messages_sent + i + 1, 
                (int)(server->messages_sent / BATCH_SIZE) + 1, i + 1);
        
        // 设置缓冲区
        iov[i][0].iov_base = messages[i];
        iov[i][0].iov_len = strlen(messages[i]);
        
        // 设置消息头
        msgvec[i].msg_hdr.msg_iov = iov[i];
        msgvec[i].msg_hdr.msg_iovlen = 1;
        msgvec[i].msg_hdr.msg_name = NULL;
        msgvec[i].msg_hdr.msg_namelen = 0;
        
        messages_to_send++;
    }
    
    // 批量发送消息
    int messages_sent = sendmmsg(client_fd, msgvec, messages_to_send, MSG_NOSIGNAL);
    
    if (messages_sent == -1) {
        if (errno == EPIPE || errno == ECONNRESET) {
            printf("客户端 %d 连接断开\n", client_fd);
            return -1;
        }
        perror("sendmmsg 失败");
        return -1;
    }
    
    // 更新统计信息
    server->messages_sent += messages_sent;
    for (int i = 0; i < messages_sent; i++) {
        server->bytes_sent += msgvec[i].msg_len;
    }
    
    printf("向客户端 %d 批量发送 %d 个消息\n", client_fd, messages_sent);
    
    return 0;
}

/**
 * 运行服务器主循环
 */
int server_run(high_perf_server_t *server) {
    printf("服务器开始运行,等待客户端连接...\n");
    
    time_t start_time = time(NULL);
    
    while (difftime(time(NULL), start_time) < 30) {  // 运行30秒
        // 使用poll等待事件
        int nfds = server->client_count + 1;
        int activity = poll(server->clients, nfds, 1000);  // 1秒超时
        
        if (activity == -1) {
            if (errno == EINTR) continue;  // 被信号中断
            perror("poll 失败");
            break;
        }
        
        if (activity == 0) {
            // 超时,继续循环
            continue;
        }
        
        // 检查服务器套接字(新连接)
        if (server->clients[0].revents & POLLIN) {
            server_accept_client(server);
            activity--;
        }
        
        // 检查客户端套接字(准备发送数据)
        for (int i = 1; i <= server->client_count && activity > 0; i++) {
            if (server->clients[i].revents & POLLOUT) {
                if (server_send_batch_messages(server, i) == -1) {
                    // 客户端断开连接,移除客户端
                    close(server->clients[i].fd);
                    // 将最后一个客户端移到当前位置
                    if (i < server->client_count) {
                        server->clients[i] = server->clients[server->client_count];
                    }
                    server->client_count--;
                    i--;  // 重新检查当前位置
                }
                activity--;
            }
        }
    }
    
    return 0;
}

/**
 * 清理服务器资源
 */
void server_cleanup(high_perf_server_t *server) {
    // 关闭所有客户端连接
    for (int i = 1; i <= server->client_count; i++) {
        close(server->clients[i].fd);
    }
    
    // 关闭服务器套接字
    if (server->server_fd != -1) {
        close(server->server_fd);
    }
    
    // 释放内存
    if (server->clients) {
        free(server->clients);
    }
    
    printf("服务器资源清理完成\n");
    printf("统计信息:\n");
    printf("  总发送消息数: %lu\n", server->messages_sent);
    printf("  总发送字节数: %lu\n", server->bytes_sent);
    if (server->messages_sent > 0) {
        printf("  平均消息大小: %.2f 字节\n", 
               (double)server->bytes_sent / server->messages_sent);
    }
}

/**
 * 演示高性能网络服务器
 */
int demo_high_performance_server() {
    high_perf_server_t server;
    
    printf("=== 高性能网络服务器示例 ===\n");
    
    // 初始化服务器
    if (server_init(&server, 8082, 10) != 0) {
        return -1;
    }
    
    // 启动测试客户端
    if (fork() == 0) {
        sleep(2);  // 等待服务器启动
        
        // 创建多个并发客户端
        for (int i = 0; i < 3; i++) {
            if (fork() == 0) {
                int client_sock = socket(AF_INET, SOCK_STREAM, 0);
                struct sockaddr_in serv_addr;
                
                memset(&serv_addr, 0, sizeof(serv_addr));
                serv_addr.sin_family = AF_INET;
                serv_addr.sin_port = htons(8082);
                serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
                
                if (connect(client_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == 0) {
                    printf("客户端 %d 连接成功\n", i + 1);
                    
                    // 接收服务器发送的消息
                    char buffer[256];
                    for (int msg = 0; msg < 20; msg++) {
                        ssize_t bytes = recv(client_sock, buffer, sizeof(buffer) - 1, 0);
                        if (bytes > 0) {
                            buffer[bytes] = '\0';
                            printf("客户端 %d 接收消息: %s\n", i + 1, buffer);
                        } else if (bytes == 0) {
                            printf("客户端 %d 连接关闭\n", i + 1);
                            break;
                        }
                    }
                }
                
                close(client_sock);
                exit(0);
            }
        }
        
        // 等待所有客户端完成
        for (int i = 0; i < 3; i++) {
            int status;
            wait(&status);
        }
        
        exit(0);
    }
    
    // 运行服务器
    server_run(&server);
    
    // 清理资源
    server_cleanup(&server);
    
    // 等待测试客户端结束
    int status;
    wait(&status);
    
    return 0;
}

int main() {
    return demo_high_performance_server();
}

示例4:实时数据流发送

#define _GNU_SOURCE
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <signal.h>

/**
 * 实时数据流结构
 */
typedef struct {
    int sockfd;
    struct sockaddr_in dest_addr;
    unsigned long packets_sent;
    unsigned long bytes_sent;
    time_t start_time;
    volatile int running;
} data_stream_t;

// 全局变量用于信号处理
static data_stream_t *g_stream = NULL;

/**
 * 信号处理函数
 */
void signal_handler(int sig) {
    if (g_stream) {
        g_stream->running = 0;
        printf("\n收到信号 %d,准备停止数据流...\n", sig);
    }
}

/**
 * 初始化数据流发送器
 */
int stream_init(data_stream_t *stream, const char *dest_ip, int dest_port) {
    memset(stream, 0, sizeof(data_stream_t));
    stream->start_time = time(NULL);
    stream->running = 1;
    
    // 设置全局指针用于信号处理
    g_stream = stream;
    
    // 注册信号处理
    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);
    
    // 创建UDP套接字
    stream->sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (stream->sockfd == -1) {
        perror("创建UDP套接字失败");
        return -1;
    }
    
    // 设置目标地址
    memset(&stream->dest_addr, 0, sizeof(stream->dest_addr));
    stream->dest_addr.sin_family = AF_INET;
    stream->dest_addr.sin_port = htons(dest_port);
    stream->dest_addr.sin_addr.s_addr = inet_addr(dest_ip);
    
    printf("数据流发送器初始化完成\n");
    printf("目标地址: %s:%d\n", dest_ip, dest_port);
    
    return 0;
}

/**
 * 发送实时数据包批次
 */
int stream_send_batch(data_stream_t *stream) {
    const int BATCH_SIZE = 16;  // 每次发送16个数据包
    struct mmsghdr msgvec[BATCH_SIZE];
    struct iovec iov[BATCH_SIZE][1];
    char packets[BATCH_SIZE][256];
    struct timespec ts;
    
    // 准备批量发送结构
    memset(msgvec, 0, sizeof(msgvec));
    
    clock_gettime(CLOCK_REALTIME, &ts);
    
    for (int i = 0; i < BATCH_SIZE; i++) {
        // 构造实时数据包
        snprintf(packets[i], sizeof(packets[i]), 
                "REALTIME_DATA:%lu.%09ld:Packet_%lu:Value_%d", 
                ts.tv_sec, ts.tv_nsec,
                stream->packets_sent + i + 1,
                rand() % 1000);
        
        // 设置缓冲区
        iov[i][0].iov_base = packets[i];
        iov[i][0].iov_len = strlen(packets[i]);
        
        // 设置消息头
        msgvec[i].msg_hdr.msg_iov = iov[i];
        msgvec[i].msg_hdr.msg_iovlen = 1;
        msgvec[i].msg_hdr.msg_name = &stream->dest_addr;
        msgvec[i].msg_hdr.msg_namelen = sizeof(stream->dest_addr);
    }
    
    // 批量发送数据包
    int packets_sent = sendmmsg(stream->sockfd, msgvec, BATCH_SIZE, 0);
    
    if (packets_sent == -1) {
        perror("sendmmsg 发送数据包失败");
        return -1;
    }
    
    // 更新统计信息
    stream->packets_sent += packets_sent;
    for (int i = 0; i < packets_sent; i++) {
        stream->bytes_sent += msgvec[i].msg_len;
    }
    
    return packets_sent;
}

/**
 * 显示实时统计信息
 */
void stream_show_stats(data_stream_t *stream) {
    time_t current_time = time(NULL);
    double uptime = difftime(current_time, stream->start_time);
    
    if (uptime > 0) {
        double packets_per_sec = stream->packets_sent / uptime;
        double bytes_per_sec = stream->bytes_sent / uptime;
        
        printf("\r运行时间: %.0fs | 数据包: %lu | 字节: %lu | "
               "速率: %.0f包/s %.2fMB/s",
               uptime, stream->packets_sent, stream->bytes_sent,
               packets_per_sec, bytes_per_sec / (1024 * 1024));
        fflush(stdout);
    }
}

/**
 * 运行数据流发送器
 */
int stream_run(data_stream_t *stream) {
    printf("数据流发送器开始运行,按 Ctrl+C 停止\n");
    
    time_t last_stats_time = time(NULL);
    int batch_count = 0;
    
    while (stream->running) {
        int result = stream_send_batch(stream);
        if (result == -1) {
            break;
        }
        
        batch_count++;
        
        // 定期显示统计信息
        time_t current_time = time(NULL);
        if (current_time - last_stats_time >= 1) {
            stream_show_stats(stream);
            last_stats_time = current_time;
        }
        
        // 控制发送速率(每秒约1000个数据包)
        if (batch_count % 64 == 0) {
            usleep(10000);  // 10ms延迟
        }
    }
    
    printf("\n数据流发送器停止\n");
    return 0;
}

/**
 * 演示实时数据流发送
 */
int demo_real_time_data_stream() {
    data_stream_t stream;
    
    printf("=== 实时数据流发送示例 ===\n");
    
    // 初始化数据流发送器
    if (stream_init(&stream, "127.0.0.1", 8083) != 0) {
        return -1;
    }
    
    // 启动数据接收器
    if (fork() == 0) {
        int server_fd;
        struct sockaddr_in server_addr, client_addr;
        socklen_t client_len = sizeof(client_addr);
        char buffer[512];
        ssize_t bytes_received;
        int packet_count = 0;
        
        server_fd = socket(AF_INET, SOCK_DGRAM, 0);
        if (server_fd == -1) {
            perror("创建接收器套接字失败");
            exit(1);
        }
        
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = INADDR_ANY;
        server_addr.sin_port = htons(8083);
        
        if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
            perror("绑定接收器套接字失败");
            close(server_fd);
            exit(1);
        }
        
        printf("数据接收器启动,监听端口 8083\n");
        
        // 设置接收超时
        struct timeval timeout;
        timeout.tv_sec = 30;
        timeout.tv_usec = 0;
        setsockopt(server_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
        
        // 接收数据包
        while (packet_count < 1000) {
            bytes_received = recvfrom(server_fd, buffer, sizeof(buffer) - 1, 0,
                                     (struct sockaddr*)&client_addr, &client_len);
            if (bytes_received == -1) {
                if (errno == EAGAIN || errno == EWOULDBLOCK) {
                    printf("接收超时\n");
                    break;
                }
                perror("接收数据包失败");
                break;
            }
            
            buffer[bytes_received] = '\0';
            packet_count++;
            
            if (packet_count % 100 == 0) {
                printf("接收器: 接收到 %d 个数据包\n", packet_count);
            }
        }
        
        printf("接收器: 总共接收到 %d 个数据包\n", packet_count);
        close(server_fd);
        exit(0);
    }
    
    // 运行数据流发送器
    srand(time(NULL));
    stream_run(&stream);
    
    // 显示最终统计
    printf("\n\n最终统计:\n");
    printf("  总发送数据包: %lu\n", stream.packets_sent);
    printf("  总发送字节数: %lu\n", stream.bytes_sent);
    printf("  平均包大小: %.2f 字节\n", 
           stream.packets_sent > 0 ? (double)stream.bytes_sent / stream.packets_sent : 0);
    
    // 清理资源
    close(stream.sockfd);
    
    // 等待接收器结束
    int status;
    wait(&status);
    
    return 0;
}

int main() {
    return demo_real_time_data_stream();
}

示例5:性能对比测试

#define _GNU_SOURCE
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>

/**
 * 性能测试结构
 */
typedef struct {
    const char *name;
    unsigned long messages_sent;
    unsigned long bytes_sent;
    struct timeval start_time;
    struct timeval end_time;
} perf_test_t;

/**
 * 使用传统sendmsg进行测试
 */
int test_sendmsg_performance(int sockfd, struct sockaddr_in *dest_addr, 
                            int message_count, const char *message) {
    struct msghdr msg;
    struct iovec iov[1];
    int sent_count = 0;
    
    // 准备发送结构
    memset(&msg, 0, sizeof(msg));
    iov[0].iov_base = (void*)message;
    iov[0].iov_len = strlen(message);
    
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    msg.msg_name = dest_addr;
    msg.msg_namelen = sizeof(*dest_addr);
    
    // 逐个发送消息
    while (sent_count < message_count) {
        if (sendmsg(sockfd, &msg, 0) == -1) {
            if (errno != EAGAIN && errno != EWOULDBLOCK) {
                perror("sendmsg 失败");
                break;
            }
        } else {
            sent_count++;
        }
    }
    
    return sent_count;
}

/**
 * 使用sendmmsg进行测试
 */
int test_sendmmsg_performance(int sockfd, struct sockaddr_in *dest_addr, 
                             int message_count, const char *message) {
    const int BATCH_SIZE = 32;
    struct mmsghdr msgvec[BATCH_SIZE];
    struct iovec iov[BATCH_SIZE][1];
    char *messages[BATCH_SIZE];
    int total_sent = 0;
    int messages_sent;
    
    // 准备批量发送结构
    memset(msgvec, 0, sizeof(msgvec));
    
    for (int i = 0; i < BATCH_SIZE; i++) {
        messages[i] = strdup(message);
        if (!messages[i]) {
            perror("分配消息缓冲区失败");
            return total_sent;
        }
        
        iov[i][0].iov_base = messages[i];
        iov[i][0].iov_len = strlen(message);
        
        msgvec[i].msg_hdr.msg_iov = iov[i];
        msgvec[i].msg_hdr.msg_iovlen = 1;
        msgvec[i].msg_hdr.msg_name = dest_addr;
        msgvec[i].msg_hdr.msg_namelen = sizeof(*dest_addr);
    }
    
    // 批量发送消息
    while (total_sent < message_count) {
        int to_send = (message_count - total_sent < BATCH_SIZE) ? 
                     message_count - total_sent : BATCH_SIZE;
        
        messages_sent = sendmmsg(sockfd, msgvec, to_send, 0);
        
        if (messages_sent == -1) {
            if (errno != EAGAIN && errno != EWOULDBLOCK) {
                perror("sendmmsg 失败");
                break;
            }
        } else {
            total_sent += messages_sent;
        }
    }
    
    // 释放内存
    for (int i = 0; i < BATCH_SIZE; i++) {
        free(messages[i]);
    }
    
    return total_sent;
}

/**
 * UDP性能测试服务器
 */
int perf_test_server(int port) {
    int server_fd;
    struct sockaddr_in server_addr;
    
    server_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (server_fd == -1) {
        perror("创建UDP服务器套接字失败");
        return -1;
    }
    
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(port);
    
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("绑定服务器套接字失败");
        close(server_fd);
        return -1;
    }
    
    printf("性能测试服务器启动,监听端口 %d\n", port);
    return server_fd;
}

/**
 * 演示性能对比测试
 */
int demo_performance_comparison() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, dest_addr;
    const int SERVER_PORT = 8084;
    const int MESSAGE_COUNT = 10000;
    const char *test_message = "Performance test message for sendmmsg vs sendmsg comparison";
    perf_test_t tests[2];
    
    printf("=== sendmmsg vs sendmsg 性能对比测试 ===\n");
    
    // 初始化测试结构
    tests[0].name = "sendmsg";
    tests[0].messages_sent = 0;
    tests[0].bytes_sent = 0;
    
    tests[1].name = "sendmmsg";
    tests[1].messages_sent = 0;
    tests[1].bytes_sent = 0;
    
    // 启动服务器
    server_fd = perf_test_server(SERVER_PORT);
    if (server_fd == -1) {
        return -1;
    }
    
    // 创建客户端套接字
    client_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (client_fd == -1) {
        perror("创建客户端套接字失败");
        close(server_fd);
        return -1;
    }
    
    // 设置目标地址
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(SERVER_PORT);
    dest_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    // 启动服务器接收线程
    if (fork() == 0) {
        char buffer[512];
        int received_count = 0;
        
        printf("服务器开始接收测试消息...\n");
        
        // 设置接收超时
        struct timeval timeout;
        timeout.tv_sec = 30;
        timeout.tv_usec = 0;
        setsockopt(server_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
        
        while (received_count < MESSAGE_COUNT * 2) {  // 两次测试
            ssize_t bytes = recv(server_fd, buffer, sizeof(buffer) - 1, 0);
            if (bytes > 0) {
                received_count++;
                if (received_count % 1000 == 0) {
                    printf("服务器已接收 %d 个消息\n", received_count);
                }
            } else if (bytes == -1) {
                if (errno == EAGAIN || errno == EWOULDBLOCK) {
                    printf("服务器接收超时\n");
                    break;
                }
            }
        }
        
        printf("服务器接收完成,总共接收 %d 个消息\n", received_count);
        close(server_fd);
        exit(0);
    }
    
    // 等待服务器启动
    sleep(1);
    
    // 测试1: 使用sendmsg
    printf("\n测试1: 使用传统sendmsg发送 %d 个消息...\n", MESSAGE_COUNT);
    gettimeofday(&tests[0].start_time, NULL);
    
    tests[0].messages_sent = test_sendmsg_performance(client_fd, &dest_addr, 
                                                     MESSAGE_COUNT, test_message);
    tests[0].bytes_sent = tests[0].messages_sent * strlen(test_message);
    
    gettimeofday(&tests[0].end_time, NULL);
    
    printf("sendmsg测试完成: 发送 %lu 个消息\n", tests[0].messages_sent);
    
    // 短暂休息
    sleep(1);
    
    // 测试2: 使用sendmmsg
    printf("\n测试2: 使用sendmmsg发送 %d 个消息...\n", MESSAGE_COUNT);
    gettimeofday(&tests[1].start_time, NULL);
    
    tests[1].messages_sent = test_sendmmsg_performance(client_fd, &dest_addr, 
                                                      MESSAGE_COUNT, test_message);
    tests[1].bytes_sent = tests[1].messages_sent * strlen(test_message);
    
    gettimeofday(&tests[1].end_time, NULL);
    
    printf("sendmmsg测试完成: 发送 %lu 个消息\n", tests[1].messages_sent);
    
    // 计算并显示结果
    printf("\n=== 性能测试结果 ===\n");
    
    for (int i = 0; i < 2; i++) {
        double elapsed_time = (tests[i].end_time.tv_sec - tests[i].start_time.tv_sec) +
                             (tests[i].end_time.tv_usec - tests[i].start_time.tv_usec) / 1000000.0;
        
        double messages_per_sec = tests[i].messages_sent / elapsed_time;
        double bytes_per_sec = tests[i].bytes_sent / elapsed_time;
        
        printf("%s 测试:\n", tests[i].name);
        printf("  发送消息数: %lu\n", tests[i].messages_sent);
        printf("  发送字节数: %lu\n", tests[i].bytes_sent);
        printf("  耗时: %.3f 秒\n", elapsed_time);
        printf("  吞吐量: %.0f 消息/秒 (%.2f MB/s)\n", 
               messages_per_sec, bytes_per_sec / (1024 * 1024));
        printf("  平均延迟: %.3f 微秒/消息\n", 
               (elapsed_time * 1000000) / tests[i].messages_sent);
        printf("\n");
    }
    
    // 计算性能提升
    double sendmsg_time = (tests[0].end_time.tv_sec - tests[0].start_time.tv_sec) +
                         (tests[0].end_time.tv_usec - tests[0].start_time.tv_usec) / 1000000.0;
    double sendmmsg_time = (tests[1].end_time.tv_sec - tests[1].start_time.tv_sec) +
                          (tests[1].end_time.tv_usec - tests[1].start_time.tv_usec) / 1000000.0;
    
    if (sendmsg_time > 0 && sendmmsg_time > 0) {
        double improvement = (sendmsg_time - sendmmsg_time) / sendmsg_time * 100;
        printf("性能提升: %.1f%%\n", improvement);
    }
    
    close(client_fd);
    
    // 等待服务器结束
    int status;
    wait(&status);
    
    return 0;
}

int main() {
    return demo_performance_comparison();
}

sendmmsg 标志参数详解

常用标志:

  • MSG_CONFIRM: 提供路径确认反馈
  • MSG_DONTROUTE: 不使用网关路由
  • MSG_DONTWAIT: 非阻塞操作
  • MSG_EOR: 发送记录结束标记
  • MSG_MORE: 还有更多数据要发送
  • MSG_NOSIGNAL: 发送时不产生SIGPIPE信号
  • MSG_OOB: 发送带外数据

高级标志:

  • MSG_PROXY: SOCKS代理相关
  • MSG_TRYHARD: 尽力发送(已废弃)

使用注意事项

系统要求:

  1. 内核版本: 需要Linux 2.6.39或更高版本
  2. glibc版本: 需要支持sendmmsg的glibc版本
  3. 编译选项: 需要定义_GNU_SOURCE

性能优化:

  1. 批量大小: 根据应用需求选择合适的批量大小
  2. 缓冲区管理: 合理分配缓冲区避免内存浪费
  3. 错误处理: 妥善处理部分发送的情况

错误处理:

  1. 部分发送: 处理实际发送消息数少于请求的情况
  2. 连接状态: 检查连接是否正常
  3. 资源清理: 及时关闭套接字和释放内存

安全考虑:

  1. 缓冲区溢出: 确保消息缓冲区大小正确
  2. 输入验证: 验证发送的数据内容
  3. 权限检查: 确保有适当的网络访问权限

sendmmsg 优势

1. 性能优势:

  • 减少系统调用: 单次调用发送多个消息
  • 降低上下文切换: 减少用户态和内核态切换
  • 提高吞吐量: 特别适用于小消息高频发送场景

2. 功能优势:

  • 完整功能: 支持sendmsg的所有功能
  • 分散缓冲区: 每个消息可使用多个缓冲区
  • 控制信息: 支持发送辅助控制数据

3. 应用场景:

  • 高并发服务器: Web服务器、游戏服务器
  • 实时系统: 音视频流传输、传感器数据采集
  • 消息队列: 批量消息处理系统
  • 网络协议: 实现自定义网络协议

总结

sendmmsg 是构建高性能网络应用的重要工具,它提供了:

  • 批量消息发送能力,显著减少系统调用开销
  • 与sendmsg相同的完整功能集
  • 更好的性能和可扩展性
  • 适用于高吞吐量的实时应用

通过合理使用 sendmmsg,可以大幅提升网络应用的性能,特别是在需要发送大量小消息的场景中效果显著。在实际应用中,需要注意批量大小的选择、错误处理和系统兼容性等问题。

发表在 linux文章 | 留下评论

sigaltstack系统调用及示例

sigaltstack 函数详解

1. 函数介绍

sigaltstack 是Linux系统调用,用于设置和获取信号处理程序的备用栈(alternate signal stack)。当进程收到信号时,内核通常在当前栈上执行信号处理程序。使用 sigaltstack 可以为信号处理程序指定一个独立的栈空间,这对于处理栈溢出等异常情况特别有用。

2. 函数原型

#include <signal.h>
int sigaltstack(const stack_t *ss, stack_t *oss);

3. 功能

sigaltstack 允许进程为信号处理程序设置一个备用的栈空间。当信号被递送到使用备用栈的信号处理程序时,内核会切换到备用栈执行信号处理程序,执行完毕后再切换回原来的栈。

4. 参数

  • *const stack_t ss: 指向新栈设置的指针(NULL表示不改变当前设置)
  • *stack_t oss: 指向存储旧栈设置的指针(NULL表示不获取旧设置)

5. 返回值

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

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

  • signal/sigaction: 设置信号处理程序
  • sigprocmask: 设置信号屏蔽字
  • setjmp/longjmp: 非局部跳转
  • getcontext/setcontext: 上下文操作

7. 示例代码

示例1:基础sigaltstack使用

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

/**
 * 信号栈结构体
 */
typedef struct {
    void *stack_ptr;
    size_t stack_size;
    int is_active;
} signal_stack_t;

/**
 * 显示当前信号栈信息
 */
void show_signal_stack_info() {
    stack_t current_stack;
    
    if (sigaltstack(NULL, &current_stack) == 0) {
        printf("=== 当前信号栈信息 ===\n");
        printf("栈指针: %p\n", current_stack.ss_sp);
        printf("栈大小: %zu 字节\n", current_stack.ss_size);
        printf("栈标志: ");
        if (current_stack.ss_flags & SS_ONSTACK) {
            printf("SS_ONSTACK (正在使用中)\n");
        } else if (current_stack.ss_flags & SS_DISABLE) {
            printf("SS_DISABLE (已禁用)\n");
        } else {
            printf("SS_ENABLED (已启用)\n");
        }
        printf("\n");
    } else {
        printf("获取信号栈信息失败: %s\n", strerror(errno));
    }
}

/**
 * 信号处理程序
 */
void signal_handler(int sig) {
    stack_t current_stack;
    
    printf("信号处理程序执行中 (信号: %d)\n", sig);
    
    // 检查当前是否在备用栈上
    if (sigaltstack(NULL, &current_stack) == 0) {
        if (current_stack.ss_flags & SS_ONSTACK) {
            printf("  ✓ 正在备用栈上执行\n");
        } else {
            printf("  ✗ 在主栈上执行\n");
        }
    }
    
    printf("  当前栈指针: %p\n", &sig);
    printf("  信号处理完成\n\n");
}

/**
 * 演示基础sigaltstack使用方法
 */
int demo_sigaltstack_basic() {
    stack_t new_stack, old_stack;
    char *stack_buffer;
    struct sigaction sa;
    
    printf("=== 基础sigaltstack使用示例 ===\n");
    
    // 显示原始信号栈信息
    printf("1. 原始信号栈信息:\n");
    show_signal_stack_info();
    
    // 分配备用栈空间
    size_t stack_size = SIGSTKSZ;  // 系统推荐的栈大小
    stack_buffer = malloc(stack_size);
    if (!stack_buffer) {
        perror("分配栈空间失败");
        return -1;
    }
    
    printf("2. 分配备用栈空间:\n");
    printf("   栈大小: %zu 字节\n", stack_size);
    printf("   栈地址: %p\n", (void*)stack_buffer);
    
    // 设置备用栈
    new_stack.ss_sp = stack_buffer;
    new_stack.ss_size = stack_size;
    new_stack.ss_flags = 0;  // 启用栈
    
    printf("3. 设置备用信号栈:\n");
    if (sigaltstack(&new_stack, &old_stack) == 0) {
        printf("   ✓ 备用栈设置成功\n");
        show_signal_stack_info();
    } else {
        printf("   ✗ 备用栈设置失败: %s\n", strerror(errno));
        free(stack_buffer);
        return -1;
    }
    
    // 设置使用备用栈的信号处理程序
    printf("4. 设置信号处理程序:\n");
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_ONSTACK;  // 使用备用栈
    
    if (sigaction(SIGUSR1, &sa, NULL) == 0) {
        printf("   ✓ 信号处理程序设置成功 (使用备用栈)\n");
    } else {
        printf("   ✗ 信号处理程序设置失败: %s\n", strerror(errno));
        free(stack_buffer);
        return -1;
    }
    
    // 发送信号测试
    printf("5. 发送测试信号:\n");
    if (kill(getpid(), SIGUSR1) == 0) {
        printf("   ✓ 信号发送成功\n");
        sleep(1);  // 等待信号处理完成
    } else {
        printf("   ✗ 信号发送失败: %s\n", strerror(errno));
    }
    
    // 禁用备用栈
    printf("6. 禁用备用栈:\n");
    stack_t disable_stack;
    disable_stack.ss_flags = SS_DISABLE;
    
    if (sigaltstack(&disable_stack, NULL) == 0) {
        printf("   ✓ 备用栈禁用成功\n");
        show_signal_stack_info();
    } else {
        printf("   ✗ 备用栈禁用失败: %s\n", strerror(errno));
    }
    
    // 清理资源
    free(stack_buffer);
    
    return 0;
}

int main() {
    return demo_sigaltstack_basic();
}

示例2:栈溢出保护演示

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <setjmp.h>

/**
 * 栈溢出保护结构
 */
typedef struct {
    stack_t alt_stack;
    char *stack_buffer;
    sigjmp_buf jump_buffer;
    int overflow_detected;
} stack_protection_t;

/**
 * 栈溢出信号处理程序
 */
void stack_overflow_handler(int sig) {
    printf("⚠ 检测到栈溢出异常 (信号: %d)\n", sig);
    
    // 检查是否在备用栈上
    stack_t current_stack;
    if (sigaltstack(NULL, &current_stack) == 0) {
        if (current_stack.ss_flags & SS_ONSTACK) {
            printf("  ✓ 在备用栈上安全处理异常\n");
        } else {
            printf("  ✗ 不在备用栈上 (异常情况)\n");
        }
    }
    
    // 恢复到安全状态
    printf("  跳转到安全恢复点...\n");
    siglongjmp(((stack_protection_t*)NULL)->jump_buffer, 1);
}

/**
 * 递归函数(可能导致栈溢出)
 */
void recursive_function(int depth) {
    char buffer[1024];  // 消耗栈空间
    
    // 填充缓冲区以确保栈使用
    memset(buffer, depth & 0xFF, sizeof(buffer));
    
    printf("递归深度: %d, 栈地址: %p\n", depth, (void*)&buffer);
    
    // 深度递归可能导致栈溢出
    if (depth < 1000) {  // 限制递归深度
        recursive_function(depth + 1);
    }
}

/**
 * 安全的递归执行
 */
int safe_recursive_execution(stack_protection_t *protection) {
    struct sigaction sa;
    int result;
    
    printf("=== 栈溢出保护演示 ===\n");
    
    // 设置信号处理程序
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = stack_overflow_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_ONSTACK;
    
    if (sigaction(SIGSEGV, &sa, NULL) != 0) {
        printf("设置SIGSEGV处理程序失败: %s\n", strerror(errno));
        return -1;
    }
    
    if (sigaction(SIGBUS, &sa, NULL) != 0) {
        printf("设置SIGBUS处理程序失败: %s\n", strerror(errno));
        return -1;
    }
    
    // 设置跳转点
    result = sigsetjmp(protection->jump_buffer, 1);
    if (result == 0) {
        printf("开始安全递归执行...\n");
        
        // 执行可能引起栈溢出的操作
        recursive_function(0);
        
        printf("递归执行正常完成\n");
        return 0;
    } else {
        printf("✓ 从栈溢出异常中成功恢复\n");
        return 1;
    }
}

/**
 * 演示栈溢出保护
 */
int demo_stack_overflow_protection() {
    stack_protection_t protection = {0};
    size_t stack_size = SIGSTKSZ * 2;  // 更大的备用栈
    
    printf("=== 栈溢出保护演示 ===\n");
    
    // 分配备用栈
    protection.stack_buffer = malloc(stack_size);
    if (!protection.stack_buffer) {
        perror("分配备用栈失败");
        return -1;
    }
    
    printf("分配备用栈: %zu 字节\n", stack_size);
    
    // 设置备用栈
    protection.alt_stack.ss_sp = protection.stack_buffer;
    protection.alt_stack.ss_size = stack_size;
    protection.alt_stack.ss_flags = 0;
    
    if (sigaltstack(&protection.alt_stack, NULL) != 0) {
        printf("设置备用栈失败: %s\n", strerror(errno));
        free(protection.stack_buffer);
        return -1;
    }
    
    printf("备用栈设置成功\n");
    show_signal_stack_info();
    
    // 执行安全递归
    int result = safe_recursive_execution(&protection);
    
    // 清理资源
    free(protection.stack_buffer);
    
    return result;
}

// 辅助函数声明
void show_signal_stack_info();

int main() {
    return demo_stack_overflow_protection();
}

示例3:多信号处理程序管理

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>

/**
 * 多栈管理器
 */
typedef struct {
    stack_t stack1;
    stack_t stack2;
    char *buffer1;
    char *buffer2;
    int current_stack;
} multi_stack_manager_t;

/**
 * 信号处理程序1(使用栈1)
 */
void signal_handler_1(int sig) {
    printf("信号处理程序1执行 (信号: %d)\n", sig);
    
    stack_t current_stack;
    if (sigaltstack(NULL, &current_stack) == 0) {
        if (current_stack.ss_flags & SS_ONSTACK) {
            printf("  使用备用栈1执行\n");
        }
    }
    
    printf("  处理程序1完成\n");
}

/**
 * 信号处理程序2(使用栈2)
 */
void signal_handler_2(int sig) {
    printf("信号处理程序2执行 (信号: %d)\n", sig);
    
    stack_t current_stack;
    if (sigaltstack(NULL, &current_stack) == 0) {
        if (current_stack.ss_flags & SS_ONSTACK) {
            printf("  使用备用栈2执行\n");
        }
    }
    
    printf("  处理程序2完成\n");
}

/**
 * 初始化多栈管理器
 */
int init_multi_stack_manager(multi_stack_manager_t *manager) {
    size_t stack_size = SIGSTKSZ;
    
    printf("=== 多栈管理器初始化 ===\n");
    
    // 分配两个栈空间
    manager->buffer1 = malloc(stack_size);
    manager->buffer2 = malloc(stack_size);
    
    if (!manager->buffer1 || !manager->buffer2) {
        printf("分配栈空间失败\n");
        if (manager->buffer1) free(manager->buffer1);
        if (manager->buffer2) free(manager->buffer2);
        return -1;
    }
    
    printf("栈1地址: %p, 大小: %zu\n", (void*)manager->buffer1, stack_size);
    printf("栈2地址: %p, 大小: %zu\n", (void*)manager->buffer2, stack_size);
    
    // 初始化栈结构
    manager->stack1.ss_sp = manager->buffer1;
    manager->stack1.ss_size = stack_size;
    manager->stack1.ss_flags = 0;
    
    manager->stack2.ss_sp = manager->buffer2;
    manager->stack2.ss_size = stack_size;
    manager->stack2.ss_flags = 0;
    
    manager->current_stack = 0;
    
    return 0;
}

/**
 * 切换备用栈
 */
int switch_alternate_stack(multi_stack_manager_t *manager, int stack_id) {
    stack_t *target_stack = (stack_id == 1) ? &manager->stack1 : &manager->stack2;
    
    printf("切换到备用栈 %d\n", stack_id);
    
    if (sigaltstack(target_stack, NULL) == 0) {
        printf("✓ 成功切换到备用栈 %d\n", stack_id);
        manager->current_stack = stack_id;
        return 0;
    } else {
        printf("✗ 切换备用栈 %d 失败: %s\n", stack_id, strerror(errno));
        return -1;
    }
}

/**
 * 演示多信号处理程序
 */
int demo_multi_signal_handlers() {
    multi_stack_manager_t manager = {0};
    struct sigaction sa1, sa2;
    
    printf("=== 多信号处理程序演示 ===\n");
    
    // 初始化多栈管理器
    if (init_multi_stack_manager(&manager) != 0) {
        printf("初始化多栈管理器失败\n");
        return -1;
    }
    
    // 设置信号处理程序1(使用栈1)
    printf("\n设置信号处理程序1:\n");
    switch_alternate_stack(&manager, 1);
    
    memset(&sa1, 0, sizeof(sa1));
    sa1.sa_handler = signal_handler_1;
    sigemptyset(&sa1.sa_mask);
    sa1.sa_flags = SA_ONSTACK;
    
    if (sigaction(SIGUSR1, &sa1, NULL) == 0) {
        printf("✓ 信号处理程序1设置成功\n");
    } else {
        printf("✗ 信号处理程序1设置失败: %s\n", strerror(errno));
    }
    
    // 设置信号处理程序2(使用栈2)
    printf("\n设置信号处理程序2:\n");
    switch_alternate_stack(&manager, 2);
    
    memset(&sa2, 0, sizeof(sa2));
    sa2.sa_handler = signal_handler_2;
    sigemptyset(&sa2.sa_mask);
    sa2.sa_flags = SA_ONSTACK;
    
    if (sigaction(SIGUSR2, &sa2, NULL) == 0) {
        printf("✓ 信号处理程序2设置成功\n");
    } else {
        printf("✗ 信号处理程序2设置失败: %s\n", strerror(errno));
    }
    
    // 测试信号处理
    printf("\n测试信号处理:\n");
    
    // 发送SIGUSR1
    printf("发送SIGUSR1信号:\n");
    switch_alternate_stack(&manager, 1);
    if (kill(getpid(), SIGUSR1) == 0) {
        printf("✓ SIGUSR1发送成功\n");
        sleep(1);
    }
    
    // 发送SIGUSR2
    printf("发送SIGUSR2信号:\n");
    switch_alternate_stack(&manager, 2);
    if (kill(getpid(), SIGUSR2) == 0) {
        printf("✓ SIGUSR2发送成功\n");
        sleep(1);
    }
    
    // 清理资源
    free(manager.buffer1);
    free(manager.buffer2);
    
    return 0;
}

int main() {
    return demo_multi_signal_handlers();
}

示例4:线程安全的信号栈管理

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>

/**
 * 线程信号栈管理器
 */
typedef struct {
    stack_t signal_stack;
    char *stack_buffer;
    pthread_t thread_id;
    int thread_num;
} thread_stack_manager_t;

/**
 * 线程信号处理程序
 */
void thread_signal_handler(int sig) {
    pthread_t current_thread = pthread_self();
    
    printf("线程 %lu 收到信号 %d\n", (unsigned long)current_thread, sig);
    
    stack_t current_stack;
    if (sigaltstack(NULL, &current_stack) == 0) {
        if (current_stack.ss_flags & SS_ONSTACK) {
            printf("  线程 %lu 在备用栈上处理信号\n", (unsigned long)current_thread);
        } else {
            printf("  线程 %lu 在主栈上处理信号\n", (unsigned long)current_thread);
        }
    }
    
    printf("  线程 %lu 信号处理完成\n", (unsigned long)current_thread);
}

/**
 * 初始化线程栈管理器
 */
int init_thread_stack_manager(thread_stack_manager_t *manager, int thread_num) {
    size_t stack_size = SIGSTKSZ;
    
    manager->thread_num = thread_num;
    manager->thread_id = pthread_self();
    
    // 分配栈空间
    manager->stack_buffer = malloc(stack_size);
    if (!manager->stack_buffer) {
        printf("线程 %d: 分配栈空间失败\n", thread_num);
        return -1;
    }
    
    // 初始化栈结构
    manager->signal_stack.ss_sp = manager->stack_buffer;
    manager->signal_stack.ss_size = stack_size;
    manager->signal_stack.ss_flags = 0;
    
    printf("线程 %d: 分配备用栈 %p, 大小 %zu\n", 
           thread_num, (void*)manager->stack_buffer, stack_size);
    
    return 0;
}

/**
 * 线程工作函数
 */
void* thread_worker(void *arg) {
    thread_stack_manager_t *manager = (thread_stack_manager_t*)arg;
    struct sigaction sa;
    sigset_t set;
    
    printf("工作线程 %d 启动 (ID: %lu)\n", 
           manager->thread_num, (unsigned long)manager->thread_id);
    
    // 设置备用栈
    if (sigaltstack(&manager->signal_stack, NULL) != 0) {
        printf("线程 %d: 设置备用栈失败: %s\n", 
               manager->thread_num, strerror(errno));
        return NULL;
    }
    
    printf("线程 %d: 备用栈设置成功\n", manager->thread_num);
    
    // 设置信号处理程序
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = thread_signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_ONSTACK;
    
    if (sigaction(SIGUSR1, &sa, NULL) != 0) {
        printf("线程 %d: 设置信号处理程序失败: %s\n", 
               manager->thread_num, strerror(errno));
        return NULL;
    }
    
    printf("线程 %d: 信号处理程序设置成功\n", manager->thread_num);
    
    // 阻塞SIGUSR1以便测试
    sigemptyset(&set);
    sigaddset(&set, SIGUSR1);
    pthread_sigmask(SIG_BLOCK, &set, NULL);
    
    // 执行工作
    for (int i = 0; i < 5; i++) {
        printf("线程 %d: 工作中... (%d/5)\n", manager->thread_num, i + 1);
        sleep(2);
    }
    
    // 解除阻塞并等待信号
    printf("线程 %d: 等待信号...\n", manager->thread_num);
    pthread_sigmask(SIG_UNBLOCK, &set, NULL);
    
    // 等待一段时间让信号处理完成
    sleep(2);
    
    printf("线程 %d: 工作完成\n", manager->thread_num);
    return NULL;
}

/**
 * 演示线程安全的信号栈管理
 */
int demo_thread_safe_signal_stacks() {
    const int num_threads = 3;
    pthread_t threads[num_threads];
    thread_stack_manager_t managers[num_threads];
    sigset_t set;
    
    printf("=== 线程安全的信号栈管理演示 ===\n");
    
    // 阻塞SIGUSR1信号
    sigemptyset(&set);
    sigaddset(&set, SIGUSR1);
    pthread_sigmask(SIG_BLOCK, &set, NULL);
    
    // 创建工作线程
    printf("创建 %d 个工作线程:\n", num_threads);
    
    for (int i = 0; i < num_threads; i++) {
        if (init_thread_stack_manager(&managers[i], i + 1) != 0) {
            printf("初始化线程 %d 失败\n", i + 1);
            // 清理已分配的资源
            for (int j = 0; j < i; j++) {
                free(managers[j].stack_buffer);
            }
            return -1;
        }
        
        if (pthread_create(&threads[i], NULL, thread_worker, &managers[i]) != 0) {
            printf("创建线程 %d 失败\n", i + 1);
            free(managers[i].stack_buffer);
            // 清理已分配的资源
            for (int j = 0; j < i; j++) {
                free(managers[j].stack_buffer);
            }
            return -1;
        }
        
        managers[i].thread_id = threads[i];
        printf("创建线程 %d: ID=%lu\n", i + 1, (unsigned long)threads[i]);
    }
    
    // 等待线程启动
    sleep(1);
    
    // 向所有线程发送信号
    printf("\n向所有线程发送SIGUSR1信号:\n");
    pthread_sigmask(SIG_UNBLOCK, &set, NULL);
    
    for (int i = 0; i < num_threads; i++) {
        // 注意:实际应用中需要更精确的线程信号发送方法
        if (kill(getpid(), SIGUSR1) == 0) {
            printf("向线程 %d 发送信号成功\n", i + 1);
        } else {
            printf("向线程 %d 发送信号失败: %s\n", i + 1, strerror(errno));
        }
        sleep(1);  // 间隔发送
    }
    
    // 等待所有线程完成
    printf("\n等待所有线程完成:\n");
    for (int i = 0; i < num_threads; i++) {
        void *result;
        pthread_join(threads[i], &result);
        printf("线程 %d 已完成\n", i + 1);
        
        // 清理资源
        free(managers[i].stack_buffer);
    }
    
    return 0;
}

int main() {
    return demo_thread_safe_signal_stacks();
}

示例5:信号栈监控和调试

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/resource.h>

/**
 * 信号栈监控器
 */
typedef struct {
    stack_t original_stack;
    stack_t current_stack;
    int stack_switch_count;
    size_t total_stack_size;
    time_t last_switch_time;
} stack_monitor_t;

/**
 * 详细的栈信息显示
 */
void show_detailed_stack_info(const char *context) {
    stack_t stack_info;
    
    printf("=== %s ===\n", context);
    
    if (sigaltstack(NULL, &stack_info) == 0) {
        printf("栈指针: %p\n", stack_info.ss_sp);
        printf("栈大小: %zu 字节\n", stack_info.ss_size);
        printf("栈标志: 0x%x\n", stack_info.ss_flags);
        
        if (stack_info.ss_flags & SS_ONSTACK) {
            printf("状态: 正在使用备用栈\n");
        } else if (stack_info.ss_flags & SS_DISABLE) {
            printf("状态: 备用栈已禁用\n");
        } else {
            printf("状态: 备用栈已启用但未使用\n");
        }
        
        // 计算栈使用情况(简化版)
        void *current_sp;
        asm volatile("mov %%rsp, %0" : "=r"(current_sp));
        printf("当前栈指针: %p\n", current_sp);
        
        if (stack_info.ss_sp) {
            ptrdiff_t distance = (char*)current_sp - (char*)stack_info.ss_sp;
            printf("距离栈底: %td 字节\n", distance);
            
            if (distance > 0 && (size_t)distance < stack_info.ss_size) {
                double usage = (double)distance / stack_info.ss_size * 100;
                printf("栈使用率: %.1f%%\n", usage);
            }
        }
    } else {
        printf("获取栈信息失败: %s\n", strerror(errno));
    }
    
    printf("\n");
}

/**
 * 信号处理程序(带监控)
 */
void monitored_signal_handler(int sig) {
    static int call_count = 0;
    call_count++;
    
    printf("监控信号处理程序执行 (第 %d 次, 信号: %d)\n", call_count, sig);
    
    show_detailed_stack_info("信号处理程序中的栈状态");
    
    // 模拟一些栈使用
    char local_buffer[512];
    memset(local_buffer, call_count & 0xFF, sizeof(local_buffer));
    
    printf("  处理程序使用了 %zu 字节本地缓冲区\n", sizeof(local_buffer));
    printf("  处理程序执行完成\n\n");
}

/**
 * 栈使用压力测试
 */
void stack_pressure_test(int depth) {
    char buffer[1024];  // 每层消耗1KB栈空间
    
    // 填充缓冲区
    memset(buffer, depth & 0xFF, sizeof(buffer));
    
    if (depth < 50) {  // 限制递归深度
        printf("递归深度: %d, 使用栈空间: %d KB\n", depth, depth);
        stack_pressure_test(depth + 1);
    } else {
        printf("达到最大递归深度: %d\n", depth);
    }
}

/**
 * 演示信号栈监控和调试
 */
int demo_stack_monitoring() {
    stack_t alt_stack, old_stack;
    char *stack_buffer;
    struct sigaction sa;
    struct rlimit rl;
    
    printf("=== 信号栈监控和调试演示 ===\n");
    
    // 显示系统栈限制
    printf("1. 系统栈限制信息:\n");
    if (getrlimit(RLIMIT_STACK, &rl) == 0) {
        printf("   主栈大小限制: %ld 字节", rl.rlim_cur);
        if (rl.rlim_cur == RLIM_INFINITY) {
            printf(" (无限制)");
        }
        printf("\n");
        printf("   最大栈大小: %ld 字节", rl.rlim_max);
        if (rl.rlim_max == RLIM_INFINITY) {
            printf(" (无限制)");
        }
        printf("\n");
    }
    
    // 显示初始栈状态
    show_detailed_stack_info("初始栈状态");
    
    // 分配备用栈
    size_t stack_size = SIGSTKSZ * 4;  // 4倍标准大小
    stack_buffer = malloc(stack_size);
    if (!stack_buffer) {
        perror("分配备用栈失败");
        return -1;
    }
    
    printf("2. 分配备用栈:\n");
    printf("   请求大小: %zu 字节 (%.1f KB)\n", stack_size, stack_size / 1024.0);
    printf("   分配地址: %p\n", (void*)stack_buffer);
    
    // 设置备用栈
    alt_stack.ss_sp = stack_buffer;
    alt_stack.ss_size = stack_size;
    alt_stack.ss_flags = 0;
    
    printf("3. 设置备用栈:\n");
    if (sigaltstack(&alt_stack, &old_stack) == 0) {
        printf("   ✓ 备用栈设置成功\n");
        show_detailed_stack_info("设置备用栈后");
    } else {
        printf("   ✗ 备用栈设置失败: %s\n", strerror(errno));
        free(stack_buffer);
        return -1;
    }
    
    // 设置监控信号处理程序
    printf("4. 设置监控信号处理程序:\n");
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = monitored_signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_ONSTACK;
    
    if (sigaction(SIGUSR1, &sa, NULL) == 0) {
        printf("   ✓ 监控信号处理程序设置成功\n");
    } else {
        printf("   ✗ 监控信号处理程序设置失败: %s\n", strerror(errno));
        free(stack_buffer);
        return -1;
    }
    
    // 发送多个信号进行测试
    printf("5. 发送测试信号:\n");
    for (int i = 1; i <= 3; i++) {
        printf("   发送第 %d 个信号:\n", i);
        if (kill(getpid(), SIGUSR1) == 0) {
            printf("     ✓ 信号发送成功\n");
            sleep(1);  // 等待处理完成
        } else {
            printf("     ✗ 信号发送失败: %s\n", strerror(errno));
        }
    }
    
    // 栈压力测试
    printf("6. 栈压力测试:\n");
    printf("   开始递归栈使用测试...\n");
    stack_pressure_test(0);
    printf("   栈压力测试完成\n");
    
    show_detailed_stack_info("压力测试后栈状态");
    
    // 禁用备用栈
    printf("7. 禁用备用栈:\n");
    stack_t disable_stack;
    disable_stack.ss_flags = SS_DISABLE;
    
    if (sigaltstack(&disable_stack, NULL) == 0) {
        printf("   ✓ 备用栈禁用成功\n");
        show_detailed_stack_info("禁用备用栈后");
    } else {
        printf("   ✗ 备用栈禁用失败: %s\n", strerror(errno));
    }
    
    // 清理资源
    free(stack_buffer);
    
    printf("=== 监控演示完成 ===\n");
    return 0;
}

int main() {
    return demo_stack_monitoring();
}

sigaltstack 使用注意事项

系统要求:

1. 内核版本: 支持信号备用栈的Linux内核
2. 权限要求: 通常不需要特殊权限
3. 架构支持: 支持所有主流架构

栈大小考虑:

  1. 最小大小: 至少MINSIGSTKSZ字节
  2. 推荐大小: 使用SIGSTKSZ或更大
  3. 动态分配: 建议在堆上分配栈空间

错误处理:

  1. ENOMEM: 内存不足
  2. EINVAL: 参数无效
  3. EPERM: 权限不足

安全考虑:

1. 栈溢出保护: 备用栈可以防止主栈溢出
2. 信号安全: 确保信号处理程序的安全执行
3. 资源管理: 及时释放分配的栈空间

最佳实践:

1. 适当大小: 根据信号处理程序的需求分配栈大小
2. 错误检查: 始终检查sigaltstack的返回值
3. 资源清理: 程序结束时释放栈空间
4. 线程安全: 多线程环境中每个线程需要独立的栈
5. 监控调试: 监控栈使用情况以便调试

信号栈标志说明

SS_ONSTACK:

  • 含义: 当前正在使用备用栈执行信号处理程序
  • 用途: 检查信号处理程序是否在备用栈上执行

SS_DISABLE:

  • 含义: 备用栈被禁用
  • 用途: 禁用备用栈功能

SS_ENABLED:

  • 含义: 备用栈已启用但未使用
  • 用途: 正常状态,可以使用备用栈

相关常量

SIGSTKSZ:

  • 含义: 系统推荐的信号栈大小
  • 典型值: 8KB或更大

MINSIGSTKSZ:

  • 含义: 信号栈的最小大小
  • 典型值: 2KB

常见使用场景

1. 栈溢出保护:

// 为可能引起栈溢出的程序设置备用栈
stack_t alt_stack;
alt_stack.ss_sp = malloc(SIGSTKSZ);
alt_stack.ss_size = SIGSTKSZ;
alt_stack.ss_flags = 0;
sigaltstack(&alt_stack, NULL);

2. 信号处理程序:

// 为信号处理程序设置独立的执行环境
struct sigaction sa;
sa.sa_handler = signal_handler;
sa.sa_flags = SA_ONSTACK;
sigaction(SIGSEGV, &sa, NULL);

3. 多线程应用:

// 每个线程设置独立的信号栈
void* thread_function(void *arg) {
    stack_t thread_stack;
    thread_stack.ss_sp = malloc(SIGSTKSZ);
    thread_stack.ss_size = SIGSTKSZ;
    thread_stack.ss_flags = 0;
    sigaltstack(&thread_stack, NULL);
    // 线程工作...
}

总结

sigaltstack 是Linux系统中重要的信号处理机制,提供了:

1. 栈隔离: 为信号处理程序提供独立的执行环境
2. 异常处理: 防止栈溢出等异常情况
3. 安全执行: 确保信号处理程序的安全执行
4. 灵活配置: 支持动态栈管理和配置

通过合理使用 sigaltstack,可以构建更加健壮和安全的信号处理系统。在实际应用中,需要注意栈大小、错误处理和资源管理等关键问题。

发表在 linux文章 | 留下评论

getcwd系统调用及示例

getcwd – 获取当前工作目录

getcwd – 获取当前工作目录

函数介绍

getcwd系统调用用于获取进程当前的工作目录路径。它返回一个以null结尾的字符串,表示当前目录的绝对路径名。

函数原型

#include <unistd.h>

char *getcwd(char *buf, size_t size);

功能

获取进程当前工作目录的绝对路径名。

参数

  • char *buf: 指向存储路径名的缓冲区
  • size_t size: 缓冲区大小(字节)

返回值

  • 成功时返回指向缓冲区的指针(buf)
  • 失败时返回NULL,并设置errno:
    • EINVAL: size参数为0且buf非NULL
    • ERANGE: 缓冲区太小
    • ENOMEM: 内存不足

相似函数

  • chdir(): 改变当前工作目录
  • fchdir(): 通过文件描述符改变当前工作目录

示例代码

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>

int main() {
    char buffer[PATH_MAX];
    char *result;
    
    printf("=== Getcwd函数示例 ===\n");
    
    // 示例1: 基本的获取当前目录操作
    printf("\n示例1: 基本的获取当前目录操作\n");
    
    // 方法1: 使用预分配的缓冲区
    result = getcwd(buffer, sizeof(buffer));
    if (result != NULL) {
        printf("当前工作目录: %s\n", buffer);
    } else {
        perror("获取当前目录失败");
    }
    
    // 方法2: 让系统分配内存(buf为NULL,size为0)
    char *dynamic_buffer = getcwd(NULL, 0);
    if (dynamic_buffer != NULL) {
        printf("动态分配的当前目录: %s\n", dynamic_buffer);
        free(dynamic_buffer);  // 需要手动释放内存
    } else {
        perror("动态获取当前目录失败");
    }
    
    // 示例2: 缓冲区大小处理
    printf("\n示例2: 缓冲区大小处理\n");
    
    // 测试不同大小的缓冲区
    size_t sizes[] = {10, 50, 100, PATH_MAX};
    for (int i = 0; i < 4; i++) {
        char small_buffer[1000];  // 足够大的缓冲区
        printf("尝试使用大小为 %zu 的缓冲区: ", sizes[i]);
        
        result = getcwd(small_buffer, sizes[i]);
        if (result != NULL) {
            printf("成功,目录长度: %zu 字节\n", strlen(small_buffer));
        } else {
            if (errno == ERANGE) {
                printf("失败 - 缓冲区太小\n");
            } else {
                printf("失败 - %s\n", strerror(errno));
            }
        }
    }
    
    // 示例3: 创建测试目录并切换
    printf("\n示例3: 目录切换演示\n");
    
    // 保存原始目录
    char original_dir[PATH_MAX];
    if (getcwd(original_dir, sizeof(original_dir)) == NULL) {
        perror("保存原始目录失败");
        exit(EXIT_FAILURE);
    }
    printf("原始目录: %s\n", original_dir);
    
    // 创建测试目录
    const char *test_dir = "test_getcwd_dir";
    if (mkdir(test_dir, 0755) == -1 && errno != EEXIST) {
        perror("创建测试目录失败");
    } else {
        printf("创建测试目录: %s\n", test_dir);
        
        // 切换到测试目录
        if (chdir(test_dir) == -1) {
            perror("切换目录失败");
        } else {
            printf("切换到测试目录\n");
            
            // 获取当前目录
            result = getcwd(buffer, sizeof(buffer));
            if (result != NULL) {
                printf("切换后当前目录: %s\n", buffer);
                
                // 验证是否正确切换
                if (strstr(buffer, test_dir) != NULL) {
                    printf("目录切换验证成功\n");
                }
            }
            
            // 在测试目录中创建子目录
            const char *sub_dir = "sub_directory";
            if (mkdir(sub_dir, 0755) == -1 && errno != EEXIST) {
                perror("创建子目录失败");
            } else {
                printf("创建子目录: %s\n", sub_dir);
                
                // 切换到子目录
                if (chdir(sub_dir) == -1) {
                    perror("切换到子目录失败");
                } else {
                    printf("切换到子目录\n");
                    
                    result = getcwd(buffer, sizeof(buffer));
                    if (result != NULL) {
                        printf("子目录路径: %s\n", buffer);
                    }
                }
            }
        }
        
        // 返回原始目录
        if (chdir(original_dir) == -1) {
            perror("返回原始目录失败");
        } else {
            printf("返回原始目录\n");
            result = getcwd(buffer, sizeof(buffer));
            if (result != NULL) {
                printf("确认当前目录: %s\n", buffer);
            }
        }
    }
    
    // 示例4: 错误处理演示
    printf("\n示例4: 错误处理演示\n");
    
    // 测试缓冲区太小的情况
    char tiny_buffer[5];
    result = getcwd(tiny_buffer, sizeof(tiny_buffer));
    if (result == NULL) {
        if (errno == ERANGE) {
            printf("缓冲区太小错误处理正确: %s\n", strerror(errno));
        } else {
            printf("其他错误: %s\n", strerror(errno));
        }
    }
    
    // 测试无效参数
    result = getcwd(buffer, 0);
    if (result == NULL) {
        if (errno == EINVAL) {
            printf("无效参数处理正确: %s\n", strerror(errno));
        }
    }
    
    // 示例5: 实际应用场景
    printf("\n示例5: 实际应用场景\n");
    
    // 场景1: 程序配置文件定位
    printf("场景1: 程序配置文件定位\n");
    result = getcwd(buffer, sizeof(buffer));
    if (result != NULL) {
        char config_path[PATH_MAX * 2];
        snprintf(config_path, sizeof(config_path), "%s/.myapp/config", buffer);
        printf("配置文件路径: %s\n", config_path);
        
        // 检查配置文件是否存在
        if (access(config_path, F_OK) == 0) {
            printf("配置文件存在\n");
        } else {
            printf("配置文件不存在,将创建默认配置\n");
        }
    }
    
    // 场景2: 相对路径转换为绝对路径
    printf("场景2: 相对路径处理\n");
    const char *relative_paths[] = {"./file.txt", "../parent_dir", "subdir/file"};
    
    for (int i = 0; i < 3; i++) {
        result = getcwd(buffer, sizeof(buffer));
        if (result != NULL) {
            char absolute_path[PATH_MAX * 2];
            snprintf(absolute_path, sizeof(absolute_path), "%s/%s", buffer, relative_paths[i]);
            printf("  %s -> %s\n", relative_paths[i], absolute_path);
        }
    }
    
    // 场景3: 目录操作安全检查
    printf("场景3: 目录操作安全检查\n");
    char current_dir[PATH_MAX];
    char previous_dir[PATH_MAX];
    
    // 保存当前目录
    if (getcwd(current_dir, sizeof(current_dir)) != NULL) {
        printf("当前工作目录: %s\n", current_dir);
        
        // 执行一些目录操作
        // ... 目录操作代码 ...
        
        // 检查目录是否发生变化
        if (getcwd(previous_dir, sizeof(previous_dir)) != NULL) {
            if (strcmp(current_dir, previous_dir) != 0) {
                printf("警告: 工作目录已改变\n");
                printf("原目录: %s\n", current_dir);
                printf("现目录: %s\n", previous_dir);
            } else {
                printf("工作目录保持不变\n");
            }
        }
    }
    
    // 场景4: 日志记录中的目录信息
    printf("场景4: 日志记录\n");
    time_t current_time = time(NULL);
    result = getcwd(buffer, sizeof(buffer));
    if (result != NULL) {
        printf("[%s] INFO: 程序在目录 '%s' 中启动\n", 
               ctime(&current_time), buffer);
    }
    
    // 示例6: 跨平台兼容性考虑
    printf("\n示例6: 跨平台兼容性\n");
    
    // 获取系统路径限制
    long path_max = pathconf(".", _PC_PATH_MAX);
    if (path_max == -1) {
        path_max = PATH_MAX;
        printf("使用默认PATH_MAX: %ld\n", path_max);
    } else {
        printf("系统PATH_MAX: %ld\n", path_max);
    }
    
    // 使用动态分配确保足够空间
    char *safe_buffer = getcwd(NULL, 0);
    if (safe_buffer != NULL) {
        printf("动态分配确保安全: %s\n", safe_buffer);
        printf("路径长度: %zu 字节\n", strlen(safe_buffer));
        free(safe_buffer);
    }
    
    // 示例7: 性能对比测试
    printf("\n示例7: 性能对比测试\n");
    
    // 测试预分配缓冲区的性能
    clock_t start = clock();
    for (int i = 0; i < 10000; i++) {
        getcwd(buffer, sizeof(buffer));
    }
    clock_t preallocated_time = clock() - start;
    
    // 测试动态分配的性能
    start = clock();
    for (int i = 0; i < 10000; i++) {
        char *temp = getcwd(NULL, 0);
        if (temp != NULL) {
            free(temp);
        }
    }
    clock_t dynamic_time = clock() - start;
    
    printf("预分配缓冲区 10000 次调用耗时: %f 秒\n", 
           ((double)preallocated_time) / CLOCKS_PER_SEC);
    printf("动态分配 10000 次调用耗时: %f 秒\n", 
           ((double)dynamic_time) / CLOCKS_PER_SEC);
    printf("预分配方式通常更快\n");
    
    // 清理测试目录
    printf("\n清理测试资源...\n");
    if (access(test_dir, F_OK) == 0) {
        if (rmdir(test_dir) == -1) {
            printf("删除测试目录失败: %s\n", strerror(errno));
        } else {
            printf("删除测试目录完成\n");
        }
    }
    
    return 0;
}
发表在 linux文章 | 留下评论

getgroups系统调用及示例

getgroups – 获取进程的补充组列表

1. 函数介绍

getgroups – 获取进程的补充组列表

getgroups 是一个 Linux 系统调用,用于获取当前进程所属的补充组(supplementary groups)列表。除了进程的主要组 ID(由 getgid() 返回)之外,进程还可以属于多个补充组,这些组决定了进程对文件和资源的额外访问权限。

补充组机制是 Unix/Linux 系统中实现灵活访问控制的重要组成部分,允许用户同时属于多个组以获得相应的权限。

2. 函数原型

#include <unistd.h>
#include <sys/types.h>

int getgroups(int size, gid_t list[]);

3. 功能

获取当前进程所属的补充组 ID 列表。补充组是除了主要组之外,进程还属于的其他组。

4. 参数

  • int size: 指定 list 数组的大小(元素个数)
    • 如果为 0:函数返回补充组的数量,不填充 list 数组
    • 如果大于 0:将补充组 ID 填充到 list 数组中
  • gid_t list[]: 指向存储组 ID 的数组
    • 如果 size 为 0:可以为 NULL
    • 如果 size 大于 0:必须指向有效的数组

5. 返回值

  • 成功时:
    • 如果 size 为 0:返回补充组的数量
    • 如果 size 大于 0:返回实际填充到数组中的组 ID 数量
  • 失败时返回 -1,并设置 errno

6. 常见 errno 错误码

  • EINVALsize 参数小于补充组的实际数量(缓冲区不足)
  • EFAULTlist 指针指向无效内存地址
  • EPERM: 在某些安全限制下可能返回(较少见)

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

  • setgroups(): 设置进程的补充组列表
  • initgroups(): 根据用户信息初始化补充组列表
  • getgid(): 获取进程的真实组 ID
  • getegid(): 获取进程的有效组 ID
  • getuid()geteuid(): 获取用户 ID 相关函数
  • setgid()setegid(): 设置组 ID
  • getgrouplist(): 获取用户的所有组(包括主要组)
  • /etc/group: 系统组信息文件
  • /etc/passwd: 用户主要组信息文件

8. 示例代码

示例1:基本使用 – 获取补充组列表

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>
#include <errno.h>
#include <string.h>

int main() {
    int group_count;
    gid_t *group_list;
    long max_groups;
    
    printf("=== 获取进程补充组列表 ===\n");
    
    // 首先获取补充组数量
    group_count = getgroups(0, NULL);
    if (group_count == -1) {
        perror("获取补充组数量失败");
        exit(EXIT_FAILURE);
    }
    
    printf("补充组数量: %d\n", group_count);
    
    if (group_count == 0) {
        printf("当前进程没有补充组\n");
        return 0;
    }
    
    // 获取系统支持的最大组数
    max_groups = sysconf(_SC_NGROUPS_MAX);
    if (max_groups == -1) {
        max_groups = 65536;  // 设置一个较大的默认值
    }
    
    printf("系统最大支持组数: %ld\n", max_groups);
    
    // 确保不会超出系统限制
    if (group_count > max_groups) {
        printf("警告: 补充组数量超出系统限制\n");
        group_count = max_groups;
    }
    
    // 分配内存存储组列表
    group_list = malloc(group_count * sizeof(gid_t));
    if (group_list == NULL) {
        perror("内存分配失败");
        exit(EXIT_FAILURE);
    }
    
    // 获取补充组列表
    int result = getgroups(group_count, group_list);
    if (result == -1) {
        perror("获取补充组列表失败");
        free(group_list);
        exit(EXIT_FAILURE);
    }
    
    printf("成功获取 %d 个补充组:\n", result);
    printf("%-8s %-10s %s\n", "序号", "组ID", "组名");
    printf("%-8s %-10s %s\n", "----", "----", "----");
    
    // 显示每个组的信息
    for (int i = 0; i < result; i++) {
        printf("%-8d %-10d ", i + 1, group_list[i]);
        
        // 尝试获取组名
        struct group *grp = getgrgid(group_list[i]);
        if (grp != NULL) {
            printf("%s", grp->gr_name);
        } else {
            printf("(未知)");
        }
        printf("\n");
    }
    
    // 显示主要组信息进行对比
    gid_t primary_gid = getgid();
    printf("\n主要组信息:\n");
    printf("  组ID: %d\n", primary_gid);
    
    struct group *primary_grp = getgrgid(primary_gid);
    if (primary_grp != NULL) {
        printf("  组名: %s\n", primary_grp->gr_name);
    }
    
    // 检查主要组是否在补充组列表中
    int found_in_supplementary = 0;
    for (int i = 0; i < result; i++) {
        if (group_list[i] == primary_gid) {
            found_in_supplementary = 1;
            break;
        }
    }
    
    printf("主要组是否在补充组中: %s\n", 
           found_in_supplementary ? "是" : "否");
    
    free(group_list);
    return 0;
}

示例2:错误处理和边界情况

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>
#include <errno.h>
#include <string.h>

void demonstrate_error_handling() {
    int result;
    gid_t small_buffer[2];  // 故意使用小缓冲区
    
    printf("=== 错误处理演示 ===\n");
    
    // 获取实际补充组数量
    int actual_count = getgroups(0, NULL);
    if (actual_count == -1) {
        perror("获取补充组数量失败");
        return;
    }
    
    printf("实际补充组数量: %d\n", actual_count);
    
    if (actual_count > 0) {
        // 使用过小的缓冲区,应该返回错误
        printf("使用过小缓冲区测试 (大小: 2):\n");
        result = getgroups(2, small_buffer);
        
        if (result == -1) {
            if (errno == EINVAL) {
                printf("  错误处理正确: 缓冲区不足 (EINVAL)\n");
            } else {
                printf("  其他错误: %s\n", strerror(errno));
            }
        } else {
            printf("  意外成功,返回数量: %d\n", result);
        }
        
        // 使用 NULL 指针但 size > 0
        printf("使用 NULL 指针测试:\n");
        result = getgroups(10, NULL);
        if (result == -1) {
            if (errno == EFAULT) {
                printf("  错误处理正确: 无效指针 (EFAULT)\n");
            } else {
                printf("  其他错误: %s\n", strerror(errno));
            }
        }
    }
}

void demonstrate_empty_groups() {
    printf("\n=== 空组列表演示 ===\n");
    
    // 获取补充组数量
    int count = getgroups(0, NULL);
    if (count == -1) {
        perror("获取组数量失败");
        return;
    }
    
    printf("当前进程补充组数量: %d\n", count);
    
    if (count == 0) {
        printf("进程没有补充组权限\n");
        printf("这可能表示:\n");
        printf("  1. 进程以最小权限运行\n");
        printf("  2. 用户不属于任何补充组\n");
        printf("  3. 在容器或受限环境中运行\n");
    }
}

int main() {
    demonstrate_error_handling();
    demonstrate_empty_groups();
    return 0;
}

示例3:完整的组权限分析工具

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>
#include <pwd.h>
#include <string.h>

typedef struct {
    gid_t gid;
    char group_name[256];
    int is_primary;
    int is_effective;
    int is_supplementary;
} group_info_t;

int compare_gids(const void *a, const void *b) {
    gid_t gid_a = ((group_info_t*)a)->gid;
    gid_t gid_b = ((group_info_t*)b)->gid;
    return (gid_a > gid_b) - (gid_a < gid_b);
}

void analyze_process_groups() {
    gid_t primary_gid, effective_gid;
    int sup_count;
    gid_t *sup_groups = NULL;
    group_info_t *all_groups = NULL;
    int total_groups = 0;
    
    printf("=== 进程组权限完整分析 ===\n");
    
    // 获取基本信息
    primary_gid = getgid();
    effective_gid = getegid();
    
    printf("进程基本信息:\n");
    printf("  真实用户 ID: %d\n", getuid());
    printf("  有效用户 ID: %d\n", geteuid());
    printf("  真实组 ID: %d\n", primary_gid);
    printf("  有效组 ID: %d\n", effective_gid);
    
    // 获取用户信息
    struct passwd *pwd = getpwuid(getuid());
    if (pwd != NULL) {
        printf("  用户名: %s\n", pwd->pw_name);
    }
    
    // 获取补充组
    sup_count = getgroups(0, NULL);
    if (sup_count > 0) {
        sup_groups = malloc(sup_count * sizeof(gid_t));
        if (sup_groups == NULL) {
            perror("内存分配失败");
            return;
        }
        
        if (getgroups(sup_count, sup_groups) == -1) {
            perror("获取补充组失败");
            free(sup_groups);
            return;
        }
    }
    
    // 创建完整的组信息列表
    total_groups = 2 + sup_count;  // primary + effective + supplementary
    all_groups = malloc(total_groups * sizeof(group_info_t));
    if (all_groups == NULL) {
        perror("内存分配失败");
        if (sup_groups) free(sup_groups);
        return;
    }
    
    int index = 0;
    
    // 添加主要组
    all_groups[index].gid = primary_gid;
    all_groups[index].is_primary = 1;
    all_groups[index].is_effective = (primary_gid == effective_gid);
    all_groups[index].is_supplementary = 0;
    struct group *grp = getgrgid(primary_gid);
    if (grp != NULL) {
        strncpy(all_groups[index].group_name, grp->gr_name, sizeof(all_groups[index].group_name) - 1);
    } else {
        snprintf(all_groups[index].group_name, sizeof(all_groups[index].group_name), "group_%d", primary_gid);
    }
    index++;
    
    // 添加有效组(如果不同于主要组)
    if (effective_gid != primary_gid) {
        all_groups[index].gid = effective_gid;
        all_groups[index].is_primary = 0;
        all_groups[index].is_effective = 1;
        all_groups[index].is_supplementary = 0;
        struct group *grp = getgrgid(effective_gid);
        if (grp != NULL) {
            strncpy(all_groups[index].group_name, grp->gr_name, sizeof(all_groups[index].group_name) - 1);
        } else {
            snprintf(all_groups[index].group_name, sizeof(all_groups[index].group_name), "group_%d", effective_gid);
        }
        index++;
    }
    
    // 添加补充组
    for (int i = 0; i < sup_count; i++) {
        // 检查是否已存在
        int exists = 0;
        for (int j = 0; j < index; j++) {
            if (all_groups[j].gid == sup_groups[i]) {
                all_groups[j].is_supplementary = 1;
                exists = 1;
                break;
            }
        }
        
        if (!exists) {
            all_groups[index].gid = sup_groups[i];
            all_groups[index].is_primary = 0;
            all_groups[index].is_effective = (sup_groups[i] == effective_gid);
            all_groups[index].is_supplementary = 1;
            struct group *grp = getgrgid(sup_groups[i]);
            if (grp != NULL) {
                strncpy(all_groups[index].group_name, grp->gr_name, sizeof(all_groups[index].group_name) - 1);
            } else {
                snprintf(all_groups[index].group_name, sizeof(all_groups[index].group_name), "group_%d", sup_groups[i]);
            }
            index++;
        }
    }
    
    total_groups = index;
    
    // 按组 ID 排序
    qsort(all_groups, total_groups, sizeof(group_info_t), compare_gids);
    
    // 显示结果
    printf("\n完整的组权限信息:\n");
    printf("%-8s %-10s %-12s %-12s %-15s %s\n", 
           "序号", "组ID", "主要组", "有效组", "补充组", "组名");
    printf("%-8s %-10s %-12s %-12s %-15s %s\n", 
           "----", "----", "----", "----", "----", "----");
    
    for (int i = 0; i < total_groups; i++) {
        printf("%-8d %-10d %-12s %-12s %-15s %s\n",
               i + 1,
               all_groups[i].gid,
               all_groups[i].is_primary ? "是" : "否",
               all_groups[i].is_effective ? "是" : "否",
               all_groups[i].is_supplementary ? "是" : "否",
               all_groups[i].group_name);
    }
    
    // 统计信息
    printf("\n统计信息:\n");
    printf("  总组数: %d\n", total_groups);
    
    int primary_count = 0, effective_count = 0, supplementary_count = 0;
    for (int i = 0; i < total_groups; i++) {
        if (all_groups[i].is_primary) primary_count++;
        if (all_groups[i].is_effective) effective_count++;
        if (all_groups[i].is_supplementary) supplementary_count++;
    }
    
    printf("  主要组: %d\n", primary_count);
    printf("  有效组: %d\n", effective_count);
    printf("  补充组: %d\n", supplementary_count);
    
    // 特殊权限检查
    printf("\n特殊权限检查:\n");
    int has_root_group = 0;
    int has_admin_group = 0;
    
    for (int i = 0; i < total_groups; i++) {
        if (all_groups[i].gid == 0) {
            has_root_group = 1;
        }
        // 检查常见的管理员组
        if (strcmp(all_groups[i].group_name, "wheel") == 0 ||
            strcmp(all_groups[i].group_name, "sudo") == 0 ||
            strcmp(all_groups[i].group_name, "adm") == 0) {
            has_admin_group = 1;
        }
    }
    
    printf("  Root 组权限: %s\n", has_root_group ? "是" : "否");
    printf("  管理员组权限: %s\n", has_admin_group ? "是" : "否");
    
    // 清理内存
    if (sup_groups) free(sup_groups);
    if (all_groups) free(all_groups);
}

int main() {
    analyze_process_groups();
    return 0;
}

示例4:组权限验证和安全检查

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>

// 检查进程是否属于指定组
int is_process_member_of_group(gid_t target_gid) {
    // 首先检查主要组和有效组
    if (getgid() == target_gid || getegid() == target_gid) {
        return 1;
    }
    
    // 检查补充组
    int sup_count = getgroups(0, NULL);
    if (sup_count <= 0) {
        return 0;
    }
    
    gid_t *sup_groups = malloc(sup_count * sizeof(gid_t));
    if (sup_groups == NULL) {
        return 0;
    }
    
    if (getgroups(sup_count, sup_groups) == -1) {
        free(sup_groups);
        return 0;
    }
    
    for (int i = 0; i < sup_count; i++) {
        if (sup_groups[i] == target_gid) {
            free(sup_groups);
            return 1;
        }
    }
    
    free(sup_groups);
    return 0;
}

// 获取特定组的权限级别
typedef enum {
    NO_ACCESS = 0,
    SUPPLEMENTARY_ACCESS = 1,
    PRIMARY_ACCESS = 2,
    EFFECTIVE_ACCESS = 3,
    ROOT_ACCESS = 4
} access_level_t;

access_level_t get_group_access_level(gid_t target_gid) {
    if (target_gid == 0 && (getgid() == 0 || getegid() == 0)) {
        return ROOT_ACCESS;
    }
    
    if (getegid() == target_gid) {
        return EFFECTIVE_ACCESS;
    }
    
    if (getgid() == target_gid) {
        return PRIMARY_ACCESS;
    }
    
    // 检查补充组
    int sup_count = getgroups(0, NULL);
    if (sup_count > 0) {
        gid_t *sup_groups = malloc(sup_count * sizeof(gid_t));
        if (sup_groups != NULL) {
            if (getgroups(sup_count, sup_groups) != -1) {
                for (int i = 0; i < sup_count; i++) {
                    if (sup_groups[i] == target_gid) {
                        free(sup_groups);
                        return SUPPLEMENTARY_ACCESS;
                    }
                }
            }
            free(sup_groups);
        }
    }
    
    return NO_ACCESS;
}

void check_common_groups() {
    printf("=== 常见组权限检查 ===\n");
    
    // 检查一些常见的系统组
    struct {
        const char *name;
        gid_t gid;
    } common_groups[] = {
        {"root", 0},
        {"wheel", 0},  // 需要查找实际 GID
        {"sudo", 0},
        {"adm", 0},
        {"disk", 0}
    };
    
    // 获取这些组的实际 GID
    for (int i = 0; i < sizeof(common_groups)/sizeof(common_groups[0]); i++) {
        struct group *grp = getgrnam(common_groups[i].name);
        if (grp != NULL) {
            common_groups[i].gid = grp->gr_gid;
        }
    }
    
    printf("%-12s %-8s %-15s %s\n", "组名", "组ID", "访问级别", "成员状态");
    printf("%-12s %-8s %-15s %s\n", "----", "----", "----", "----");
    
    for (int i = 0; i < sizeof(common_groups)/sizeof(common_groups[0]); i++) {
        if (common_groups[i].gid != 0) {
            access_level_t level = get_group_access_level(common_groups[i].gid);
            int is_member = is_process_member_of_group(common_groups[i].gid);
            
            const char *level_str;
            switch (level) {
                case ROOT_ACCESS: level_str = "Root"; break;
                case EFFECTIVE_ACCESS: level_str = "Effective"; break;
                case PRIMARY_ACCESS: level_str = "Primary"; break;
                case SUPPLEMENTARY_ACCESS: level_str = "Supplementary"; break;
                default: level_str = "None"; break;
            }
            
            printf("%-12s %-8d %-15s %s\n",
                   common_groups[i].name,
                   common_groups[i].gid,
                   level_str,
                   is_member ? "是" : "否");
        }
    }
}

int main() {
    check_common_groups();
    return 0;
}

9. 实际应用场景

getgroups 在以下场景中非常有用:

场景1:权限验证

int can_access_resource(gid_t required_group) {
    return is_process_member_of_group(required_group);
}

场景2:安全审计

void audit_process_groups() {
    int count = getgroups(0, NULL);
    syslog(LOG_INFO, "进程拥有 %d 个补充组", count);
}

场景3:访问控制

int check_file_group_permission(const char *filename) {
    struct stat file_stat;
    if (stat(filename, &file_stat) == 0) {
        return is_process_member_of_group(file_stat.st_gid);
    }
    return 0;
}

10. 注意事项

使用 getgroups 时需要注意:

  1. 缓冲区大小: 确保缓冲区足够大,避免 EINVAL 错误
  2. 内存管理: 正确分配和释放内存
  3. 错误处理: 检查返回值和 errno
  4. 系统限制: 了解 NGROUPS_MAX 限制
  5. 并发安全: 在多线程环境中注意数据一致性

总结

getgroups 是管理进程组权限的重要函数,关键要点:

  1. 补充组获取: 获取进程除主要组外的所有组
  2. 双重调用: 通常需要先获取数量,再获取实际数据
  3. 权限检查: 是权限验证和访问控制的基础
  4. 安全相关: 在安全审计和权限管理中广泛使用
  5. 系统集成: 与 /etc/group 等系统文件紧密相关

正确使用 getgroups 可以帮助程序准确了解当前的组权限状态,实现更精细的访问控制和安全检查

发表在 linux文章 | 留下评论

semctl系统调用及示例

semctl – 信号量控制

函数介绍

semctl系统调用用于控制System V信号量集,可以获取和设置信号量的各种属性和状态。它是信号量管理的重要工具,用于初始化、查询、修改和删除信号量。

semctl – 信号量控制
(https://www.calcguide.tech/2025/08/18/semctl%e7%b3%bb%e7%bb%9f%e8%b0%83%e7%94%a8%e5%8f%8a%e7%a4%ba%e4%be%8b/)

函数介绍
semctl系统调用用于控制System V信号量集,可以获取和设置信号量的各种属性和状态。它是信号量管理的重要工具,用于初始化、查询、修改和删除信号量。

函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, ...);

功能

对信号量集执行各种控制操作,包括设置值、获取状态、删除信号量集等。

参数

  • int semid: 信号量集的标识符
  • int semnum: 信号量在集合中的索引号(某些命令需要)
  • int cmd: 要执行的控制命令
    • SETVAL: 设置单个信号量的值
    • GETVAL: 获取单个信号量的值
    • SETALL: 设置所有信号量的值
    • GETALL: 获取所有信号量的值
    • IPC_RMID: 删除信号量集
    • IPC_STAT: 获取信号量集状态
    • IPC_SET: 设置信号量集状态
  • ...: 可选参数,根据命令不同而不同

返回值

  • 根据命令不同返回不同值:
    • SETVALSETALLIPC_RMID: 成功返回0,失败返回-1
    • GETVAL: 成功返回信号量值,失败返回-1
    • GETALL: 成功返回0,失败返回-1
    • IPC_STAT: 成功返回0,失败返回-1
  • 失败时设置errno

相似函数

  • semget(): 获取信号量集标识符
  • semop(): 操作信号量集
  • ftok(): 生成System V IPC键值

示例代码

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <string.h>

// 信号量操作联合体
union semun {
    int val;               // 用于SETVAL
    struct semid_ds *buf;  // 用于IPC_STAT和IPC_SET
    unsigned short *array; // 用于GETALL和SETALL
};

int main() {
    key_t key;
    int semid;
    union semun arg;
    struct semid_ds sem_info;
    
    printf("=== Semctl函数示例 ===\n");
    
    // 创建信号量集
    key = ftok(".", 'c');
    if (key == -1) {
        perror("ftok失败");
        exit(EXIT_FAILURE);
    }
    
    // 先尝试删除可能存在的同名信号量集
    semid = semget(key, 1, 0666);
    if (semid != -1) {
        semctl(semid, 0, IPC_RMID);
    }
    
    // 创建包含3个信号量的信号量集
    semid = semget(key, 3, 0666 | IPC_CREAT | IPC_EXCL);
    if (semid == -1) {
        perror("创建信号量集失败");
        exit(EXIT_FAILURE);
    }
    printf("成功创建信号量集,标识符: %d\n", semid);
    
    // 示例1: 设置单个信号量的值
    printf("\n示例1: 设置单个信号量的值\n");
    
    // 设置信号量0的值为5
    arg.val = 5;
    if (semctl(semid, 0, SETVAL, arg) == -1) {
        perror("设置信号量0失败");
    } else {
        printf("  成功设置信号量0的值为: %d\n", semctl(semid, 0, GETVAL));
    }
    
    // 设置信号量1的值为10
    arg.val = 10;
    if (semctl(semid, 1, SETVAL, arg) == -1) {
        perror("设置信号量1失败");
    } else {
        printf("  成功设置信号量1的值为: %d\n", semctl(semid, 1, GETVAL));
    }
    
    // 设置信号量2的值为0
    arg.val = 0;
    if (semctl(semid, 2, SETVAL, arg) == -1) {
        perror("设置信号量2失败");
    } else {
        printf("  成功设置信号量2的值为: %d\n", semctl(semid, 2, GETVAL));
    }
    
    // 示例2: 获取单个信号量的值
    printf("\n示例2: 获取单个信号量的值\n");
    int val0 = semctl(semid, 0, GETVAL);
    int val1 = semctl(semid, 1, GETVAL);
    int val2 = semctl(semid, 2, GETVAL);
    
    if (val0 != -1 && val1 != -1 && val2 != -1) {
        printf("  信号量0的值: %d\n", val0);
        printf("  信号量1的值: %d\n", val1);
        printf("  信号量2的值: %d\n", val2);
    } else {
        perror("获取信号量值失败");
    }
    
    // 示例3: 批量设置所有信号量的值
    printf("\n示例3: 批量设置所有信号量的值\n");
    unsigned short values[3] = {3, 7, 2};
    arg.array = values;
    
    if (semctl(semid, 0, SETALL, arg) == -1) {
        perror("批量设置信号量值失败");
    } else {
        printf("  成功批量设置信号量值\n");
        
        // 验证设置结果
        unsigned short get_values[3];
        arg.array = get_values;
        if (semctl(semid, 0, GETALL, arg) == -1) {
            perror("批量获取信号量值失败");
        } else {
            printf("  当前信号量值: [%d, %d, %d]\n", 
                   get_values[0], get_values[1], get_values[2]);
        }
    }
    
    // 示例4: 获取信号量集状态信息
    printf("\n示例4: 获取信号量集状态信息\n");
    arg.buf = &sem_info;
    if (semctl(semid, 0, IPC_STAT, arg) == -1) {
        perror("获取信号量集状态失败");
    } else {
        printf("  信号量集状态信息:\n");
        printf("    键值: %d\n", sem_info.sem_perm.__key);
        printf("    信号量数量: %ld\n", sem_info.sem_nsems);
        printf("    最后操作时间: %ld\n", sem_info.sem_otime);
        printf("    最后修改时间: %ld\n", sem_info.sem_ctime);
        printf("    创建者UID: %d\n", sem_info.sem_perm.cuid);
        printf("    创建者GID: %d\n", sem_info.sem_perm.cgid);
        printf("    所有者UID: %d\n", sem_info.sem_perm.uid);
        printf("    所有者GID: %d\n", sem_info.sem_perm.gid);
        printf("    权限: %o\n", sem_info.sem_perm.mode);
    }
    
    // 示例5: 修改信号量集权限
    printf("\n示例5: 修改信号量集权限\n");
    struct semid_ds new_info;
    memcpy(&new_info, &sem_info, sizeof(struct semid_ds));
    new_info.sem_perm.mode = 0644;  // 修改为读写权限
    
    arg.buf = &new_info;
    if (semctl(semid, 0, IPC_SET, arg) == -1) {
        perror("修改信号量集权限失败");
    } else {
        printf("  成功修改信号量集权限为: %o\n", new_info.sem_perm.mode);
    }
    
    // 示例6: 错误处理演示
    printf("\n示例6: 错误处理演示\n");
    
    // 尝试操作不存在的信号量集
    if (semctl(999999, 0, GETVAL) == -1) {
        printf("  操作不存在的信号量集: %s\n", strerror(errno));
    }
    
    // 尝试操作不存在的信号量索引
    if (semctl(semid, 999, GETVAL) == -1) {
        printf("  操作不存在的信号量索引: %s\n", strerror(errno));
    }
    
    // 示例7: 删除信号量集
    printf("\n示例7: 删除信号量集\n");
    if (semctl(semid, 0, IPC_RMID) == -1) {
        perror("删除信号量集失败");
    } else {
        printf("  成功删除信号量集 %d\n", semid);
        
        // 验证删除结果
        if (semctl(semid, 0, GETVAL) == -1) {
            printf("  验证: 信号量集已不存在\n");
        }
    }
    
    return 0;
}
发表在 未分类 | 留下评论

semget系统调用及示例

semget – 获取信号量集标识符

函数介绍

semget系统调用用于创建或访问一个System V信号量集。信号量是一种用于进程间同步的机制,主要用于控制对共享资源的访问,防止多个进程同时访问同一资源造成冲突。

函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);

功能

创建新的信号量集或获取现有信号量集的标识符。

参数

  • key_t key: 信号量集的键值
    • IPC_PRIVATE: 创建私有信号量集
    • 正整数: 通过ftok()函数生成的键值
  • int nsems: 信号量集中信号量的个数(创建时使用)
  • int semflg: 标志位组合
    • IPC_CREAT: 如果信号量集不存在则创建
    • IPC_EXCL: 与IPC_CREAT配合使用,如果已存在则失败
    • 权限位: 如0666(读写权限)

返回值

  • 成功时返回信号量集的标识符(非负整数)
  • 失败时返回-1,并设置errno:
    • EACCES: 权限不足
    • EEXIST: 信号量集已存在(使用IPC_EXCL时)
    • EINVAL: 参数无效
    • ENOENT: 信号量集不存在且未指定IPC_CREAT

相似函数

  • semctl(): 控制信号量集
  • semop(): 操作信号量集
  • ftok(): 生成System V IPC键值

示例代码

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <string.h>

// 信号量操作联合体(某些系统需要)
union semun {
    int val;               // 用于SETVAL
    struct semid_ds *buf;  // 用于IPC_STAT和IPC_SET
    unsigned short *array; // 用于GETALL和SETALL
};

int main() {
    key_t key;
    int semid;
    
    printf("=== Semget函数示例 ===\n");
    
    // 示例1: 使用IPC_PRIVATE创建私有信号量集
    printf("\n示例1: 创建私有信号量集\n");
    semid = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT);
    if (semid == -1) {
        perror("semget IPC_PRIVATE失败");
        exit(EXIT_FAILURE);
    }
    printf("  成功创建私有信号量集,标识符: %d\n", semid);
    
    // 删除信号量集
    if (semctl(semid, 0, IPC_RMID) == -1) {
        perror("删除信号量集失败");
    } else {
        printf("  成功删除信号量集\n");
    }
    
    // 示例2: 使用ftok生成键值创建信号量集
    printf("\n示例2: 使用ftok创建信号量集\n");
    
    // 生成键值(基于当前文件和项目ID)
    key = ftok(".", 'a');
    if (key == -1) {
        perror("ftok失败");
        exit(EXIT_FAILURE);
    }
    printf("  生成的键值: %d\n", key);
    
    // 创建信号量集(包含3个信号量)
    semid = semget(key, 3, 0666 | IPC_CREAT);
    if (semid == -1) {
        perror("semget创建失败");
        exit(EXIT_FAILURE);
    }
    printf("  成功创建信号量集,标识符: %d\n", semid);
    
    // 示例3: 尝试创建已存在的信号量集
    printf("\n示例3: 尝试重复创建信号量集\n");
    int semid2 = semget(key, 3, 0666 | IPC_CREAT | IPC_EXCL);
    if (semid2 == -1) {
        if (errno == EEXIST) {
            printf("  信号量集已存在\n");
        } else {
            perror("  semget失败");
        }
    } else {
        printf("  意外创建了新的信号量集: %d\n", semid2);
    }
    
    // 示例4: 获取已存在的信号量集
    printf("\n示例4: 获取已存在的信号量集\n");
    int semid3 = semget(key, 0, 0666);
    if (semid3 == -1) {
        perror("获取信号量集失败");
    } else {
        printf("  成功获取信号量集,标识符: %d\n", semid3);
        // 验证是否为同一个信号量集
        if (semid3 == semid) {
            printf("  确认为同一个信号量集\n");
        }
    }
    
    // 示例5: 错误处理演示
    printf("\n示例5: 错误处理\n");
    
    // 尝试访问不存在的信号量集(不创建)
    int semid4 = semget(12345, 1, 0666);
    if (semid4 == -1) {
        if (errno == ENOENT) {
            printf("  信号量集不存在: %s\n", strerror(errno));
        } else {
            perror("  其他错误");
        }
    }
    
    // 清理:删除创建的信号量集
    printf("\n清理资源...\n");
    if (semctl(semid, 0, IPC_RMID) == -1) {
        perror("清理信号量集失败");
    } else {
        printf("  成功清理信号量集\n");
    }
    
    return 0;
}
发表在 linux文章 | 留下评论

semop系统调用及示例

semop – 信号量操作

函数介绍

semop系统调用用于对System V信号量集执行操作。它是信号量机制的核心操作函数,通过原子性地执行一系列信号量操作来实现进程同步。
](https://www.calcguide.tech/2025/08/18/semop%e7%b3%bb%e7%bb%9f%e8%b0%83%e7%94%a8%e5%8f%8a%e7%a4%ba%e4%be%8b/)

函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, unsigned nsops);

功能

对信号量集执行原子操作,用于进程间同步。

参数

  • int semid: 信号量集的标识符(由semget返回)
  • struct sembuf *sops: 指向sembuf结构体数组的指针struct sembuf { unsigned short sem_num; // 信号量在集合中的索引 short sem_op; // 操作类型 short sem_flg; // 操作标志 };
    • sem_op > 0: 释放资源(V操作),将sem_op加到信号量值上
    • sem_op < 0: 请求资源(P操作),从信号量值中减去sem_op的绝对值
    • sem_op = 0: 等待信号量值变为0
  • unsigned nsops: sops数组中操作的数量

返回值

  • 成功时返回0
  • 失败时返回-1,并设置errno:
    • EAGAIN: 操作会阻塞但设置了IPC_NOWAIT
    • EIDRM: 信号量集已被删除
    • EINTR: 系统调用被信号中断
    • EINVAL: 参数无效
    • EFBIG: sem_op绝对值过大

相似函数

  • semtimedop(): 带超时的信号量操作
  • semget(): 获取信号量集
  • semctl(): 控制信号量集

示例代码

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>

// 信号量操作联合体
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

// P操作(等待/减1)
int P(int semid, int sem_num) {
    struct sembuf sb;
    sb.sem_num = sem_num;
    sb.sem_op = -1;  // P操作
    sb.sem_flg = 0;
    return semop(semid, &sb, 1);
}

// V操作(发送/加1)
int V(int semid, int sem_num) {
    struct sembuf sb;
    sb.sem_num = sem_num;
    sb.sem_op = 1;   // V操作
    sb.sem_flg = 0;
    return semop(semid, &sb, 1);
}

// 等待信号量为0
int wait_zero(int semid, int sem_num) {
    struct sembuf sb;
    sb.sem_num = sem_num;
    sb.sem_op = 0;
    sb.sem_flg = 0;
    return semop(semid, &sb, 1);
}

int main() {
    key_t key;
    int semid;
    union semun arg;
    
    printf("=== Semop函数示例 ===\n");
    
    // 创建信号量集
    key = ftok(".", 'b');
    if (key == -1) {
        perror("ftok失败");
        exit(EXIT_FAILURE);
    }
    
    semid = semget(key, 2, 0666 | IPC_CREAT | IPC_EXCL);
    if (semid == -1) {
        if (errno == EEXIST) {
            // 如果已存在,先删除再创建
            semid = semget(key, 2, 0666);
            semctl(semid, 0, IPC_RMID);
            semid = semget(key, 2, 0666 | IPC_CREAT | IPC_EXCL);
        } else {
            perror("semget失败");
            exit(EXIT_FAILURE);
        }
    }
    
    if (semid == -1) {
        perror("创建信号量集失败");
        exit(EXIT_FAILURE);
    }
    printf("成功创建信号量集,标识符: %d\n", semid);
    
    // 初始化信号量
    // 信号量0:用于互斥,初始值为1(二进制信号量)
    // 信号量1:用于资源计数,初始值为3(表示有3个资源)
    unsigned short init_values[2] = {1, 3};
    arg.array = init_values;
    if (semctl(semid, 0, SETALL, arg) == -1) {
        perror("初始化信号量失败");
        semctl(semid, 0, IPC_RMID);
        exit(EXIT_FAILURE);
    }
    printf("信号量初始化完成\n");
    printf("  信号量0(互斥): %d\n", semctl(semid, 0, GETVAL));
    printf("  信号量1(资源): %d\n", semctl(semid, 1, GETVAL));
    
    // 示例1: 基本的P/V操作
    printf("\n示例1: 基本的P/V操作\n");
    printf("操作前 - 信号量0: %d, 信号量1: %d\n",
           semctl(semid, 0, GETVAL), semctl(semid, 1, GETVAL));
    
    // P操作获取互斥锁
    printf("执行P操作获取互斥锁...\n");
    if (P(semid, 0) == -1) {
        perror("P操作失败");
    } else {
        printf("成功获取互斥锁\n");
        printf("操作后 - 信号量0: %d, 信号量1: %d\n",
               semctl(semid, 0, GETVAL), semctl(semid, 1, GETVAL));
        
        // V操作释放互斥锁
        printf("执行V操作释放互斥锁...\n");
        if (V(semid, 0) == -1) {
            perror("V操作失败");
        } else {
            printf("成功释放互斥锁\n");
            printf("操作后 - 信号量0: %d, 信号量1: %d\n",
                   semctl(semid, 0, GETVAL), semctl(semid, 1, GETVAL));
        }
    }
    
    // 示例2: 多进程同步演示
    printf("\n示例2: 多进程同步演示\n");
    pid_t pid = fork();
    
    if (pid == -1) {
        perror("fork失败");
        semctl(semid, 0, IPC_RMID);
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // 子进程
        printf("子进程 %d 开始执行\n", getpid());
        
        // 模拟需要互斥访问的临界区
        printf("子进程尝试获取互斥锁...\n");
        if (P(semid, 0) == 0) {
            printf("子进程 %d 进入临界区\n", getpid());
            printf("子进程在临界区工作2秒...\n");
            sleep(2);
            printf("子进程 %d 离开临界区\n", getpid());
            V(semid, 0);  // 释放锁
        }
        
        exit(EXIT_SUCCESS);
    } else {
        // 父进程
        sleep(1);  // 让子进程先运行
        
        printf("父进程 %d 开始执行\n", getpid());
        printf("父进程尝试获取互斥锁...\n");
        
        if (P(semid, 0) == 0) {
            printf("父进程 %d 进入临界区\n", getpid());
            printf("父进程在临界区工作2秒...\n");
            sleep(2);
            printf("父进程 %d 离开临界区\n", getpid());
            V(semid, 0);  // 释放锁
        }
        
        // 等待子进程结束
        wait(NULL);
    }
    
    // 示例3: 资源计数信号量演示
    printf("\n示例3: 资源计数信号量演示\n");
    printf("当前可用资源数: %d\n", semctl(semid, 1, GETVAL));
    
    // 模拟多个进程竞争资源
    for (int i = 0; i < 4; i++) {
        pid = fork();
        if (pid == 0) {
            // 子进程
            printf("进程 %d 尝试获取资源...\n", getpid());
            
            // P操作获取资源
            if (P(semid, 1) == 0) {
                printf("进程 %d 成功获取资源,剩余资源: %d\n", 
                       getpid(), semctl(semid, 1, GETVAL));
                
                // 使用资源
                printf("进程 %d 使用资源2秒...\n", getpid());
                sleep(2);
                
                // V操作释放资源
                V(semid, 1);
                printf("进程 %d 释放资源,剩余资源: %d\n", 
                       getpid(), semctl(semid, 1, GETVAL));
            } else {
                printf("进程 %d 获取资源失败\n", getpid());
            }
            
            exit(EXIT_SUCCESS);
        }
    }
    
    // 等待所有子进程结束
    for (int i = 0; i < 4; i++) {
        wait(NULL);
    }
    
    // 清理资源
    printf("\n清理资源...\n");
    if (semctl(semid, 0, IPC_RMID) == -1) {
        perror("删除信号量集失败");
    } else {
        printf("成功删除信号量集\n");
    }
    
    return 0;
}
发表在 linux文章 | 留下评论

semtimedop系统调用及示例

semtimedop 函数详解

1. 函数介绍

semtimedop 是Linux系统调用,用于对System V信号量集执行原子操作,并支持超时控制。它是 semop 函数的增强版本,允许指定操作的超时时间,避免无限期阻塞。信号量是进程间同步的重要机制,常用于控制对共享资源的访问。

2. 函数原型

#include <sys/sem.h>
#include <sys/types.h>

int semtimedop(int semid, struct sembuf *sops, size_t nsops,
               const struct timespec *timeout);

3. 功能

semtimedop 对指定的信号量集执行一系列原子操作,如果操作不能立即完成且设置了阻塞标志,则在指定的超时时间内等待。该函数提供了精确的时间控制,使得程序可以在等待信号量时避免永久阻塞。

4. 参数

  • int semid: 信号量集标识符(由semget返回)
  • *struct sembuf sops: 信号量操作数组
  • size_t nsops: 操作数组中元素的个数
  • *const struct timespec timeout: 超时时间(NULL表示无限等待)

5. 返回值

  • 成功: 返回0
  • 超时: 返回-1,并设置errno为EAGAIN
  • 失败: 返回-1,并设置相应的errno

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

  • semop: 不带超时的信号量操作函数
  • semget: 获取信号量集
  • semctl: 信号量控制操作
  • semctl: 信号量控制函数

7. 示例代码

示例1:基础semtimedop使用

#include <sys/sem.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>

/**
 * 信号量操作结构
 */
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *__buf;
};

/**
 * 初始化信号量
 */
int init_semaphore(key_t key, int nsems, int init_val) {
    int semid;
    union semun arg;
    
    // 创建或获取信号量集
    semid = semget(key, nsems, IPC_CREAT | 0666);
    if (semid == -1) {
        perror("semget 失败");
        return -1;
    }
    
    // 初始化信号量值
    arg.val = init_val;
    if (semctl(semid, 0, SETVAL, arg) == -1) {
        perror("semctl 初始化失败");
        return -1;
    }
    
    return semid;
}

/**
 * 演示基础semtimedop使用方法
 */
int demo_semtimedop_basic() {
    int semid;
    struct sembuf sop;
    struct timespec timeout;
    key_t key = ftok(".", 's');
    
    printf("=== 基础semtimedop使用示例 ===\n");
    
    // 初始化信号量(初始值为1,表示资源可用)
    semid = init_semaphore(key, 1, 1);
    if (semid == -1) {
        return -1;
    }
    
    printf("创建信号量集,ID: %d\n", semid);
    
    // 设置超时时间(5秒)
    timeout.tv_sec = 5;
    timeout.tv_nsec = 0;
    
    // 定义P操作(获取信号量)
    sop.sem_num = 0;      // 操作第0个信号量
    sop.sem_op = -1;      // P操作:减1
    sop.sem_flg = 0;      // 阻塞操作
    
    printf("执行P操作(获取信号量)...\n");
    if (semtimedop(semid, &sop, 1, &timeout) == -1) {
        if (errno == EAGAIN) {
            printf("操作超时\n");
        } else {
            perror("semtimedop P操作失败");
        }
        goto cleanup;
    }
    
    printf("成功获取信号量\n");
    
    // 模拟使用资源
    printf("使用资源中...(3秒)\n");
    sleep(3);
    
    // 定义V操作(释放信号量)
    sop.sem_op = 1;       // V操作:加1
    
    printf("执行V操作(释放信号量)...\n");
    if (semtimedop(semid, &sop, 1, &timeout) == -1) {
        perror("semtimedop V操作失败");
        goto cleanup;
    }
    
    printf("成功释放信号量\n");
    
cleanup:
    // 清理信号量集
    if (semctl(semid, 0, IPC_RMID) == -1) {
        perror("清理信号量集失败");
    }
    
    return 0;
}

int main() {
    return demo_semtimedop_basic();
}

示例2:多进程同步演示

#include <sys/sem.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

/**
 * 创建二进制信号量(互斥锁)
 */
int create_binary_semaphore(key_t key) {
    int semid;
    union semun arg;
    
    semid = semget(key, 1, IPC_CREAT | 0666);
    if (semid == -1) {
        perror("创建信号量失败");
        return -1;
    }
    
    // 初始化为1(可用状态)
    arg.val = 1;
    if (semctl(semid, 0, SETVAL, arg) == -1) {
        perror("初始化信号量失败");
        semctl(semid, 0, IPC_RMID);
        return -1;
    }
    
    return semid;
}

/**
 * 获取互斥锁
 */
int acquire_mutex(int semid, int timeout_sec) {
    struct sembuf sop;
    struct timespec timeout;
    
    sop.sem_num = 0;
    sop.sem_op = -1;
    sop.sem_flg = 0;
    
    timeout.tv_sec = timeout_sec;
    timeout.tv_nsec = 0;
    
    return semtimedop(semid, &sop, 1, &timeout);
}

/**
 * 释放互斥锁
 */
int release_mutex(int semid) {
    struct sembuf sop;
    
    sop.sem_num = 0;
    sop.sem_op = 1;
    sop.sem_flg = 0;
    
    return semop(semid, &sop, 1);
}

/**
 * 演示多进程同步
 */
int demo_multiprocess_synchronization() {
    int semid;
    key_t key = ftok(".", 'm');
    int shared_resource = 0;
    
    printf("=== 多进程同步演示 ===\n");
    
    // 创建互斥信号量
    semid = create_binary_semaphore(key);
    if (semid == -1) {
        return -1;
    }
    
    printf("创建互斥信号量,ID: %d\n", semid);
    
    // 创建多个子进程
    for (int i = 0; i < 3; i++) {
        if (fork() == 0) {
            // 子进程代码
            pid_t pid = getpid();
            printf("子进程 %d 启动\n", pid);
            
            // 尝试获取互斥锁(超时3秒)
            if (acquire_mutex(semid, 3) == -1) {
                if (errno == EAGAIN) {
                    printf("子进程 %d: 获取锁超时\n", pid);
                } else {
                    printf("子进程 %d: 获取锁失败: %s\n", pid, strerror(errno));
                }
                exit(1);
            }
            
            printf("子进程 %d: 成功获取锁\n", pid);
            
            // 模拟临界区操作
            printf("子进程 %d: 访问共享资源\n", pid);
            sleep(2);  // 模拟处理时间
            
            // 释放互斥锁
            if (release_mutex(semid) == -1) {
                printf("子进程 %d: 释放锁失败: %s\n", pid, strerror(errno));
            } else {
                printf("子进程 %d: 成功释放锁\n", pid);
            }
            
            exit(0);
        }
    }
    
    // 父进程等待所有子进程完成
    for (int i = 0; i < 3; i++) {
        int status;
        wait(&status);
        if (WIFEXITED(status)) {
            printf("子进程 %d 退出,状态码: %d\n", 
                   i + 1, WEXITSTATUS(status));
        }
    }
    
    // 清理信号量
    semctl(semid, 0, IPC_RMID);
    
    return 0;
}

int main() {
    return demo_multiprocess_synchronization();
}

示例3:生产者-消费者模型

#include <sys/sem.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>

#define BUFFER_SIZE 5
#define MAX_ITEMS 10

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

// 信号量索引定义
#define EMPTY_SEM 0    // 空槽位计数
#define FULL_SEM 1     // 满槽位计数
#define MUTEX_SEM 2    // 缓冲区互斥锁

/**
 * 初始化生产者-消费者信号量
 */
int init_pc_semaphores(key_t key) {
    int semid;
    union semun arg;
    unsigned short values[3] = {BUFFER_SIZE, 0, 1};  // empty, full, mutex
    
    // 创建包含3个信号量的信号量集
    semid = semget(key, 3, IPC_CREAT | 0666);
    if (semid == -1) {
        perror("创建信号量集失败");
        return -1;
    }
    
    // 初始化信号量值
    arg.array = values;
    if (semctl(semid, 0, SETALL, arg) == -1) {
        perror("初始化信号量失败");
        semctl(semid, 0, IPC_RMID);
        return -1;
    }
    
    return semid;
}

/**
 * 生产者操作
 */
int producer_operation(int semid, int item, int timeout_sec) {
    struct sembuf sops[2];
    struct timespec timeout;
    
    // 设置超时时间
    timeout.tv_sec = timeout_sec;
    timeout.tv_nsec = 0;
    
    // 1. 等待空槽位 (P(empty))
    sops[0].sem_num = EMPTY_SEM;
    sops[0].sem_op = -1;
    sops[0].sem_flg = 0;
    
    // 2. 获取缓冲区互斥锁 (P(mutex))
    sops[1].sem_num = MUTEX_SEM;
    sops[1].sem_op = -1;
    sops[1].sem_flg = 0;
    
    printf("生产者: 尝试生产项目 %d\n", item);
    
    // 执行P操作
    if (semtimedop(semid, sops, 2, &timeout) == -1) {
        if (errno == EAGAIN) {
            printf("生产者: 等待超时,无法生产项目 %d\n", item);
        } else {
            printf("生产者: 操作失败: %s\n", strerror(errno));
        }
        return -1;
    }
    
    // 模拟生产过程
    printf("生产者: 正在生产项目 %d\n", item);
    sleep(1);
    printf("生产者: 生产完成项目 %d\n", item);
    
    // 释放互斥锁和增加满槽位计数
    sops[0].sem_num = MUTEX_SEM;
    sops[0].sem_op = 1;   // V(mutex)
    sops[1].sem_num = FULL_SEM;
    sops[1].sem_op = 1;   // V(full)
    
    if (semop(semid, sops, 2) == -1) {
        perror("生产者: 释放信号量失败");
        return -1;
    }
    
    return 0;
}

/**
 * 消费者操作
 */
int consumer_operation(int semid, int *item, int timeout_sec) {
    struct sembuf sops[2];
    struct timespec timeout;
    
    // 设置超时时间
    timeout.tv_sec = timeout_sec;
    timeout.tv_nsec = 0;
    
    // 1. 等待满槽位 (P(full))
    sops[0].sem_num = FULL_SEM;
    sops[0].sem_op = -1;
    sops[0].sem_flg = 0;
    
    // 2. 获取缓冲区互斥锁 (P(mutex))
    sops[1].sem_num = MUTEX_SEM;
    sops[1].sem_op = -1;
    sops[1].sem_flg = 0;
    
    printf("消费者: 尝试消费项目\n");
    
    // 执行P操作
    if (semtimedop(semid, sops, 2, &timeout) == -1) {
        if (errno == EAGAIN) {
            printf("消费者: 等待超时,无项目可消费\n");
        } else {
            printf("消费者: 操作失败: %s\n", strerror(errno));
        }
        return -1;
    }
    
    // 模拟消费过程
    *item = rand() % 100;  // 模拟消费的项目
    printf("消费者: 正在消费项目 %d\n", *item);
    sleep(1);
    printf("消费者: 消费完成项目 %d\n", *item);
    
    // 释放互斥锁和增加空槽位计数
    sops[0].sem_num = MUTEX_SEM;
    sops[0].sem_op = 1;   // V(mutex)
    sops[1].sem_num = EMPTY_SEM;
    sops[1].sem_op = 1;   // V(empty)
    
    if (semop(semid, sops, 2) == -1) {
        perror("消费者: 释放信号量失败");
        return -1;
    }
    
    return 0;
}

/**
 * 演示生产者-消费者模型
 */
int demo_producer_consumer() {
    int semid;
    key_t key = ftok(".", 'p');
    
    printf("=== 生产者-消费者模型演示 ===\n");
    printf("缓冲区大小: %d\n", BUFFER_SIZE);
    printf("生产/消费项目数: %d\n", MAX_ITEMS);
    
    // 初始化信号量
    semid = init_pc_semaphores(key);
    if (semid == -1) {
        return -1;
    }
    
    printf("创建生产者-消费者信号量集,ID: %d\n", semid);
    
    // 创建生产者进程
    if (fork() == 0) {
        // 生产者进程
        srand(getpid());
        printf("生产者进程启动 (PID: %d)\n", getpid());
        
        for (int i = 1; i <= MAX_ITEMS; i++) {
            if (producer_operation(semid, i, 5) == -1) {
                printf("生产者: 生产项目 %d 失败\n", i);
                break;
            }
            sleep(1);  // 生产间隔
        }
        
        printf("生产者进程结束\n");
        exit(0);
    }
    
    // 创建消费者进程
    if (fork() == 0) {
        // 消费者进程
        srand(getpid() + 1000);
        printf("消费者进程启动 (PID: %d)\n", getpid());
        
        for (int i = 1; i <= MAX_ITEMS; i++) {
            int item;
            if (consumer_operation(semid, &item, 5) == -1) {
                printf("消费者: 消费项目失败\n");
                break;
            }
            sleep(2);  // 消费间隔
        }
        
        printf("消费者进程结束\n");
        exit(0);
    }
    
    // 父进程等待子进程完成
    int status;
    wait(&status);
    wait(&status);
    
    // 显示最终信号量状态
    union semun arg;
    unsigned short values[3];
    arg.array = values;
    
    if (semctl(semid, 0, GETALL, arg) != -1) {
        printf("\n最终信号量状态:\n");
        printf("  空槽位: %d\n", values[EMPTY_SEM]);
        printf("  满槽位: %d\n", values[FULL_SEM]);
        printf("  互斥锁: %d\n", values[MUTEX_SEM]);
    }
    
    // 清理信号量集
    semctl(semid, 0, IPC_RMID);
    
    return 0;
}

int main() {
    return demo_producer_consumer();
}

示例4:超时控制演示

#include <sys/sem.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

/**
 * 创建计数信号量
 */
int create_counting_semaphore(key_t key, int initial_value) {
    int semid;
    union semun arg;
    
    semid = semget(key, 1, IPC_CREAT | 0666);
    if (semid == -1) {
        perror("创建计数信号量失败");
        return -1;
    }
    
    arg.val = initial_value;
    if (semctl(semid, 0, SETVAL, arg) == -1) {
        perror("初始化计数信号量失败");
        semctl(semid, 0, IPC_RMID);
        return -1;
    }
    
    return semid;
}

/**
 * 演示不同超时设置
 */
int demo_timeout_settings() {
    int semid;
    struct sembuf sop;
    struct timespec timeout;
    key_t key = ftok(".", 't');
    
    printf("=== 超时控制演示 ===\n");
    
    // 创建计数信号量,初始值为0(无资源可用)
    semid = create_counting_semaphore(key, 0);
    if (semid == -1) {
        return -1;
    }
    
    printf("创建计数信号量(初始值: 0),ID: %d\n", semid);
    
    // 定义P操作
    sop.sem_num = 0;
    sop.sem_op = -1;  // 请求资源
    sop.sem_flg = 0;  // 阻塞操作
    
    printf("\n1. 测试短超时(1秒):\n");
    timeout.tv_sec = 1;
    timeout.tv_nsec = 0;
    
    clock_t start = clock();
    if (semtimedop(semid, &sop, 1, &timeout) == -1) {
        if (errno == EAGAIN) {
            clock_t end = clock();
            double elapsed = ((double)(end - start)) / CLOCKS_PER_SEC;
            printf("  操作超时,等待时间: %.2f 秒\n", elapsed);
        } else {
            printf("  操作失败: %s\n", strerror(errno));
        }
    }
    
    printf("\n2. 测试中等超时(3秒):\n");
    timeout.tv_sec = 3;
    timeout.tv_nsec = 0;
    
    start = clock();
    if (semtimedop(semid, &sop, 1, &timeout) == -1) {
        if (errno == EAGAIN) {
            clock_t end = clock();
            double elapsed = ((double)(end - start)) / CLOCKS_PER_SEC;
            printf("  操作超时,等待时间: %.2f 秒\n", elapsed);
        } else {
            printf("  操作失败: %s\n", strerror(errno));
        }
    }
    
    printf("\n3. 测试精确超时(1.5秒):\n");
    timeout.tv_sec = 1;
    timeout.tv_nsec = 500000000;  // 500毫秒
    
    start = clock();
    if (semtimedop(semid, &sop, 1, &timeout) == -1) {
        if (errno == EAGAIN) {
            clock_t end = clock();
            double elapsed = ((double)(end - start)) / CLOCKS_PER_SEC;
            printf("  操作超时,等待时间: %.2f 秒\n", elapsed);
        } else {
            printf("  操作失败: %s\n", strerror(errno));
        }
    }
    
    printf("\n4. 测试零超时(立即返回):\n");
    timeout.tv_sec = 0;
    timeout.tv_nsec = 0;
    
    if (semtimedop(semid, &sop, 1, &timeout) == -1) {
        if (errno == EAGAIN) {
            printf("  立即返回,操作无法完成\n");
        } else {
            printf("  操作失败: %s\n", strerror(errno));
        }
    }
    
    printf("\n5. 测试NULL超时(无限等待):\n");
    printf("  注意:这会导致无限期等待,需要外部干预\n");
    printf("  演示跳过此测试以避免阻塞\n");
    
    // 启动一个进程来释放信号量
    if (fork() == 0) {
        sleep(2);  // 等待主进程开始等待
        
        // 释放信号量
        sop.sem_op = 1;  // V操作
        if (semop(semid, &sop, 1) == -1) {
            perror("释放信号量失败");
        } else {
            printf("  后台进程:信号量已释放\n");
        }
        exit(0);
    }
    
    // 等待一小段时间后测试
    sleep(1);
    timeout.tv_sec = 5;
    timeout.tv_nsec = 0;
    
    printf("  等待信号量释放(5秒超时)...\n");
    if (semtimedop(semid, &sop, 1, &timeout) == 0) {
        printf("  成功获取信号量\n");
    } else {
        printf("  获取信号量失败: %s\n", strerror(errno));
    }
    
    // 等待后台进程完成
    int status;
    wait(&status);
    
    // 清理
    semctl(semid, 0, IPC_RMID);
    
    return 0;
}

int main() {
    return demo_timeout_settings();
}

示例5:复杂信号量操作

#include <sys/sem.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

/**
 * 创建多个信号量的信号量集
 */
int create_multi_semaphore(key_t key, int nsems) {
    int semid;
    
    semid = semget(key, nsems, IPC_CREAT | 0666);
    if (semid == -1) {
        perror("创建多信号量集失败");
        return -1;
    }
    
    // 初始化所有信号量为1
    unsigned short *values = malloc(nsems * sizeof(unsigned short));
    if (!values) {
        perror("分配内存失败");
        semctl(semid, 0, IPC_RMID);
        return -1;
    }
    
    for (int i = 0; i < nsems; i++) {
        values[i] = 1;
    }
    
    union semun arg;
    arg.array = values;
    if (semctl(semid, 0, SETALL, arg) == -1) {
        perror("初始化多信号量失败");
        free(values);
        semctl(semid, 0, IPC_RMID);
        return -1;
    }
    
    free(values);
    return semid;
}

/**
 * 演示复杂的多信号量操作
 */
int demo_complex_operations() {
    int semid;
    struct sembuf sops[5];
    struct timespec timeout;
    key_t key = ftok(".", 'c');
    int num_semaphores = 5;
    
    printf("=== 复杂信号量操作演示 ===\n");
    printf("创建包含 %d 个信号量的信号量集\n", num_semaphores);
    
    // 创建信号量集
    semid = create_multi_semaphore(key, num_semaphores);
    if (semid == -1) {
        return -1;
    }
    
    printf("信号量集创建成功,ID: %d\n", semid);
    
    // 设置超时时间
    timeout.tv_sec = 10;
    timeout.tv_nsec = 0;
    
    // 演示原子性操作:同时操作多个信号量
    printf("\n1. 原子性多信号量操作:\n");
    printf("   同时获取信号量 0, 1, 2\n");
    
    // 同时获取3个信号量(P操作)
    for (int i = 0; i < 3; i++) {
        sops[i].sem_num = i;
        sops[i].sem_op = -1;
        sops[i].sem_flg = 0;
    }
    
    if (semtimedop(semid, sops, 3, &timeout) == 0) {
        printf("   成功原子性获取3个信号量\n");
        
        // 模拟使用资源
        printf("   使用获取的资源...\n");
        sleep(2);
        
        // 同时释放3个信号量(V操作)
        for (int i = 0; i < 3; i++) {
            sops[i].sem_op = 1;
        }
        
        if (semop(semid, sops, 3) == 0) {
            printf("   成功原子性释放3个信号量\n");
        } else {
            perror("   释放信号量失败");
        }
    } else {
        printf("   获取信号量失败: %s\n", strerror(errno));
    }
    
    // 演示混合操作:获取和释放不同信号量
    printf("\n2. 混合信号量操作:\n");
    printf("   获取信号量0,释放信号量1\n");
    
    sops[0].sem_num = 0;
    sops[0].sem_op = -1;  // 获取
    sops[0].sem_flg = 0;
    
    sops[1].sem_num = 1;
    sops[1].sem_op = 1;   // 释放
    sops[1].sem_flg = 0;
    
    if (semtimedop(semid, sops, 2, &timeout) == 0) {
        printf("   混合操作成功\n");
    } else {
        printf("   混合操作失败: %s\n", strerror(errno));
    }
    
    // 演示条件操作:使用SEM_UNDO标志
    printf("\n3. 带自动回滚的操作:\n");
    printf("   使用SEM_UNDO标志,进程退出时自动释放\n");
    
    sops[0].sem_num = 2;
    sops[0].sem_op = -1;
    sops[0].sem_flg = SEM_UNDO;  // 自动回滚
    
    if (semtimedop(semid, sops, 1, &timeout) == 0) {
        printf("   获取信号量2(带自动回滚)\n");
        printf("   程序退出时会自动释放该信号量\n");
        
        // 模拟程序继续运行
        printf("   程序继续运行中...\n");
        sleep(1);
        
        // 不显式释放,依赖SEM_UNDO
    } else {
        printf("   获取信号量失败: %s\n", strerror(errno));
    }
    
    // 演示NOWAIT操作
    printf("\n4. 非阻塞操作:\n");
    printf("   使用IPC_NOWAIT标志\n");
    
    sops[0].sem_num = 3;
    sops[0].sem_op = -1;
    sops[0].sem_flg = IPC_NOWAIT;  // 非阻塞
    
    if (semtimedop(semid, sops, 1, NULL) == 0) {
        printf("   非阻塞获取信号量3成功\n");
        // 立即释放
        sops[0].sem_op = 1;
        sops[0].sem_flg = 0;
        semop(semid, sops, 1);
    } else {
        if (errno == EAGAIN) {
            printf("   信号量3不可用,立即返回\n");
        } else {
            printf("   操作失败: %s\n", strerror(errno));
        }
    }
    
    // 显示当前信号量状态
    printf("\n5. 当前信号量状态:\n");
    unsigned short *values = malloc(num_semaphores * sizeof(unsigned short));
    if (values) {
        union semun arg;
        arg.array = values;
        
        if (semctl(semid, 0, GETALL, arg) != -1) {
            for (int i = 0; i < num_semaphores; i++) {
                printf("   信号量 %d: %d\n", i, values[i]);
            }
        }
        free(values);
    }
    
    // 清理
    semctl(semid, 0, IPC_RMID);
    
    return 0;
}

int main() {
    return demo_complex_operations();
}

semtimedop 使用注意事项

系统要求:

  1. 内核版本: 需要Linux 2.6.12或更高版本
  2. 架构支持: 支持所有主流架构
  3. 编译环境: 需要正确的头文件包含

参数验证:

  1. 信号量ID有效性: 确保semid有效且未被删除
  2. 操作数组合法性: 确保sops指针有效且nsops合理
  3. 超时参数: timeout可以为NULL(无限等待)或有效时间结构

错误处理:

  1. EAGAIN: 操作超时
  2. EINTR: 被信号中断
  3. EINVAL: 参数无效
  4. EACCES: 权限不足
  5. EIDRM: 信号量集已被删除
  6. ENOMEM: 内存不足

性能考虑:

1. 超时设置: 合理设置超时时间避免过度等待
2. 原子性: 多信号量操作保证原子性
3. 资源清理: 及时清理不再使用的信号量

最佳实践:

1. 超时控制: 始终考虑设置合理的超时时间
2. 错误恢复: 妥善处理超时和错误情况
3. 资源管理: 及时清理信号量资源
4. 并发安全: 确保多进程/线程环境下的正确性

信号量操作结构详解

struct sembuf 结构:

struct sembuf {
    unsigned short sem_num;  // 信号量编号
    short sem_op;           // 操作类型
    short sem_flg;          // 操作标志
};

操作类型 (sem_op):

  • 正值: V操作(释放资源),将值加到信号量上
  • 负值: P操作(获取资源),从信号量中减去该值
  • 零值: 等待信号量变为0

操作标志 (sem_flg):

  • SEM_UNDO: 操作完成后自动撤销(进程退出时)
  • IPC_NOWAIT: 非阻塞操作,如果不能立即完成则返回错误
  • 0: 默认阻塞操作

常见使用场景

1. 互斥锁:

// 获取锁
sop.sem_op = -1;
semtimedop(semid, &sop, 1, &timeout);

// 释放锁
sop.sem_op = 1;
semop(semid, &sop, 1);

2. 资源计数:

// 请求资源
sop.sem_op = -1;
semtimedop(semid, &sop, 1, &timeout);

// 释放资源
sop.sem_op = 1;
semop(semid, &sop, 1);

3. 条件同步:

// 等待条件满足(信号量为0)
sop.sem_op = 0;
semtimedop(semid, &sop, 1, &timeout);

总结

semtimedop 是一个功能强大的信号量操作函数,提供了:

1. 超时控制: 避免无限期等待,提高程序健壮性
2. 原子操作: 保证多信号量操作的原子性
3. 灵活配置: 支持多种操作标志和模式
4. 广泛应用: 适用于各种进程间同步场景

通过合理使用 semtimedop,可以构建更加可靠和高效的并发程序,特别是在需要精确时间控制的场景中表现出色。在实际应用中,需要注意超时设置、错误处理和资源管理等关键问题。

发表在 未分类 | 留下评论

seccomp系统调用及示例

1. 函数介绍

seccomp 是Linux系统调用过滤机制,用于限制进程可以执行的系统调用。它通过Berkeley Packet Filter (BPF) 程序来定义哪些系统调用是允许的,哪些是禁止的。seccomp 是构建沙箱环境、提高应用程序安全性的重要工具,可以有效防止恶意代码执行危险的系统调用。

2. 函数原型

#include <linux/seccomp.h>
#include <linux/filter.h>
#include <sys/prctl.h>
#include <unistd.h>

int prctl(int option, unsigned long arg2, unsigned long arg3, 
          unsigned long arg4, unsigned long arg5);

int seccomp(unsigned int operation, unsigned int flags, void *args);

3. 功能

seccomp 提供了系统调用级别的安全控制,可以:

  • 限制进程可执行的系统调用集合
  • 定义系统调用的执行策略(允许、错误、终止)
  • 使用BPF程序实现复杂的过滤逻辑
  • 构建安全的沙箱环境

4. 参数

prctl方式:

  • int option: 控制选项(如PR_SET_SECCOMP)
  • unsigned long arg2: seccomp模式(SECCOMP_MODE_STRICT/SECCOMP_MODE_FILTER)
  • 其他参数: 根据选项而定

seccomp系统调用:

  • unsigned int operation: 操作类型(SECCOMP_SET_MODE_STRICT/SECCOMP_SET_MODE_FILTER)
  • unsigned int flags: 标志位(通常为0)
  • *void args: 操作参数(BPF程序指针等)

5. 返回值

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

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

  • prctl: 进程控制接口
  • personality: 设置进程执行特性
  • chroot: 改变根目录
  • capset: 设置进程权限

7. 示例代码

示例1:基础seccomp使用

#define _GNU_SOURCE
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <sys/prctl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/syscall.h>

/**
 * 演示基础seccomp使用方法
 */
int demo_seccomp_basic() {
    printf("=== 基础seccomp使用示例 ===\n");
    
    // 显示当前seccomp状态
    int current_mode = prctl(PR_GET_SECCOMP, 0, 0, 0, 0);
    printf("当前seccomp模式: ");
    switch (current_mode) {
        case 0:
            printf("SECCOMP_MODE_DISABLED (禁用)\n");
            break;
        case 1:
            printf("SECCOMP_MODE_STRICT (严格模式)\n");
            break;
        case 2:
            printf("SECCOMP_MODE_FILTER (过滤模式)\n");
            break;
        default:
            printf("未知模式 (%d)\n", current_mode);
            break;
    }
    
    // 测试普通系统调用(应该成功)
    printf("测试普通系统调用...\n");
    write(STDOUT_FILENO, "  普通write调用成功\n", 21);
    
    // 启用严格模式seccomp
    printf("启用seccomp严格模式...\n");
    if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT, 0, 0, 0) == -1) {
        printf("启用seccomp失败: %s\n", strerror(errno));
        printf("注意:严格模式只允许read/write/exit/exit_group系统调用\n");
        return -1;
    }
    
    printf("seccomp严格模式启用成功\n");
    printf("当前seccomp模式: %d\n", prctl(PR_GET_SECCOMP, 0, 0, 0, 0));
    
    // 测试允许的系统调用
    printf("测试允许的系统调用...\n");
    write(STDOUT_FILENO, "  write调用仍然允许\n", 20);
    
    // 测试不允许的系统调用(这会导致程序终止)
    printf("测试不允许的系统调用(程序将终止)...\n");
    printf("  尝试调用getpid()...\n");
    
    // 注意:下面的调用会导致程序被SIGKILL终止
    // 为了演示目的,我们注释掉危险操作
    /*
    pid_t pid = getpid();  // 这会导致程序终止!
    printf("getpid()返回: %d\n", pid);
    */
    
    printf("  注意:getpid()等系统调用在严格模式下会被禁止\n");
    printf("  实际执行会导致程序被SIGKILL终止\n");
    
    return 0;
}

int main() {
    return demo_seccomp_basic();
}

示例2:自定义BPF过滤器

#define _GNU_SOURCE
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/audit.h>
#include <sys/prctl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/syscall.h>

/**
 * 创建允许特定系统调用的BPF过滤器
 */
int demo_custom_bpf_filter() {
    printf("=== 自定义BPF过滤器示例 ===\n");
    
    // 定义BPF过滤器程序
    // 允许的系统调用:read, write, exit, exit_group
    struct sock_filter filter[] = {
        // 加载系统调用号到累加器
        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
        
        // 允许 read 系统调用 (SYS_read = 0)
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_read, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        
        // 允许 write 系统调用 (SYS_write = 1)
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_write, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        
        // 允许 exit 系统调用 (SYS_exit = 60 on x86_64)
#ifdef __x86_64__
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 60, 0, 1),
#elif defined(__i386__)
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, 1),
#endif
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        
        // 允许 exit_group 系统调用
#ifdef __x86_64__
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 231, 0, 1),
#elif defined(__i386__)
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 252, 0, 1),
#endif
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        
        // 其他系统调用返回EPERM错误
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (EPERM & 0xFFFF)),
    };
    
    struct sock_fprog prog = {
        .len = sizeof(filter) / sizeof(filter[0]),
        .filter = filter,
    };
    
    // 显示过滤器信息
    printf("创建BPF过滤器,允许系统调用:\n");
    printf("  read(%d), write(%d), exit(%d), exit_group(%d)\n", 
#ifdef __x86_64__
           SYS_read, SYS_write, 60, 231
#elif defined(__i386__)
           SYS_read, SYS_write, 1, 252
#endif
    );
    printf("其他系统调用将返回EPERM错误\n");
    
    // 应用BPF过滤器
    if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog, 0, 0) == -1) {
        printf("应用BPF过滤器失败: %s\n", strerror(errno));
        printf("可能的原因:\n");
        printf("  1. 内核不支持seccomp BPF\n");
        printf("  2. 缺少CAP_SYS_ADMIN权限\n");
        printf("  3. 已经设置了seccomp策略\n");
        return -1;
    }
    
    printf("BPF过滤器应用成功\n");
    
    // 测试允许的系统调用
    printf("\n测试允许的系统调用:\n");
    write(STDOUT_FILENO, "  write调用成功\n", 16);
    
    char buffer[10];
    ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer));
    if (bytes_read >= 0) {
        printf("  read调用成功\n");
    }
    
    // 测试不允许的系统调用
    printf("\n测试不允许的系统调用:\n");
    long result = syscall(SYS_getpid);
    if (result == -1) {
        printf("  getpid调用被阻止: %s\n", strerror(errno));
    } else {
        printf("  getpid调用意外成功: %ld\n", result);
    }
    
    result = syscall(SYS_open, "/etc/passwd", 0);
    if (result == -1) {
        printf("  open调用被阻止: %s\n", strerror(errno));
    } else {
        printf("  open调用意外成功: %ld\n", result);
    }
    
    printf("\n安全的系统调用仍然可以正常工作\n");
    
    return 0;
}

int main() {
    return demo_custom_bpf_filter();
}

示例3:只读沙箱环境

#define _GNU_SOURCE
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/audit.h>
#include <sys/prctl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <sys/stat.h>

/**
 * 创建只读沙箱环境的BPF过滤器
 */
int demo_readonly_sandbox() {
    printf("=== 只读沙箱环境示例 ===\n");
    
    // 定义只读沙箱的BPF过滤器
    // 允许读操作和基本系统调用,禁止写操作
    struct sock_filter filter[] = {
        // 加载系统调用号
        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
        
        // 允许 read 系统调用
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_read, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        
        // 允许 write 系统调用(仅允许写到stdout/stderr)
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_write, 0, 5),
        // 检查文件描述符是否为stdout(1)或stderr(2)
        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, 1),  // stdout
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 2, 0, 1),  // stderr
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (EPERM & 0xFFFF)),
        
        // 允许 exit 和 exit_group
#ifdef __x86_64__
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 60, 0, 1),   // exit
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 231, 0, 1),  // exit_group
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
#elif defined(__i386__)
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, 1),    // exit
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 252, 0, 1),  // exit_group
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
#endif
        
        // 允许 read-only 文件操作
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_open, 0, 3),
        // 检查打开标志是否包含O_RDONLY
        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[1])),
        BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, O_RDONLY, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (EPERM & 0xFFFF)),
        
        // 允许 close 系统调用
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_close, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        
        // 禁止其他所有系统调用
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (EPERM & 0xFFFF)),
    };
    
    struct sock_fprog prog = {
        .len = sizeof(filter) / sizeof(filter[0]),
        .filter = filter,
    };
    
    printf("创建只读沙箱环境\n");
    printf("允许的操作:\n");
    printf("  - 读取文件(只读模式)\n");
    printf("  - 写入标准输出和标准错误\n");
    printf("  - 基本的进程控制\n");
    printf("禁止的操作:\n");
    printf("  - 写入文件\n");
    printf("  - 网络操作\n");
    printf("  - 进程创建\n");
    printf("  - 其他危险操作\n");
    
    // 应用过滤器
    if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog, 0, 0) == -1) {
        printf("创建沙箱失败: %s\n", strerror(errno));
        return -1;
    }
    
    printf("只读沙箱创建成功\n");
    
    // 测试沙箱功能
    printf("\n=== 沙箱功能测试 ===\n");
    
    // 测试允许的读操作
    printf("1. 测试允许的读操作:\n");
    int fd = open("/etc/passwd", O_RDONLY);
    if (fd != -1) {
        char buffer[100];
        ssize_t bytes = read(fd, buffer, sizeof(buffer));
        if (bytes > 0) {
            printf("  读取/etc/passwd成功 (%zd 字节)\n", bytes);
        }
        close(fd);
    } else {
        printf("  打开/etc/passwd失败: %s\n", strerror(errno));
    }
    
    // 测试允许的写操作(stdout/stderr)
    printf("\n2. 测试允许的写操作:\n");
    write(STDOUT_FILENO, "  写入stdout成功\n", 17);
    write(STDERR_FILENO, "  写入stderr成功\n", 17);
    
    // 测试禁止的写操作
    printf("\n3. 测试禁止的写操作:\n");
    fd = open("/tmp/test_seccomp", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        printf("  创建文件被阻止: %s\n", strerror(errno));
    } else {
        printf("  创建文件意外成功\n");
        close(fd);
        unlink("/tmp/test_seccomp");
    }
    
    // 测试禁止的系统调用
    printf("\n4. 测试禁止的系统调用:\n");
    long result = syscall(SYS_fork);
    if (result == -1) {
        printf("  fork被阻止: %s\n", strerror(errno));
    }
    
    result = syscall(SYS_socket, AF_INET, SOCK_STREAM, 0);
    if (result == -1) {
        printf("  socket被阻止: %s\n", strerror(errno));
    }
    
    printf("\n沙箱环境测试完成\n");
    
    return 0;
}

int main() {
    return demo_readonly_sandbox();
}

示例4:进程监控和日志

#define _GNU_SOURCE
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/audit.h>
#include <sys/prctl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/syscall.h>
#include <signal.h>
#include <sys/wait.h>

/**
 * 信号处理函数
 */
void signal_handler(int sig) {
    printf("捕获信号 %d\n", sig);
    if (sig == SIGSYS) {
        printf("检测到被禁止的系统调用\n");
    }
}

/**
 * 演示seccomp的监控和日志功能
 */
int demo_seccomp_monitoring() {
    printf("=== seccomp监控和日志示例 ===\n");
    
    // 注册信号处理程序来捕获SIGSYS
    signal(SIGSYS, signal_handler);
    
    // 创建带日志的BPF过滤器
    struct sock_filter filter[] = {
        // 加载系统调用号
        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
        
        // 允许基本的读写操作
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_read, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_write, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        
        // 允许exit相关调用
#ifdef __x86_64__
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 60, 0, 1),   // exit
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 231, 0, 1),  // exit_group
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
#endif
        
        // 对于其他系统调用,返回追踪标志(用于日志)
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRACE | (1 & 0xFFFF)),
    };
    
    struct sock_fprog prog = {
        .len = sizeof(filter) / sizeof(filter[0]),
        .filter = filter,
    };
    
    printf("创建带监控的日志过滤器\n");
    printf("SECCOMP_RET_TRACE可以用于:\n");
    printf("  - 系统调用追踪\n");
    printf("  - 安全审计\n");
    printf("  - 调试和分析\n");
    
    // 启用seccomp
    if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) {
        printf("启用seccomp失败: %s\n", strerror(errno));
        return -1;
    }
    
    printf("seccomp监控启用成功\n");
    
    // 测试监控功能
    printf("\n测试监控功能:\n");
    
    // 允许的系统调用
    write(STDOUT_FILENO, "允许的write调用\n", 17);
    
    // 被监控的系统调用
    printf("测试被监控的系统调用:\n");
    
    pid_t pid = getpid();
    printf("getpid()返回: %d\n", (int)pid);
    
    uid_t uid = getuid();
    printf("getuid()返回: %d\n", (int)uid);
    
    printf("注意:在实际应用中,SECCOMP_RET_TRACE会触发ptrace事件\n");
    printf("这需要额外的监控进程来处理追踪事件\n");
    
    return 0;
}

int main() {
    return demo_seccomp_monitoring();
}

示例5:安全沙箱应用

#define _GNU_SOURCE
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/audit.h>
#include <sys/prctl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>

/**
 * 安全沙箱配置
 */
typedef struct {
    int allow_network;
    int allow_file_write;
    int allow_process_creation;
    int allow_memory_mapping;
} sandbox_config_t;

/**
 * 创建安全沙箱
 */
int create_secure_sandbox(const sandbox_config_t *config) {
    printf("=== 创建安全沙箱 ===\n");
    
    // 根据配置创建BPF过滤器
    struct sock_filter filter[100];
    int filter_index = 0;
    
    // 基础加载系统调用号指令
    filter[filter_index++] = BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 
                                     offsetof(struct seccomp_data, nr));
    
    // 始终允许的系统调用
    int essential_calls[] = {SYS_read, SYS_write, 
#ifdef __x86_64__
                           60,  // exit
                           231  // exit_group
#elif defined(__i386__)
                           1,   // exit
                           252  // exit_group
#endif
    };
    
    for (size_t i = 0; i < sizeof(essential_calls)/sizeof(essential_calls[0]); i++) {
        filter[filter_index++] = BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 
                                         essential_calls[i], 0, 1);
        filter[filter_index++] = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
    }
    
    // 根据配置允许额外的系统调用
    if (config->allow_file_write) {
        filter[filter_index++] = BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_open, 0, 1);
        filter[filter_index++] = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
        
        filter[filter_index++] = BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_openat, 0, 1);
        filter[filter_index++] = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
        
        filter[filter_index++] = BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_close, 0, 1);
        filter[filter_index++] = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
    }
    
    if (config->allow_network) {
        filter[filter_index++] = BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_socket, 0, 1);
        filter[filter_index++] = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
        
        filter[filter_index++] = BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_connect, 0, 1);
        filter[filter_index++] = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
    }
    
    if (config->allow_process_creation) {
        filter[filter_index++] = BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_fork, 0, 1);
        filter[filter_index++] = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
        
        filter[filter_index++] = BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_clone, 0, 1);
        filter[filter_index++] = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
    }
    
    if (config->allow_memory_mapping) {
        filter[filter_index++] = BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_mmap, 0, 1);
        filter[filter_index++] = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
        
        filter[filter_index++] = BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_munmap, 0, 1);
        filter[filter_index++] = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
    }
    
    // 默认拒绝所有其他系统调用
    filter[filter_index++] = BPF_STMT(BPF_RET | BPF_K, 
                                     SECCOMP_RET_ERRNO | (EPERM & 0xFFFF));
    
    struct sock_fprog prog = {
        .len = filter_index,
        .filter = filter,
    };
    
    printf("沙箱配置:\n");
    printf("  网络访问: %s\n", config->allow_network ? "允许" : "禁止");
    printf("  文件写入: %s\n", config->allow_file_write ? "允许" : "禁止");
    printf("  进程创建: %s\n", config->allow_process_creation ? "允许" : "禁止");
    printf("  内存映射: %s\n", config->allow_memory_mapping ? "允许" : "禁止");
    
    // 应用沙箱
    if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog, 0, 0) == -1) {
        printf("创建沙箱失败: %s\n", strerror(errno));
        return -1;
    }
    
    printf("安全沙箱创建成功\n");
    return 0;
}

/**
 * 演示不同安全级别的沙箱
 */
int demo_security_levels() {
    sandbox_config_t configs[3] = {
        // 最严格:只允许基本I/O
        {0, 0, 0, 0},
        
        // 中等:允许文件操作
        {0, 1, 0, 1},
        
        // 宽松:允许网络和进程创建
        {1, 1, 1, 1}
    };
    
    const char *level_names[] = {"最高安全", "中等安全", "较低安全"};
    
    printf("=== 不同安全级别沙箱演示 ===\n");
    
    for (int level = 0; level < 3; level++) {
        printf("\n--- %s级别沙箱 ---\n", level_names[level]);
        
        if (create_secure_sandbox(&configs[level]) == 0) {
            printf("沙箱 %s 创建成功\n", level_names[level]);
            
            // 测试沙箱功能
            write(STDOUT_FILENO, "基本I/O测试成功\n", 17);
            
            if (configs[level].allow_network) {
                printf("网络功能可用\n");
            }
            
            if (configs[level].allow_file_write) {
                printf("文件写入功能可用\n");
            }
            
            // 由于seccomp策略一旦设置就不能放松,我们需要在子进程中测试
            break;  // 只测试第一个配置
        }
    }
    
    return 0;
}

/**
 * 演示沙箱的实际应用
 */
int demo_practical_sandbox() {
    printf("=== 实际沙箱应用演示 ===\n");
    
    // 创建一个限制性的沙箱:只允许基本操作
    sandbox_config_t config = {0, 0, 0, 0};  // 最严格
    
    if (create_secure_sandbox(&config) != 0) {
        return -1;
    }
    
    printf("\n沙箱环境中运行测试程序:\n");
    
    // 测试基本功能
    printf("1. 基本输出测试:\n");
    printf("   标准输出工作正常\n");
    write(STDOUT_FILENO, "   write系统调用工作正常\n", 24);
    
    // 测试被限制的功能
    printf("\n2. 被限制功能测试:\n");
    
    // 尝试网络操作
    long result = syscall(SYS_socket, AF_INET, SOCK_STREAM, 0);
    if (result == -1) {
        printf("   网络操作被成功阻止: %s\n", strerror(errno));
    }
    
    // 尝试文件写入
    result = syscall(SYS_open, "/tmp/test", O_WRONLY | O_CREAT, 0644);
    if (result == -1) {
        printf("   文件写入被成功阻止: %s\n", strerror(errno));
    }
    
    // 尝试进程创建
    result = syscall(SYS_fork);
    if (result == -1) {
        printf("   进程创建被成功阻止: %s\n", strerror(errno));
    }
    
    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() {
    printf("seccomp - Linux系统调用过滤机制\n");
    printf("================================\n\n");
    
    // 由于seccomp策略一旦设置就会影响整个进程,
    // 我们分别在不同的子进程中演示不同功能
    
    if (fork() == 0) {
        return demo_practical_sandbox();
    }
    
    int status;
    wait(&status);
    
    return 0;
}

seccomp 使用注意事项

系统要求:

  1. 内核版本: 需要Linux 3.5或更高版本
  2. 架构支持: 支持多种CPU架构
  3. 编译选项: 需要内核编译时启用CONFIG_SECCOMP

权限要求:

1. CAP_SYS_ADMIN: 通常需要管理员权限
2. 无特权进程: 可以使用SECCOMP_MODE_STRICT
3. 容器环境: Docker等容器可能有限制

安全考虑:

1. 策略不可逆: 一旦应用,seccomp策略不能放松
2. 调试困难: 被阻止的系统调用可能难以调试
3. 兼容性: 可能影响程序的正常功能
4. 性能影响: BPF过滤会增加系统调用开销

最佳实践:

  1. 渐进式应用: 从宽松策略开始,逐步收紧
  2. 充分测试: 在生产环境前充分测试
  3. 错误处理: 妥善处理被阻止的系统调用
  4. 日志记录: 记录安全相关事件
  5. 备份方案: 提供策略失效时的处理方案

seccomp 模式详解

SECCOMP_MODE_STRICT (模式1):

  • 特点: 最简单的模式,只允许read/write/exit/exit_group
  • 优点: 简单、高效、安全
  • 缺点: 功能极其有限
  • 适用: 极度安全要求的简单程序

SECCOMP_MODE_FILTER (模式2):

  • 特点: 使用BPF程序定义复杂过滤规则
  • 优点: 灵活、功能强大
  • 缺点: 配置复杂
  • 适用: 大多数实际应用场景

常见系统调用编号

x86_64架构:

  • SYS_read = 0
  • SYS_write = 1
  • SYS_open = 2
  • SYS_close = 3
  • SYS_stat = 4
  • SYS_fstat = 5
  • SYS_lstat = 6
  • SYS_poll = 7
  • SYS_lseek = 8
  • SYS_mmap = 9
  • SYS_mprotect = 10
  • SYS_munmap = 11
  • SYS_brk = 12
  • SYS_rt_sigaction = 13
  • SYS_rt_sigprocmask = 14
  • SYS_rt_sigreturn = 15
  • SYS_ioctl = 16
  • SYS_pread64 = 17
  • SYS_pwrite64 = 18
  • SYS_readv = 19
  • SYS_writev = 20
  • SYS_access = 21
  • SYS_pipe = 22
  • SYS_select = 23
  • SYS_sched_yield = 24
  • SYS_mremap = 25
  • SYS_msync = 26
  • SYS_mincore = 27
  • SYS_madvise = 28
  • SYS_shmget = 29
  • SYS_shmat = 30
  • SYS_shmctl = 31
  • SYS_dup = 32
  • SYS_dup2 = 33
  • SYS_pause = 34
  • SYS_nanosleep = 35
  • SYS_getitimer = 36
  • SYS_alarm = 37
  • SYS_setitimer = 38
  • SYS_getpid = 39
  • SYS_sendfile = 40
  • SYS_socket = 41
  • SYS_connect = 42
  • SYS_accept = 43
  • SYS_sendto = 44
  • SYS_recvfrom = 45
  • SYS_sendmsg = 46
  • SYS_recvmsg = 47
  • SYS_shutdown = 48
  • SYS_bind = 49
  • SYS_listen = 50
  • SYS_getsockname = 51
  • SYS_getpeername = 52
  • SYS_socketpair = 53
  • SYS_setsockopt = 54
  • SYS_getsockopt = 55
  • SYS_clone = 56
  • SYS_fork = 57
  • SYS_vfork = 58
  • SYS_execve = 59
  • SYS_exit = 60
  • SYS_wait4 = 61
  • SYS_kill = 62
  • SYS_uname = 63
  • SYS_semget = 64
  • SYS_semop = 65
  • SYS_semctl = 66
  • SYS_shmdt = 67
  • SYS_msgget = 68
  • SYS_msgsnd = 69
  • SYS_msgrcv = 70
  • SYS_msgctl = 71
  • SYS_fcntl = 72
  • SYS_flock = 73
  • SYS_fsync = 74
  • SYS_fdatasync = 75
  • SYS_truncate = 76
  • SYS_ftruncate = 77
  • SYS_getdents = 78
  • SYS_getcwd = 79
  • SYS_chdir = 80
  • SYS_fchdir = 81
  • SYS_rename = 82
  • SYS_mkdir = 83
  • SYS_rmdir = 84
  • SYS_creat = 85
  • SYS_link = 86
  • SYS_unlink = 87
  • SYS_symlink = 88
  • SYS_readlink = 89
  • SYS_chmod = 90
  • SYS_fchmod = 91
  • SYS_chown = 92
  • SYS_fchown = 93
  • SYS_lchown = 94
  • SYS_umask = 95
  • SYS_gettimeofday = 96
  • SYS_getrlimit = 97
  • SYS_getrusage = 98
  • SYS_sysinfo = 99
  • SYS_times = 100
  • SYS_ptrace = 101
  • SYS_getuid = 102
  • SYS_syslog = 103
  • SYS_getgid = 104
  • SYS_setuid = 105
  • SYS_setgid = 106
  • SYS_geteuid = 107
  • SYS_getegid = 108
  • SYS_setpgid = 109
  • SYS_getppid = 110
  • SYS_getpgrp = 111
  • SYS_setsid = 112
  • SYS_setreuid = 113
  • SYS_setregid = 114
  • SYS_getgroups = 115
  • SYS_setgroups = 116
  • SYS_setresuid = 117
  • SYS_getresuid = 118
  • SYS_setresgid = 119
  • SYS_getresgid = 120
  • SYS_getpgid = 121
  • SYS_setfsuid = 122
  • SYS_setfsgid = 123
  • SYS_getsid = 124
  • SYS_capget = 125
  • SYS_capset = 126
  • SYS_rt_sigpending = 127
  • SYS_rt_sigtimedwait = 128
  • SYS_rt_sigqueueinfo = 129
  • SYS_rt_sigsuspend = 130
  • SYS_sigaltstack = 131
  • SYS_utime = 132
  • SYS_mknod = 133
  • SYS_uselib = 134
  • SYS_personality = 135
  • SYS_ustat = 136
  • SYS_statfs = 137
  • SYS_fstatfs = 138
  • SYS_sysfs = 139
  • SYS_getpriority = 140
  • SYS_setpriority = 141
  • SYS_sched_setparam = 142
  • SYS_sched_getparam = 143
  • SYS_sched_setscheduler = 144
  • SYS_sched_getscheduler = 145
  • SYS_sched_get_priority_max = 146
  • SYS_sched_get_priority_min = 147
  • SYS_sched_rr_get_interval = 148
  • SYS_mlock = 149
  • SYS_munlock = 150
  • SYS_mlockall = 151
  • SYS_munlockall = 152
  • SYS_vhangup = 153
  • SYS_modify_ldt = 154
  • SYS_pivot_root = 155
  • SYS__sysctl = 156
  • SYS_prctl = 157
  • SYS_arch_prctl = 158
  • SYS_adjtimex = 159
  • SYS_setrlimit = 160
  • SYS_chroot = 161
  • SYS_sync = 162
  • SYS_acct = 163
  • SYS_settimeofday = 164
  • SYS_mount = 165
  • SYS_umount2 = 166
  • SYS_swapon = 167
  • SYS_swapoff = 168
  • SYS_reboot = 169
  • SYS_sethostname = 170
  • SYS_setdomainname = 171
  • SYS_iopl = 172
  • SYS_ioperm = 173
  • SYS_create_module = 174
  • SYS_init_module = 175
  • SYS_delete_module = 176
  • SYS_get_kernel_syms = 177
  • SYS_query_module = 178
  • SYS_quotactl = 179
  • SYS_nfsservctl = 180
  • SYS_getpmsg = 181
  • SYS_putpmsg = 182
  • SYS_afs_syscall = 183
  • SYS_tuxcall = 184
  • SYS_security = 185
  • SYS_gettid = 186
  • SYS_readahead = 187
  • SYS_setxattr = 188
  • SYS_lsetxattr = 189
  • SYS_fsetxattr = 190
  • SYS_getxattr = 191
  • SYS_lgetxattr = 192
  • SYS_fgetxattr = 193
  • SYS_listxattr = 194
  • SYS_llistxattr = 195
  • SYS_flistxattr = 196
  • SYS_removexattr = 197
  • SYS_lremovexattr = 198
  • SYS_fremovexattr = 199
  • SYS_tkill = 200
  • SYS_time = 201
  • SYS_futex = 202
  • SYS_sched_setaffinity = 203
  • SYS_sched_getaffinity = 204
  • SYS_set_thread_area = 205
  • SYS_io_setup = 206
  • SYS_io_destroy = 207
  • SYS_io_getevents = 208
  • SYS_io_submit = 209
  • SYS_io_cancel = 210
  • SYS_get_thread_area = 211
  • SYS_lookup_dcookie = 212
  • SYS_epoll_create = 213
  • SYS_epoll_ctl_old = 214
  • SYS_epoll_wait_old = 215
  • SYS_remap_file_pages = 216
  • SYS_getdents64 = 217
  • SYS_set_tid_address = 218
  • SYS_restart_syscall = 219
  • SYS_semtimedop = 220
  • SYS_fadvise64 = 221
  • SYS_timer_create = 222
  • SYS_timer_settime = 223
  • SYS_timer_gettime = 224
  • SYS_timer_getoverrun = 225
  • SYS_timer_delete = 226
  • SYS_clock_settime = 227
  • SYS_clock_gettime = 228
  • SYS_clock_getres = 229
  • SYS_clock_nanosleep = 230
  • SYS_exit_group = 231
  • SYS_epoll_wait = 232
  • SYS_epoll_ctl = 233
  • SYS_tgkill = 234
  • SYS_utimes = 235
  • SYS_vserver = 236
  • SYS_mbind = 237
  • SYS_set_mempolicy = 238
  • SYS_get_mempolicy = 239
  • SYS_mq_open = 240
  • SYS_mq_unlink = 241
  • SYS_mq_timedsend = 242
  • SYS_mq_timedreceive = 243
  • SYS_mq_notify = 244
  • SYS_mq_getsetattr = 245
  • SYS_kexec_load = 246
  • SYS_waitid = 247
  • SYS_add_key = 248
  • SYS_request_key = 249
  • SYS_keyctl = 250
  • SYS_ioprio_set = 251
  • SYS_ioprio_get = 252
  • SYS_inotify_init = 253
  • SYS_inotify_add_watch = 254
  • SYS_inotify_rm_watch = 255
  • SYS_migrate_pages = 256
  • SYS_openat = 257
  • SYS_mkdirat = 258
  • SYS_mknodat = 259
  • SYS_fchownat = 260
  • SYS_futimesat = 261
  • SYS_newfstatat = 262
  • SYS_unlinkat = 263
  • SYS_renameat = 264
  • SYS_linkat = 265
  • SYS_symlinkat = 266
  • SYS_readlinkat = 267
  • SYS_fchmodat = 268
  • SYS_faccessat = 269
  • SYS_pselect6 = 270
  • SYS_ppoll = 271
  • SYS_unshare = 272
  • SYS_set_robust_list = 273
  • SYS_get_robust_list = 274
  • SYS_splice = 275
  • SYS_tee = 276
  • SYS_sync_file_range = 277
  • SYS_vmsplice = 278
  • SYS_move_pages = 279
  • SYS_utimensat = 280
  • SYS_epoll_pwait = 281
  • SYS_signalfd = 282
  • SYS_timerfd_create = 283
  • SYS_eventfd = 284
  • SYS_fallocate = 285
  • SYS_timerfd_settime = 286
  • SYS_timerfd_gettime = 287
  • SYS_accept4 = 288
  • SYS_signalfd4 = 289
  • SYS_eventfd2 = 290
  • SYS_epoll_create1 = 291
  • SYS_dup3 = 292
  • SYS_pipe2 = 293
  • SYS_inotify_init1 = 294
  • SYS_preadv = 295
  • SYS_pwritev = 296
  • SYS_rt_tgsigqueueinfo = 297
  • SYS_perf_event_open = 298
  • SYS_recvmmsg = 299
  • SYS_fanotify_init = 300
  • SYS_fanotify_mark = 301
  • SYS_prlimit64 = 302
  • SYS_name_to_handle_at = 303
  • SYS_open_by_handle_at = 304
  • SYS_clock_adjtime = 305
  • SYS_syncfs = 306
  • SYS_sendmmsg = 307
  • SYS_setns = 308
  • SYS_getcpu = 309
  • SYS_process_vm_readv = 310
  • SYS_process_vm_writev = 311
  • SYS_kcmp = 312
  • SYS_finit_module = 313
  • SYS_sched_setattr = 314
  • SYS_sched_getattr = 315
  • SYS_renameat2 = 316
  • SYS_seccomp = 317
  • SYS_getrandom = 318
  • SYS_memfd_create = 319
  • SYS_kexec_file_load = 320
  • SYS_bpf = 321
  • SYS_execveat = 322
  • SYS_userfaultfd = 323
  • SYS_membarrier = 324
  • SYS_mlock2 = 325
  • SYS_copy_file_range = 326
  • SYS_preadv2 = 327
  • SYS_pwritev2 = 328
  • SYS_pkey_mprotect = 329
  • SYS_pkey_alloc = 330
  • SYS_pkey_free = 331
  • SYS_statx = 332
  • SYS_io_pgetevents = 333
  • SYS_rseq = 334

总结

seccomp 是Linux系统中强大的安全机制,提供了:

1. 系统调用级别的访问控制: 精确控制进程可以执行的操作
2. 灵活的策略定义: 通过BPF程序实现复杂过滤逻辑
3. 高效的执行: 内核级别的过滤,性能开销小
4. 广泛的应用场景: 适用于沙箱、容器、安全审计等

通过合理使用seccomp,可以显著提高应用程序的安全性,构建更加安全可靠的计算环境。在实际应用中,需要仔细设计过滤策略,充分测试,并考虑错误处理和调试需求。

发表在 linux文章 | 留下评论

pipe系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 pipe 函数,它是实现进程间通信 (IPC – Inter-Process Communication) 的基础机制之一,尤其适用于具有亲缘关系的进程(如父子进程、兄弟进程)之间进行单向数据传输。


1. 函数介绍

pipe 是一个 Linux 系统调用,用于创建一个匿名管道 (anonymous pipe)。管道是一种半双工(单向)的通信通道,具有固定的读端和写端。

你可以把管道想象成一个单向的水管传送带

  • 一端是写入端 (write end):数据被“放入”管道。
  • 另一端是读取端 (read end):数据从管道中被“取出”。
  • 数据在管道内部按照先进先出 (FIFO) 的顺序流动。
  • 管道有有限的容量(通常由 PIPE_BUF 常量定义,Linux 上通常是 65536 字节)。如果管道满了,写入操作会阻塞;如果管道空了,读取操作会阻塞。

匿名管道最常见的用途是在相关进程(通过 fork 创建的父子进程或兄弟进程)之间传递数据。


2. 函数原型

#include <unistd.h> // 必需

int pipe(int pipefd[2]);

3. 功能

  • 创建管道: 请求内核创建一个新的匿名管道。
  • 返回文件描述符: 在成功创建后,将两个关联的文件描述符通过 pipefd 数组返回给调用者:
    • pipefd[0]读端 (read end) 的文件描述符。
    • `pipefd[1]**: 写端 (write end) 的文件描述符。
  • 初始化状态: 刚创建时,管道是空的。

4. 参数

  • int pipefd[2]: 这是一个包含两个整数的数组,用于接收 pipe 调用返回的文件描述符。
    • pipefd[0]: 管道的读取端。进程可以对此文件描述符调用 read 来获取数据。
    • pipefd[1]: 管道的写入端。进程可以对此文件描述符调用 write 来放入数据。

5. 返回值

  • 成功时: 返回 0。同时,pipefd[0] 和 pipefd[1] 被填充为有效的文件描述符。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EMFILE 进程打开的文件描述符已达上限,ENFILE 系统打开的文件总数已达上限等)。

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

  • socketpair: 创建一对相互连接的匿名套接字,可以实现双向进程间通信。
  • 命名管道 (FIFO): 通过 mkfifo 或 mknod 创建的特殊文件,允许无亲缘关系的进程进行通信。
  • readwrite: 用于对管道的读端和写端进行实际的数据传输。
  • close: 用于关闭管道的读端或写端。关闭写端会使读端在数据读完后 read 返回 0(EOF);关闭读端会使写端 write 产生 SIGPIPE 信号(默认终止进程)。
  • fork: 通常与 pipe 结合使用,子进程和父进程通过继承的管道文件描述符进行通信。

7. 示例代码

示例 1:父子进程通过管道通信

这个经典的例子演示了如何使用 pipe 在父进程和子进程之间传递数据。

#include <unistd.h>  // pipe, fork, read, write, close
#include <sys/wait.h> // wait
#include <stdio.h>   // perror, printf
#include <stdlib.h>  // exit
#include <string.h>  // strlen

int main() {
    int pipefd[2];          // 用于存储管道的两个文件描述符
    pid_t cpid;             // 子进程 ID
    char buf;               // 用于逐字节读取的缓冲区

    // 1. 创建管道
    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    // 2. 创建子进程
    cpid = fork();
    if (cpid == -1) {
        perror("fork");
        // 创建子进程失败,需要关闭已创建的管道
        close(pipefd[0]);
        close(pipefd[1]);
        exit(EXIT_FAILURE);
    }

    // 3. 根据进程 ID 执行不同代码
    if (cpid == 0) { // 子进程执行代码
        // --- 子进程 ---
        // 关闭不需要的写端
        if (close(pipefd[1]) == -1) {
            perror("child: close write end");
            _exit(EXIT_FAILURE); // 子进程中使用 _exit
        }

        printf("Child process (PID %d): Reading from pipe...\n", getpid());

        // 从管道读端读取数据,直到遇到 EOF
        while (read(pipefd[0], &buf, 1) > 0) {
            write(STDOUT_FILENO, &buf, 1); // 写入到标准输出 (屏幕)
        }

        // 检查 read 是否因错误而失败
        if (read(pipefd[0], &buf, 1) == -1) {
            perror("child: read");
            _exit(EXIT_FAILURE);
        }

        printf("Child process: Finished reading. Exiting.\n");

        // 关闭读端
        if (close(pipefd[0]) == -1) {
            perror("child: close read end");
            _exit(EXIT_FAILURE);
        }

        _exit(EXIT_SUCCESS); // 子进程成功退出

    } else { // 父进程执行代码
        // --- 父进程 ---
        // 关闭不需要的读端
        if (close(pipefd[0]) == -1) {
            perror("parent: close read end");
            // 清理子进程?
            exit(EXIT_FAILURE);
        }

        const char *message = "Message from parent to child through pipe!\n";

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

        // 向管道写端写入数据
        if (write(pipefd[1], message, strlen(message)) != (ssize_t)strlen(message)) {
            perror("parent: write");
            // 可能需要 kill 子进程
            exit(EXIT_FAILURE);
        }

        printf("Parent process: Message sent. Closing write end.\n");

        // 关闭写端,这会使子进程的 read() 在读完数据后返回 0 (EOF)
        if (close(pipefd[1]) == -1) {
            perror("parent: close write end");
            exit(EXIT_FAILURE);
        }

        // 等待子进程结束
        int status;
        if (wait(&status) == -1) {
            perror("parent: wait");
            exit(EXIT_FAILURE);
        }

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

    return 0;
}

代码解释:

1. 调用 pipe(pipefd) 创建管道,成功后 pipefd[0] 是读端,pipefd[1] 是写端。
2. 调用 fork() 创建子进程。fork 之后,父子进程都拥有管道两端的文件描述符副本。
3. 子进程 (cpid == 0):
* 关闭不需要的写端 pipefd[1]
* 进入循环,调用 read(pipefd[0], &buf, 1) 从管道读取数据(一次读一个字节)。
* 将读到的字节写入标准输出。
* 当 read 返回 0 时,表示已到达 EOF(因为父进程关闭了写端),循环结束。
* 关闭读端 pipefd[0]
* 使用 _exit() 退出(在子进程中通常推荐使用 _exit 而非 exit,以避免刷新 stdio 缓冲区可能带来的问题)。
4. 父进程 (cpid > 0):
* 关闭不需要的读端 pipefd[0]
* 定义要发送的消息。
* 调用 write(pipefd[1], message, ...) 将消息写入管道。
* 关闭写端 pipefd[1]这一步很重要,它会通知子进程数据已发送完毕(读端 read 会返回 0)。
* 调用 wait() 等待子进程结束,并检查其退出状态。

示例 2:使用管道实现简单的命令行管道 (ls | wc -l)

这个例子模拟了 shell 中 ls | wc -l 的功能,即列出当前目录内容并统计行数。

#include <unistd.h>  // pipe, fork, dup2, execvp, close
#include <sys/wait.h> // wait
#include <stdio.h>   // perror, fprintf, stderr
#include <stdlib.h>  // exit

int main() {
    int pipefd[2];
    pid_t pid1, pid2;

    // 1. 创建管道
    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    // 2. 创建第一个子进程来执行 'ls'
    pid1 = fork();
    if (pid1 == -1) {
        perror("fork ls");
        close(pipefd[0]);
        close(pipefd[1]);
        exit(EXIT_FAILURE);
    }

    if (pid1 == 0) { // 第一个子进程
        // --- 'ls' 进程 ---
        // 关闭不需要的读端
        close(pipefd[0]);

        // 将标准输出重定向到管道的写端
        // dup2(oldfd, newfd): 关闭 newfd, 然后使 newfd 成为 oldfd 的副本
        if (dup2(pipefd[1], STDOUT_FILENO) == -1) {
            perror("dup2 ls");
            _exit(EXIT_FAILURE);
        }

        // 关闭原始的管道写端文件描述符 (因为已经复制到 STDOUT_FILENO)
        close(pipefd[1]);

        // 执行 'ls' 命令
        // execlp 在 PATH 中查找程序
        execlp("ls", "ls", (char *)NULL);

        // 如果 execlp 返回,说明执行失败
        perror("execlp ls failed");
        _exit(EXIT_FAILURE);
    }

    // 3. 创建第二个子进程来执行 'wc -l'
    pid2 = fork();
    if (pid2 == -1) {
        perror("fork wc");
        // 可能需要 kill pid1?
        close(pipefd[0]);
        close(pipefd[1]);
        exit(EXIT_FAILURE);
    }

    if (pid2 == 0) { // 第二个子进程
        // --- 'wc -l' 进程 ---
        // 关闭不需要的写端
        close(pipefd[1]);

        // 将标准输入重定向到管道的读端
        if (dup2(pipefd[0], STDIN_FILENO) == -1) {
            perror("dup2 wc");
            _exit(EXIT_FAILURE);
        }

        // 关闭原始的管道读端文件描述符
        close(pipefd[0]);

        // 执行 'wc -l' 命令
        char *cmd[] = {"wc", "-l", NULL};
        execvp(cmd[0], cmd); // execvp 需要 char *const argv[]

        // 如果 execvp 返回,说明执行失败
        perror("execvp wc failed");
        _exit(EXIT_FAILURE);
    }

    // 4. 父进程
    // 父进程不需要使用管道,所以关闭两端
    close(pipefd[0]);
    close(pipefd[1]);

    // 等待两个子进程结束
    // 注意:waitpid 可能更精确地等待特定子进程
    int status;
    if (waitpid(pid1, &status, 0) == -1) {
        perror("waitpid ls");
    }
    if (waitpid(pid2, &status, 0) == -1) {
        perror("waitpid wc");
    }

    printf("Parent process: Both 'ls' and 'wc -l' have finished.\n");

    return 0;
}

代码解释:

1. 调用 pipe(pipefd) 创建管道。
2. 第一次 fork() 创建子进程 pid1
3. 在 pid1 子进程中:
* 关闭管道读端。
* 使用 dup2(pipefd[1], STDOUT_FILENO) 将子进程的标准输出 (STDOUT_FILENO,即文件描述符 1) 重定向到管道的写端。这意味着 ls 命令的所有输出都会被写入管道。
* 关闭原始的管道写端文件描述符 pipefd[1]
* 调用 execlp("ls", "ls", NULL) 执行 ls 命令。因为标准输出已被重定向,ls 的输出会进入管道。
4. 第二次 fork() 创建子进程 pid2
5. 在 pid2 子进程中:
* 关闭管道写端。
* 使用 dup2(pipefd[0], STDIN_FILENO) 将子进程的标准输入 (STDIN_FILENO,即文件描述符 0) 重定向到管道的读端。这意味着 wc 命令会从管道读取输入。
* 关闭原始的管道读端文件描述符 pipefd[0]
* 调用 execvp("wc", cmd) 执行 wc -l 命令。因为标准输入已被重定向,wc 会从管道读取数据并统计行数,结果输出到标准输出(通常是屏幕)。
6. 父进程:
* 关闭自己的管道文件描述符(不再需要)。
* 调用 waitpid 等待两个子进程结束。

这个例子很好地展示了管道如何连接两个进程的标准输入和输出,从而实现数据流的传递,就像在 shell 中使用 | 一样。

总结:

pipe 函数是 Linux 进程间通信的基础工具之一。它创建的匿名管道简单高效,特别适合于有亲缘关系的进程之间的单向数据传输。理解其与 forkdup2readwrite 等函数的配合使用是掌握 Linux IPC 的关键。

发表在 linux文章 | 标签为 | 留下评论

pipe系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 pipe 函数,它用于创建匿名管道,这是一种在相关进程(如父子进程)之间进行单向数据通信的重要机制。

pipe 函数介绍


1. 函数介绍

pipe 是一个 Linux 系统调用,用于创建一个匿名管道(Anonymous Pipe)。管道是一种半双工(单向)的进程间通信(IPC)机制,数据只能在一个方向上流动。

管道通常用于具有亲缘关系的进程之间通信,最常见的场景是父进程和子进程之间的数据传递。创建管道后,会得到两个文件描述符:一个用于读取(read end),一个用于写入(write end)。写入端写入的数据会被内核缓冲,然后可以从读取端读取出来。

管道是 Unix/Linux “一切皆文件” 哲学的体现,管道的两端都可以像普通文件一样使用 read() 和 write() 系统调用进行操作。

重要特性:

  • 匿名性: 管道没有名字,不能通过文件系统路径访问。
  • 单向性: 数据只能从写端流向读端。
  • 亲缘性: 通常用于有亲缘关系的进程间通信。
  • 阻塞性: 默认情况下,读写操作可能会阻塞。
  • 缓冲性: 内核提供缓冲区来存储管道中的数据。

2. 函数原型

#include <unistd.h> // 必需

int pipe(int pipefd[2]);

3. 功能

  • 创建管道: 在内核中创建一个管道缓冲区。
  • 返回文件描述符: 通过 pipefd 数组返回两个文件描述符:
    • pipefd[0]: 管道的读取端(read end)。
    • pipefd[1]: 管道的写入端(write end)。

4. 参数

  • int pipefd[2]: 一个包含两个整数的数组,用于接收返回的文件描述符。
    • pipefd[0]: 管道的读取端文件描述符。进程可以使用 read(pipefd[0], buffer, size) 从此端读取数据。
    • pipefd[1]: 管道的写入端文件描述符。进程可以使用 write(pipefd[1], buffer, size) 向此端写入数据。
    • 注意: 创建管道后,通常会使用 fork() 创建子进程,然后父子进程分别关闭不需要的端。例如,父进程负责写入,则应关闭 pipefd[0];子进程负责读取,则应关闭 pipefd[1]

5. 返回值

  • 成功时: 返回 0。
  • 失败时:
    • 返回 -1,并设置全局变量 errno 来指示具体的错误原因:
      • EFAULTpipefd 数组指针无效。
      • EMFILE: 进程已打开的文件描述符数量达到上限 (RLIMIT_NOFILE)。
      • ENFILE: 系统范围内已打开的文件数量达到上限。

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

  • pipe2(int pipefd[2], int flags)pipe 的扩展版本,允许设置额外的标志,如 O_CLOEXEC(执行时关闭)或 O_NONBLOCK(非阻塞模式)。
  • mkfifo(const char *pathname, mode_t mode): 创建命名管道(FIFO),它是一个存在于文件系统中的特殊文件,可以让无亲缘关系的进程通信。
  • socketpair(int domain, int type, int protocol, int sv[2]): 创建一对相互连接的套接字,可以实现全双工通信。
  • popen(const char *command, const char *type)pclose(FILE *stream): 高级函数,创建一个管道并启动一个 shell 来执行命令,方便地实现程序间的数据交换。

7. 示例代码

示例 1:基本的父子进程管道通信

这个例子演示了最经典的管道使用场景:父进程向子进程发送数据。

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

#define BUFFER_SIZE 256

int main() {
    int pipefd[2];
    pid_t pid;
    char buffer[BUFFER_SIZE];

    printf("=== 基本父子进程管道通信 ===\n");

    // 1. 创建管道
    if (pipe(pipefd) == -1) {
        perror("pipe 创建失败");
        exit(EXIT_FAILURE);
    }
    printf("管道创建成功: 读端=%d, 写端=%d\n", pipefd[0], pipefd[1]);

    // 2. 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork 失败");
        // 清理管道文件描述符
        close(pipefd[0]);
        close(pipefd[1]);
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        // 子进程:读取数据
        printf("子进程 (PID: %d) 开始读取数据...\n", getpid());
        
        // 关闭写端(子进程不需要写入)
        close(pipefd[1]);
        
        // 从管道读取数据
        ssize_t bytes_read = read(pipefd[0], buffer, BUFFER_SIZE - 1);
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0'; // 确保字符串以 null 结尾
            printf("子进程读取到数据: %s", buffer);
        } else if (bytes_read == 0) {
            printf("子进程读取结束 (写端已关闭)\n");
        } else {
            perror("子进程读取失败");
        }
        
        // 关闭读端
        close(pipefd[0]);
        printf("子进程结束\n");
        exit(EXIT_SUCCESS);
        
    } else {
        // 父进程:写入数据
        printf("父进程 (PID: %d) 开始写入数据...\n", getpid());
        
        // 关闭读端(父进程不需要读取)
        close(pipefd[0]);
        
        // 向管道写入数据
        const char *message = "Hello from parent process through pipe!\n";
        ssize_t bytes_written = write(pipefd[1], message, strlen(message));
        if (bytes_written == -1) {
            perror("父进程写入失败");
        } else {
            printf("父进程写入 %ld 字节数据\n", bytes_written);
        }
        
        // 关闭写端,这会通知读端数据已写完
        close(pipefd[1]);
        
        // 等待子进程结束
        int status;
        waitpid(pid, &status, 0);
        if (WIFEXITED(status)) {
            printf("子进程正常退出,退出码: %d\n", WEXITSTATUS(status));
        } else {
            printf("子进程异常退出\n");
        }
        
        printf("父进程结束\n");
    }

    return 0;
}

代码解释:

  1. 首先调用 pipe(pipefd) 创建管道,得到读端 pipefd[0] 和写端 pipefd[1]
  2. 调用 fork() 创建子进程。此时,父子进程都拥有管道两端的文件描述符副本。
  3. 在子进程中:
    • 关闭不需要的写端 pipefd[1]
    • 使用 read(pipefd[0], ...) 从管道读取数据。
    • 读取完成后关闭读端 pipefd[0]
  4. 在父进程中:
    • 关闭不需要的读端 pipefd[0]
    • 使用 write(pipefd[1], ...) 向管道写入数据。
    • 写入完成后关闭写端 pipefd[1],这会通知读端没有更多数据。
    • 使用 waitpid() 等待子进程结束。

示例 2:双向管道通信

这个例子演示如何使用两个管道实现父子进程之间的双向通信。

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

#define BUFFER_SIZE 256

int main() {
    int parent_to_child_pipe[2];  // 父进程向子进程发送数据
    int child_to_parent_pipe[2];  // 子进程向父进程发送数据
    pid_t pid;
    char buffer[BUFFER_SIZE];

    printf("=== 双向管道通信 ===\n");

    // 1. 创建两个管道
    if (pipe(parent_to_child_pipe) == -1 || pipe(child_to_parent_pipe) == -1) {
        perror("管道创建失败");
        exit(EXIT_FAILURE);
    }
    printf("管道创建成功\n");
    printf("父到子管道: 读端=%d, 写端=%d\n", parent_to_child_pipe[0], parent_to_child_pipe[1]);
    printf("子到父管道: 读端=%d, 写端=%d\n", child_to_parent_pipe[0], child_to_parent_pipe[1]);

    // 2. 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork 失败");
        close(parent_to_child_pipe[0]);
        close(parent_to_child_pipe[1]);
        close(child_to_parent_pipe[0]);
        close(child_to_parent_pipe[1]);
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        // 子进程
        printf("子进程 (PID: %d) 启动\n", getpid());
        
        // 关闭不需要的文件描述符
        close(parent_to_child_pipe[1]);  // 子进程不写入父到子管道
        close(child_to_parent_pipe[0]);  // 子进程不读取子到父管道
        
        // 1. 从父进程接收消息
        printf("子进程等待接收父进程消息...\n");
        ssize_t bytes_read = read(parent_to_child_pipe[0], buffer, BUFFER_SIZE - 1);
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            printf("子进程收到消息: %s", buffer);
            
            // 2. 向父进程发送回复
            const char *reply = "Hello parent! I'm child process.\n";
            ssize_t bytes_written = write(child_to_parent_pipe[1], reply, strlen(reply));
            if (bytes_written == -1) {
                perror("子进程发送回复失败");
            } else {
                printf("子进程发送回复 (%ld 字节)\n", bytes_written);
            }
        }
        
        // 关闭管道
        close(parent_to_child_pipe[0]);
        close(child_to_parent_pipe[1]);
        printf("子进程结束\n");
        exit(EXIT_SUCCESS);
        
    } else {
        // 父进程
        printf("父进程 (PID: %d) 启动\n", getpid());
        
        // 关闭不需要的文件描述符
        close(parent_to_child_pipe[0]);  // 父进程不读取父到子管道
        close(child_to_parent_pipe[1]);  // 父进程不写入子到父管道
        
        // 1. 向子进程发送消息
        const char *message = "Hello child! I'm parent process.\n";
        printf("父进程向子进程发送消息...\n");
        ssize_t bytes_written = write(parent_to_child_pipe[1], message, strlen(message));
        if (bytes_written == -1) {
            perror("父进程发送消息失败");
        } else {
            printf("父进程发送消息 (%ld 字节)\n", bytes_written);
        }
        
        // 2. 等待并接收子进程的回复
        printf("父进程等待子进程回复...\n");
        ssize_t bytes_read = read(child_to_parent_pipe[0], buffer, BUFFER_SIZE - 1);
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            printf("父进程收到回复: %s", buffer);
        }
        
        // 关闭管道
        close(parent_to_child_pipe[1]);
        close(child_to_parent_pipe[0]);
        
        // 等待子进程结束
        int status;
        waitpid(pid, &status, 0);
        printf("父进程结束\n");
    }

    return 0;
}

代码解释:

1. 创建两个管道:一个用于父进程向子进程发送数据,另一个用于子进程向父进程发送数据。
2. 在父子进程中分别关闭不需要的管道端。
3. 通过协调读写操作,实现双向通信。

示例 3:管道与错误处理

这个例子重点演示管道的错误处理和一些特殊情况。

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

#define BUFFER_SIZE 256

// 演示管道错误处理
void demonstrate_pipe_errors() {
    printf("=== 管道错误处理演示 ===\n");
    
    // 1. 传递无效指针
    printf("1. 传递无效指针给 pipe()...\n");
    if (pipe(NULL) == -1) {
        printf("   错误: %s\n", strerror(errno));
        if (errno == EFAULT) {
            printf("   说明: pipe() 参数不能为 NULL\n");
        }
    }
    
    printf("\n");
}

// 演示管道读写特性
void demonstrate_pipe_characteristics() {
    printf("=== 管道特性演示 ===\n");
    
    int pipefd[2];
    if (pipe(pipefd) == -1) {
        perror("pipe 创建失败");
        return;
    }
    
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork 失败");
        close(pipefd[0]);
        close(pipefd[1]);
        return;
    }
    
    if (pid == 0) {
        // 子进程
        close(pipefd[1]); // 关闭写端
        
        char buffer[BUFFER_SIZE];
        printf("子进程: 尝试从空管道读取 (会阻塞)...\n");
        
        // 读取数据
        ssize_t bytes_read = read(pipefd[0], buffer, BUFFER_SIZE - 1);
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            printf("子进程: 读取到数据: %s", buffer);
        } else if (bytes_read == 0) {
            printf("子进程: 读取到文件结束 (所有写端都已关闭)\n");
        } else {
            perror("子进程: 读取失败");
        }
        
        close(pipefd[0]);
        exit(EXIT_SUCCESS);
    } else {
        // 父进程
        close(pipefd[0]); // 关闭读端
        
        // 写入一些数据
        const char *message = "Message from parent\n";
        printf("父进程: 向管道写入数据...\n");
        write(pipefd[1], message, strlen(message));
        
        // 关闭写端,通知子进程结束
        printf("父进程: 关闭写端,通知子进程结束...\n");
        close(pipefd[1]);
        
        wait(NULL);
    }
    
    printf("\n");
}

// 演示管道容量和阻塞行为
void demonstrate_pipe_capacity() {
    printf("=== 管道容量和阻塞行为演示 ===\n");
    printf("注意: 这个演示可能需要较长时间\n");
    
    int pipefd[2];
    if (pipe(pipefd) == -1) {
        perror("pipe 创建失败");
        return;
    }
    
    // 获取管道容量(Linux 特定)
    int pipe_capacity = fcntl(pipefd[1], F_GETPIPE_SZ);
    if (pipe_capacity != -1) {
        printf("管道容量: %d 字节\n", pipe_capacity);
    }
    
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork 失败");
        close(pipefd[0]);
        close(pipefd[1]);
        return;
    }
    
    if (pid == 0) {
        // 子进程:读取端
        close(pipefd[1]); // 关闭写端
        
        sleep(2); // 让父进程先填满管道
        
        char buffer[1024];
        int total_read = 0;
        ssize_t bytes_read;
        
        printf("子进程: 开始读取数据...\n");
        while ((bytes_read = read(pipefd[0], buffer, sizeof(buffer))) > 0) {
            total_read += bytes_read;
            printf("子进程: 读取 %ld 字节,总计 %d 字节\n", bytes_read, total_read);
        }
        
        printf("子进程: 读取完成,总计 %d 字节\n", total_read);
        close(pipefd[0]);
        exit(EXIT_SUCCESS);
    } else {
        // 父进程:写入端
        close(pipefd[0]); // 关闭读端
        
        char data[1024];
        memset(data, 'A', sizeof(data) - 1);
        data[sizeof(data) - 1] = '\0';
        
        int total_written = 0;
        ssize_t bytes_written;
        
        printf("父进程: 开始写入大量数据...\n");
        // 写入数据直到管道满(会阻塞)
        for (int i = 0; i < 100; i++) {
            bytes_written = write(pipefd[1], data, strlen(data));
            if (bytes_written == -1) {
                perror("父进程: 写入失败");
                break;
            }
            total_written += bytes_written;
            printf("父进程: 写入 %ld 字节,总计 %d 字节\n", bytes_written, total_written);
        }
        
        printf("父进程: 写入完成,总计 %d 字节\n", total_written);
        close(pipefd[1]);
        wait(NULL);
    }
    
    printf("\n");
}

// 演示 pipe2 和非阻塞管道
void demonstrate_pipe2_and_nonblocking() {
    printf("=== pipe2 和非阻塞管道演示 ===\n");
    
#ifdef __linux__
    int pipefd[2];
    
    // 使用 pipe2 创建非阻塞管道
    if (pipe2(pipefd, O_NONBLOCK) == -1) {
        perror("pipe2 创建失败");
        printf("可能是因为系统不支持 pipe2\n");
        return;
    }
    
    printf("使用 pipe2 创建了非阻塞管道: 读端=%d, 写端=%d\n", pipefd[0], pipefd[1]);
    
    // 尝试从空的非阻塞管道读取
    char buffer[10];
    ssize_t bytes_read = read(pipefd[0], buffer, sizeof(buffer));
    if (bytes_read == -1) {
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            printf("从空的非阻塞管道读取: %s\n", strerror(errno));
            printf("说明: 非阻塞模式下,没有数据时立即返回错误\n");
        } else {
            perror("读取失败");
        }
    }
    
    close(pipefd[0]);
    close(pipefd[1]);
#else
    printf("pipe2 在此系统上不可用\n");
#endif
    
    printf("\n");
}

int main() {
    printf("管道 (pipe) 函数演示程序\n");
    printf("当前进程 PID: %d\n\n", getpid());
    
    demonstrate_pipe_errors();
    demonstrate_pipe_characteristics();
    // demonstrate_pipe_capacity(); // 这个演示可能需要较长时间,可选择性运行
    demonstrate_pipe2_and_nonblocking();
    
    printf("=== 总结 ===\n");
    printf("管道 (pipe) 关键知识点:\n");
    printf("1. 单向通信: 数据只能从写端流向读端\n");
    printf("2. 亲缘进程: 通常用于父子进程通信\n");
    printf("3. 阻塞特性: 默认情况下读写可能阻塞\n");
    printf("4. 文件结束: 当所有写端关闭时,读端返回 0\n");
    printf("5. 缓冲机制: 内核提供缓冲区存储数据\n");
    printf("6. 错误处理: 注意 EFAULT, EMFILE, ENFILE 等错误\n");
    printf("7. 资源清理: 使用完后必须关闭文件描述符\n");
    printf("8. 双向通信: 需要创建两个管道\n\n");
    
    printf("最佳实践:\n");
    printf("- 及时关闭不需要的管道端\n");
    printf("- 正确处理读写返回值\n");
    printf("- 考虑使用 pipe2 设置 O_CLOEXEC 标志\n");
    printf("- 对于复杂通信,考虑使用命名管道 (FIFO) 或套接字\n");
    
    return 0;
}

代码解释:

1. demonstrate_pipe_errors 演示了传递无效参数给 pipe() 的错误处理。
2. demonstrate_pipe_characteristics 演示了管道的基本读写行为和文件结束条件。
3. demonstrate_pipe_capacity 演示了管道的容量限制和阻塞行为(注释掉了,因为可能运行时间较长)。
4. demonstrate_pipe2_and_nonblocking 演示了 pipe2 函数和非阻塞模式的使用。
5. main 函数协调各个演示部分,并在最后总结关键知识点。

编译和运行:

# 编译示例
gcc -o pipe_example1 pipe_example1.c
gcc -o pipe_example2 pipe_example2.c
gcc -o pipe_example3 pipe_example3.c

# 运行示例
./pipe_example1
./pipe_example2
./pipe_example3

总结:

pipe 函数是 Linux 进程间通信的基础工具之一。它简单高效,特别适合父子进程间的数据传递。理解其单向性、阻塞性和缓冲机制对于正确使用管道至关重要。在实际编程中,要注意及时关闭不需要的文件描述符,正确处理各种返回值和错误情况,并根据需要考虑使用 pipe2 或其他更高级的 IPC 机制。

发表在 linux文章 | 留下评论

getcpu系统调用及示例

getcpu – 获取当前 CPU 和 NUMA 节点信息]()

1. 函数介绍

getcpu 是一个 Linux 系统调用,用于获取当前线程正在运行的 CPU 核心编号和 NUMA(Non-Uniform Memory Access)节点编号。这个函数对于性能分析、负载均衡、线程亲和性设置等场景非常有用。

NUMA 是一种多处理器计算机内存设计,其中内存访问时间取决于内存相对于处理器的位置。了解当前线程在哪个 CPU 核心和 NUMA 节点上运行,有助于优化程序性能。

2. 函数原型

#define _GNU_SOURCE
#include <linux/getcpu.h>
#include <sys/syscall.h>
#include <unistd.h>

long getcpu(unsigned *cpu, unsigned *node, struct getcpu_cache *tcache);

注意:该函数不是标准 C 库的一部分,需要通过 syscall() 调用或者包含 glibc 2.29+ 版本后才能直接调用。

3. 功能

获取当前线程正在运行的 CPU 核心编号和 NUMA 节点编号。这是一个轻量级的操作,通常用于性能监控和调度优化。

4. 参数

  • unsigned *cpu: 指向存储 CPU 核心编号的变量的指针
    • 如果为 NULL:不返回 CPU 信息
    • 如果非 NULL:存储当前 CPU 核心编号(从 0 开始)
  • unsigned *node: 指向存储 NUMA 节点编号的变量的指针
    • 如果为 NULL:不返回 NUMA 节点信息
    • 如果非 NULL:存储当前 NUMA 节点编号(从 0 开始)
  • struct getcpu_cache *tcache: 缓存结构体指针,用于优化性能
    • 通常传入 NULL(内核会使用默认缓存机制)
    • 主要供内核内部使用

5. 返回值

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

6. 常见 errno 错误码

  • EFAULT: 指针参数指向无效内存地址
  • EINVAL: 无效参数
  • ENOSYS: 系统不支持该功能

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

  • sched_getcpu(): glibc 提供的获取当前 CPU 的便捷函数
  • sched_setaffinity(): 设置进程 CPU 亲和性
  • sched_getaffinity(): 获取进程 CPU 亲和性
  • pthread_setaffinity_np(): 设置线程 CPU 亲和性
  • pthread_getaffinity_np(): 获取线程 CPU 亲和性
  • /proc/cpuinfo: 查看 CPU 信息
  • /sys/devices/system/cpu/: CPU 拓扑信息
  • numactl: NUMA 控制工具

8. 示例代码

示例1:基本使用 – 获取当前 CPU 和 NUMA 节点信息

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/getcpu.h>
#include <errno.h>
#include <string.h>

#ifndef SYS_getcpu
# define SYS_getcpu 309  // x86_64 架构下的系统调用号
#endif

int main() {
    unsigned cpu, node;
    long result;
    
    printf("=== 获取当前 CPU 和 NUMA 节点信息 ===\n");
    
    // 使用 syscall 调用 getcpu
    result = syscall(SYS_getcpu, &cpu, &node, NULL);
    
    if (result == -1) {
        printf("getcpu 调用失败: %s\n", strerror(errno));
        return 1;
    }
    
    printf("当前线程运行信息:\n");
    printf("  CPU 核心编号: %u\n", cpu);
    printf("  NUMA 节点编号: %u\n", node);
    
    // 验证系统 CPU 数量
    long nprocs = sysconf(_SC_NPROCESSORS_ONLN);
    printf("  系统在线 CPU 数: %ld\n", nprocs);
    
    if (cpu >= nprocs) {
        printf("  警告: CPU 编号超出系统 CPU 数范围\n");
    }
    
    return 0;
}

示例2:使用 glibc 提供的 sched_getcpu 函数

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <errno.h>
#include <string.h>

int main() {
    int cpu;
    
    printf("=== 使用 sched_getcpu 获取 CPU 信息 ===\n");
    
    cpu = sched_getcpu();
    if (cpu == -1) {
        perror("sched_getcpu 调用失败");
        return 1;
    }
    
    printf("当前 CPU 核心编号: %d\n", cpu);
    
    // 获取系统信息进行验证
    long nprocs_conf = sysconf(_SC_NPROCESSORS_CONF);
    long nprocs_onln = sysconf(_SC_NPROCESSORS_ONLN);
    
    printf("系统配置 CPU 数: %ld\n", nprocs_conf);
    printf("系统在线 CPU 数: %ld\n", nprocs_onln);
    
    if (cpu >= nprocs_onln) {
        printf("警告: CPU 编号超出在线 CPU 范围\n");
    }
    
    return 0;
}

示例3:监控 CPU 迁移情况

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/getcpu.h>
#include <sched.h>
#include <time.h>

#ifndef SYS_getcpu
# define SYS_getcpu 309
#endif

void monitor_cpu_migration(int duration_seconds) {
    unsigned cpu, node;
    unsigned last_cpu = (unsigned)-1;
    unsigned last_node = (unsigned)-1;
    int migrations = 0;
    time_t start_time = time(NULL);
    time_t current_time;
    
    printf("开始监控 CPU 迁移情况 (%d 秒)...\n", duration_seconds);
    printf("%-10s %-6s %-6s %-15s\n", "时间", "CPU", "Node", "状态");
    printf("%-10s %-6s %-6s %-15s\n", "----", "---", "----", "----");
    
    while ((current_time = time(NULL)) - start_time < duration_seconds) {
        if (syscall(SYS_getcpu, &cpu, &node, NULL) == 0) {
            char status[20] = "运行中";
            
            if (last_cpu != (unsigned)-1) {
                if (cpu != last_cpu) {
                    snprintf(status, sizeof(status), "CPU迁移 %u->%u", last_cpu, cpu);
                    migrations++;
                } else if (node != last_node) {
                    snprintf(status, sizeof(status), "Node迁移 %u->%u", last_node, node);
                    migrations++;
                }
            }
            
            printf("%-10ld %-6u %-6u %-15s\n", 
                   current_time - start_time, cpu, node, status);
            
            last_cpu = cpu;
            last_node = node;
        }
        
        usleep(500000);  // 休眠 0.5 秒
    }
    
    printf("\n监控结束:\n");
    printf("  总迁移次数: %d\n", migrations);
    printf("  平均迁移间隔: %.2f 秒\n", 
           migrations > 0 ? (double)duration_seconds / migrations : 0.0);
}

int main() {
    printf("=== CPU 迁移监控演示 ===\n");
    
    // 获取初始信息
    unsigned cpu, node;
    if (syscall(SYS_getcpu, &cpu, &node, NULL) == 0) {
        printf("初始位置: CPU %u, Node %u\n", cpu, node);
    }
    
    // 监控 10 秒钟的 CPU 迁移情况
    monitor_cpu_migration(10);
    
    return 0;
}

示例4:多线程环境下的 CPU 信息

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <linux/getcpu.h>
#include <sched.h>

#ifndef SYS_getcpu
# define SYS_getcpu 309
#endif

void* thread_function(void *arg) {
    int thread_id = *(int*)arg;
    unsigned cpu, node;
    cpu_set_t cpuset;
    int ret;
    
    // 获取当前线程的 CPU 信息
    if (syscall(SYS_getcpu, &cpu, &node, NULL) == 0) {
        printf("线程 %d: 初始位置 CPU %u, Node %u\n", thread_id, cpu, node);
    }
    
    // 获取当前 CPU 亲和性
    ret = pthread_getaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
    if (ret == 0) {
        printf("线程 %d: 可运行在 CPU ", thread_id);
        for (int i = 0; i < CPU_SETSIZE; i++) {
            if (CPU_ISSET(i, &cpuset)) {
                printf("%d ", i);
            }
        }
        printf("\n");
    }
    
    // 执行一些工作
    for (int i = 0; i < 5; i++) {
        sleep(1);
        if (syscall(SYS_getcpu, &cpu, &node, NULL) == 0) {
            printf("线程 %d: 当前位置 CPU %u, Node %u\n", thread_id, cpu, node);
        }
    }
    
    return NULL;
}

int main() {
    pthread_t threads[3];
    int thread_ids[3] = {1, 2, 3};
    
    printf("=== 多线程 CPU 信息演示 ===\n");
    
    // 创建多个线程
    for (int i = 0; i < 3; i++) {
        if (pthread_create(&threads[i], NULL, thread_function, &thread_ids[i]) != 0) {
            perror("创建线程失败");
            return 1;
        }
    }
    
    // 等待所有线程完成
    for (int i = 0; i < 3; i++) {
        pthread_join(threads[i], NULL);
    }
    
    printf("所有线程执行完成\n");
    
    return 0;
}

9. NUMA 系统信息查看

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void print_numa_info() {
    FILE *fp;
    char line[256];
    
    printf("\n=== NUMA 系统信息 ===\n");
    
    // 检查 NUMA 支持
    if (access("/proc/numa_maps", F_OK) == 0) {
        printf("系统支持 NUMA\n");
    } else {
        printf("系统不支持 NUMA 或无访问权限\n");
        return;
    }
    
    // 查看 NUMA 节点信息
    fp = fopen("/sys/devices/system/node/online", "r");
    if (fp) {
        if (fgets(line, sizeof(line), fp)) {
            printf("在线 NUMA 节点: %s", line);
        }
        fclose(fp);
    }
    
    // 查看 CPU 拓扑
    system("lscpu | grep -E 'NUMA|CPU(s)'");
}

10. 性能优化应用场景

getcpu 在以下场景中非常有用:

场景1:线程绑定优化

#include <sched.h>
#include <pthread.h>

void bind_thread_to_cpu(int target_cpu) {
    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(target_cpu, &cpuset);
    pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
}

场景2:内存分配优化

#include <numa.h>

void* allocate_local_memory(size_t size) {
    if (numa_available() == -1) {
        return malloc(size);  // NUMA 不可用,使用普通分配
    }
    return numa_alloc_local(size);  // 分配在本地 NUMA 节点
}

11. 实际系统中的应用

1. 数据库系统: 将查询线程绑定到特定 CPU 以提高缓存命中率
2. Web 服务器: 均衡分配请求处理线程到不同 CPU 核心
3. 高性能计算: 优化 MPI 进程的 CPU 和 NUMA 布局
4. 实时系统: 确保关键任务在指定 CPU 上运行
5. 容器技术: Docker 和 Kubernetes 中的 CPU 资源管理

总结

getcpu 是一个重要的系统调用,用于获取当前线程的 CPU 和 NUMA 节点信息。关键要点:

1. 轻量级操作: 快速获取当前执行环境信息
2. NUMA 感知: 支持现代多核 NUMA 系统
3. 性能优化: 为调度和内存访问优化提供基础信息
4. 多线程支持: 在并发环境中准确反映线程位置
5. 系统监控: 用于负载均衡和性能分析工具

在编写高性能应用程序时,了解和利用 CPU 和 NUMA 信息可以显著提升程序性能,特别是在多核和 NUMA 系统上。】

CSDN-LINK:https://blog.csdn.net/timberwolf007/article/details/150473820?sharetype=blogdetail&sharerId=150473820&sharerefer=PC&sharesource=zidier215&spm=1011.2480.3001.8118

发表在 linux文章 | 留下评论

flock系统调用及示例

flock – 文件锁flock系统调用及示例

函数介绍

flock系统调用用于对文件进行加锁操作,实现进程间的文件访问同步。文件锁可以防止多个进程同时修改同一文件,保证数据的一致性。
(https://www.calcguide.tech/2025/08/17/flock%e7%b3%bb%e7%bb%9f%e8%b0%83%e7%94%a8%e5%8f%8a%e7%a4%ba%e4%be%8b/)

函数原型

#include <sys/file.h>

int flock(int fd, int operation);

功能

对指定文件描述符对应的文件进行加锁或解锁操作。

参数

  • int fd: 文件描述符
  • int operation: 锁操作类型
    • LOCK_SH: 共享锁(读锁),多个进程可以同时持有
    • LOCK_EX: 排他锁(写锁),只能有一个进程持有
    • LOCK_UN: 解锁
    • LOCK_NB: 非阻塞模式(与上述操作组合使用)

返回值

  • 成功时返回0
  • 失败时返回-1,并设置errno:
    • EAGAIN/EWOULDBLOCK: 非阻塞模式下无法获取锁
    • EBADF: 文件描述符无效
    • EINTR: 系统调用被信号中断
    • EINVAL: 参数无效
    • EOPNOTSUPP: 文件系统不支持锁

相似函数

  • fcntl(): 更灵活的文件锁操作
  • lockf(): POSIX文件锁接口

示例代码

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

int main() {
    int fd;
    pid_t pid;
    
    printf("=== Flock函数示例 ===\n");
    
    // 创建测试文件
    fd = open("test_flock.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        exit(EXIT_FAILURE);
    }
    printf("成功创建测试文件,文件描述符: %d\n", fd);
    
    // 写入初始数据
    const char *initial_data = "Initial data for flock test\n";
    if (write(fd, initial_data, strlen(initial_data)) == -1) {
        perror("写入初始数据失败");
        close(fd);
        exit(EXIT_FAILURE);
    }
    
    // 示例1: 基本的排他锁操作
    printf("\n示例1: 基本的排他锁操作\n");
    
    // 获取排他锁
    if (flock(fd, LOCK_EX) == -1) {
        perror("获取排他锁失败");
    } else {
        printf("  成功获取排他锁\n");
        
        // 在锁保护下写入数据
        const char *exclusive_data = "Data written with exclusive lock\n";
        if (write(fd, exclusive_data, strlen(exclusive_data)) == -1) {
            perror("  写入数据失败");
        } else {
            printf("  成功写入数据到文件\n");
        }
        
        // 释放锁
        if (flock(fd, LOCK_UN) == -1) {
            perror("  释放锁失败");
        } else {
            printf("  成功释放排他锁\n");
        }
    }
    
    // 示例2: 共享锁操作
    printf("\n示例2: 共享锁操作\n");
    
    // 获取共享锁
    if (flock(fd, LOCK_SH) == -1) {
        perror("获取共享锁失败");
    } else {
        printf("  成功获取共享锁\n");
        
        // 在锁保护下读取数据
        char buffer[200];
        lseek(fd, 0, SEEK_SET);
        ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            printf("  读取文件内容:\n%s", buffer);
        }
        
        // 释放锁
        if (flock(fd, LOCK_UN) == -1) {
            perror("  释放共享锁失败");
        } else {
            printf("  成功释放共享锁\n");
        }
    }
    
    // 示例3: 非阻塞锁操作
    printf("\n示例3: 非阻塞锁操作\n");
    
    // 先获取一个排他锁
    if (flock(fd, LOCK_EX) == -1) {
        perror("获取排他锁失败");
    } else {
        printf("  进程已持有排他锁\n");
        
        // 尝试非阻塞获取另一个排他锁(应该失败)
        if (flock(fd, LOCK_EX | LOCK_NB) == -1) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                printf("  非阻塞获取排他锁: 立即返回EAGAIN(锁被占用)\n");
            } else {
                perror("  非阻塞获取锁失败");
            }
        } else {
            printf("  非阻塞获取排他锁成功(不应该发生)\n");
            flock(fd, LOCK_UN);  // 释放意外获取的锁
        }
        
        // 尝试非阻塞获取共享锁(也应该失败,因为有排他锁)
        if (flock(fd, LOCK_SH | LOCK_NB) == -1) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                printf("  非阻塞获取共享锁: 立即返回EAGAIN(有排他锁)\n");
            } else {
                perror("  非阻塞获取共享锁失败");
            }
        } else {
            printf("  非阻塞获取共享锁成功(不应该发生)\n");
            flock(fd, LOCK_UN);  // 释放意外获取的锁
        }
        
        // 释放锁
        flock(fd, LOCK_UN);
        printf("  释放排他锁\n");
    }
    
    // 示例4: 多进程锁演示
    printf("\n示例4: 多进程锁演示\n");
    
    pid = fork();
    if (pid == -1) {
        perror("fork失败");
        close(fd);
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // 子进程
        printf("  子进程 %d 开始执行\n", getpid());
        
        // 子进程打开同一个文件
        int child_fd = open("test_flock.txt", O_RDWR);
        if (child_fd == -1) {
            perror("  子进程打开文件失败");
            exit(EXIT_FAILURE);
        }
        
        printf("  子进程尝试获取排他锁...\n");
        if (flock(child_fd, LOCK_EX) == -1) {
            perror("  子进程获取排他锁失败");
        } else {
            printf("  子进程成功获取排他锁\n");
            
            // 写入子进程数据
            char child_data[100];
            sprintf(child_data, "Data from child process %d\n", getpid());
            if (write(child_fd, child_data, strlen(child_data)) == -1) {
                perror("  子进程写入数据失败");
            } else {
                printf("  子进程写入数据成功\n");
            }
            
            sleep(3);  // 持有锁3秒
            
            // 释放锁
            if (flock(child_fd, LOCK_UN) == -1) {
                perror("  子进程释放锁失败");
            } else {
                printf("  子进程释放排他锁\n");
            }
        }
        
        close(child_fd);
        exit(EXIT_SUCCESS);
    } else {
        // 父进程
        sleep(1);  // 让子进程先运行
        
        printf("  父进程 %d 尝试获取排他锁...\n", getpid());
        
        // 父进程尝试获取排他锁(会被阻塞直到子进程释放)
        printf("  父进程获取排他锁(会被阻塞)...\n");
        if (flock(fd, LOCK_EX) == -1) {
            perror("  父进程获取排他锁失败");
        } else {
            printf("  父进程成功获取排他锁(子进程已释放)\n");
            
            // 写入父进程数据
            char parent_data[100];
            sprintf(parent_data, "Data from parent process %d\n", getpid());
            if (write(fd, parent_data, strlen(parent_data)) == -1) {
                perror("  父进程写入数据失败");
            } else {
                printf("  父进程写入数据成功\n");
            }
            
            // 释放锁
            if (flock(fd, LOCK_UN) == -1) {
                perror("  父进程释放锁失败");
            } else {
                printf("  父进程释放排他锁\n");
            }
        }
        
        // 等待子进程结束
        wait(NULL);
    }
    
    // 示例5: 共享锁并发演示
    printf("\n示例5: 共享锁并发演示\n");
    
    // 创建多个子进程同时获取共享锁
    for (int i = 0; i < 3; i++) {
        pid = fork();
        if (pid == 0) {
            // 子进程
            int child_fd = open("test_flock.txt", O_RDONLY);
            if (child_fd != -1) {
                printf("  子进程 %d 尝试获取共享锁...\n", getpid());
                
                if (flock(child_fd, LOCK_SH) == 0) {
                    printf("  子进程 %d 成功获取共享锁\n", getpid());
                    
                    // 读取数据
                    char read_buffer[200];
                    lseek(child_fd, 0, SEEK_SET);
                    ssize_t bytes_read = read(child_fd, read_buffer, sizeof(read_buffer) - 1);
                    if (bytes_read > 0) {
                        read_buffer[bytes_read] = '\0';
                        printf("  子进程 %d 读取数据成功\n", getpid());
                    }
                    
                    sleep(2);  // 持有共享锁2秒
                    
                    flock(child_fd, LOCK_UN);
                    printf("  子进程 %d 释放共享锁\n", getpid());
                }
                
                close(child_fd);
            }
            exit(EXIT_SUCCESS);
        }
    }
    
    // 等待所有子进程结束
    for (int i = 0; i < 3; i++) {
        wait(NULL);
    }
    
    // 示例6: 锁的继承和关闭行为
    printf("\n示例6: 锁的继承和关闭行为\n");
    
    if (flock(fd, LOCK_EX) == 0) {
        printf("  进程持有排他锁\n");
        
        // 复制文件描述符
        int dup_fd = dup(fd);
        if (dup_fd != -1) {
            printf("  复制文件描述符: %d -> %d\n", fd, dup_fd);
            
            // 使用复制的fd释放锁
            if (flock(dup_fd, LOCK_UN) == 0) {
                printf("  使用复制的fd释放锁成功\n");
            }
            
            close(dup_fd);
        }
        
        // 重新获取锁
        if (flock(fd, LOCK_EX) == 0) {
            printf("  重新获取锁成功\n");
            flock(fd, LOCK_UN);
        }
    }
    
    // 示例7: 错误处理演示
    printf("\n示例7: 错误处理演示\n");
    
    // 尝试对无效文件描述符加锁
    if (flock(999, LOCK_EX) == -1) {
        printf("  对无效文件描述符加锁: %s\n", strerror(errno));
    }
    
    // 清理资源
    printf("\n清理资源...\n");
    if (close(fd) == -1) {
        perror("关闭文件失败");
    } else {
        printf("成功关闭文件描述符 %d\n", fd);
    }
    
    // 删除测试文件
    if (unlink("test_flock.txt") == -1) {
        perror("删除测试文件失败");
    } else {
        printf("成功删除测试文件\n");
    }
    
    return 0;
}


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

int main() {
    int fd;
    pid_t pid;
    
    printf("=== Flock函数示例 ===\n");
    
    // 创建测试文件
    fd = open("test_flock.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        exit(EXIT_FAILURE);
    }
    printf("成功创建测试文件,文件描述符: %d\n", fd);
    
    // 写入初始数据
    const char *initial_data = "Initial data for flock test\n";
    if (write(fd, initial_data, strlen(initial_data)) == -1) {
        perror("写入初始数据失败");
        close(fd);
        exit(EXIT_FAILURE);
    }
    
    // 示例1: 基本的排他锁操作
    printf("\n示例1: 基本的排他锁操作\n");
    
    // 获取排他锁
    if (flock(fd, LOCK_EX) == -1) {
        perror("获取排他锁失败");
    } else {
        printf("  成功获取排他锁\n");
        
        // 在锁保护下写入数据
        const char *exclusive_data = "Data written with exclusive lock\n";
        if (write(fd, exclusive_data, strlen(exclusive_data)) == -1) {
            perror("  写入数据失败");
        } else {
            printf("  成功写入数据到文件\n");
        }
        
        // 释放锁
        if (flock(fd, LOCK_UN) == -1) {
            perror("  释放锁失败");
        } else {
            printf("  成功释放排他锁\n");
        }
    }
    
    // 示例2: 共享锁操作
    printf("\n示例2: 共享锁操作\n");
    
    // 获取共享锁
    if (flock(fd, LOCK_SH) == -1) {
        perror("获取共享锁失败");
    } else {
        printf("  成功获取共享锁\n");
        
        // 在锁保护下读取数据
        char buffer[200];
        lseek(fd, 0, SEEK_SET);
        ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            printf("  读取文件内容:\n%s", buffer);
        }
        
        // 释放锁
        if (flock(fd, LOCK_UN) == -1) {
            perror("  释放共享锁失败");
        } else {
            printf("  成功释放共享锁\n");
        }
    }
    
    // 示例3: 非阻塞锁操作
    printf("\n示例3: 非阻塞锁操作\n");
    
    // 先获取一个排他锁
    if (flock(fd, LOCK_EX) == -1) {
        perror("获取排他锁失败");
    } else {
        printf("  进程已持有排他锁\n");
        
        // 尝试非阻塞获取另一个排他锁(应该失败)
        if (flock(fd, LOCK_EX | LOCK_NB) == -1) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                printf("  非阻塞获取排他锁: 立即返回EAGAIN(锁被占用)\n");
            } else {
                perror("  非阻塞获取锁失败");
            }
        } else {
            printf("  非阻塞获取排他锁成功(不应该发生)\n");
            flock(fd, LOCK_UN);  // 释放意外获取的锁
        }
        
        // 尝试非阻塞获取共享锁(也应该失败,因为有排他锁)
        if (flock(fd, LOCK_SH | LOCK_NB) == -1) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                printf("  非阻塞获取共享锁: 立即返回EAGAIN(有排他锁)\n");
            } else {
                perror("  非阻塞获取共享锁失败");
            }
        } else {
            printf("  非阻塞获取共享锁成功(不应该发生)\n");
            flock(fd, LOCK_UN);  // 释放意外获取的锁
        }
        
        // 释放锁
        flock(fd, LOCK_UN);
        printf("  释放排他锁\n");
    }
    
    // 示例4: 多进程锁演示
    printf("\n示例4: 多进程锁演示\n");
    
    pid = fork();
    if (pid == -1) {
        perror("fork失败");
        close(fd);
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // 子进程
        printf("  子进程 %d 开始执行\n", getpid());
        
        // 子进程打开同一个文件
        int child_fd = open("test_flock.txt", O_RDWR);
        if (child_fd == -1) {
            perror("  子进程打开文件失败");
            exit(EXIT_FAILURE);
        }
        
        printf("  子进程尝试获取排他锁...\n");
        if (flock(child_fd, LOCK_EX) == -1) {
            perror("  子进程获取排他锁失败");
        } else {
            printf("  子进程成功获取排他锁\n");
            
            // 写入子进程数据
            char child_data[100];
            sprintf(child_data, "Data from child process %d\n", getpid());
            if (write(child_fd, child_data, strlen(child_data)) == -1) {
                perror("  子进程写入数据失败");
            } else {
                printf("  子进程写入数据成功\n");
            }
            
            sleep(3);  // 持有锁3秒
            
            // 释放锁
            if (flock(child_fd, LOCK_UN) == -1) {
                perror("  子进程释放锁失败");
            } else {
                printf("  子进程释放排他锁\n");
            }
        }
        
        close(child_fd);
        exit(EXIT_SUCCESS);
    } else {
        // 父进程
        sleep(1);  // 让子进程先运行
        
        printf("  父进程 %d 尝试获取排他锁...\n", getpid());
        
        // 父进程尝试获取排他锁(会被阻塞直到子进程释放)
        printf("  父进程获取排他锁(会被阻塞)...\n");
        if (flock(fd, LOCK_EX) == -1) {
            perror("  父进程获取排他锁失败");
        } else {
            printf("  父进程成功获取排他锁(子进程已释放)\n");
            
            // 写入父进程数据
            char parent_data[100];
            sprintf(parent_data, "Data from parent process %d\n", getpid());
            if (write(fd, parent_data, strlen(parent_data)) == -1) {
                perror("  父进程写入数据失败");
            } else {
                printf("  父进程写入数据成功\n");
            }
            
            // 释放锁
            if (flock(fd, LOCK_UN) == -1) {
                perror("  父进程释放锁失败");
            } else {
                printf("  父进程释放排他锁\n");
            }
        }
        
        // 等待子进程结束
        wait(NULL);
    }
    
    // 示例5: 共享锁并发演示
    printf("\n示例5: 共享锁并发演示\n");
    
    // 创建多个子进程同时获取共享锁
    for (int i = 0; i < 3; i++) {
        pid = fork();
        if (pid == 0) {
            // 子进程
            int child_fd = open("test_flock.txt", O_RDONLY);
            if (child_fd != -1) {
                printf("  子进程 %d 尝试获取共享锁...\n", getpid());
                
                if (flock(child_fd, LOCK_SH) == 0) {
                    printf("  子进程 %d 成功获取共享锁\n", getpid());
                    
                    // 读取数据
                    char read_buffer[200];
                    lseek(child_fd, 0, SEEK_SET);
                    ssize_t bytes_read = read(child_fd, read_buffer, sizeof(read_buffer) - 1);
                    if (bytes_read > 0) {
                        read_buffer[bytes_read] = '\0';
                        printf("  子进程 %d 读取数据成功\n", getpid());
                    }
                    
                    sleep(2);  // 持有共享锁2秒
                    
                    flock(child_fd, LOCK_UN);
                    printf("  子进程 %d 释放共享锁\n", getpid());
                }
                
                close(child_fd);
            }
            exit(EXIT_SUCCESS);
        }
    }
    
    // 等待所有子进程结束
    for (int i = 0; i < 3; i++) {
        wait(NULL);
    }
    
    // 示例6: 锁的继承和关闭行为
    printf("\n示例6: 锁的继承和关闭行为\n");
    
    if (flock(fd, LOCK_EX) == 0) {
        printf("  进程持有排他锁\n");
        
        // 复制文件描述符
        int dup_fd = dup(fd);
        if (dup_fd != -1) {
            printf("  复制文件描述符: %d -> %d\n", fd, dup_fd);
            
            // 使用复制的fd释放锁
            if (flock(dup_fd, LOCK_UN) == 0) {
                printf("  使用复制的fd释放锁成功\n");
            }
            
            close(dup_fd);
        }
        
        // 重新获取锁
        if (flock(fd, LOCK_EX) == 0) {
            printf("  重新获取锁成功\n");
            flock(fd, LOCK_UN);
        }
    }
    
    // 示例7: 错误处理演示
    printf("\n示例7: 错误处理演示\n");
    
    // 尝试对无效文件描述符加锁
    if (flock(999, LOCK_EX) == -1) {
        printf("  对无效文件描述符加锁: %s\n", strerror(errno));
    }
    
    // 清理资源
    printf("\n清理资源...\n");
    if (close(fd) == -1) {
        perror("关闭文件失败");
    } else {
        printf("成功关闭文件描述符 %d\n", fd);
    }
    
    // 删除测试文件
    if (unlink("test_flock.txt") == -1) {
        perror("删除测试文件失败");
    } else {
        printf("成功删除测试文件\n");
    }
    
    return 0;
}
发表在 linux文章 | 标签为 | 留下评论

Python二进制文件编码探测工具

Python二进制文件编码探测工具

背景
实现基于python语言cchardet库的二进制文件分析程序,按照预设分段参数对文件进行读取和cchardet的文本编码探测。脚本具备跳过文件头n字节,按照m字节分段二进制文件及分段后数据连续4字节探测功能。
结果输出会展示每段的序号,偏移起始,片内置信度识别偏移字节,片大小,编码方式,置信度,高置信度提示信息字段;

如何使用脚本:

# 1. 基本用法:分析整个文件
python encoding_detector.py myfile.bin

# 2. 指定块大小
python encoding_detector.py -s 512 myfile.bin

# 3. 跳过每个块的前 10 个字节
python encoding_detector.py -s 100 -h 10 myfile.bin

# 4. 从文件偏移 1116 开始分析
python encoding_detector.py -s 100 -o 1116 ../ftp-pcap/ftp-utf8-long.pcap

# 5. 结合使用:从偏移 1000 开始,每块 256 字节,跳过每块前 20 字节
python encoding_detector.py -s 256 -h 20 -o 1000 myfile.bin

# 6. 通过管道输入
cat myfile.bin | python encoding_detector.py -s 512

Python脚本是实现

#!/usr/bin/env python3
import cchardet
import sys
import os

def print_hex(data, width=16):
    """以十六进制和ASCII形式打印字节数据"""
    for i in range(0, len(data), width):
        # 十六进制部分
        hex_part = ' '.join(f'{byte:02x}' for byte in data[i:i+width])
        # ASCII部分 (可打印字符或'.')
        ascii_part = ''.join(chr(byte) if 32 <= byte <= 126 else '.' for byte in data[i:i+width])
        # 打印地址偏移、十六进制和ASCII
        print(f'{i:08x}: {hex_part:<{width*3}} |{ascii_part}|')

def detect_chunks_from_file(filename, chunk_size=1024, from_head_bytes=0, from_file_offset=0):
    """
    将文件按指定大小切块,并对每个块进行编码检测。
    如果检测置信度为0,则尝试偏移1-4字节重新检测。
    from_file_offset: 从文件的哪个字节偏移开始读取。
    """
    if not os.path.exists(filename):
        print(f"Error: File '{filename}' does not exist.", file=sys.stderr)
        return

    try:
        file_size = os.path.getsize(filename)
        print(f"Analyzing file: {filename} (Total size: {file_size} bytes)")
        print(f"Chunk size: {chunk_size} bytes")
        if from_head_bytes > 0:
            print(f"Skipping first {from_head_bytes} bytes of each chunk for detection.")
        if from_file_offset > 0:
            print(f"Starting analysis from file offset: {from_file_offset}")
        print("-" * 50)

        with open(filename, 'rb') as f:
            # 定位到文件的起始偏移
            if from_file_offset > 0:
                f.seek(from_file_offset)
            
            chunk_number = 0
            while True:
                chunk_data = f.read(chunk_size)
                if not chunk_
                    break

                # 计算当前块在原始文件中的基础偏移量
                offset = from_file_offset + chunk_number * chunk_size

                # 裁剪用于检测的数据(跳过头部字节)
                detection_data = chunk_data[from_head_bytes:] if len(chunk_data) > from_head_bytes else b''

                # --- 初始检测 ---
                encoding = None
                confidence = 0.0
                offset_by_used = 0 # 记录最终使用的偏移量

                if len(detection_data) > 0:
                    try:
                        result = cchardet.detect(detection_data)
                        if isinstance(result, dict):
                            encoding = result.get('encoding')
                            temp_confidence = result.get('confidence')
                            if temp_confidence is None:
                                confidence = 0.0
                            else:
                                confidence = temp_confidence
                            
                            if encoding is not None and not isinstance(encoding, str):
                                print(f"Warning: Unexpected encoding type in chunk {chunk_number}: {type(encoding)}", file=sys.stderr)
                                encoding = str(encoding) if encoding is not None else None
                        else:
                            print(f"Warning: cchardet returned unexpected type in chunk {chunk_number}: {type(result)}", file=sys.stderr)
                    except Exception as e:
                        print(f"Warning: cchardet failed on chunk {chunk_number}: {e}", file=sys.stderr)
                        encoding = "Error"
                        confidence = 0.0

                # --- 偏移优化逻辑 ---
                max_offset_attempts = 4
                if confidence == 0.0 and len(detection_data) > max_offset_attempts:
                    for offset_by in range(1, max_offset_attempts + 1):
                        if len(detection_data) > offset_by:
                            adjusted_detection_data = detection_data[offset_by:]
                            if len(adjusted_detection_data) > 0:
                                try:
                                    adjusted_result = cchardet.detect(adjusted_detection_data)
                                    if isinstance(adjusted_result, dict):
                                        adjusted_confidence = adjusted_result.get('confidence')
                                        if adjusted_confidence is None:
                                            adjusted_confidence = 0.0
                                        
                                        if adjusted_confidence > confidence:
                                            encoding = adjusted_result.get('encoding')
                                            confidence = adjusted_confidence
                                            offset_by_used = offset_by # 记录使用的偏移量
                                            
                                            if confidence > 0.0:
                                                break
                                except Exception:
                                    pass
                        else:
                            break

                # --- 格式化输出 ---
                encoding_display = encoding if encoding is not None else "N/A"
                output_line = (f"Chunk {chunk_number:4d} | Offset {offset:8d} | "
                               f"offset_by {offset_by_used:2d} | "
                               f"Size {len(chunk_data):4d} | "
                               f"Encoding: {encoding_display:>12} | "
                               f"Confidence: {confidence:6.4f}")
                
                # 可以根据置信度调整输出格式,例如高亮高置信度结果
                if confidence >= 0.75:
                     print(output_line) # 或用不同颜色/符号标记,这里简化为普通打印
                else:
                     print(output_line)

                # 如果置信度为0,可以选择打印数据内容(当前被注释掉)
                # if confidence == 0.0 and len(chunk_data) > 0:
                #     print ("\n")
                #     print_hex(chunk_data)
                #     print ("\n")
                    
                chunk_number += 1

            # 文件读取结束后的检查
            # f.tell() 在 seek 后返回的是绝对位置
            absolute_tell = f.tell()
            if absolute_tell < file_size:
                 print(f"Warning: Stopped reading before end of file '{filename}'. "
                      f"Read up to file offset {absolute_tell} bytes out of {file_size} bytes.", file=sys.stderr)

    except IOError as e:
        print(f"Error reading file '{filename}': {e}", file=sys.stderr)
    except Exception as e:
        print(f"An unexpected error occurred while processing '{filename}': {e}", file=sys.stderr)
    
    print("-" * 50 + f" Analysis of '{filename}' finished. " + "-" * 10 + "\n")


def detect_chunks_from_bytes(data, source_name="Byte Input", chunk_size=1024, from_head_bytes=0):
    """
    将字节数据按指定大小切块,并对每个块进行编码检测。
    如果检测置信度为0,则尝试偏移1-3字节重新检测。
    """
    data_len = len(data)
    print(f"Analyzing data from: {source_name} (Total size: {data_len} bytes)")
    print(f"Chunk size: {chunk_size} bytes")
    if from_head_bytes > 0:
        print(f"Skipping first {from_head_bytes} bytes of each chunk for detection.")
    print("-" * 50)

    if data_len == 0:
        print("Input data is empty.")
        return

    chunk_number = 0
    for i in range(0, data_len, chunk_size):
        chunk_data = data[i:i + chunk_size]
        if not chunk_
            break

        offset = i
        detection_data = chunk_data[from_head_bytes:] if len(chunk_data) > from_head_bytes else b''

        encoding = None
        confidence = 0.0
        
        if len(detection_data) > 0:
            try:
                result = cchardet.detect(detection_data)
                if isinstance(result, dict):
                    encoding = result.get('encoding')
                    temp_confidence = result.get('confidence')
                    if temp_confidence is None:
                        confidence = 0.0
                    else:
                        confidence = temp_confidence
                    
                    if encoding is not None and not isinstance(encoding, str):
                        print(f"Warning: Unexpected encoding type in chunk {chunk_number}: {type(encoding)}", file=sys.stderr)
                        encoding = str(encoding) if encoding is not None else None
                else:
                    print(f"Warning: cchardet returned unexpected type in chunk {chunk_number}: {type(result)}", file=sys.stderr)
            except Exception as e:
                print(f"Warning: cchardet failed on chunk {chunk_number}: {e}", file=sys.stderr)
                encoding = "Error"
                confidence = 0.0

        # --- 偏移优化逻辑 (针对 bytes 输入)---
        max_offset_attempts = 3
        offset_by_used = 0
        if confidence == 0.0 and len(detection_data) > max_offset_attempts:
            for offset_by in range(1, max_offset_attempts + 1):
                if len(detection_data) > offset_by:
                    adjusted_detection_data = detection_data[offset_by:]
                    if len(adjusted_detection_data) > 0:
                        try:
                            adjusted_result = cchardet.detect(adjusted_detection_data)
                            if isinstance(adjusted_result, dict):
                                adjusted_confidence = adjusted_result.get('confidence')
                                if adjusted_confidence is None:
                                    adjusted_confidence = 0.0
                                
                                if adjusted_confidence > confidence:
                                    encoding = adjusted_result.get('encoding')
                                    confidence = adjusted_confidence
                                    offset_by_used = offset_by
                                    
                                    if confidence > 0.0:
                                        break
                        except Exception:
                            pass
                else:
                    break

        # 格式化输出 (bytes 输入也显示 offset_by)
        encoding_display = encoding if encoding is not None else "N/A"
        print(f"Chunk {chunk_number:4d} | Offset {offset:8d} | "
              f"offset_by {offset_by_used:2d} | " # 添加 offset_by 显示
              f"Size {len(chunk_data):4d} | "
              f"Encoding: {encoding_display:>12} | "
              f"Confidence: {confidence:6.4f}")

        # 如果置信度为0,打印数据内容
        # if confidence == 0.0 and len(chunk_data) > 0:
        #     print ("\n")
        #     print_hex(chunk_data)
        #     print ("\n")

        chunk_number += 1

    print("-" * 50 + f" Analysis of '{source_name}' finished. " + "-" * 10 + "\n")


def main():
    """
    主函数,处理命令行参数并调用相应的检测函数。
    """
    if len(sys.argv) < 2:
        print("No filename provided. Reading binary data from STDIN...", file=sys.stderr)
        try:
            data = sys.stdin.buffer.read()
            detect_chunks_from_bytes(data, source_name="STDIN", chunk_size=1024)
        except KeyboardInterrupt:
            print("\nInterrupted by user.", file=sys.stderr)
        except Exception as e:
            print(f"Error reading from STDIN: {e}", file=sys.stderr)
        sys.exit(0)

    # 默认参数
    chunk_size = 1024
    from_head_bytes = 0
    from_file_offset = 0 # 新增默认参数
    filenames = []

    # 解析命令行参数
    i = 1
    while i < len(sys.argv):
        if sys.argv[i] == '-s':
            if i + 1 < len(sys.argv):
                try:
                    chunk_size = int(sys.argv[i + 1])
                    if chunk_size <= 0:
                        raise ValueError("Chunk size must be positive.")
                    i += 2
                except ValueError as e:
                    print(f"Error: Invalid chunk size '-s {sys.argv[i + 1]}': {e}", file=sys.stderr)
                    sys.exit(1)
            else:
                print("Error: Option '-s' requires an argument.", file=sys.stderr)
                sys.exit(1)
        elif sys.argv[i] == '-h':
             if i + 1 < len(sys.argv):
                try:
                    from_head_bytes = int(sys.argv[i + 1])
                    if from_head_bytes < 0:
                        raise ValueError("Head bytes to skip must be non-negative.")
                    i += 2
                except ValueError as e:
                    print(f"Error: Invalid head bytes '-h {sys.argv[i + 1]}': {e}", file=sys.stderr)
                    sys.exit(1)
             else:
                print("Error: Option '-h' requires an argument.", file=sys.stderr)
                sys.exit(1)
        # --- 新增:解析 -o 参数 ---
        elif sys.argv[i] == '-o':
             if i + 1 < len(sys.argv):
                try:
                    from_file_offset = int(sys.argv[i + 1])
                    if from_file_offset < 0:
                        raise ValueError("File offset must be non-negative.")
                    i += 2
                except ValueError as e:
                    print(f"Error: Invalid file offset '-o {sys.argv[i + 1]}': {e}", file=sys.stderr)
                    sys.exit(1)
             else:
                print("Error: Option '-o' requires an argument.", file=sys.stderr)
                sys.exit(1)
        # --- 新增结束 ---
        else:
            filenames.append(sys.argv[i])
            i += 1

    if not filenames:
        print("Error: No filename provided.", file=sys.stderr)
        sys.exit(1)

    # 对每个提供的文件进行处理
    for filename in filenames:
        # --- 修改:传递 from_file_offset 参数 ---
        detect_chunks_from_file(filename, chunk_size, from_head_bytes, from_file_offset)


if __name__ == "__main__":
    main()

关键词(keywords):文本编码,二进制,python文本编码,cchardet

发表在 linux文章 | 留下评论

kcmp系统调用及示例

kcmp 函数详解

1. 函数介绍

kcmp 是 Linux 系统中用于比较两个进程的内核资源的系统调用。可以把 kcmp 想象成”内核级别的资源比较器”——它能够检查两个进程是否共享相同的内核资源(如文件描述符、虚拟内存区域等),就像比较两个人是否使用相同的银行账户或信用卡一样。

在容器化和虚拟化环境中,kcmp 非常有用,因为它可以帮助确定两个进程是否属于同一个容器或命名空间,或者它们之间是否存在资源共享关系。。

2. 函数原型

#include <linux/kcmp.h>
#include <sys/syscall.h>
#include <unistd.h>

int kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2);

3. 功能

kcmp 函数用于比较两个进程的指定内核资源。它可以确定两个进程的特定资源是否指向内核中的同一个对象。。

4. 参数

  • pid1: 第一个进程的进程 ID(0 表示调用进程)
  • pid2: 第二个进程的进程 ID(0 表示调用进程)
  • type: 比较的资源类型
  • idx1: 第一个进程的资源索引(根据 type 而定)
  • idx2: 第二个进程的资源索引(根据 type 而定)

5. 资源类型(type 参数)

类型说明
KCMP_FILE0比较文件描述符
KCMP_VM1比较虚拟内存
KCMP_FILES2比较文件描述符表
KCMP_FS3比较文件系统信息
KCMP_SIGHAND4比较信号处理信息
KCMP_IO5比较 I/O 信息
KCMP_SYSVSEM6比较 System V 信号量

6. 返回值

  • 0: 两个资源相同(指向内核中的同一对象)
  • 1: 两个资源不同
  • 2: 目标进程不存在或无法访问
  • 负值: 错误,设置相应的 errno

7. 错误码

  • EPERM: 权限不足(无法访问目标进程信息)
  • ESRCH: 进程不存在
  • EINVAL: 参数无效
  • EACCES: 访问被拒绝

8. 相似函数或关联函数

  • clone: 创建进程时可以共享资源
  • unshare: 使进程脱离共享资源
  • setns: 加入命名空间
  • /proc 文件系统: 查看进程信息
  • ptrace: 进程跟踪和调试

9. 示例代码

示例1:基础用法 – 比较文件描述符。

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

// kcmp 系统调用包装函数
int kcmp_wrapper(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2) {
    return syscall(SYS_kcmp, pid1, pid2, type, idx1, idx2);
}

// 将 kcmp 结果转换为字符串
const char* kcmp_result_to_string(int result) {
    switch (result) {
        case 0: return "相同";
        case 1: return "不同";
        case 2: return "进程不存在或无法访问";
        default: return "错误";
    }
}

int main() {
    int fd1, fd2, fd3;
    pid_t current_pid = getpid();
    int result;
    
    printf("=== kcmp 基础示例 - 文件描述符比较 ===\n\n");
    
    // 创建测试文件
    fd1 = open("test1.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    fd2 = open("test2.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    fd3 = dup(fd1);  // fd3 是 fd1 的副本
    
    if (fd1 == -1 || fd2 == -1 || fd3 == -1) {
        perror("open/dup");
        return 1;
    }
    
    printf("创建的文件描述符:\n");
    printf("  fd1: %d (test1.txt)\n", fd1);
    printf("  fd2: %d (test2.txt)\n", fd2);
    printf("  fd3: %d (test1.txt 的副本)\n", fd3);
    printf("\n");
    
    // 比较相同的文件描述符
    printf("比较相同文件描述符:\n");
    result = kcmp_wrapper(current_pid, current_pid, KCMP_FILE, fd1, fd1);
    printf("  fd1 vs fd1: %s (结果: %d)\n", kcmp_result_to_string(result), result);
    
    // 比较不同的文件描述符(不同文件)
    printf("比较不同文件的文件描述符:\n");
    result = kcmp_wrapper(current_pid, current_pid, KCMP_FILE, fd1, fd2);
    printf("  fd1 vs fd2: %s (结果: %d)\n", kcmp_result_to_string(result), result);
    
    // 比较相同的文件描述符(通过 dup 创建)
    printf("比较相同文件的文件描述符:\n");
    result = kcmp_wrapper(current_pid, current_pid, KCMP_FILE, fd1, fd3);
    printf("  fd1 vs fd3: %s (结果: %d)\n", kcmp_result_to_string(result), result);
    
    // 比较文件描述符表
    printf("比较文件描述符表:\n");
    result = kcmp_wrapper(current_pid, current_pid, KCMP_FILES, 0, 0);
    printf("  FILES 表: %s (结果: %d)\n", kcmp_result_to_string(result), result);
    
    // 清理资源
    close(fd1);
    close(fd2);
    close(fd3);
    unlink("test1.txt");
    unlink("test2.txt");
    
    return 0;
}

示例2:进程间资源比较。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/kcmp.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/stat.h>

// kcmp 系统调用包装函数
int kcmp_wrapper(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2) {
    return syscall(SYS_kcmp, pid1, pid2, type, idx1, idx2);
}

// 显示 kcmp 结果
void show_kcmp_result(const char* description, int result) {
    printf("%-30s: ", description);
    switch (result) {
        case 0: printf("相同\n"); break;
        case 1: printf("不同\n"); break;
        case 2: printf("进程不存在或无法访问\n"); break;
        default: printf("错误 (%s)\n", strerror(errno)); break;
    }
}

// 创建测试环境
int setup_test_environment(int *fd1, int *fd2) {
    // 创建测试文件
    *fd1 = open("shared_file.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    *fd2 = open("different_file.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    
    if (*fd1 == -1 || *fd2 == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    // 写入一些数据
    const char *data = "测试数据";
    write(*fd1, data, strlen(data));
    write(*fd2, data, strlen(data));
    
    return 0;
}

int main() {
    pid_t parent_pid = getpid();
    pid_t child_pid;
    int fd1, fd2;
    int result;
    
    printf("=== kcmp 进程间资源比较示例 ===\n\n");
    
    // 设置测试环境
    if (setup_test_environment(&fd1, &fd2) == -1) {
        return 1;
    }
    
    printf("父进程 PID: %d\n", parent_pid);
    printf("测试文件描述符: fd1=%d, fd2=%d\n\n", fd1, fd2);
    
    // 创建子进程
    child_pid = fork();
    if (child_pid == -1) {
        perror("fork");
        close(fd1);
        close(fd2);
        unlink("shared_file.txt");
        unlink("different_file.txt");
        return 1;
    }
    
    if (child_pid == 0) {
        // 子进程
        int child_fd1, child_fd2;
        pid_t my_pid = getpid();
        
        printf("子进程 PID: %d\n", my_pid);
        
        // 子进程打开相同的文件
        child_fd1 = open("shared_file.txt", O_RDWR);
        child_fd2 = open("different_file.txt", O_RDWR);
        
        if (child_fd1 == -1 || child_fd2 == -1) {
            perror("子进程打开文件失败");
            exit(1);
        }
        
        printf("子进程文件描述符: fd1=%d, fd2=%d\n\n", child_fd1, child_fd2);
        
        // 比较父子进程的文件描述符
        printf("父子进程文件描述符比较:\n");
        result = kcmp_wrapper(parent_pid, my_pid, KCMP_FILE, fd1, child_fd1);
        show_kcmp_result("父fd1 vs 子fd1 (相同文件)", result);
        
        result = kcmp_wrapper(parent_pid, my_pid, KCMP_FILE, fd2, child_fd2);
        show_kcmp_result("父fd2 vs 子fd2 (相同文件)", result);
        
        result = kcmp_wrapper(parent_pid, my_pid, KCMP_FILE, fd1, child_fd2);
        show_kcmp_result("父fd1 vs 子fd2 (不同文件)", result);
        
        // 比较进程资源表
        printf("\n进程资源表比较:\n");
        result = kcmp_wrapper(parent_pid, my_pid, KCMP_FILES, 0, 0);
        show_kcmp_result("文件描述符表", result);
        
        result = kcmp_wrapper(parent_pid, my_pid, KCMP_VM, 0, 0);
        show_kcmp_result("虚拟内存", result);
        
        result = kcmp_wrapper(parent_pid, my_pid, KCMP_FS, 0, 0);
        show_kcmp_result("文件系统信息", result);
        
        result = kcmp_wrapper(parent_pid, my_pid, KCMP_SIGHAND, 0, 0);
        show_kcmp_result("信号处理", result);
        
        // 清理子进程资源
        close(child_fd1);
        close(child_fd2);
        
        exit(0);
    } else {
        // 父进程等待子进程完成
        int status;
        waitpid(child_pid, &status, 0);
        
        // 清理父进程资源
        close(fd1);
        close(fd2);
        unlink("shared_file.txt");
        unlink("different_file.txt");
        
        printf("\n=== 容器和命名空间检测示例 ===\n");
        printf("kcmp 在容器技术中的应用:\n");
        printf("1. 检测进程是否在相同容器中\n");
        printf("2. 验证资源隔离效果\n");
        printf("3. 调试容器间资源共享\n");
    }
    
    return 0;
}

示例3:完整的进程关系分析工具。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/kcmp.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <pwd.h>
#include <grp.h>

// kcmp 系统调用包装函数
int kcmp_wrapper(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2) {
    return syscall(SYS_kcmp, pid1, pid2, type, idx1, idx2);
}

// 资源类型名称
const char* get_resource_type_name(int type) {
    switch (type) {
        case KCMP_FILE: return "FILE";
        case KCMP_VM: return "VM";
        case KCMP_FILES: return "FILES";
        case KCMP_FS: return "FS";
        case KCMP_SIGHAND: return "SIGHAND";
        case KCMP_IO: return "IO";
        case KCMP_SYSVSEM: return "SYSVSEM";
        default: return "UNKNOWN";
    }
}

// 显示详细的 kcmp 结果
void show_detailed_result(const char* description, int result, int show_details) {
    printf("%-25s: ", description);
    
    switch (result) {
        case 0:
            printf("相同 ✓");
            if (show_details) printf(" (共享同一内核资源)");
            break;
        case 1:
            printf("不同 ✗");
            if (show_details) printf(" (使用不同内核资源)");
            break;
        case 2:
            printf("无法访问");
            if (show_details) printf(" (进程不存在或权限不足)");
            break;
        default:
            printf("错误 (%d)", result);
            if (show_details) printf(" (%s)", strerror(errno));
            break;
    }
    printf("\n");
}

// 分析两个进程的关系
void analyze_process_relationship(pid_t pid1, pid_t pid2) {
    printf("=== 进程关系分析 ===\n");
    printf("进程1 PID: %d\n", pid1);
    printf("进程2 PID: %d\n\n", pid2);
    
    // 基本信息检查
    if (pid1 == pid2) {
        printf("注意: 比较的是同一进程\n\n");
    }
    
    // 逐个比较资源类型
    int resource_types[] = {
        KCMP_FILES, KCMP_VM, KCMP_FS, KCMP_SIGHAND, KCMP_IO, KCMP_SYSVSEM
    };
    int num_types = sizeof(resource_types) / sizeof(resource_types[0]);
    
    printf("资源共享分析:\n");
    printf("%-25s  %s\n", "资源类型", "状态");
    printf("%-25s  %s\n", "--------", "----");
    
    int shared_count = 0;
    for (int i = 0; i < num_types; i++) {
        int result = kcmp_wrapper(pid1, pid2, resource_types[i], 0, 0);
        show_detailed_result(get_resource_type_name(resource_types[i]), result, 0);
        
        if (result == 0) {
            shared_count++;
        }
    }
    
    printf("\n共享资源统计: %d/%d 类型共享\n", shared_count, num_types);
    
    // 关系判断
    printf("\n关系判断:\n");
    if (shared_count == num_types) {
        printf("✓ 进程很可能有父子关系或克隆关系\n");
    } else if (shared_count >= 3) {
        printf("○ 进程可能在相同环境中运行\n");
    } else if (shared_count > 0) {
        printf("⚠ 进程部分资源共享\n");
    } else {
        printf("✗ 进程完全独立\n");
    }
    
    // 容器检测提示
    if (shared_count < 3) {
        printf("\n容器环境检测:\n");
        printf("  如果进程在不同容器中,大多数资源应该显示为'不同'\n");
    }
}

// 比较特定文件描述符
void compare_file_descriptors(pid_t pid1, pid_t pid2, int fd1, int fd2) {
    printf("\n=== 文件描述符比较 ===\n");
    
    int result = kcmp_wrapper(pid1, pid2, KCMP_FILE, fd1, fd2);
    
    printf("进程 %d fd%d vs 进程 %d fd%d: ", pid1, fd1, pid2, fd2);
    switch (result) {
        case 0: printf("指向同一文件 ✓\n"); break;
        case 1: printf("指向不同文件 ✗\n"); break;
        case 2: printf("无法访问进程\n"); break;
        default: printf("比较失败 (%s)\n", strerror(errno)); break;
    }
}

// 显示帮助信息
void show_help(const char *program_name) {
    printf("用法: %s [选项]\n", program_name);
    printf("\n选项:\n");
    printf("  -1, --pid1=PID         第一个进程 ID (默认: 当前进程)\n");
    printf("  -2, --pid2=PID         第二个进程 ID (默认: 父进程)\n");
    printf("  -f, --fd=FD1:FD2       比较特定文件描述符\n");
    printf("  -t, --type=TYPE        比较特定资源类型\n");
    printf("  -a, --all              显示详细分析\n");
    printf("  -h, --help             显示此帮助信息\n");
    printf("\n资源类型:\n");
    printf("  file    - 文件描述符\n");
    printf("  vm      - 虚拟内存\n");
    printf("  files   - 文件描述符表\n");
    printf("  fs      - 文件系统信息\n");
    printf("  sighand - 信号处理\n");
    printf("  io      - I/O 信息\n");
    printf("  sysvsem - System V 信号量\n");
    printf("\n示例:\n");
    printf("  %s -1 1234 -2 5678     # 比较进程 1234 和 5678\n");
    printf("  %s -f 3:4              # 比较当前进程的 fd3 和 fd4\n");
    printf("  %s -t vm               # 比较虚拟内存\n");
}

int main(int argc, char *argv[]) {
    pid_t pid1 = 0;  // 0 表示当前进程
    pid_t pid2 = getppid();  // 默认比较当前进程和父进程
    int fd1 = -1, fd2 = -1;
    int resource_type = -1;
    int show_all = 0;
    int specific_fd = 0;
    
    // 解析命令行参数
    static struct option long_options[] = {
        {"pid1",    required_argument, 0, '1'},
        {"pid2",    required_argument, 0, '2'},
        {"fd",      required_argument, 0, 'f'},
        {"type",    required_argument, 0, 't'},
        {"all",     no_argument,       0, 'a'},
        {"help",    no_argument,       0, 'h'},
        {0, 0, 0, 0}
    };
    
    int c;
    while (1) {
        int option_index = 0;
        c = getopt_long(argc, argv, "1:2:f:t:ah", long_options, &option_index);
        
        if (c == -1)
            break;
            
        switch (c) {
            case '1':
                pid1 = atoi(optarg);
                break;
            case '2':
                pid2 = atoi(optarg);
                break;
            case 'f':
                if (sscanf(optarg, "%d:%d", &fd1, &fd2) == 2) {
                    specific_fd = 1;
                } else {
                    fprintf(stderr, "错误: 文件描述符格式应为 FD1:FD2\n");
                    return 1;
                }
                break;
            case 't':
                if (strcmp(optarg, "file") == 0) resource_type = KCMP_FILE;
                else if (strcmp(optarg, "vm") == 0) resource_type = KCMP_VM;
                else if (strcmp(optarg, "files") == 0) resource_type = KCMP_FILES;
                else if (strcmp(optarg, "fs") == 0) resource_type = KCMP_FS;
                else if (strcmp(optarg, "sighand") == 0) resource_type = KCMP_SIGHAND;
                else if (strcmp(optarg, "io") == 0) resource_type = KCMP_IO;
                else if (strcmp(optarg, "sysvsem") == 0) resource_type = KCMP_SYSVSEM;
                else {
                    fprintf(stderr, "错误: 未知的资源类型 '%s'\n", optarg);
                    return 1;
                }
                break;
            case 'a':
                show_all = 1;
                break;
            case 'h':
                show_help(argv[0]);
                return 0;
            case '?':
                return 1;
        }
    }
    
    printf("=== kcmp 进程资源比较工具 ===\n\n");
    
    // 如果指定了特定文件描述符比较
    if (specific_fd) {
        compare_file_descriptors(pid1, pid2, fd1, fd2);
        return 0;
    }
    
    // 如果指定了特定资源类型
    if (resource_type != -1) {
        int result = kcmp_wrapper(pid1, pid2, resource_type, 0, 0);
        printf("进程 %d 和 %d 的 %s 资源比较: ", 
               pid1 ? pid1 : getpid(), pid2 ? pid2 : getppid(),
               get_resource_type_name(resource_type));
        
        switch (result) {
            case 0: printf("相同\n"); break;
            case 1: printf("不同\n"); break;
            case 2: printf("无法访问\n"); break;
            default: printf("错误 (%s)\n", strerror(errno)); break;
        }
        return 0;
    }
    
    // 执行完整的进程关系分析
    analyze_process_relationship(pid1 ? pid1 : getpid(), pid2 ? pid2 : getppid());
    
    // 显示系统信息
    printf("\n=== 系统信息 ===\n");
    printf("当前进程: %d\n", getpid());
    printf("父进程: %d\n", getppid());
    printf("用户 ID: %d\n", getuid());
    
    struct passwd *pwd = getpwuid(getuid());
    if (pwd) {
        printf("用户名: %s\n", pwd->pw_name);
    }
    
    // 使用建议
    printf("\n=== kcmp 使用建议 ===\n");
    printf("典型应用场景:\n");
    printf("1. 容器技术: 检测进程是否在同一容器中\n");
    printf("2. 调试工具: 分析进程间资源共享情况\n");
    printf("3. 安全审计: 验证进程隔离效果\n");
    printf("4. 系统监控: 跟踪进程关系变化\n");
    printf("\n注意事项:\n");
    printf("1. 需要适当权限才能比较其他进程\n");
    printf("2. 结果可能受 SELinux 等安全模块影响\n");
    printf("3. 某些资源类型可能不适用于所有内核版本\n");
    
    return 0;
}

编译和运行说明

# 编译示例程序
gcc -o kcmp_example1 example1.c
gcc -o kcmp_example2 example2.c
gcc -o kcmp_example3 example3.c

# 运行示例
./kcmp_example1
./kcmp_example2
./kcmp_example3 --help
./kcmp_example3 -a
./kcmp_example3 -1 1 -2 $$

系统要求检查

# 检查内核版本(需要 3.5+)
uname -r

# 检查 kcmp 系统调用支持
grep -w kcmp /usr/include/asm/unistd_64.h

# 查看系统调用表
cat /proc/kallsyms | grep kcmp

重要注意事项

1. 内核版本: 需要 Linux 3.5+ 内核支持
2. 权限要求: 通常需要 ptrace 权限才能比较其他进程
3. 安全限制: SELinux、AppArmor 等可能限制访问
4. 进程存在性: 目标进程必须存在且可访问
5. 错误处理: 始终检查返回值和 errno。

实际应用场景

1. 容器技术: Docker、LXC 等容器中检测进程关系
2. 系统调试: 分析进程间资源共享情况
3. 安全审计: 验证进程隔离和沙箱效果
4. 性能分析: 理解进程资源使用模式
5. 故障排查: 诊断进程间意外的资源共享。

容器环境中的应用

// 检测进程是否在同一容器中
int are_processes_in_same_container(pid_t pid1, pid_t pid2) {
    // 比较关键资源
    int results[] = {
        kcmp_wrapper(pid1, pid2, KCMP_FILES, 0, 0),
        kcmp_wrapper(pid1, pid2, KCMP_FS, 0, 0),
        kcmp_wrapper(pid1, pid2, KCMP_SIGHAND, 0, 0)
    };
    
    // 如果大多数关键资源不同,可能在不同容器中
    int different_count = 0;
    for (int i = 0; i < 3; i++) {
        if (results[i] == 1) different_count++;
    }
    
    return (different_count >= 2) ? 0 : 1;  // 0=不同容器, 1=可能同容器
}

最佳实践

// 安全地使用 kcmp
int safe_kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2) {
    // 验证参数
    if (type < KCMP_FILE || type > KCMP_SYSVSEM) {
        errno = EINVAL;
        return -1;
    }
    
    // 检查权限
    if ((pid1 != 0 && pid1 != getpid()) || (pid2 != 0 && pid2 != getpid())) {
        // 可能需要额外权限检查
        printf("注意: 比较其他进程需要适当权限\n");
    }
    
    int result = kcmp_wrapper(pid1, pid2, type, idx1, idx2);
    
    // 处理常见错误
    if (result == 2) {
        printf("警告: 目标进程可能不存在或无法访问\n");
    } else if (result < 0) {
        printf("错误: kcmp 调用失败 (%s)\n", strerror(errno));
    }
    
    return result;
}

文档标题: kcmp系统调用及示例 在这些示例中,展示了kcmp函数的多种使用情况,从基本的资源比较到完整的进程关系分析工具,有助于全面了解Linux系统中的进程资源比较机制。

发表在 linux文章 | 留下评论

kexec_load系统调用及示例

kexec_load – 加载新的内核镜像用于快速重启

1. 函数介绍

kexec_load 是一个 Linux 系统调用,用于加载新的内核镜像到内存中,以便通过 kexec 机制进行快速内核切换。kexec 是一种允许在不经过 BIOS/UEFI 初始化过程的情况下直接启动新内核的技术,大大减少了系统重启时间。

2. 函数原型

#include <linux/kexec.h>

long kexec_load(unsigned long entry, unsigned long nr_segments,
                struct kexec_segment *segments, unsigned long flags);

注意:这不是标准 C 库函数,需要通过 syscall() 调用。

3. 功能

将指定的内核镜像加载到内存中,为后续的 kexec 调用做准备。加载的内核可以在任何时候通过 reboot() 系统调用的 LINUX_REBOOT_CMD_KEXEC 命令激活。

4. 参数

  • unsigned long entry: 新内核的入口点地址
  • unsigned long nr_segments: 段的数量
  • struct kexec_segment *segments: 指向段描述符数组的指针
  • unsigned long flags: 控制标志
    • KEXEC_ON_CRASH: 为内核崩溃转储加载内核
    • KEXEC_PRESERVE_CONTEXT: 保留 CPU 状态
    • KEXEC_ARCH_MASK: 架构相关标志

5. kexec_segment 结构体

struct kexec_segment {
    void *buf;          /* 缓冲区指针 */
    size_t bufsz;       /* 缓冲区大小 */
    void *mem;          /* 内存地址 */
    size_t memsz;       /* 内存段大小 */
};

6. 返回值

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

7. 常见 errno 错误码

  • EBUSY: kexec 子系统正在使用中
  • EINVAL: 参数无效
  • EPERM: 权限不足(需要 CAP_SYS_BOOT 能力)
  • ENOMEM: 内存不足
  • EADDRNOTAVAIL: 指定的内存地址不可用
  • ENOEXEC: 内核镜像格式无效

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

  • reboot(): 系统重启函数,用于激活已加载的内核
  • kexec_file_load(): 更现代的内核加载接口(Linux 3.17+)
  • syscall(): 系统调用接口
  • /sbin/kexec: 用户态 kexec 工具
  • /proc/iomem: 查看系统内存布局
  • /sys/kernel/kexec_crash_loaded: 检查崩溃内核是否已加载

9. 示例代码

示例1:基本使用 – kexec 加载框架

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/kexec.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>

#ifndef SYS_kexec_load
# define SYS_kexec_load 246  // x86_64 架构下的系统调用号
#endif

#ifndef SYS_reboot
# define SYS_reboot 169
#endif

#define LINUX_REBOOT_MAGIC1 0xfee1dead
#define LINUX_REBOOT_MAGIC2 0x28121969
#define LINUX_REBOOT_CMD_KEXEC 0x45584543

// 检查 kexec 支持
int check_kexec_support() {
    if (access("/proc/iomem", R_OK) == -1) {
        printf("系统不支持 /proc/iomem\n");
        return -1;
    }
    
    FILE *fp = fopen("/proc/cmdline", "r");
    if (fp) {
        char cmdline[1024];
        if (fgets(cmdline, sizeof(cmdline), fp)) {
            if (strstr(cmdline, "nokexec")) {
                printf("内核启动参数禁用了 kexec\n");
                fclose(fp);
                return -1;
            }
        }
        fclose(fp);
    }
    
    return 0;
}

// 检查权限
int check_kexec_permissions() {
    if (geteuid() != 0) {
        printf("需要 root 权限执行 kexec\n");
        return -1;
    }
    
    // 检查 CAP_SYS_BOOT 能力
    // 这里简化处理,实际需要使用 libcap
    
    return 0;
}

// 加载内核文件到内存
void* load_kernel_file(const char *filename, size_t *size) {
    int fd = open(filename, O_RDONLY);
    if (fd == -1) {
        printf("无法打开内核文件: %s\n", filename);
        return NULL;
    }
    
    struct stat st;
    if (fstat(fd, &st) == -1) {
        printf("无法获取文件状态: %s\n", strerror(errno));
        close(fd);
        return NULL;
    }
    
    void *buffer = malloc(st.st_size);
    if (!buffer) {
        printf("内存分配失败\n");
        close(fd);
        return NULL;
    }
    
    ssize_t bytes_read = read(fd, buffer, st.st_size);
    if (bytes_read != st.st_size) {
        printf("读取文件失败\n");
        free(buffer);
        close(fd);
        return NULL;
    }
    
    *size = st.st_size;
    close(fd);
    return buffer;
}

int main() {
    printf("=== kexec 加载框架演示 ===\n");
    
    // 检查系统支持
    if (check_kexec_support() == -1) {
        printf("系统不支持 kexec 功能\n");
        return 1;
    }
    
    // 检查权限
    if (check_kexec_permissions() == -1) {
        printf("权限不足,无法执行 kexec\n");
        return 1;
    }
    
    printf("系统支持 kexec,权限检查通过\n");
    
    // 显示系统信息
    printf("当前内核版本: ");
    system("uname -r");
    
    printf("系统架构: ");
    system("uname -m");
    
    // 检查 kexec 状态
    printf("\n当前 kexec 状态:\n");
    system("ls /sys/kernel/kexec* 2>/dev/null || echo 'kexec 信息不可用'");
    
    return 0;
}

示例2:kexec 文件加载接口(现代方法)

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

#ifndef SYS_kexec_file_load
# define SYS_kexec_file_load 311  // x86_64 架构下的系统调用号
#endif

// kexec_file_load 系统调用(Linux 3.17+)
long kexec_file_load_syscall(int kernel_fd, int initrd_fd,
                            unsigned long cmdline_len,
                            const char *cmdline_ptr,
                            unsigned long flags) {
    return syscall(SYS_kexec_file_load, kernel_fd, initrd_fd,
                   cmdline_len, cmdline_ptr, flags);
}

void demonstrate_kexec_file_load() {
    printf("=== kexec_file_load 演示 ===\n");
    
    if (geteuid() != 0) {
        printf("需要 root 权限\n");
        return;
    }
    
    // 检查系统是否支持 kexec_file_load
    long result = syscall(SYS_kexec_file_load, -1, -1, 0, NULL, 0);
    if (result == -1 && errno == ENOSYS) {
        printf("系统不支持 kexec_file_load 系统调用\n");
        printf("需要 Linux 内核 3.17 或更高版本\n");
        return;
    }
    
    printf("系统支持 kexec_file_load 接口\n");
    
    // 显示可用的内核文件
    printf("\n可用的内核文件:\n");
    system("ls /boot/vmlinuz* 2>/dev/null | head -5 || echo '未找到内核文件'");
    
    // 显示 initrd 文件
    printf("\n可用的 initrd 文件:\n");
    system("ls /boot/initrd* /boot/initramfs* 2>/dev/null | head -5 || echo '未找到 initrd 文件'");
    
    // 显示当前 kexec 状态
    printf("\n当前 kexec 状态:\n");
    system("cat /sys/kernel/kexec_loaded 2>/dev/null || echo 'kexec_loaded 不可用'");
    system("cat /sys/kernel/kexec_crash_loaded 2>/dev/null || echo 'kexec_crash_loaded 不可用'");
}

int main() {
    demonstrate_kexec_file_load();
    return 0;
}

示例3:kexec 状态检查工具

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

void check_kexec_status() {
    printf("=== kexec 状态检查 ===\n");
    
    // 检查内核配置
    printf("内核 kexec 支持:\n");
    system("grep CONFIG_KEXEC /boot/config-$(uname -r) 2>/dev/null || "
           "zgrep CONFIG_KEXEC /proc/config.gz 2>/dev/null || "
           "echo '无法确定内核配置'");
    
    // 检查 kexec 模块参数
    printf("\nkexec 模块信息:\n");
    system("lsmod | grep kexec 2>/dev/null || echo 'kexec 作为内建功能或未加载'");
    
    // 检查 sysfs 接口
    printf("\n系统 kexec 状态:\n");
    system("echo 'kexec 加载状态: ' && "
           "cat /sys/kernel/kexec_loaded 2>/dev/null || echo '未知'");
    system("echo '崩溃内核状态: ' && "
           "cat /sys/kernel/kexec_crash_loaded 2>/dev/null || echo '未知'");
    system("echo 'kexec 锁定状态: ' && "
           "cat /sys/kernel/kexec_crash_size 2>/dev/null || echo '未知'");
    
    // 检查内存布局
    printf("\n系统内存布局 (相关部分):\n");
    system("grep -E 'System RAM|Kernel code|Kernel data' /proc/iomem 2>/dev/null || "
           "echo '无法读取内存布局'");
    
    // 检查权限
    printf("\n权限检查:\n");
    printf("当前 UID: %d\n", getuid());
    printf("当前 EUID: %d\n", geteuid());
    
    if (geteuid() == 0) {
        printf("✓ 以 root 身份运行\n");
    } else {
        printf("✗ 需要 root 权限\n");
    }
    
    // 检查能力
    printf("\n能力检查:\n");
    system("cat /proc/self/status | grep Cap 2>/dev/null || echo '无法读取能力信息'");
}

void show_kexec_limits() {
    printf("\n=== kexec 限制和配置 ===\n");
    
    // 显示硬件限制
    printf("硬件架构限制:\n");
    system("uname -m");
    
    // 显示内存限制
    printf("\n内存相关信息:\n");
    system("free -h");
    
    // 显示 kexec 相关的内核参数
    printf("\n相关内核参数:\n");
    system("sysctl -a 2>/dev/null | grep -E 'kexec|crash' | head -10 || "
           "echo '无法读取内核参数'");
    
    // 显示安全限制
    printf("\n安全相关设置:\n");
    system("cat /proc/sys/kernel/kptr_restrict 2>/dev/null || echo 'kptr_restrict 未设置'");
    system("cat /proc/sys/kernel/perf_event_paranoid 2>/dev/null || echo 'perf_event_paranoid 未设置'");
}

void demonstrate_kexec_commands() {
    printf("\n=== kexec 相关命令 ===\n");
    
    printf("常用 kexec 命令:\n");
    printf("  kexec -l <kernel>          # 加载内核\n");
    printf("  kexec -e                   # 执行已加载的内核\n");
    printf("  kexec -p <kernel>          # 加载崩溃内核\n");
    printf("  kexec -u                   # 卸载当前内核\n");
    
    printf("\n示例用法:\n");
    printf("  # 加载新内核\n");
    printf("  kexec -l /boot/vmlinuz-$(uname -r) \\\n");
    printf("        --initrd=/boot/initrd.img-$(uname -r) \\\n");
    printf("        --command-line=\"$(cat /proc/cmdline)\"\n");
    printf("  \n");
    printf("  # 快速重启\n");
    printf("  kexec -e\n");
    
    // 检查 kexec 命令是否存在
    printf("\n系统中的 kexec 工具:\n");
    system("which kexec 2>/dev/null && echo '✓ kexec 命令可用' || echo '✗ kexec 命令不可用'");
}

int main() {
    check_kexec_status();
    show_kexec_limits();
    demonstrate_kexec_commands();
    
    return 0;
}

示例4:kexec 安全和监控工具

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

void security_analysis() {
    printf("=== kexec 安全分析 ===\n");
    
    // 检查 kexec 锁定状态
    printf("kexec 锁定检查:\n");
    int fd = open("/proc/sys/kernel/kexec_load_disabled", O_RDONLY);
    if (fd != -1) {
        char buf[16];
        ssize_t n = read(fd, buf, sizeof(buf) - 1);
        if (n > 0) {
            buf[n] = '\0';
            printf("kexec 加载已%s\n", atoi(buf) ? "禁用" : "启用");
        }
        close(fd);
    } else {
        printf("无法检查 kexec 锁定状态\n");
    }
    
    // 检查安全启动状态
    printf("\n安全启动状态:\n");
    system("mokutil --sb-state 2>/dev/null || echo 'mokutil 不可用或安全启动未启用'");
    
    // 检查 IMA/EVM 状态
    printf("\nIMA/EVM 状态:\n");
    system("cat /sys/kernel/security/ima/policy_count 2>/dev/null || echo 'IMA 不可用'");
    
    // 检查 SELinux/AppArmor 状态
    printf("\n强制访问控制:\n");
    system("getenforce 2>/dev/null || echo 'SELinux 不可用'");
    system("aa-status 2>/dev/null | head -5 || echo 'AppArmor 信息不可用'");
}

void performance_benchmark() {
    printf("\n=== kexec 性能优势 ===\n");
    
    printf("传统重启 vs kexec 重启:\n");
    printf("  传统重启时间:     30-120 秒\n");
    printf("  kexec 重启时间:   2-10 秒\n");
    printf("  时间节省:         80-95%%\n");
    
    printf("\n适用场景:\n");
    printf("  • 高可用性集群\n");
    printf("  • 实时系统维护\n");
    printf("  • 内核升级测试\n");
    printf("  • 故障恢复\n");
    printf("  • 开发调试\n");
    
    printf("\n性能考虑:\n");
    printf("  • 需要足够的内存保留空间\n");
    printf("  • 内核镜像大小影响加载时间\n");
    printf("  • 内存碎片可能影响加载成功率\n");
}

void crash_dump_analysis() {
    printf("\n=== 崩溃转储支持 ===\n");
    
    printf("kexec 崩溃转储功能:\n");
    printf("  • 内核崩溃时保存内存状态\n");
    printf("  • 快速重启并保存转储信息\n");
    printf("  • 支持 vmcore 分析\n");
    
    // 检查崩溃转储配置
    printf("\n崩溃转储配置:\n");
    system("cat /proc/sys/kernel/panic 2>/dev/null || echo 'panic 设置不可用'");
    system("cat /proc/sys/kernel/panic_on_oops 2>/dev/null || echo 'panic_on_oops 设置不可用'");
    system("cat /sys/kernel/kexec_crash_size 2>/dev/null || echo '崩溃内存大小未设置'");
    
    printf("\n相关工具:\n");
    printf("  • crash: 内存转储分析工具\n");
    printf("  • makedumpfile: 转储文件处理工具\n");
    printf("  • kgdb: 内核调试器\n");
}

void best_practices() {
    printf("\n=== kexec 最佳实践 ===\n");
    
    printf("使用建议:\n");
    printf("  1. 确保新内核与硬件兼容\n");
    printf("  2. 备份重要数据\n");
    printf("  3. 测试内核参数\n");
    printf("  4. 监控系统日志\n");
    printf("  5. 准备回滚方案\n");
    
    printf("\n安全注意事项:\n");
    printf("  • 验证内核镜像完整性\n");
    printf("  • 限制 kexec 权限\n");
    printf("  • 启用审计日志\n");
    printf("  • 定期更新内核\n");
    
    printf("\n故障排除:\n");
    printf("  • 检查内核日志: dmesg | grep kexec\n");
    printf("  • 验证内存: free -h\n");
    printf("  • 检查权限: ls -l /proc/sys/kernel/kexec*\n");
}

int main() {
    printf("=== kexec 综合分析工具 ===\n");
    
    security_analysis();
    performance_benchmark();
    crash_dump_analysis();
    best_practices();
    
    printf("\n=== 总结 ===\n");
    printf("kexec 是 Linux 系统中重要的快速重启技术,\n");
    printf("适用于需要最小化停机时间的场景。\n");
    printf("使用时需注意安全性和兼容性问题。\n");
    
    return 0;
}

10. kexec 相关结构体和常量

// kexec 标志位
#define KEXEC_ON_CRASH          0x00000001
#define KEXEC_PRESERVE_CONTEXT  0x00000002
#define KEXEC_ARCH_MASK         0xffff0000

// 架构相关标志
#define KEXEC_ARCH_DEFAULT      (0 << 16)
#define KEXEC_ARCH_386          (3 << 16)
#define KEXEC_ARCH_68K          (4 << 16)
#define KEXEC_ARCH_PARISC       (15 << 16)
#define KEXEC_ARCH_X86_64       (62 << 16)
#define KEXEC_ARCH_PPC          (20 << 16)
#define KEXEC_ARCH_PPC64        (21 << 16)
#define KEXEC_ARCH_IA_64        (50 << 16)
#define KEXEC_ARCH_ARM          (40 << 16)
#define KEXEC_ARCH_S390         (22 << 16)
#define KEXEC_ARCH_SH           (42 << 16)
#define KEXEC_ARCH_MIPS_LE      (10 << 16)
#define KEXEC_ARCH_MIPS         (8 << 16)

// 重启命令
#define LINUX_REBOOT_CMD_KEXEC  0x45584543

11. 实际应用场景

场景1:高可用性集群

void ha_cluster_kexec() {
    // 在 HA 集群中使用 kexec 进行快速故障转移
    // 减少服务中断时间
}

场景2:内核开发调试

void kernel_development_kexec() {
    // 快速测试新内核版本
    // 避免漫长的 BIOS/UEFI 初始化过程
}

场景3:系统维护

void system_maintenance_kexec() {
    // 在维护窗口期间快速重启
    // 最大化可用性
}

12. 注意事项

使用 kexec 时需要注意:

1. 权限要求: 需要 CAP_SYS_BOOT 能力或 root 权限
2. 内存需求: 需要足够的内存来加载新内核
3. 兼容性: 新内核必须与硬件兼容
4. 安全性: 内核镜像需要验证完整性
5. 调试困难: kexec 后的调试信息可能有限
6. 硬件初始化: 跳过 BIOS/UEFI 可能影响某些硬件

13. 现代替代方案

// kexec_file_load (推荐的现代接口)
long kexec_file_load(int kernel_fd, int initrd_fd,
                    unsigned long cmdline_len,
                    const char *cmdline_ptr,
                    unsigned long flags);

总结

kexec_load 是 Linux 系统中实现快速内核切换的核心系统调用:

关键特性:
1. 快速重启: 跳过 BIOS/UEFI 初始化
2. 内存加载: 将新内核加载到内存中
3. 灵活控制: 支持多种加载选项
4. 崩溃支持: 支持崩溃转储功能

主要应用:
1. 高可用性系统快速故障恢复
2. 内核开发和测试
3. 系统维护和升级
4. 崩溃分析和调试

使用要点:
1. 需要特殊权限和内核支持
2. 注意内存和兼容性要求
3. 建议使用现代的 kexec_file_load 接口
4. 配合用户态工具使用效果更佳

kexec 技术显著提高了 Linux 系统的可用性和维护效率,是企业级系统管理的重要工具。

发表在 linux文章 | 留下评论

keyctl – 密钥管理控制接口

keyctl – 密钥管理控制接口

1. 函数介绍

keyctl 是一个 Linux 系统调用,用于管理和操作内核密钥保留服务(Key Retention Service)。它提供了对内核密钥管理子系统的完整控制接口,允许进程创建、检索、更新和删除各种类型的密钥。

密钥保留服务是 Linux 内核的安全基础设施组件,用于安全地存储和管理密钥、密码、证书等敏感信息。

2. 函数原型

#include <sys/types.h>
#include <keyutils.h>

long keyctl(int cmd, ...);

注意:这不是标准 C 库函数,需要通过 syscall() 调用或使用 libkeyutils 库。

3. 功能

执行各种密钥管理操作,包括:

  • 创建和删除密钥
  • 设置和获取密钥属性
  • 链接和取消链接密钥
  • 搜索和查找密钥
  • 设置密钥权限
  • 管理密钥环

4. 常用命令参数

// 密钥管理命令
#define KEYCTL_GET_KEYRING_ID     0  /* 获取密钥环 ID */
#define KEYCTL_JOIN_SESSION_KEYRING 1  /* 加入会话密钥环 */
#define KEYCTL_UPDATE             2  /* 更新密钥 */
#define KEYCTL_REVOKE             3  /* 撤销密钥 */
#define KEYCTL_CHOWN              4  /* 更改密钥所有者 */
#define KEYCTL_SETPERM            5  /* 设置密钥权限 */
#define KEYCTL_DESCRIBE           6  /* 描述密钥 */
#define KEYCTL_CLEAR              7  /* 清空密钥环 */
#define KEYCTL_LINK               8  /* 链接密钥 */
#define KEYCTL_UNLINK             9  /* 取消链接密钥 */
#define KEYCTL_SEARCH            10  /* 搜索密钥 */
#define KEYCTL_READ              11  /* 读取密钥 */
#define KEYCTL_INSTANTIATE       12  /* 实例化密钥 */
#define KEYCTL_NEGATE            13  /* 否定密钥 */
#define KEYCTL_SET_REQKEY_KEYRING 14 /* 设置请求密钥环 */
#define KEYCTL_SET_TIMEOUT       15  /* 设置密钥超时 */
#define KEYCTL_ASSUME_AUTHORITY  16  /* 假设权限 */
#define KEYCTL_GET_SECURITY      17  /* 获取安全上下文 */
#define KEYCTL_SESSION_TO_PARENT 18  /* 会话到父进程 */
#define KEYCTL_REJECT            19  /* 拒绝密钥 */
#define KEYCTL_INSTANTIATE_IOV   20  /* 实例化密钥 (iov) */
#define KEYCTL_INVALIDATE        21  /* 使密钥无效 */
#define KEYCTL_GET_PERSISTENT    22  /* 获取持久密钥环 */

5. 密钥类型

// 常见密钥类型
"user"        // 用户定义的密钥
"logon"       // 登录凭证密钥
"trusted"     // 受信任的密钥
"encrypted"   // 加密密钥
"dns_resolver" // DNS 解析器密钥
"rxrpc"       // RxRPC 密钥
"syzkaller"   // 系统调用模糊测试密钥

6. 返回值

  • 成功时:返回值取决于具体命令
  • 失败时:返回 -1,并设置 errno

7. 常见 errno 错误码

  • ENOKEY: 密钥不存在
  • EKEYEXPIRED: 密钥已过期
  • EKEYREVOKED: 密钥已被撤销
  • EACCES: 权限不足
  • EPERM: 操作被拒绝
  • EINVAL: 参数无效
  • ENOMEM: 内存不足
  • EDQUOT: 配额超限
  • EOPNOTSUPP: 操作不支持

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

  • add_key(): 添加新密钥
  • request_key(): 请求密钥
  • keyctl() 系列函数
  • /sbin/keyctl: 用户态密钥管理工具
  • /proc/keys: 查看系统密钥信息
  • /proc/key-users: 查看密钥用户信息

9. 示例代码

示例1:基本使用 – 密钥创建和管理

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <keyutils.h>
#include <errno.h>
#include <string.h>

#ifndef SYS_keyctl
# define SYS_keyctl 250  // x86_64 架构下的系统调用号
#endif

// keyctl 系统调用包装函数
long keyctl_wrapper(int cmd, ...) {
    va_list args;
    va_start(args, cmd);
    long result = syscall(SYS_keyctl, cmd, va_arg(args, long), 
                         va_arg(args, long), va_arg(args, long), 
                         va_arg(args, long));
    va_end(args);
    return result;
}

int main() {
    printf("=== keyctl 基本使用演示 ===\n");
    
    // 检查密钥支持
    key_serial_t session_keyring = keyctl(KEYCTL_GET_KEYRING_ID, 
                                         KEY_SPEC_SESSION_KEYRING, 0);
    if (session_keyring == -1) {
        printf("错误: 系统不支持密钥保留服务: %s\n", strerror(errno));
        return 1;
    }
    
    printf("✓ 密钥保留服务可用\n");
    printf("会话密钥环 ID: %d\n", session_keyring);
    
    // 创建一个用户密钥
    const char *key_desc = "my_test_key";
    const char *key_data = "This is my secret data";
    
    key_serial_t key = add_key("user", key_desc, key_data, strlen(key_data), 
                              KEY_SPEC_SESSION_KEYRING);
    if (key == -1) {
        printf("创建密钥失败: %s\n", strerror(errno));
        return 1;
    }
    
    printf("✓ 成功创建密钥,ID: %d\n", key);
    
    // 描述密钥
    char description[256];
    long desc_len = keyctl(KEYCTL_DESCRIBE, key, (long)description, 
                          sizeof(description), 0);
    if (desc_len != -1) {
        description[desc_len] = '\0';
        printf("密钥描述: %s\n", description);
    }
    
    // 读取密钥数据
    char read_data[256];
    long data_len = keyctl(KEYCTL_READ, key, (long)read_data, 
                          sizeof(read_data), 0);
    if (data_len != -1) {
        read_data[data_len] = '\0';
        printf("密钥数据: %s\n", read_data);
    }
    
    // 设置密钥超时(30秒后过期)
    if (keyctl(KEYCTL_SET_TIMEOUT, key, 30, 0, 0) == 0) {
        printf("✓ 设置密钥超时为 30 秒\n");
    }
    
    // 获取密钥安全上下文
    char security_context[256];
    long sec_len = keyctl(KEYCTL_GET_SECURITY, key, 
                         (long)security_context, sizeof(security_context), 0);
    if (sec_len != -1) {
        security_context[sec_len] = '\0';
        printf("安全上下文: %s\n", security_context);
    }
    
    // 撤销密钥
    if (keyctl(KEYCTL_REVOKE, key, 0, 0, 0) == 0) {
        printf("✓ 成功撤销密钥\n");
    }
    
    return 0;
}

示例2:密钥环操作

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <keyutils.h>
#include <errno.h>
#include <string.h>

void demonstrate_keyring_operations() {
    printf("=== 密钥环操作演示 ===\n");
    
    // 获取各种密钥环 ID
    key_serial_t session_ring = keyctl(KEYCTL_GET_KEYRING_ID, 
                                      KEY_SPEC_SESSION_KEYRING, 0);
    key_serial_t process_ring = keyctl(KEYCTL_GET_KEYRING_ID, 
                                      KEY_SPEC_PROCESS_KEYRING, 0);
    key_serial_t thread_ring = keyctl(KEYCTL_GET_KEYRING_ID, 
                                     KEY_SPEC_THREAD_KEYRING, 0);
    
    printf("密钥环 ID:\n");
    printf("  会话密钥环: %d\n", session_ring);
    printf("  进程密钥环: %d\n", process_ring);
    printf("  线程密钥环: %d\n", thread_ring);
    
    // 创建自定义密钥环
    key_serial_t custom_ring = add_key("keyring", "my_custom_ring", 
                                      NULL, 0, KEY_SPEC_SESSION_KEYRING);
    if (custom_ring != -1) {
        printf("✓ 创建自定义密钥环: %d\n", custom_ring);
        
        // 在自定义密钥环中创建密钥
        key_serial_t ring_key = add_key("user", "ring_key", 
                                       "data in ring", 12, custom_ring);
        if (ring_key != -1) {
            printf("✓ 在自定义密钥环中创建密钥: %d\n", ring_key);
        }
        
        // 清空自定义密钥环
        if (keyctl(KEYCTL_CLEAR, custom_ring, 0, 0, 0) == 0) {
            printf("✓ 清空自定义密钥环\n");
        }
        
        // 撤销自定义密钥环
        if (keyctl(KEYCTL_REVOKE, custom_ring, 0, 0, 0) == 0) {
            printf("✓ 撤销自定义密钥环\n");
        }
    }
    
    // 加入新的会话密钥环
    key_serial_t new_session = keyctl(KEYCTL_JOIN_SESSION_KEYRING, 
                                     (long)"new_session", 0, 0, 0);
    if (new_session != -1) {
        printf("✓ 加入新的会话密钥环: %d\n", new_session);
    }
}

void demonstrate_key_search() {
    printf("\n=== 密钥搜索演示 ===\n");
    
    // 创建测试密钥
    key_serial_t test_key = add_key("user", "search_test", 
                                   "search data", 11, 
                                   KEY_SPEC_SESSION_KEYRING);
    if (test_key == -1) {
        printf("创建测试密钥失败: %s\n", strerror(errno));
        return;
    }
    
    printf("创建测试密钥: %d\n", test_key);
    
    // 搜索密钥
    key_serial_t found_key = keyctl(KEYCTL_SEARCH, 
                                   KEY_SPEC_SESSION_KEYRING,
                                   (long)"user", (long)"search_test", 0);
    if (found_key != -1) {
        printf("✓ 找到密钥: %d\n", found_key);
        
        // 验证找到的密钥
        if (found_key == test_key) {
            printf("✓ 验证成功:找到的密钥 ID 匹配\n");
        }
    } else {
        printf("搜索密钥失败: %s\n", strerror(errno));
    }
    
    // 撤销测试密钥
    keyctl(KEYCTL_REVOKE, test_key, 0, 0, 0);
}

void demonstrate_key_permissions() {
    printf("\n=== 密钥权限演示 ===\n");
    
    // 创建测试密钥
    key_serial_t perm_key = add_key("user", "perm_test", 
                                   "permission data", 15, 
                                   KEY_SPEC_SESSION_KEYRING);
    if (perm_key == -1) {
        printf("创建权限测试密钥失败: %s\n", strerror(errno));
        return;
    }
    
    printf("创建权限测试密钥: %d\n", perm_key);
    
    // 设置密钥权限
    // 权限格式: possessor|user|group|other
    // 每个字段: view|read|write|search|link|setattr|all
    key_perm_t permissions = KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ;
    
    if (keyctl(KEYCTL_SETPERM, perm_key, permissions, 0, 0) == 0) {
        printf("✓ 设置密钥权限成功\n");
        
        // 描述密钥查看权限
        char desc[256];
        long desc_len = keyctl(KEYCTL_DESCRIBE, perm_key, 
                              (long)desc, sizeof(desc), 0);
        if (desc_len != -1) {
            desc[desc_len] = '\0';
            printf("更新后的密钥描述: %s", desc);
        }
    } else {
        printf("设置密钥权限失败: %s\n", strerror(errno));
    }
    
    // 更改密钥所有者(需要特权)
    if (keyctl(KEYCTL_CHOWN, perm_key, getuid(), 0, 0) == 0) {
        printf("✓ 更改密钥所有者成功\n");
    } else {
        if (errno == EPERM) {
            printf("ℹ 更改所有者需要特权权限\n");
        } else {
            printf("更改所有者失败: %s\n", strerror(errno));
        }
    }
    
    // 撤销密钥
    keyctl(KEYCTL_REVOKE, perm_key, 0, 0, 0);
}

int main() {
    printf("=== keyctl 密钥环操作演示 ===\n");
    
    // 检查密钥支持
    if (keyctl(KEYCTL_GET_KEYRING_ID, KEY_SPEC_SESSION_KEYRING, 0) == -1) {
        printf("错误: 系统不支持密钥保留服务\n");
        return 1;
    }
    
    demonstrate_keyring_operations();
    demonstrate_key_search();
    demonstrate_key_permissions();
    
    return 0;
}

示例3:密钥安全和加密操作

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

void demonstrate_encrypted_keys() {
    printf("=== 加密密钥演示 ===\n");
    
    // 检查是否支持加密密钥
    printf("系统支持的密钥类型:\n");
    system("cat /proc/key-types 2>/dev/null | grep -E 'user|encrypted|trusted' || "
           "echo '无法读取密钥类型信息'");
    
    // 创建用户密钥(基础加密)
    const char *encrypted_data = "sensitive_encrypted_data";
    key_serial_t enc_key = add_key("user", "encrypted_secret", 
                                  encrypted_data, strlen(encrypted_data),
                                  KEY_SPEC_SESSION_KEYRING);
    
    if (enc_key != -1) {
        printf("✓ 创建加密数据密钥: %d\n", enc_key);
        
        // 设置超时以增强安全性
        if (keyctl(KEYCTL_SET_TIMEOUT, enc_key, 300, 0, 0) == 0) {  // 5分钟
            printf("✓ 设置密钥超时为 5 分钟\n");
        }
        
        // 读取密钥数据
        char buffer[256];
        long read_len = keyctl(KEYCTL_READ, enc_key, (long)buffer, 
                              sizeof(buffer), 0);
        if (read_len != -1) {
            buffer[read_len] = '\0';
            printf("读取加密数据长度: %ld 字节\n", read_len);
        }
    } else {
        printf("创建加密密钥失败: %s\n", strerror(errno));
    }
}

void demonstrate_key_lifecycle() {
    printf("\n=== 密钥生命周期管理 ===\n");
    
    // 创建密钥
    const char *key_data = "lifecycle_test_data";
    key_serial_t key = add_key("user", "lifecycle_test", 
                              key_data, strlen(key_data),
                              KEY_SPEC_SESSION_KEYRING);
    
    if (key == -1) {
        printf("创建密钥失败: %s\n", strerror(errno));
        return;
    }
    
    printf("1. 创建密钥: %d\n", key);
    
    // 描述密钥状态
    char desc[256];
    long desc_len = keyctl(KEYCTL_DESCRIBE, key, (long)desc, sizeof(desc), 0);
    if (desc_len != -1) {
        desc[desc_len] = '\0';
        printf("2. 密钥状态: %s", desc);
    }
    
    // 更新密钥数据
    const char *new_data = "updated_lifecycle_data";
    if (keyctl(KEYCTL_UPDATE, key, (long)new_data, strlen(new_data), 0) == 0) {
        printf("3. ✓ 更新密钥数据成功\n");
    }
    
    // 设置短期超时
    if (keyctl(KEYCTL_SET_TIMEOUT, key, 10, 0, 0) == 0) {  // 10秒
        printf("4. ✓ 设置 10 秒超时\n");
    }
    
    // 等待几秒观察超时效果
    printf("5. 等待 12 秒观察超时效果...\n");
    sleep(12);
    
    // 尝试访问已过期的密钥
    char buffer[256];
    long read_len = keyctl(KEYCTL_READ, key, (long)buffer, sizeof(buffer), 0);
    if (read_len == -1) {
        if (errno == EKEYEXPIRED) {
            printf("6. ✓ 密钥已正确过期\n");
        } else {
            printf("6. 访问密钥失败: %s\n", strerror(errno));
        }
    }
    
    // 撤销密钥(即使已过期)
    if (keyctl(KEYCTL_REVOKE, key, 0, 0, 0) == 0) {
        printf("7. ✓ 撤销密钥成功\n");
    }
}

void demonstrate_key_security_analysis() {
    printf("\n=== 密钥安全分析 ===\n");
    
    // 显示当前用户的密钥使用情况
    printf("当前密钥使用情况:\n");
    system("cat /proc/key-users 2>/dev/null | head -10 || echo '无法读取密钥用户信息'");
    
    // 显示系统密钥信息
    printf("\n系统密钥信息:\n");
    system("cat /proc/keys 2>/dev/null | head -10 || echo '无法读取密钥信息'");
    
    // 显示密钥配额信息
    printf("\n密钥配额信息:\n");
    system("cat /proc/sys/kernel/keys/* 2>/dev/null || echo '无法读取密钥配额'");
    
    // 安全建议
    printf("\n密钥安全最佳实践:\n");
    printf("  • 使用适当的超时设置\n");
    printf("  • 设置最小必要权限\n");
    printf("  • 定期清理过期密钥\n");
    printf("  • 避免在日志中记录密钥数据\n");
    printf("  • 使用加密存储敏感数据\n");
    printf("  • 监控密钥使用情况\n");
}

int main() {
    printf("=== keyctl 安全和加密操作演示 ===\n");
    
    // 检查密钥支持
    if (keyctl(KEYCTL_GET_KEYRING_ID, KEY_SPEC_SESSION_KEYRING, 0) == -1) {
        printf("错误: 系统不支持密钥保留服务\n");
        return 1;
    }
    
    printf("✓ 密钥保留服务可用\n");
    
    demonstrate_encrypted_keys();
    demonstrate_key_lifecycle();
    demonstrate_key_security_analysis();
    
    return 0;
}

示例4:密钥管理工具和监控

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

void show_key_statistics() {
    printf("=== 密钥统计信息 ===\n");
    
    // 显示密钥数量
    printf("系统密钥统计:\n");
    system("wc -l /proc/keys 2>/dev/null | awk '{print \"总密钥数: \" $1-1}' || "
           "echo '无法统计密钥数量'");
    
    // 显示密钥用户信息
    printf("\n密钥用户统计:\n");
    system("cat /proc/key-users 2>/dev/null | head -5 || echo '无法读取用户统计'");
    
    // 显示密钥类型分布
    printf("\n密钥类型分布:\n");
    system("awk '{print $4}' /proc/keys 2>/dev/null | sort | uniq -c | head -10 || "
           "echo '无法分析密钥类型'");
}

void interactive_key_manager() {
    int choice;
    char input[256];
    
    while (1) {
        printf("\n=== 密钥管理菜单 ===\n");
        printf("1. 列出当前密钥\n");
        printf("2. 创建新密钥\n");
        printf("3. 查找密钥\n");
        printf("4. 删除密钥\n");
        printf("5. 显示密钥统计\n");
        printf("6. 清空会话密钥环\n");
        printf("0. 退出\n");
        printf("请选择操作: ");
        
        if (scanf("%d", &choice) != 1) {
            printf("输入无效,请重新选择\n");
            while (getchar() != '\n');  // 清空输入缓冲区
            continue;
        }
        
        switch (choice) {
            case 1:
                printf("当前会话密钥:\n");
                system("keyctl list @s 2>/dev/null || echo '无法列出密钥'");
                break;
                
            case 2: {
                printf("输入密钥描述: ");
                scanf("%255s", input);
                
                printf("输入密钥数据: ");
                scanf("%255s", input + 128);  // 简化处理
                
                key_serial_t new_key = add_key("user", input, 
                                              input + 128, 
                                              strlen(input + 128),
                                              KEY_SPEC_SESSION_KEYRING);
                if (new_key != -1) {
                    printf("✓ 成功创建密钥: %d\n", new_key);
                } else {
                    printf("❌ 创建密钥失败: %s\n", strerror(errno));
                }
                break;
            }
            
            case 3: {
                printf("输入要查找的密钥描述: ");
                scanf("%255s", input);
                
                key_serial_t found_key = keyctl(KEYCTL_SEARCH,
                                               KEY_SPEC_SESSION_KEYRING,
                                               (long)"user", (long)input, 0);
                if (found_key != -1) {
                    printf("✓ 找到密钥: %d\n", found_key);
                } else {
                    printf("❌ 未找到密钥: %s\n", strerror(errno));
                }
                break;
            }
            
            case 4: {
                printf("输入要删除的密钥 ID: ");
                int key_id;
                if (scanf("%d", &key_id) == 1) {
                    if (keyctl(KEYCTL_REVOKE, key_id, 0, 0, 0) == 0) {
                        printf("✓ 成功撤销密钥: %d\n", key_id);
                    } else {
                        printf("❌ 撤销密钥失败: %s\n", strerror(errno));
                    }
                } else {
                    printf("输入无效\n");
                }
                break;
            }
            
            case 5:
                show_key_statistics();
                break;
                
            case 6:
                if (keyctl(KEYCTL_CLEAR, KEY_SPEC_SESSION_KEYRING, 0, 0, 0) == 0) {
                    printf("✓ 成功清空会话密钥环\n");
                } else {
                    printf("❌ 清空密钥环失败: %s\n", strerror(errno));
                }
                break;
                
            case 0:
                printf("退出密钥管理工具\n");
                return;
                
            default:
                printf("无效选择,请重新输入\n");
                break;
        }
    }
}

void demonstrate_advanced_key_operations() {
    printf("\n=== 高级密钥操作 ===\n");
    
    // 创建密钥环
    key_serial_t keyring = add_key("keyring", "advanced_test_ring", 
                                  NULL, 0, KEY_SPEC_SESSION_KEYRING);
    if (keyring != -1) {
        printf("✓ 创建测试密钥环: %d\n", keyring);
        
        // 在密钥环中创建多个密钥
        for (int i = 1; i <= 3; i++) {
            char desc[32], data[32];
            snprintf(desc, sizeof(desc), "key_%d", i);
            snprintf(data, sizeof(data), "data_%d", i);
            
            key_serial_t key = add_key("user", desc, data, strlen(data), keyring);
            if (key != -1) {
                printf("  ✓ 创建密钥 %s: %d\n", desc, key);
            }
        }
        
        // 列出密钥环内容
        printf("密钥环内容:\n");
        char buffer[1024];
        long len = keyctl(KEYCTL_READ, keyring, (long)buffer, sizeof(buffer), 0);
        if (len != -1) {
            // 简化显示
            printf("  密钥环包含 %ld 字节数据\n", len);
        }
        
        // 清空密钥环
        if (keyctl(KEYCTL_CLEAR, keyring, 0, 0, 0) == 0) {
            printf("✓ 清空密钥环成功\n");
        }
        
        // 撤销密钥环
        keyctl(KEYCTL_REVOKE, keyring, 0, 0, 0);
    }
}

void key_monitoring_demo() {
    printf("\n=== 密钥监控演示 ===\n");
    
    printf("实时密钥监控 (按 Ctrl+C 停止):\n");
    
    // 显示初始状态
    printf("初始密钥数量: ");
    system("wc -l /proc/keys 2>/dev/null | awk '{print $1-1}' || echo 'unknown'");
    
    // 创建测试密钥并监控变化
    key_serial_t test_key = add_key("user", "monitor_test", 
                                   "monitor data", 12, 
                                   KEY_SPEC_SESSION_KEYRING);
    if (test_key != -1) {
        printf("创建测试密钥后数量: ");
        system("wc -l /proc/keys 2>/dev/null | awk '{print $1-1}' || echo 'unknown'");
        
        // 撤销密钥
        keyctl(KEYCTL_REVOKE, test_key, 0, 0, 0);
        printf("撤销测试密钥后数量: ");
        system("wc -l /proc/keys 2>/dev/null | awk '{print $1-1}' || echo 'unknown'");
    }
}

int main() {
    printf("=== keyctl 高级管理和监控工具 ===\n");
    
    // 检查密钥支持
    if (keyctl(KEYCTL_GET_KEYRING_ID, KEY_SPEC_SESSION_KEYRING, 0) == -1) {
        printf("错误: 系统不支持密钥保留服务\n");
        return 1;
    }
    
    printf("✓ 密钥保留服务可用\n");
    
    // 显示系统信息
    printf("\n系统密钥信息:\n");
    system("uname -a");
    
    // 执行高级操作演示
    demonstrate_advanced_key_operations();
    
    // 显示统计信息
    show_key_statistics();
    
    // 监控演示
    key_monitoring_demo();
    
    // 启动交互式管理器(如果需要)
    char choice;
    printf("\n是否启动交互式密钥管理器? (y/N): ");
    if (scanf(" %c", &choice) == 1 && (choice == 'y' || choice == 'Y')) {
        interactive_key_manager();
    }
    
    return 0;
}

10. 密钥权限说明

// 密钥权限位定义
#define KEY_POS_VIEW    0x01000000  /* 持有者可查看 */
#define KEY_POS_READ    0x02000000  /* 持有者可读取 */
#define KEY_POS_WRITE   0x04000000  /* 持有者可写入 */
#define KEY_POS_SEARCH  0x08000000  /* 持有者可搜索 */
#define KEY_POS_LINK    0x10000000  /* 持有者可链接 */
#define KEY_POS_SETATTR 0x20000000  /* 持有者可设置属性 */
#define KEY_POS_ALL     0x3f000000  /* 持有者所有权限 */

#define KEY_USR_VIEW    0x00010000  /* 用户可查看 */
#define KEY_USR_READ    0x00020000  /* 用户可读取 */
#define KEY_USR_WRITE   0x00040000  /* 用户可写入 */
#define KEY_USR_SEARCH  0x00080000  /* 用户可搜索 */
#define KEY_USR_LINK    0x00100000  /* 用户可链接 */
#define KEY_USR_SETATTR 0x00200000  /* 用户可设置属性 */
#define KEY_USR_ALL     0x003f0000  /* 用户所有权限 */

#define KEY_GRP_VIEW    0x00000100  /* 组可查看 */
#define KEY_GRP_READ    0x00000200  /* 组可读取 */
#define KEY_GRP_WRITE   0x00000400  /* 组可写入 */
#define KEY_GRP_SEARCH  0x00000800  /* 组可搜索 */
#define KEY_GRP_LINK    0x00001000  /* 组可链接 */
#define KEY_GRP_SETATTR 0x00002000  /* 组可设置属性 */
#define KEY_GRP_ALL     0x00003f00  /* 组所有权限 */

#define KEY_OTH_VIEW    0x00000001  /* 其他可查看 */
#define KEY_OTH_READ    0x00000002  /* 其他可读取 */
#define KEY_OTH_WRITE   0x00000004  /* 其他可写入 */
#define KEY_OTH_SEARCH  0x00000008  /* 其他可搜索 */
#define KEY_OTH_LINK    0x00000010  /* 其他可链接 */
#define KEY_OTH_SETATTR 0x00000020  /* 其他可设置属性 */
#define KEY_OTH_ALL     0x0000003f  /* 其他所有权限 */

11. 实际应用场景

场景1:网络认证

void network_authentication_keys() {
    // 存储网络认证凭证
    add_key("user", "wifi_password", "secret123", 9, 
            KEY_SPEC_SESSION_KEYRING);
}

场景2:文件系统加密

void filesystem_encryption_keys() {
    // 存储加密文件系统密钥
    add_key("encrypted", "fs_encryption_key", key_data, key_len,
            KEY_SPEC_SESSION_KEYRING);
}

场景3:安全通信

void secure_communication_keys() {
    // 存储 TLS/SSL 会话密钥
    add_key("user", "tls_session_key", session_key, key_len,
            KEY_SPEC_PROCESS_KEYRING);
}

12. 注意事项

使用 keyctl 时需要注意:

  1. 权限要求: 某些操作需要特殊权限
  2. 内存安全: 密钥数据在内存中的安全处理
  3. 生命周期: 正确管理密钥的创建、使用和销毁
  4. 超时设置: 合理设置密钥超时以增强安全性
  5. 权限控制: 设置最小必要权限
  6. 系统资源: 监控密钥使用对系统资源的影响

13. 系统配置检查

# 检查密钥支持
grep CONFIG_KEYS /boot/config-$(uname -r)

# 查看密钥信息
cat /proc/keys
cat /proc/key-users

# 检查密钥配额
cat /proc/sys/kernel/keys/maxkeys
cat /proc/sys/kernel/keys/maxbytes

# 使用 keyctl 工具
keyctl list @s
keyctl show

总结

keyctl 是 Linux 系统中强大的密钥管理接口:

关键特性:
1. 安全存储: 内核级密钥安全存储
2. 权限控制: 细粒度的访问控制
3. 生命周期管理: 完整的密钥生命周期控制
4. 多种密钥类型: 支持各种应用场景
5. 系统集成: 与 Linux 安全子系统深度集成

主要应用:
1. 网络认证和安全通信
2. 文件系统加密
3. 应用程序密钥管理
4. 系统安全服务
5. 容器和虚拟化安全

使用要点:
1. 需要理解密钥权限模型
2. 注意密钥生命周期管理
3. 合理设置超时和权限
4. 监控密钥使用情况
5. 配合用户态工具使用效果更佳

正确使用 keyctl 可以显著提高应用程序的安全性,是现代 Linux 系统安全架构的重要组成部分。

发表在 linux文章 | 留下评论

kill系统调用及示例

kill – 发送信号给进程

函数介绍

kill系统调用用于向指定的进程发送信号。信号是Linux系统中进程间通信的一种方式,用于通知进程发生了某种事件。通过kill可以控制其他进程的行为,如终止、暂停、继续等。

函数原型

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

功能

向指定进程发送信号,用于进程控制和通信。

参数

  • pid_t pid: 目标进程ID
    • > 0: 发送给指定进程ID
    • = 0: 发送给当前进程组的所有进程
    • -1: 发送给有权限发送的所有进程(除init)
    • < -1: 发送给进程组ID为-pid的所有进程
  • int sig: 要发送的信号编号
    • 0: 空信号,用于检查进程是否存在
    • SIGTERM(15): 终止信号(默认)
    • SIGKILL(9): 强制终止信号
    • SIGSTOP(17): 暂停信号
    • SIGCONT(19): 继续信号

返回值

  • 成功时返回0
  • 失败时返回-1,并设置errno:
    • EINVAL: 信号编号无效
    • EPERM: 权限不足
    • ESRCH: 进程不存在

相似函数

  • raise(): 向当前进程发送信号
  • killpg(): 向进程组发送信号
  • signal(): 设置信号处理函数
  • sigaction(): 更高级的信号处理函数

示例代码

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>
#include <string.h>

int main() {
    pid_t current_pid = getpid();
    pid_t parent_pid = getppid();
    
    printf("=== Kill函数示例 ===\n");
    printf("当前进程PID: %d\n", current_pid);
    printf("父进程PID: %d\n", parent_pid);
    
    // 示例1: 发送空信号检查进程是否存在
    printf("\n示例1: 检查进程是否存在\n");
    if (kill(current_pid, 0) == 0) {
        printf("  进程 %d 存在\n", current_pid);
    } else {
        printf("  进程 %d 不存在\n", current_pid);
    }
    
    // 示例2: 向自己发送SIGTERM信号
    printf("\n示例2: 向自己发送SIGTERM信号\n");
    printf("  发送SIGTERM信号前...\n");
    
    // 创建子进程来演示,避免终止主进程
    pid_t child_pid = fork();
    if (child_pid == 0) {
        // 子进程
        printf("  子进程 %d 准备接收信号\n", getpid());
        sleep(1);
        printf("  子进程退出\n");
        exit(0);
    } else if (child_pid > 0) {
        // 父进程
        sleep(1); // 等待子进程准备就绪
        printf("  向子进程 %d 发送SIGTERM信号\n", child_pid);
        if (kill(child_pid, SIGTERM) == 0) {
            printf("  信号发送成功\n");
        } else {
            perror("  kill失败");
        }
        wait(NULL); // 等待子进程结束
    }
    
    // 示例3: 演示不同信号的效果
    printf("\n示例3: 不同信号的效果\n");
    
    // 创建用于演示的子进程
    child_pid = fork();
    if (child_pid == 0) {
        // 子进程循环运行
        printf("  子进程 %d 开始运行...\n", getpid());
        int i = 0;
        while (i < 10) {
            printf("  子进程运行中... %d\n", i++);
            sleep(1);
        }
        printf("  子进程正常退出\n");
        exit(0);
    } else if (child_pid > 0) {
        // 父进程
        sleep(2); // 让子进程运行一会儿
        
        printf("  向子进程 %d 发送SIGSTOP信号(暂停)\n", child_pid);
        kill(child_pid, SIGSTOP);
        sleep(2);
        
        printf("  向子进程 %d 发送SIGCONT信号(继续)\n", child_pid);
        kill(child_pid, SIGCONT);
        sleep(2);
        
        printf("  向子进程 %d 发送SIGTERM信号(终止)\n", child_pid);
        kill(child_pid, SIGTERM);
        wait(NULL);
    }
    
    // 示例4: 错误处理
    printf("\n示例4: 错误处理\n");
    if (kill(999999, SIGTERM) == -1) {
        printf("  向不存在的进程发送信号: %s\n", strerror(errno));
    }
    
    if (kill(parent_pid, 999) == -1) {
        printf("  发送无效信号: %s\n", strerror(errno));
    }
    
    printf("\n程序执行完毕\n");
    return 0;
}

(https://www.calcguide.tech/2025/08/16/kill%e7%b3%bb%e7%bb%9f%e8%b0%83%e7%94%a8%e5%8f%8a%e7%a4%ba%e4%be%8b/)

发表在 linux文章 | 留下评论

listen系统调用及示例

继续学习 Linux 系统编程中的重要函数。这次我们介绍 listen 函数,它是 TCP 服务器模型中不可或缺的一环,用于将一个已绑定的套接字置于监听状态,准备接收来自客户端的连接请求。


1. 函数介绍

listen 是一个 Linux 系统调用,专门用于 TCP 服务器。它的核心作用是将一个已经绑定到本地地址(通过 bind)的套接字的状态从默认的主动打开(active open)转变为被动打开(passive open)。

简单来说,listen 告诉操作系统内核:

“嘿,内核,我这个套接字(sockfd)已经绑定了一个地址(IP 和端口),现在我想开始监听这个地址,等待客户端的连接请求。请帮我管理这些连接请求,把它们排好队,等我用 accept 来处理。”

你可以把 listen 想象成商店开门营业

  • 商店(套接字)已经选好了地址(通过 bind)。
  • listen 就像是老板在门口挂上“营业中”的牌子,并告诉店员(内核):“有人来敲门(连接请求),先让他们在门外等一会儿(排队),别让他们直接冲进来。”
  • accept 则像是店员去开门,把排队的顾客(客户端)迎进来,开始一对一的服务。

2. 函数原型

#include <sys/socket.h> // 必需

int listen(int sockfd, int backlog);

3. 功能

  • 启用监听: 将套接字 sockfd 的状态设置为监听模式。
  • 建立队列: 告诉内核为此套接字创建两个队列(具体实现可能有所不同,但概念如此):
    1. 未完成连接队列 (incomplete connection queue):存放那些正在执行 TCP 三次握手但尚未完成的连接请求。
    2. 已完成连接队列 (completed connection queue):存放那些已经完成 TCP 三次握手、等待服务器程序通过 accept 接受的连接。
  • 限制队列长度backlog 参数用于提示内核这两个队列的最大总长度。当队列满时,新的连接请求可能会被忽略或拒绝。

4. 参数

  • int sockfd: 这是一个已经成功调用 bind 的套接字文件描述符
    • 必须是面向连接的套接字,如 SOCK_STREAM (TCP)。
    • 不能是无连接的套接字,如 SOCK_DGRAM (UDP)。对 UDP 套接字调用 listen 会失败。
  • int backlog: 这个参数用于指定连接请求队列的最大长度
    • 它告诉内核,最多允许多少个已完成(或接近完成)的连接请求在此套接字上排队等待 accept
    • 实际队列长度: 内核可能会将这个值视为一个提示,并可能根据系统资源或配置将其调整为一个不同的、通常是不超过 SOMAXCONN 的值。SOMAXCONN 是系统定义的最大队列长度(在 Linux 上通常是 128 或 4096)。
    • 选择合适的值:
      • 过小: 可能导致客户端连接被拒绝(ECONNREFUSED),特别是在高并发场景下。
      • 过大: 可能消耗过多内核资源。
      • 常见做法: 传统上使用 5 (#define LISTENQ 5)。现代高性能服务器可能会设置一个更大的值,如 128 或 1024。#define LISTENQ 1024 是一个常用的较大值。
      • 现代建议: 可以直接使用 SOMAXCONN 常量,让系统决定最大值。

5. 返回值

  • 成功时: 返回 0。套接字 sockfd 现在处于监听状态。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EADDRINUSE 本地地址已被使用,EBADF sockfd 无效,EINVAL 套接字未绑定或不支持监听,ENOMEM 内存不足等)。

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

  • socket: 创建套接字。
  • bind: 将套接字绑定到本地地址,是 listen 的前置步骤。
  • accept: 从 listen 创建的已完成连接队列中取出一个连接,是 listen 的后续步骤。
  • connect: 客户端使用此函数向监听的服务器发起连接请求。

7. 示例代码

示例 1:标准的 TCP 服务器 socket -> bind -> listen 流程

这个例子演示了设置一个 TCP 服务器的标准三步流程。

// tcp_listen_server.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PORT 8088
// 使用 SOMAXCONN 作为 backlog,让系统选择合适的最大队列长度
#define BACKLOG SOMAXCONN
// 或者使用一个自定义值,如 #define BACKLOG 128

int main() {
    int server_fd;
    struct sockaddr_in address;
    int opt = 1;

    // 1. 创建套接字 (第一步)
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
    printf("Step 1: Socket created successfully (fd: %d)\n", server_fd);

    // 2. 设置套接字选项
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 3. 配置服务器地址结构
    memset(&address, 0, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 4. 绑定套接字到地址和端口 (第二步)
    printf("Step 2: Binding socket to port %d...\n", PORT);
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    printf("Socket bound successfully to 0.0.0.0:%d\n", PORT);

    // 5. 使套接字进入监听状态 (第三步,关键)
    printf("Step 3: Putting socket into listening mode with backlog %d...\n", BACKLOG);
    if (listen(server_fd, BACKLOG) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    printf("Server is now LISTENING on port %d with backlog %d.\n", PORT, BACKLOG);

    printf("\nServer setup complete. Waiting for connections...\n");
    printf("Run a client to connect, e.g., 'telnet localhost %d' or 'nc localhost %d'\n", PORT, PORT);

    // --- 服务器已准备好,可以调用 accept() 来接受连接 ---
    // 按 Ctrl+C 退出程序
    pause(); // 永久挂起,直到收到信号

    close(server_fd);
    printf("Server socket closed.\n");
    return 0;
}

代码解释:

  1. 创建套接字socket(AF_INET, SOCK_STREAM, 0) 创建一个 IPv4 TCP 套接字。
  2. 设置选项setsockopt(... SO_REUSEADDR ...) 设置地址重用选项。
  3. 绑定地址bind(...) 将套接字绑定到所有接口 (INADDR_ANY) 的 PORT 端口。
  4. **监听连接 **(关键步骤) 调用 listen(server_fd, BACKLOG)
    • server_fd: 要监听的套接字。
    • BACKLOG: 连接队列的最大长度。这里使用 SOMAXCONN,让系统决定。
  5. 调用成功后,服务器套接字进入监听状态。内核开始为该套接字维护连接请求队列。
  6. 程序挂起,等待客户端连接。实际的连接处理需要在后续调用 accept()

示例 2:演示 listen 失败的情况

这个例子演示了在错误的情况下调用 listen 会发生什么。

// listen_failures.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    int sock;

    // --- 情况 1: 对未绑定的套接字调用 listen ---
    printf("--- Test 1: listen() on an unbound socket ---\n");
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    if (listen(sock, 5) < 0) {
        perror("listen on unbound socket failed (expected)");
        // 这通常会失败,errno 为 EINVAL
    } else {
        printf("listen on unbound socket unexpectedly succeeded.\n");
    }
    close(sock);

    // --- 情况 2: 对 UDP 套接字调用 listen ---
    printf("\n--- Test 2: listen() on a UDP socket ---\n");
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        perror("UDP socket failed");
        exit(EXIT_FAILURE);
    }

    if (listen(sock, 5) < 0) {
        perror("listen on UDP socket failed (expected)");
        // 这会失败,errno 通常为 EOPNOTSUPP (Operation not supported)
    } else {
        printf("listen on UDP socket unexpectedly succeeded.\n");
    }
    close(sock);

    // --- 情况 3: 对已关闭的套接字调用 listen ---
    printf("\n--- Test 3: listen() on a closed socket ---\n");
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
    close(sock); // 先关闭

    if (listen(sock, 5) < 0) {
        perror("listen on closed socket failed (expected)");
        // 这会失败,errno 通常为 EBADF (Bad file descriptor)
    } else {
        printf("listen on closed socket unexpectedly succeeded.\n");
    }

    printf("\nAll failure tests completed.\n");
    return 0;
}

代码解释:

1. 测试 1: 创建一个 TCP 套接字后不调用 bind,直接调用 listen。这会失败,通常 errno 为 EINVAL(Invalid argument)。
2. 测试 2: 创建一个 UDP (SOCK_DGRAM) 套接字,然后调用 listen。这会失败,通常 errno 为 EOPNOTSUPP(Operation not supported)。
3. 测试 3: 创建一个套接字,调用 close 关闭它,然后再调用 listen。这会失败,通常 errno 为 EBADF(Bad file descriptor)。

示例 3:listen 与 accept 的结合使用

这个例子将 listen 和 accept 结合起来,展示一个完整的、但简化的服务器循环。

// listen_accept_demo.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h> // inet_ntoa

#define PORT 8089
#define BACKLOG 10

void handle_client(int client_fd, struct sockaddr_in *client_addr) {
    printf("Handling client %s:%d (fd: %d)\n",
           inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port), client_fd);
    // 在实际应用中,这里会进行数据读写
    // 为了演示,我们立即关闭连接
    close(client_fd);
    printf("Closed connection to client %s:%d\n",
           inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port));
}

int main() {
    int server_fd, client_fd;
    struct sockaddr_in address, client_address;
    socklen_t client_addr_len = sizeof(client_address);
    int opt = 1;

    // 1. 创建套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 2. 设置套接字选项
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 3. 配置并绑定地址
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 4. 关键:监听连接
    if (listen(server_fd, BACKLOG) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    printf("Server listening on port %d (backlog: %d)\n", PORT, BACKLOG);

    printf("Accepting connections for 10 seconds...\n");

    // 5. 循环接受连接 (只接受几个演示)
    time_t start_time = time(NULL);
    int connections_handled = 0;
    while (difftime(time(NULL), start_time) < 10.0) {
        // accept 是阻塞调用,会等待直到有连接或出错
        client_fd = accept(server_fd, (struct sockaddr *)&client_address, &client_addr_len);
        if (client_fd < 0) {
            perror("accept failed");
            continue;
        }

        connections_handled++;
        printf("New connection #%d accepted.\n", connections_handled);
        handle_client(client_fd, &client_address);

        // 简单限制演示连接数
        if (connections_handled >= 3) {
            break;
        }
    }

    printf("Handled %d connections in 10 seconds. Shutting down.\n", connections_handled);
    close(server_fd);
    return 0;
}

如何测试:

  1. 编译并运行服务器:gcc -o listen_accept_demo listen_accept_demo.c ./listen_accept_demo
  2. 在另一个或多个终端中,快速运行客户端命令:telnet localhost 8089 # 或者 nc localhost 8089

代码解释:

1. 执行标准的 socket -> setsockopt -> bind -> listen 流程。
2. 进入一个循环,持续调用 accept(server_fd, ...)
3. accept 是一个阻塞调用。如果没有待处理的连接,程序会在此处挂起等待。
4. 当有客户端连接请求到达并完成三次握手后,accept 会从已完成连接队列中取出该连接,返回一个新的文件描述符 client_fd,专门用于与该客户端通信。
5. 调用 handle_client 函数(这里只是简单地打印信息并关闭连接)。
6. 主循环继续调用 accept,处理下一个连接。


重要提示与注意事项:

1. 顺序至关重要: 必须严格按照 socket() -> bind() -> listen() -> accept() 的顺序进行。
2. 仅用于面向连接的套接字listen 只能用于 SOCK_STREAM (TCP) 类型的套接字。对 SOCK_DGRAM (UDP) 调用会失败。
3. backlog 的含义: 理解 backlog 是队列长度的提示,而不是严格保证。内核可能会调整它。对于高并发服务器,设置一个较大的 backlog 是明智的。
4. accept 是关键listen 只是设置了监听状态和队列,真正接受连接的操作是由 accept 完成的。
5. 错误处理: 始终检查 listen 的返回值。最常见的错误是 EINVAL(套接字未绑定)和 EOPNOTSUPP(套接字类型不支持)。
6. 队列溢出: 如果连接请求的速度超过了服务器 accept 的速度,且队列已满,新的连接请求可能会被内核丢弃,客户端会收到连接被拒(ECONNREFUSED)的错误。

总结:

listen 是 TCP 服务器编程模型的核心组件之一。它将一个绑定好的套接字转变为可以接收连接请求的状态,并由内核管理一个连接队列。理解其作用和参数(特别是 backlog)对于构建能够处理并发连接请求的服务器至关重要。它是连接 bind 和 accept 的桥梁。

发表在 linux文章 | 留下评论

open_by_handle_at系统调用及示例

open_by_handle_at 函数详解

1. 函数介绍

open_by_handle_at 是 Linux 系统中用于通过文件句柄打开文件的系统调用。可以把文件句柄想象成”文件的身份证号码”,而 open_by_handle_at 就是通过这个”身份证号码”来访问文件的工具。

与传统的通过路径名打开文件不同,open_by_handle_at 不依赖于文件路径,即使文件被移动、重命名或删除后恢复,只要文件系统支持,仍然可以通过句柄访问文件。这就像你通过身份证号码在任何地方都能找到一个人一样。

2. 函数原型

#define _GNU_SOURCE
#include <fcntl.h>

int open_by_handle_at(int mount_fd, struct file_handle *handle, int flags);

3. 功能

open_by_handle_at 函数用于通过文件句柄打开文件,返回一个文件描述符,可以像普通文件一样进行读写操作。

4. 参数

  • mount_fd: 挂载点文件描述符
    • 可以是任何该文件系统中的文件描述符
    • 通常使用 AT_FDCWD 表示当前工作目录
    • 也可以是该文件系统根目录的文件描述符
  • handle: 指向 file_handle 结构体的指针
    • 包含之前通过 name_to_handle_at 获取的文件句柄
  • flags: 文件打开标志
    • O_RDONLY: 只读打开
    • O_WRONLY: 只写打开
    • O_RDWR: 读写打开
    • O_CREATO_TRUNC 等标志不适用(文件必须已存在)

5. file_handle 结构体

struct file_handle {
    unsigned int  handle_bytes;   /* 句柄数据的字节数 */
    int           handle_type;    /* 句柄类型 */
    unsigned char f_handle[0];    /* 句柄数据(变长数组)*/
};

6. 返回值

  • 成功: 返回文件描述符(非负整数)
  • 失败: 返回 -1,并设置相应的 errno 错误码

7. 常见错误码

  • EACCES: 权限不足
  • EBADF: mount_fd 不是有效的文件描述符
  • EFAULT: handle 指针无效
  • EINVAL: 参数无效(如 handle 为 NULL 或 flags 无效)
  • EMFILE: 进程文件描述符过多
  • ENFILE: 系统文件描述符过多
  • ENOMEM: 内存不足
  • ENOSPC: 磁盘空间不足(写操作)
  • ENOTDIR: mount_fd 不是目录
  • EOPNOTSUPP: 文件系统不支持文件句柄
  • ESTALE: 文件句柄已失效(文件可能已被删除)

8. 相似函数或关联函数

  • name_to_handle_at: 获取文件句柄
  • open/openat: 通过路径名打开文件
  • openat2: 增强版的 openat
  • fstat: 通过文件描述符获取文件状态
  • read/write: 文件读写操作

9. 示例代码

示例1:基础用法 – 通过句柄打开文件

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

// 创建测试文件
int create_test_file(const char *filename) {
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    const char *content = "这是测试文件的内容\n用于演示通过句柄打开文件的功能\n";
    ssize_t bytes_written = write(fd, content, strlen(content));
    if (bytes_written == -1) {
        perror("写入文件失败");
        close(fd);
        return -1;
    }
    
    printf("创建测试文件: %s (写入 %zd 字节)\n", filename, bytes_written);
    close(fd);
    return 0;
}

// 获取文件句柄
int get_file_handle(const char *filename, struct file_handle **handle, int *mount_id) {
    size_t handle_size = sizeof(struct file_handle);
    *handle = malloc(handle_size);
    if (!*handle) {
        perror("内存分配失败");
        return -1;
    }
    
    (*handle)->handle_bytes = 0;
    
    int result = name_to_handle_at(AT_FDCWD, filename, *handle, mount_id, 0);
    if (result == -1 && errno == EOVERFLOW) {
        handle_size = sizeof(struct file_handle) + (*handle)->handle_bytes;
        free(*handle);
        
        *handle = malloc(handle_size);
        if (!*handle) {
            perror("内存分配失败");
            return -1;
        }
        
        result = name_to_handle_at(AT_FDCWD, filename, *handle, mount_id, 0);
    }
    
    return result;
}

// 通过句柄打开文件
int open_file_by_handle(struct file_handle *handle, int flags) {
    printf("通过句柄打开文件 (标志: 0x%x)...\n", flags);
    
    int fd = open_by_handle_at(AT_FDCWD, handle, flags);
    if (fd != -1) {
        printf("✓ 成功打开文件,文件描述符: %d\n", fd);
        
        // 获取文件信息
        struct stat st;
        if (fstat(fd, &st) == 0) {
            printf("  文件大小: %ld 字节\n", (long)st.st_size);
            printf("  修改时间: %s", ctime(&st.st_mtime));
            printf("  权限: %o\n", st.st_mode & 0777);
        }
        
        return fd;
    } else {
        printf("✗ 打开文件失败: %s\n", strerror(errno));
        return -1;
    }
}

// 读取文件内容
int read_file_content(int fd) {
    char buffer[256];
    ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
    
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("读取到的内容 (%zd 字节):\n%s", bytes_read, buffer);
        return 0;
    } else if (bytes_read == 0) {
        printf("文件为空\n");
        return 0;
    } else {
        perror("读取文件失败");
        return -1;
    }
}

int main() {
    const char *test_file = "handle_open_test.txt";
    struct file_handle *handle = NULL;
    int mount_id;
    int fd;
    
    printf("=== open_by_handle_at 基础示例 ===\n\n");
    
    // 创建测试文件
    if (create_test_file(test_file) == -1) {
        return 1;
    }
    
    // 获取文件句柄
    printf("\n1. 获取文件句柄:\n");
    if (get_file_handle(test_file, &handle, &mount_id) == 0) {
        printf("  ✓ 成功获取文件句柄\n");
        printf("  挂载 ID: %d\n", mount_id);
        printf("  句柄类型: %d\n", handle->handle_type);
        printf("  句柄大小: %u 字节\n", handle->handle_bytes);
    } else {
        printf("  ✗ 获取文件句柄失败: %s\n", strerror(errno));
        unlink(test_file);
        return 1;
    }
    
    // 通过句柄以只读方式打开文件
    printf("\n2. 通过句柄以只读方式打开文件:\n");
    fd = open_file_by_handle(handle, O_RDONLY);
    if (fd != -1) {
        read_file_content(fd);
        close(fd);
    }
    
    // 通过句柄以读写方式打开文件
    printf("\n3. 通过句柄以读写方式打开文件:\n");
    fd = open_file_by_handle(handle, O_RDWR);
    if (fd != -1) {
        printf("  向文件追加内容...\n");
        const char *append_content = "追加的内容\n";
        lseek(fd, 0, SEEK_END);  // 移动到文件末尾
        ssize_t bytes_written = write(fd, append_content, strlen(append_content));
        if (bytes_written > 0) {
            printf("  ✓ 成功追加 %zd 字节\n", bytes_written);
        }
        
        // 重新读取文件内容
        printf("  重新读取文件内容:\n");
        lseek(fd, 0, SEEK_SET);  // 移动到文件开头
        read_file_content(fd);
        
        close(fd);
    }
    
    // 测试错误情况
    printf("\n4. 测试错误情况:\n");
    printf("  尝试使用无效句柄打开文件:\n");
    struct file_handle invalid_handle = {0};
    int invalid_fd = open_by_handle_at(AT_FDCWD, &invalid_handle, O_RDONLY);
    if (invalid_fd == -1) {
        printf("    ✓ 正确处理无效句柄: %s\n", strerror(errno));
    }
    
    // 清理资源
    if (handle) {
        free(handle);
    }
    unlink(test_file);
    
    printf("\n=== 文件句柄打开特点 ===\n");
    printf("1. 路径无关: 不依赖文件路径名\n");
    printf("2. 持久性: 文件移动后仍可访问\n");
    printf("3. 安全性: 防止路径遍历攻击\n");
    printf("4. 唯一性: 每个文件有唯一句柄\n");
    printf("5. 系统级: 由内核维护,无法伪造\n");
    printf("\n");
    printf("使用场景:\n");
    printf("1. 文件监控系统\n");
    printf("2. 备份和同步工具\n");
    printf("3. 容器文件系统\n");
    printf("4. 网络文件传输\n");
    printf("5. 安全文件访问\n");
    
    return 0;
}

示例2:文件句柄的持久性和安全性

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

// 文件信息结构体
struct persistent_file {
    char original_name[256];
    char current_name[256];
    struct file_handle *handle;
    int mount_id;
    time_t create_time;
};

// 创建测试文件
int create_test_file_with_content(const char *filename, const char *content) {
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    ssize_t bytes_written = write(fd, content, strlen(content));
    if (bytes_written == -1) {
        perror("写入文件失败");
        close(fd);
        return -1;
    }
    
    printf("创建测试文件: %s (%zd 字节)\n", filename, bytes_written);
    close(fd);
    return 0;
}

// 获取文件句柄
int get_file_handle_safe(const char *filename, struct file_handle **handle, int *mount_id) {
    size_t handle_size = sizeof(struct file_handle);
    *handle = malloc(handle_size);
    if (!*handle) {
        return -1;
    }
    
    (*handle)->handle_bytes = 0;
    
    int result = name_to_handle_at(AT_FDCWD, filename, *handle, mount_id, 0);
    if (result == -1 && errno == EOVERFLOW) {
        handle_size = sizeof(struct file_handle) + (*handle)->handle_bytes;
        free(*handle);
        
        *handle = malloc(handle_size);
        if (!*handle) {
            return -1;
        }
        
        result = name_to_handle_at(AT_FDCWD, filename, *handle, mount_id, 0);
    }
    
    return result;
}

// 通过句柄安全地打开文件
int open_file_by_handle_safe(struct file_handle *handle, int flags, const char *description) {
    printf("通过句柄打开文件: %s\n", description ? description : "未知文件");
    
    int fd = open_by_handle_at(AT_FDCWD, handle, flags);
    if (fd != -1) {
        printf("  ✓ 成功打开文件 (fd: %d)\n", fd);
        return fd;
    } else {
        printf("  ✗ 打开文件失败: %s\n", strerror(errno));
        return -1;
    }
}

// 读取并验证文件内容
int verify_file_content(int fd, const char *expected_content, const char *description) {
    if (lseek(fd, 0, SEEK_SET) == -1) {
        perror("定位文件开头失败");
        return -1;
    }
    
    char *buffer = malloc(strlen(expected_content) + 1);
    if (!buffer) {
        perror("内存分配失败");
        return -1;
    }
    
    ssize_t bytes_read = read(fd, buffer, strlen(expected_content));
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("  %s内容验证: ", description ? description : "");
        if (strcmp(buffer, expected_content) == 0) {
            printf("通过 ✓\n");
            free(buffer);
            return 0;
        } else {
            printf("失败 ✗\n");
            printf("    期望: %s", expected_content);
            printf("    实际: %s", buffer);
            free(buffer);
            return -1;
        }
    } else {
        perror("读取文件失败");
        free(buffer);
        return -1;
    }
}

int main() {
    struct persistent_file file_info;
    const char *original_name = "persistent_original.txt";
    const char *renamed_name = "persistent_renamed.txt";
    const char *content = "这是持久化文件的内容\n创建时间: ";
    
    printf("=== 文件句柄持久性和安全性示例 ===\n\n");
    
    // 构造带时间戳的内容
    char full_content[512];
    time_t now = time(NULL);
    snprintf(full_content, sizeof(full_content), "%s%s", content, ctime(&now));
    
    // 创建测试文件
    printf("1. 创建测试文件:\n");
    if (create_test_file_with_content(original_name, full_content) == -1) {
        return 1;
    }
    
    strncpy(file_info.original_name, original_name, sizeof(file_info.original_name) - 1);
    file_info.original_name[sizeof(file_info.original_name) - 1] = '\0';
    file_info.create_time = now;
    
    // 获取文件句柄
    printf("\n2. 获取文件句柄:\n");
    if (get_file_handle_safe(original_name, &file_info.handle, &file_info.mount_id) == 0) {
        printf("  ✓ 成功获取文件句柄\n");
        printf("  挂载 ID: %d\n", file_info.mount_id);
        printf("  句柄大小: %u 字节\n", file_info.handle->handle_bytes);
    } else {
        printf("  ✗ 获取文件句柄失败: %s\n", strerror(errno));
        unlink(original_name);
        return 1;
    }
    
    // 通过句柄访问原始文件
    printf("\n3. 通过句柄访问原始文件:\n");
    int fd = open_file_by_handle_safe(file_info.handle, O_RDONLY, "原始文件");
    if (fd != -1) {
        if (verify_file_content(fd, full_content, "原始文件") == 0) {
            printf("  ✓ 原始文件内容验证通过\n");
        }
        close(fd);
    }
    
    // 重命名文件
    printf("\n4. 重命名文件 (模拟文件移动):\n");
    if (rename(original_name, renamed_name) == 0) {
        printf("  ✓ 成功重命名文件: %s -> %s\n", original_name, renamed_name);
        strncpy(file_info.current_name, renamed_name, sizeof(file_info.current_name) - 1);
        file_info.current_name[sizeof(file_info.current_name) - 1] = '\0';
    } else {
        printf("  ✗ 重命名文件失败: %s\n", strerror(errno));
        free(file_info.handle);
        unlink(original_name);
        return 1;
    }
    
    // 通过句柄访问重命名后的文件
    printf("\n5. 通过句柄访问重命名后的文件:\n");
    fd = open_file_by_handle_safe(file_info.handle, O_RDONLY, "重命名后的文件");
    if (fd != -1) {
        if (verify_file_content(fd, full_content, "重命名文件") == 0) {
            printf("  ✓ 重命名文件内容验证通过\n");
            printf("  ✓ 证明: 文件句柄不受文件名变化影响\n");
        }
        close(fd);
    }
    
    // 创建符号链接并测试
    printf("\n6. 创建符号链接测试:\n");
    const char *symlink_name = "persistent_symlink.txt";
    if (symlink(renamed_name, symlink_name) == 0) {
        printf("  ✓ 创建符号链接: %s -> %s\n", symlink_name, renamed_name);
        
        // 获取符号链接的句柄
        struct file_handle *symlink_handle = NULL;
        int symlink_mount_id;
        if (get_file_handle_safe(symlink_name, &symlink_handle, &symlink_mount_id) == 0) {
            printf("  ✓ 获取符号链接句柄成功\n");
            
            // 通过符号链接句柄打开
            fd = open_file_by_handle_safe(symlink_handle, O_RDONLY, "符号链接");
            if (fd != -1) {
                if (verify_file_content(fd, full_content, "符号链接") == 0) {
                    printf("  ✓ 符号链接内容验证通过\n");
                }
                close(fd);
            }
            free(symlink_handle);
        }
        unlink(symlink_name);
    }
    
    // 测试不同打开标志
    printf("\n7. 测试不同打开标志:\n");
    
    // 只读打开
    printf("  只读打开 (O_RDONLY):\n");
    fd = open_file_by_handle_safe(file_info.handle, O_RDONLY, "只读模式");
    if (fd != -1) {
        printf("    ✓ 只读打开成功\n");
        close(fd);
    }
    
    // 读写打开
    printf("  读写打开 (O_RDWR):\n");
    fd = open_file_by_handle_safe(file_info.handle, O_RDWR, "读写模式");
    if (fd != -1) {
        printf("    ✓ 读写打开成功\n");
        close(fd);
    }
    
    // 只写打开
    printf("  只写打开 (O_WRONLY):\n");
    fd = open_file_by_handle_safe(file_info.handle, O_WRONLY, "只写模式");
    if (fd != -1) {
        printf("    ✓ 只写打开成功\n");
        close(fd);
    }
    
    // 尝试写入只读打开的文件
    printf("  测试写入权限:\n");
    fd = open_file_by_handle_safe(file_info.handle, O_RDONLY, "只读模式测试写入");
    if (fd != -1) {
        const char *test_write = "测试写入";
        ssize_t write_result = write(fd, test_write, strlen(test_write));
        if (write_result == -1) {
            printf("    ✓ 正确拒绝写入操作: %s\n", strerror(errno));
        } else {
            printf("    ✗ 意外允许写入操作\n");
        }
        close(fd);
    }
    
    // 清理资源
    printf("\n8. 清理资源:\n");
    free(file_info.handle);
    unlink(renamed_name);
    printf("  ✓ 清理完成\n");
    
    printf("\n=== 文件句柄安全性和持久性总结 ===\n");
    printf("安全性优势:\n");
    printf("1. 路径无关: 不受符号链接攻击影响\n");
    printf("2. 权限控制: 仍然遵循文件系统权限\n");
    printf("3. 系统级: 由内核维护,无法伪造\n");
    printf("4. 访问控制: 可以通过打开标志控制访问权限\n");
    printf("\n");
    printf("持久性优势:\n");
    printf("1. 文件移动: 重命名后句柄仍然有效\n");
    printf("2. 目录重组: 目录结构调整不影响句柄\n");
    printf("3. 跨会话: 可以在不同进程间传递\n");
    printf("4. 稳定标识: 提供稳定的文件标识机制\n");
    printf("\n");
    printf("适用场景:\n");
    printf("1. 文件监控和审计\n");
    printf("2. 备份和同步系统\n");
    printf("3. 容器文件系统\n");
    printf("4. 网络文件传输\n");
    printf("5. 安全文件访问控制\n");
    
    return 0;
}

示例3:完整的文件句柄管理工具

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

// 配置结构体
struct handle_tool_config {
    char *filename;
    char *handle_file;
    int create_handle;
    int open_file;
    int show_info;
    int verbose;
    int flags;
    char *output_file;
};

// 保存文件句柄到文件
int save_handle_to_file(const struct file_handle *handle, int mount_id, const char *filename) {
    FILE *fp = fopen(filename, "wb");
    if (!fp) {
        perror("打开句柄文件失败");
        return -1;
    }
    
    // 写入挂载 ID
    if (fwrite(&mount_id, sizeof(mount_id), 1, fp) != 1) {
        perror("写入挂载 ID 失败");
        fclose(fp);
        return -1;
    }
    
    // 写入句柄大小
    if (fwrite(&handle->handle_bytes, sizeof(handle->handle_bytes), 1, fp) != 1) {
        perror("写入句柄大小失败");
        fclose(fp);
        return -1;
    }
    
    // 写入句柄类型
    if (fwrite(&handle->handle_type, sizeof(handle->handle_type), 1, fp) != 1) {
        perror("写入句柄类型失败");
        fclose(fp);
        return -1;
    }
    
    // 写入句柄数据
    if (fwrite(handle->f_handle, handle->handle_bytes, 1, fp) != 1) {
        perror("写入句柄数据失败");
        fclose(fp);
        return -1;
    }
    
    fclose(fp);
    printf("✓ 句柄已保存到: %s\n", filename);
    return 0;
}

// 从文件加载文件句柄
struct file_handle* load_handle_from_file(const char *filename, int *mount_id) {
    FILE *fp = fopen(filename, "rb");
    if (!fp) {
        perror("打开句柄文件失败");
        return NULL;
    }
    
    // 读取挂载 ID
    if (fread(mount_id, sizeof(*mount_id), 1, fp) != 1) {
        perror("读取挂载 ID 失败");
        fclose(fp);
        return NULL;
    }
    
    // 读取句柄大小
    unsigned int handle_bytes;
    if (fread(&handle_bytes, sizeof(handle_bytes), 1, fp) != 1) {
        perror("读取句柄大小失败");
        fclose(fp);
        return NULL;
    }
    
    // 分配句柄内存
    size_t handle_size = sizeof(struct file_handle) + handle_bytes;
    struct file_handle *handle = malloc(handle_size);
    if (!handle) {
        perror("内存分配失败");
        fclose(fp);
        return NULL;
    }
    
    handle->handle_bytes = handle_bytes;
    
    // 读取句柄类型
    if (fread(&handle->handle_type, sizeof(handle->handle_type), 1, fp) != 1) {
        perror("读取句柄类型失败");
        free(handle);
        fclose(fp);
        return NULL;
    }
    
    // 读取句柄数据
    if (fread(handle->f_handle, handle_bytes, 1, fp) != 1) {
        perror("读取句柄数据失败");
        free(handle);
        fclose(fp);
        return NULL;
    }
    
    fclose(fp);
    printf("✓ 从 %s 加载句柄成功\n", filename);
    return handle;
}

// 获取文件句柄
int get_file_handle_safe(const char *filename, struct file_handle **handle, int *mount_id) {
    size_t handle_size = sizeof(struct file_handle);
    *handle = malloc(handle_size);
    if (!*handle) {
        return -1;
    }
    
    (*handle)->handle_bytes = 0;
    
    int result = name_to_handle_at(AT_FDCWD, filename, *handle, mount_id, 0);
    if (result == -1 && errno == EOVERFLOW) {
        handle_size = sizeof(struct file_handle) + (*handle)->handle_bytes;
        free(*handle);
        
        *handle = malloc(handle_size);
        if (!*handle) {
            return -1;
        }
        
        result = name_to_handle_at(AT_FDCWD, filename, *handle, mount_id, 0);
    }
    
    return result;
}

// 通过句柄打开文件
int open_file_by_handle_safe(struct file_handle *handle, int flags, const char *description) {
    if (description) {
        printf("通过句柄打开文件: %s\n", description);
    }
    
    int fd = open_by_handle_at(AT_FDCWD, handle, flags);
    if (fd != -1) {
        if (description) {
            printf("  ✓ 成功打开文件 (fd: %d)\n", fd);
        }
        return fd;
    } else {
        if (description) {
            printf("  ✗ 打开文件失败: %s\n", strerror(errno));
        }
        return -1;
    }
}

// 显示句柄信息
void show_handle_info(const struct file_handle *handle, int mount_id) {
    printf("=== 文件句柄信息 ===\n");
    printf("挂载 ID: %d\n", mount_id);
    printf("句柄类型: %d\n", handle->handle_type);
    printf("句柄大小: %u 字节\n", handle->handle_bytes);
    
    printf("句柄数据 (十六进制): ");
    for (unsigned int i = 0; i < handle->handle_bytes && i < 64; i++) {
        printf("%02x", handle->f_handle[i]);
    }
    if (handle->handle_bytes > 64) {
        printf("...(还有 %u 字节)", handle->handle_bytes - 64);
    }
    printf("\n");
}

// 复制文件内容
int copy_file_content(int src_fd, const char *output_filename) {
    int dst_fd = open(output_filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (dst_fd == -1) {
        perror("创建输出文件失败");
        return -1;
    }
    
    char buffer[4096];
    ssize_t bytes_read, bytes_written;
    off_t total_bytes = 0;
    
    while ((bytes_read = read(src_fd, buffer, sizeof(buffer))) > 0) {
        bytes_written = write(dst_fd, buffer, bytes_read);
        if (bytes_written != bytes_read) {
            perror("写入输出文件失败");
            close(dst_fd);
            return -1;
        }
        total_bytes += bytes_written;
    }
    
    if (bytes_read == -1) {
        perror("读取源文件失败");
        close(dst_fd);
        return -1;
    }
    
    close(dst_fd);
    printf("✓ 成功复制 %ld 字节到: %s\n", (long)total_bytes, output_filename);
    return 0;
}

// 显示帮助信息
void show_help(const char *program_name) {
    printf("用法: %s [选项]\n", program_name);
    printf("\n选项:\n");
    printf("  -f, --file=FILE        源文件名\n");
    printf("  -h, --handle=FILE      句柄文件名\n");
    printf("  -c, --create           创建文件句柄\n");
    printf("  -o, --open             通过句柄打开文件\n");
    printf("  -i, --info             显示句柄信息\n");
    printf("  -r, --read-only        以只读方式打开\n");
    printf("  -w, --read-write       以读写方式打开\n");
    printf("  -O, --output=FILE      输出文件名(用于复制)\n");
    printf("  -v, --verbose          详细输出\n");
    printf("  --help                 显示此帮助信息\n");
    printf("\n示例:\n");
    printf("  %s -f /etc/passwd -c -h passwd.handle    # 创建句柄\n", program_name);
    printf("  %s -h passwd.handle -o -r                # 通过句柄只读打开\n", program_name);
    printf("  %s -h passwd.handle -o -w -O copy.txt    # 通过句柄复制文件\n", program_name);
    printf("  %s -h passwd.handle -i                   # 显示句柄信息\n", program_name);
}

int main(int argc, char *argv[]) {
    struct handle_tool_config config = {
        .filename = NULL,
        .handle_file = NULL,
        .create_handle = 0,
        .open_file = 0,
        .show_info = 0,
        .verbose = 0,
        .flags = O_RDONLY,
        .output_file = NULL
    };
    
    printf("=== 文件句柄管理工具 ===\n\n");
    
    // 解析命令行参数
    static struct option long_options[] = {
        {"file",      required_argument, 0, 'f'},
        {"handle",    required_argument, 0, 'h'},
        {"create",    no_argument,       0, 'c'},
        {"open",      no_argument,       0, 'o'},
        {"info",      no_argument,       0, 'i'},
        {"read-only", no_argument,       0, 'r'},
        {"read-write", no_argument,      0, 'w'},
        {"output",    required_argument, 0, 'O'},
        {"verbose",   no_argument,       0, 'v'},
        {"help",      no_argument,       0, 1000},
        {0, 0, 0, 0}
    };
    
    int opt;
    while ((opt = getopt_long(argc, argv, "f:h:coirwO:v", long_options, NULL)) != -1) {
        switch (opt) {
            case 'f':
                config.filename = optarg;
                break;
            case 'h':
                config.handle_file = optarg;
                break;
            case 'c':
                config.create_handle = 1;
                break;
            case 'o':
                config.open_file = 1;
                break;
            case 'i':
                config.show_info = 1;
                break;
            case 'r':
                config.flags = O_RDONLY;
                break;
            case 'w':
                config.flags = O_RDWR;
                break;
            case 'O':
                config.output_file = optarg;
                break;
            case 'v':
                config.verbose = 1;
                break;
            case 1000:  // --help
                show_help(argv[0]);
                return 0;
            default:
                fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv[0]);
                return 1;
        }
    }
    
    // 验证参数
    if (!config.create_handle && !config.open_file && !config.show_info) {
        show_help(argv[0]);
        return 0;
    }
    
    struct file_handle *handle = NULL;
    int mount_id;
    
    // 如果需要创建句柄
    if (config.create_handle && config.filename) {
        if (access(config.filename, F_OK) != 0) {
            fprintf(stderr, "文件不存在: %s\n", config.filename);
            return 1;
        }
        
        printf("创建文件句柄: %s\n", config.filename);
        
        if (get_file_handle_safe(config.filename, &handle, &mount_id) == 0) {
            printf("✓ 成功获取文件句柄\n");
            
            if (config.show_info) {
                show_handle_info(handle, mount_id);
            }
            
            if (config.handle_file) {
                if (save_handle_to_file(handle, mount_id, config.handle_file) == 0) {
                    printf("✓ 句柄保存成功\n");
                } else {
                    fprintf(stderr, "句柄保存失败\n");
                    free(handle);
                    return 1;
                }
            }
        } else {
            fprintf(stderr, "获取文件句柄失败: %s\n", strerror(errno));
            return 1;
        }
    }
    // 如果需要加载句柄
    else if (config.handle_file) {
        if (access(config.handle_file, F_OK) != 0) {
            fprintf(stderr, "句柄文件不存在: %s\n", config.handle_file);
            return 1;
        }
        
        handle = load_handle_from_file(config.handle_file, &mount_id);
        if (!handle) {
            return 1;
        }
        
        if (config.show_info) {
            show_handle_info(handle, mount_id);
        }
    } else {
        fprintf(stderr, "需要指定文件或句柄文件\n");
        show_help(argv[0]);
        return 1;
    }
    
    // 通过句柄打开文件
    if (config.open_file && handle) {
        int fd = open_file_by_handle_safe(handle, config.flags, 
                                         config.filename ? config.filename : "加载的句柄");
        if (fd != -1) {
            printf("✓ 文件打开成功\n");
            
            // 获取文件信息
            struct stat st;
            if (fstat(fd, &st) == 0) {
                printf("文件信息:\n");
                printf("  大小: %ld 字节\n", (long)st.st_size);
                printf("  权限: %o\n", st.st_mode & 0777);
                printf("  修改时间: %s", ctime(&st.st_mtime));
            }
            
            // 如果指定了输出文件,复制内容
            if (config.output_file) {
                if (copy_file_content(fd, config.output_file) != 0) {
                    close(fd);
                    free(handle);
                    return 1;
                }
            }
            // 否则显示部分内容
            else if (config.flags & (O_RDONLY | O_RDWR)) {
                printf("文件内容预览:\n");
                char buffer[512];
                ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
                if (bytes_read > 0) {
                    buffer[bytes_read] = '\0';
                    // 只显示前 200 个字符
                    if (strlen(buffer) > 200) {
                        buffer[200] = '\0';
                        printf("%s...\n", buffer);
                    } else {
                        printf("%s\n", buffer);
                    }
                }
            }
            
            close(fd);
        } else {
            free(handle);
            return 1;
        }
    }
    
    // 清理资源
    if (handle) {
        free(handle);
    }
    
    printf("\n=== 文件句柄工具使用建议 ===\n");
    printf("适用场景:\n");
    printf("1. 文件监控和审计系统\n");
    printf("2. 备份和同步工具\n");
    printf("3. 容器和虚拟化环境\n");
    printf("4. 网络文件传输\n");
    printf("5. 安全文件访问控制\n");
    printf("\n");
    printf("安全建议:\n");
    printf("1. 妥善保管句柄文件\n");
    printf("2. 使用适当的文件权限\n");
    printf("3. 验证句柄的有效性\n");
    printf("4. 及时关闭文件描述符\n");
    printf("5. 处理句柄失效的情况\n");
    printf("\n");
    printf("性能优化:\n");
    printf("1. 批量处理多个文件\n");
    printf("2. 缓存常用文件句柄\n");
    printf("3. 异步操作大文件\n");
    printf("4. 合理设置缓冲区大小\n");
    
    return 0;
}

编译和运行说明

# 编译示例程序
gcc -o open_by_handle_at_example1 example1.c
gcc -o open_by_handle_at_example2 example2.c
gcc -o open_by_handle_at_example3 example3.c

# 运行示例
./open_by_handle_at_example1
./open_by_handle_at_example2
./open_by_handle_at_example3 --help

# 基本操作示例
./open_by_handle_at_example3 -f /etc/passwd -c -h passwd.handle
./open_by_handle_at_example3 -h passwd.handle -o -r
./open_by_handle_at_example3 -h passwd.handle -i
./open_by_handle_at_example3 -h passwd.handle -o -w -O passwd_copy.txt

系统要求检查

# 检查内核版本(需要 2.6.39+)
uname -r

# 检查文件系统支持
grep -i handle /boot/config-$(uname -r)

# 检查系统调用支持
grep -w open_by_handle_at /usr/include/asm/unistd_64.h

# 查看文件系统类型
df -T /etc/passwd

# 检查当前用户权限
id

重要注意事项

  1. 内核版本: 需要 Linux 2.6.39+ 内核支持
  2. 文件系统: 不是所有文件系统都支持文件句柄
  3. 权限要求: 需要对文件有适当访问权限
  4. 错误处理: 始终检查返回值和 errno
  5. 内存管理: 正确分配和释放句柄内存
  6. 文件描述符: 及时关闭打开的文件描述符
  7. 句柄失效: 处理文件删除导致的句柄失效

实际应用场景

  1. 文件监控: 监控特定文件的变更而不依赖路径
  2. 备份系统: 标识和跟踪备份文件
  3. 容器技术: 容器内文件系统管理
  4. 网络传输: 安全的文件标识和传输
  5. 审计系统: 文件访问审计和追踪
  6. 数据库系统: 文件标识和管理

最佳实践

// 安全的文件句柄打开函数
int safe_open_by_handle(struct file_handle *handle, int flags, const char *description) {
    // 验证参数
    if (!handle) {
        errno = EINVAL;
        return -1;
    }
    
    // 验证标志
    if (flags & (O_CREAT | O_EXCL | O_TRUNC)) {
        fprintf(stderr, "警告: 文件句柄打开不支持创建/截断标志\n");
        flags &= ~(O_CREAT | O_EXCL | O_TRUNC);
    }
    
    // 打开文件
    int fd = open_by_handle_at(AT_FDCWD, handle, flags);
    if (fd == -1) {
        switch (errno) {
            case EACCES:
                fprintf(stderr, "权限不足访问文件");
                if (description) fprintf(stderr, ": %s", description);
                fprintf(stderr, "\n");
                break;
            case ESTALE:
                fprintf(stderr, "文件句柄已失效");
                if (description) fprintf(stderr, ": %s", description);
                fprintf(stderr, "\n");
                break;
            case EOPNOTSUPP:
                fprintf(stderr, "文件系统不支持文件句柄");
                if (description) fprintf(stderr, ": %s", description);
                fprintf(stderr, "\n");
                break;
        }
    } else if (description) {
        printf("通过句柄成功打开文件: %s (fd: %d)\n", description, fd);
    }
    
    return fd;
}

// 句柄管理结构体
typedef struct {
    struct file_handle *handle;
    int mount_id;
    int fd;
    char *filename;
    time_t create_time;
} handle_manager_t;

// 初始化句柄管理器
int handle_manager_init(handle_manager_t *mgr, const char *filename) {
    mgr->filename = strdup(filename);
    if (!mgr->filename) {
        return -1;
    }
    
    mgr->create_time = time(NULL);
    mgr->fd = -1;
    mgr->handle = NULL;
    
    return get_file_handle_safe(filename, &mgr->handle, &mgr->mount_id);
}

// 通过句柄打开文件
int handle_manager_open(handle_manager_t *mgr, int flags) {
    if (mgr->fd != -1) {
        close(mgr->fd);
    }
    
    mgr->fd = safe_open_by_handle(mgr->handle, flags, mgr->filename);
    return mgr->fd;
}

// 清理句柄管理器
void handle_manager_cleanup(handle_manager_t *mgr) {
    if (mgr->fd != -1) {
        close(mgr->fd);
        mgr->fd = -1;
    }
    
    if (mgr->handle) {
        free(mgr->handle);
        mgr->handle = NULL;
    }
    
    if (mgr->filename) {
        free(mgr->filename);
        mgr->filename = NULL;
    }
}

这些示例展示了 open_by_handle_at 函数的各种使用方法,从基础的句柄打开到完整的管理工具,帮助你全面掌握 Linux 系统中通过文件句柄访问文件的机制。

发表在 linux文章 | 留下评论

openat系统调用及示例

openat – 相对于目录文件描述符打开文件

1. 函数介绍

openat 是一个 Linux 系统调用,用于相对于指定目录文件描述符打开文件。它是 open 函数的扩展版本,提供了更灵活的文件打开方式,支持相对路径操作,避免了某些竞态条件。

2. 函数原型

#include <fcntl.h>

int openat(int dirfd, const char *pathname, int flags);
int openat(int dirfd, const char *pathname, int flags, mode_t mode);

3. 功能

相对于目录文件描述符 dirfd 打开或创建文件。如果 pathname 是相对路径,则相对于 dirfd 指定的目录解析;如果是绝对路径,则忽略 dirfd

4. 参数

  • int dirfd: 目录文件描述符
    • AT_FDCWD: 使用当前工作目录
    • 有效的目录文件描述符:相对于该目录进行操作
    • 负数:特殊值(如 AT_FDCWD
  • const char *pathname: 文件路径名
    • 绝对路径:从根目录开始
    • 相对路径:相对于 dirfd 指定的目录
  • int flags: 文件打开标志
    • O_RDONLY: 只读打开
    • O_WRONLY: 只写打开
    • O_RDWR: 读写打开
    • O_CREAT: 文件不存在时创建
    • O_EXCL: 与 O_CREAT 配合使用,确保原子创建
    • O_TRUNC: 截断已存在的文件
    • O_APPEND: 追加模式
    • O_NONBLOCK: 非阻塞模式
    • O_SYNC: 同步写入
    • 等等…
  • mode_t mode: 文件权限模式(当使用 O_CREAT 时必需)
    • 例如:06440755 等

5. 返回值

  • 成功时返回新的文件描述符(非负整数)
  • 失败时返回 -1,并设置 errno

6. 常见 errno 错误码

  • EACCES: 权限不足
  • EEXIST: 文件已存在(使用 O_CREAT|O_EXCL 时)
  • EISDIR: 路径指向目录而非文件
  • ENOENT: 文件或路径不存在
  • ENOTDIRdirfd 不是目录文件描述符
  • EACCES: 访问权限不足
  • ELOOP: 符号链接层级过深
  • ENAMETOOLONG: 路径名过长
  • ENOMEM: 内存不足
  • ENOSPC: 磁盘空间不足
  • EROFS: 文件系统为只读

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

  • open(): 传统的文件打开函数
  • creat(): 创建文件(已废弃,使用 open 代替)
  • close(): 关闭文件描述符
  • read()write(): 文件读写操作
  • fstatat(): 相对于目录文件描述符获取文件状态
  • mkdirat(): 相对于目录文件描述符创建目录
  • unlinkat(): 相对于目录文件描述符删除文件
  • readlinkat(): 相对于目录文件描述符读取符号链接

8. 示例代码

示例1:基本使用 – 相对路径文件操作

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

int main() {
    printf("=== openat 基本使用演示 ===\n");
    
    // 创建测试目录结构
    if (mkdir("test_dir", 0755) == -1 && errno != EEXIST) {
        perror("创建测试目录失败");
        exit(EXIT_FAILURE);
    }
    
    printf("创建测试目录: test_dir\n");
    
    // 方法1: 使用 AT_FDCWD(当前工作目录)打开文件
    int fd1 = openat(AT_FDCWD, "test_dir/test_file1.txt", 
                     O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd1 == -1) {
        perror("使用 AT_FDCWD 创建文件失败");
        rmdir("test_dir");
        exit(EXIT_FAILURE);
    }
    
    printf("✓ 使用 AT_FDCWD 成功创建文件: test_dir/test_file1.txt (fd: %d)\n", fd1);
    
    // 写入数据
    const char *content1 = "This is test file 1 content.\n";
    write(fd1, content1, strlen(content1));
    close(fd1);
    
    // 方法2: 打开目录获取文件描述符,然后相对打开文件
    int dirfd = open("test_dir", O_RDONLY);
    if (dirfd == -1) {
        perror("打开目录失败");
        unlink("test_dir/test_file1.txt");
        rmdir("test_dir");
        exit(EXIT_FAILURE);
    }
    
    printf("✓ 成功打开目录: test_dir (dirfd: %d)\n", dirfd);
    
    // 相对于目录文件描述符创建文件
    int fd2 = openat(dirfd, "test_file2.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd2 == -1) {
        perror("相对目录创建文件失败");
        close(dirfd);
        unlink("test_dir/test_file1.txt");
        rmdir("test_dir");
        exit(EXIT_FAILURE);
    }
    
    printf("✓ 相对目录成功创建文件: test_file2.txt (fd: %d)\n", fd2);
    
    // 写入数据
    const char *content2 = "This is test file 2 content.\n";
    write(fd2, content2, strlen(content2));
    close(fd2);
    
    // 验证文件创建结果
    printf("\n验证创建的文件:\n");
    
    // 读取第一个文件
    int fd_read1 = openat(AT_FDCWD, "test_dir/test_file1.txt", O_RDONLY);
    if (fd_read1 != -1) {
        char buffer[256];
        ssize_t bytes_read = read(fd_read1, buffer, sizeof(buffer) - 1);
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            printf("  test_file1.txt 内容: %s", buffer);
        }
        close(fd_read1);
    }
    
    // 相对读取第二个文件
    int fd_read2 = openat(dirfd, "test_file2.txt", O_RDONLY);
    if (fd_read2 != -1) {
        char buffer[256];
        ssize_t bytes_read = read(fd_read2, buffer, sizeof(buffer) - 1);
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            printf("  test_file2.txt 内容: %s", buffer);
        }
        close(fd_read2);
    }
    
    // 清理资源
    close(dirfd);
    unlink("test_dir/test_file1.txt");
    unlink("test_dir/test_file2.txt");
    rmdir("test_dir");
    
    printf("✓ 完成 openat 基本使用演示\n");
    
    return 0;
}

示例2:错误处理和特殊情况

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

void test_openat_errors(int dirfd, const char *pathname, int flags, const char *description) {
    printf("\n测试 %s:\n", description);
    printf("  dirfd: %d\n", dirfd);
    printf("  pathname: %s\n", pathname);
    printf("  flags: 0x%x\n", flags);
    
    int fd = openat(dirfd, pathname, flags);
    if (fd == -1) {
        printf("  结果: 失败 - %s\n", strerror(errno));
        switch (errno) {
            case EACCES:
                printf("    原因: 权限不足\n");
                break;
            case ENOENT:
                printf("    原因: 文件或目录不存在\n");
                break;
            case ENOTDIR:
                printf("    原因: dirfd 不是目录文件描述符\n");
                break;
            case EISDIR:
                printf("    原因: 路径指向目录\n");
                break;
            case EEXIST:
                printf("    原因: 文件已存在 (O_CREAT|O_EXCL)\n");
                break;
            case ELOOP:
                printf("    原因: 符号链接层级过深\n");
                break;
            case ENAMETOOLONG:
                printf("    原因: 路径名过长\n");
                break;
            default:
                printf("    原因: 其他错误\n");
                break;
        }
    } else {
        printf("  结果: 成功 (fd: %d)\n", fd);
        close(fd);
    }
}

int main() {
    printf("=== openat 错误处理测试 ===\n");
    
    // 创建测试环境
    if (mkdir("error_test_dir", 0755) == -1 && errno != EEXIST) {
        perror("创建测试目录失败");
        exit(EXIT_FAILURE);
    }
    
    // 创建测试文件
    int test_fd = open("error_test_dir/test_file.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (test_fd != -1) {
        write(test_fd, "test content", 12);
        close(test_fd);
        printf("创建测试文件: error_test_dir/test_file.txt\n");
    }
    
    // 打开目录获取文件描述符
    int dirfd = open("error_test_dir", O_RDONLY);
    if (dirfd == -1) {
        perror("打开测试目录失败");
        unlink("error_test_dir/test_file.txt");
        rmdir("error_test_dir");
        exit(EXIT_FAILURE);
    }
    
    printf("打开测试目录: error_test_dir (dirfd: %d)\n", dirfd);
    
    // 测试正常情况
    test_openat_errors(dirfd, "test_file.txt", O_RDONLY, "正常相对路径打开");
    test_openat_errors(AT_FDCWD, "error_test_dir/test_file.txt", O_RDONLY, "AT_FDCWD 绝对路径打开");
    
    // 测试各种错误情况
    test_openat_errors(dirfd, "nonexistent.txt", O_RDONLY, "不存在的文件");
    test_openat_errors(-2, "test_file.txt", O_RDONLY, "无效的 dirfd");
    test_openat_errors(dirfd, "../outside_file.txt", O_RDONLY, "跳出目录的路径");
    test_openat_errors(dirfd, "", O_RDONLY, "空路径名");
    
    // 测试创建已存在的文件(不使用 O_EXCL)
    test_openat_errors(dirfd, "test_file.txt", O_CREAT | O_WRONLY, "创建已存在的文件");
    
    // 测试原子创建(使用 O_EXCL)
    test_openat_errors(dirfd, "test_file.txt", O_CREAT | O_EXCL | O_WRONLY, 0644, 
                      "原子创建已存在的文件");
    
    // 测试目录作为文件打开
    test_openat_errors(AT_FDCWD, "error_test_dir", O_RDONLY, "将目录作为文件打开");
    
    // 清理测试环境
    close(dirfd);
    unlink("error_test_dir/test_file.txt");
    rmdir("error_test_dir");
    
    return 0;
}

示例3:相对路径和安全性演示

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

void demonstrate_security_benefits() {
    printf("=== openat 安全性优势演示 ===\n");
    
    // 创建测试目录结构
    if (mkdir("security_test", 0755) == -1 && errno != EEXIST) {
        perror("创建安全测试目录失败");
        return;
    }
    
    // 在测试目录中创建文件
    int fd = open("security_test/data.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd != -1) {
        write(fd, "sensitive data", 14);
        close(fd);
        printf("创建敏感数据文件: security_test/data.txt\n");
    }
    
    // 打开目录获取文件描述符
    int dirfd = open("security_test", O_RDONLY);
    if (dirfd == -1) {
        perror("打开安全测试目录失败");
        unlink("security_test/data.txt");
        rmdir("security_test");
        return;
    }
    
    printf("✓ 成功打开安全目录 (dirfd: %d)\n", dirfd);
    
    // 演示安全性优势
    printf("\n安全性优势对比:\n");
    
    // 传统方式:可能受到竞态条件影响
    printf("传统方式风险:\n");
    printf("  1. 先检查文件是否存在\n");
    printf("  2. 再打开文件进行操作\n");
    printf("  3. 在步骤1和2之间,文件可能被修改\n");
    
    // openat 方式:更安全
    printf("\nopenat 方式优势:\n");
    printf("  1. 基于已打开的目录文件描述符操作\n");
    printf("  2. 避免路径解析过程中的竞态条件\n");
    printf("  3. 防止目录遍历攻击\n");
    printf("  4. 确保操作在指定目录范围内\n");
    
    // 演示相对路径限制
    printf("\n相对路径限制演示:\n");
    
    // 尝试访问上级目录(应该失败或受限)
    int restricted_fd = openat(dirfd, "../outside_file.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (restricted_fd != -1) {
        printf("  警告: 能够访问上级目录文件\n");
        close(restricted_fd);
        unlink("../outside_file.txt");
    } else {
        printf("  ✓ 正确限制了目录遍历访问\n");
    }
    
    // 正常的相对路径操作
    int normal_fd = openat(dirfd, "data.txt", O_RDONLY);
    if (normal_fd != -1) {
        char buffer[64];
        ssize_t bytes_read = read(normal_fd, buffer, sizeof(buffer) - 1);
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            printf("  ✓ 正常访问相对路径文件: %s\n", buffer);
        }
        close(normal_fd);
    }
    
    // 清理资源
    close(dirfd);
    unlink("security_test/data.txt");
    rmdir("security_test");
}

void demonstrate_relative_path_operations() {
    printf("\n=== 相对路径操作演示 ===\n");
    
    // 创建多层目录结构
    if (mkdir("multi_level", 0755) == -1 && errno != EEXIST) {
        perror("创建多层目录失败");
        return;
    }
    
    if (mkdir("multi_level/subdir1", 0755) == -1 && errno != EEXIST) {
        perror("创建子目录1失败");
        rmdir("multi_level");
        return;
    }
    
    if (mkdir("multi_level/subdir2", 0755) == -1 && errno != EEXIST) {
        perror("创建子目录2失败");
        rmdir("multi_level/subdir1");
        rmdir("multi_level");
        return;
    }
    
    printf("创建多层目录结构:\n");
    printf("  multi_level/\n");
    printf("    subdir1/\n");
    printf("    subdir2/\n");
    
    // 打开根目录
    int root_fd = open("multi_level", O_RDONLY);
    if (root_fd == -1) {
        perror("打开根目录失败");
        rmdir("multi_level/subdir1");
        rmdir("multi_level/subdir2");
        rmdir("multi_level");
        return;
    }
    
    printf("✓ 打开根目录 (fd: %d)\n", root_fd);
    
    // 相对于根目录在 subdir1 中创建文件
    int subdir1_fd = openat(root_fd, "subdir1", O_RDONLY);
    if (subdir1_fd != -1) {
        int file1_fd = openat(subdir1_fd, "file1.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
        if (file1_fd != -1) {
            write(file1_fd, "Content in subdir1", 18);
            close(file1_fd);
            printf("✓ 在 subdir1 中创建文件\n");
        }
        close(subdir1_fd);
    }
    
    // 相对于根目录在 subdir2 中创建文件
    int subdir2_fd = openat(root_fd, "subdir2", O_RDONLY);
    if (subdir2_fd != -1) {
        int file2_fd = openat(subdir2_fd, "file2.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
        if (file2_fd != -1) {
            write(file2_fd, "Content in subdir2", 18);
            close(file2_fd);
            printf("✓ 在 subdir2 中创建文件\n");
        }
        close(subdir2_fd);
    }
    
    // 验证创建的文件
    printf("\n验证创建的文件:\n");
    
    // 读取 subdir1 中的文件
    subdir1_fd = openat(root_fd, "subdir1", O_RDONLY);
    if (subdir1_fd != -1) {
        int read_fd = openat(subdir1_fd, "file1.txt", O_RDONLY);
        if (read_fd != -1) {
            char buffer[64];
            ssize_t bytes_read = read(read_fd, buffer, sizeof(buffer) - 1);
            if (bytes_read > 0) {
                buffer[bytes_read] = '\0';
                printf("  subdir1/file1.txt: %s\n", buffer);
            }
            close(read_fd);
        }
        close(subdir1_fd);
    }
    
    // 读取 subdir2 中的文件
    subdir2_fd = openat(root_fd, "subdir2", O_RDONLY);
    if (subdir2_fd != -1) {
        int read_fd = openat(subdir2_fd, "file2.txt", O_RDONLY);
        if (read_fd != -1) {
            char buffer[64];
            ssize_t bytes_read = read(read_fd, buffer, sizeof(buffer) - 1);
            if (bytes_read > 0) {
                buffer[bytes_read] = '\0';
                printf("  subdir2/file2.txt: %s\n", buffer);
            }
            close(read_fd);
        }
        close(subdir2_fd);
    }
    
    // 清理资源
    close(root_fd);
    
    // 删除创建的文件和目录
    unlink("multi_level/subdir1/file1.txt");
    unlink("multi_level/subdir2/file2.txt");
    rmdir("multi_level/subdir1");
    rmdir("multi_level/subdir2");
    rmdir("multi_level");
    
    printf("✓ 完成相对路径操作演示\n");
}

int main() {
    printf("=== openat 安全性和相对路径演示 ===\n");
    
    demonstrate_security_benefits();
    demonstrate_relative_path_operations();
    
    return 0;
}

示例4:高级文件操作工具

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

typedef struct {
    int fd;
    char path[256];
    int is_dir;
} file_handle_t;

#define MAX_HANDLES 64
static file_handle_t handles[MAX_HANDLES];
static int handle_count = 0;

int register_handle(int fd, const char *path, int is_dir) {
    if (handle_count >= MAX_HANDLES) {
        return -1;
    }
    
    handles[handle_count].fd = fd;
    strncpy(handles[handle_count].path, path, sizeof(handles[handle_count].path) - 1);
    handles[handle_count].path[sizeof(handles[handle_count].path) - 1] = '\0';
    handles[handle_count].is_dir = is_dir;
    
    return handle_count++;
}

int unregister_handle(int fd) {
    for (int i = 0; i < handle_count; i++) {
        if (handles[i].fd == fd) {
            // 将后续句柄前移
            for (int j = i; j < handle_count - 1; j++) {
                handles[j] = handles[j + 1];
            }
            handle_count--;
            return 0;
        }
    }
    return -1;
}

void list_handles() {
    printf("=== 当前打开的文件句柄 ===\n");
    
    if (handle_count == 0) {
        printf("没有打开的文件句柄\n");
        return;
    }
    
    printf("%-4s %-6s %-8s %s\n", "ID", "FD", "类型", "路径");
    printf("%-4s %-6s %-8s %s\n", "--", "--", "--", "--");
    
    for (int i = 0; i < handle_count; i++) {
        printf("%-4d %-6d %-8s %s\n",
               i, handles[i].fd,
               handles[i].is_dir ? "目录" : "文件",
               handles[i].path);
    }
    
    printf("总计: %d 个句柄\n", handle_count);
}

void interactive_file_manager() {
    int choice;
    char path[256];
    int dirfd;
    int flags;
    mode_t mode;
    
    while (1) {
        printf("\n=== openat 文件管理工具 ===\n");
        printf("1. 相对打开文件\n");
        printf("2. 打开目录\n");
        printf("3. 创建文件\n");
        printf("4. 关闭文件句柄\n");
        printf("5. 列出所有句柄\n");
        printf("6. 读取文件内容\n");
        printf("7. 写入文件内容\n");
        printf("8. 显示文件状态\n");
        printf("0. 退出\n");
        printf("请选择操作: ");
        
        if (scanf("%d", &choice) != 1) {
            printf("输入无效\n");
            while (getchar() != '\n');  // 清空输入缓冲区
            continue;
        }
        
        switch (choice) {
            case 1:
                printf("相对打开文件:\n");
                printf("输入目录句柄 ID (或 -1 表示 AT_FDCWD): ");
                int dir_id;
                if (scanf("%d", &dir_id) == 1) {
                    if (dir_id == -1) {
                        dirfd = AT_FDCWD;
                    } else if (dir_id >= 0 && dir_id < handle_count && handles[dir_id].is_dir) {
                        dirfd = handles[dir_id].fd;
                    } else {
                        printf("无效的目录句柄 ID\n");
                        break;
                    }
                    
                    printf("输入文件路径: ");
                    scanf("%255s", path);
                    
                    printf("输入打开标志 (O_RDONLY=0, O_WRONLY=1, O_RDWR=2): ");
                    int flag_choice;
                    if (scanf("%d", &flag_choice) == 1) {
                        switch (flag_choice) {
                            case 0: flags = O_RDONLY; break;
                            case 1: flags = O_WRONLY; break;
                            case 2: flags = O_RDWR; break;
                            default: flags = O_RDONLY; break;
                        }
                        
                        int fd = openat(dirfd, path, flags);
                        if (fd != -1) {
                            char full_path[512];
                            if (dir_id == -1) {
                                snprintf(full_path, sizeof(full_path), "%s", path);
                            } else {
                                snprintf(full_path, sizeof(full_path), "%s/%s", 
                                        handles[dir_id].path, path);
                            }
                            
                            int id = register_handle(fd, full_path, 0);
                            if (id != -1) {
                                printf("✓ 成功打开文件 (ID: %d, FD: %d)\n", id, fd);
                            } else {
                                printf("✗ 注册句柄失败\n");
                                close(fd);
                            }
                        } else {
                            printf("✗ 打开文件失败: %s\n", strerror(errno));
                        }
                    }
                }
                break;
                
            case 2:
                printf("打开目录:\n");
                printf("输入目录路径: ");
                scanf("%255s", path);
                
                int dir_fd = open(path, O_RDONLY);
                if (dir_fd != -1) {
                    int id = register_handle(dir_fd, path, 1);
                    if (id != -1) {
                        printf("✓ 成功打开目录 (ID: %d, FD: %d)\n", id, dir_fd);
                    } else {
                        printf("✗ 注册句柄失败\n");
                        close(dir_fd);
                    }
                } else {
                    printf("✗ 打开目录失败: %s\n", strerror(errno));
                }
                break;
                
            case 3:
                printf("创建文件:\n");
                printf("输入目录句柄 ID (或 -1 表示 AT_FDCWD): ");
                if (scanf("%d", &dir_id) == 1) {
                    if (dir_id == -1) {
                        dirfd = AT_FDCWD;
                    } else if (dir_id >= 0 && dir_id < handle_count && handles[dir_id].is_dir) {
                        dirfd = handles[dir_id].fd;
                    } else {
                        printf("无效的目录句柄 ID\n");
                        break;
                    }
                    
                    printf("输入文件路径: ");
                    scanf("%255s", path);
                    
                    flags = O_CREAT | O_WRONLY | O_TRUNC;
                    mode = 0644;
                    
                    int fd = openat(dirfd, path, flags, mode);
                    if (fd != -1) {
                        char full_path[512];
                        if (dir_id == -1) {
                            snprintf(full_path, sizeof(full_path), "%s", path);
                        } else {
                            snprintf(full_path, sizeof(full_path), "%s/%s", 
                                    handles[dir_id].path, path);
                        }
                        
                        int id = register_handle(fd, full_path, 0);
                        if (id != -1) {
                            printf("✓ 成功创建文件 (ID: %d, FD: %d)\n", id, fd);
                        } else {
                            printf("✗ 注册句柄失败\n");
                            close(fd);
                        }
                    } else {
                        printf("✗ 创建文件失败: %s\n", strerror(errno));
                    }
                }
                break;
                
            case 4: {
                if (handle_count == 0) {
                    printf("没有打开的句柄\n");
                    break;
                }
                
                list_handles();
                printf("输入要关闭的句柄 ID: ");
                int handle_id;
                if (scanf("%d", &handle_id) == 1) {
                    if (handle_id >= 0 && handle_id < handle_count) {
                        printf("关闭句柄: %s (FD: %d)\n", 
                               handles[handle_id].path, handles[handle_id].fd);
                        
                        close(handles[handle_id].fd);
                        unregister_handle(handles[handle_id].fd);
                        printf("✓ 成功关闭句柄\n");
                    } else {
                        printf("无效的句柄 ID\n");
                    }
                }
                break;
            }
            
            case 5:
                list_handles();
                break;
                
            case 6: {
                if (handle_count == 0) {
                    printf("没有打开的句柄\n");
                    break;
                }
                
                list_handles();
                printf("输入要读取的文件句柄 ID: ");
                int handle_id;
                if (scanf("%d", &handle_id) == 1) {
                    if (handle_id >= 0 && handle_id < handle_count && !handles[handle_id].is_dir) {
                        char buffer[256];
                        ssize_t bytes_read = read(handles[handle_id].fd, buffer, sizeof(buffer) - 1);
                        if (bytes_read > 0) {
                            buffer[bytes_read] = '\0';
                            printf("文件内容:\n%s\n", buffer);
                        } else if (bytes_read == 0) {
                            printf("文件为空\n");
                        } else {
                            printf("读取文件失败: %s\n", strerror(errno));
                        }
                    } else {
                        printf("无效的文件句柄 ID\n");
                    }
                }
                break;
            }
            
            case 7: {
                if (handle_count == 0) {
                    printf("没有打开的句柄\n");
                    break;
                }
                
                list_handles();
                printf("输入要写入的文件句柄 ID: ");
                int handle_id;
                if (scanf("%d", &handle_id) == 1) {
                    if (handle_id >= 0 && handle_id < handle_count && !handles[handle_id].is_dir) {
                        printf("输入要写入的内容: ");
                        char content[256];
                        scanf("%255s", content);
                        
                        ssize_t bytes_written = write(handles[handle_id].fd, content, strlen(content));
                        if (bytes_written > 0) {
                            printf("✓ 成功写入 %zd 字节\n", bytes_written);
                        } else {
                            printf("✗ 写入文件失败: %s\n", strerror(errno));
                        }
                    } else {
                        printf("无效的文件句柄 ID\n");
                    }
                }
                break;
            }
            
            case 8: {
                if (handle_count == 0) {
                    printf("没有打开的句柄\n");
                    break;
                }
                
                list_handles();
                printf("输入要查看状态的句柄 ID: ");
                int handle_id;
                if (scanf("%d", &handle_id) == 1) {
                    if (handle_id >= 0 && handle_id < handle_count) {
                        struct stat st;
                        if (fstat(handles[handle_id].fd, &st) == 0) {
                            printf("文件状态:\n");
                            printf("  路径: %s\n", handles[handle_id].path);
                            printf("  大小: %ld 字节\n", (long)st.st_size);
                            printf("  权限: %o\n", st.st_mode & 0777);
                            printf("  inode: %ld\n", (long)st.st_ino);
                            printf("  链接数: %ld\n", (long)st.st_nlink);
                            printf("  所有者: %d\n", st.st_uid);
                            printf("  组: %d\n", st.st_gid);
                        } else {
                            printf("获取文件状态失败: %s\n", strerror(errno));
                        }
                    } else {
                        printf("无效的句柄 ID\n");
                    }
                }
                break;
            }
            
            case 0:
                printf("退出文件管理工具\n");
                // 清理所有打开的句柄
                for (int i = 0; i < handle_count; i++) {
                    close(handles[i].fd);
                }
                handle_count = 0;
                return;
                
            default:
                printf("无效选择\n");
                break;
        }
    }
}

void demonstrate_openat_features() {
    printf("=== openat 特性演示 ===\n");
    
    printf("openat 主要特性:\n");
    printf("1. 相对路径支持: 相对于目录文件描述符打开文件\n");
    printf("2. 安全性提升: 避免路径解析竞态条件\n");
    printf("3. 灵活性: 支持 AT_FDCWD 和绝对路径\n");
    printf("4. 原子操作: 结合 O_CREAT|O_EXCL 实现原子创建\n");
    printf("5. 容器友好: 在受限环境中更安全\n");
    
    printf("\n使用场景:\n");
    printf("• 容器和沙箱环境中的文件操作\n");
    printf("• 多线程程序中的安全文件访问\n");
    printf("• 需要避免竞态条件的文件操作\n");
    printf("• 相对路径操作的系统工具\n");
}

int main() {
    printf("=== openat 高级文件操作工具 ===\n");
    
    // 显示系统信息
    printf("系统信息:\n");
    printf("  PID: %d\n", getpid());
    printf("  页面大小: %ld 字节\n", (long)getpagesize());
    
    // 演示特性
    demonstrate_openat_features();
    
    // 启动交互式管理器
    char choice;
    printf("\n是否启动交互式文件管理器? (y/N): ");
    if (scanf(" %c", &choice) == 1 && (choice == 'y' || choice == 'Y')) {
        interactive_file_manager();
    }
    
    return 0;
}

9. openat 与 open 的对比

// open vs openat 对比:

// 传统 open 函数:
int fd1 = open("/path/to/file.txt", O_RDONLY);
// • 只能使用绝对或相对于当前目录的路径
// • 可能存在竞态条件
// • 在多线程环境中不够安全

// 现代 openat 函数:
int dirfd = open("/path/to", O_RDONLY);
int fd2 = openat(dirfd, "file.txt", O_RDONLY);
// • 支持相对路径操作
// • 基于已打开的目录文件描述符
// • 避免路径解析竞态条件
// • 更安全的文件操作方式

// 特殊值 AT_FDCWD:
int fd3 = openat(AT_FDCWD, "relative/path.txt", O_RDONLY);
// • 等同于传统的相对路径 open
// • 提供统一的接口

10. 实际应用场景

场景1:安全的文件操作

int secure_file_operation(const char *base_dir, const char *filename) {
    // 打开基础目录
    int dirfd = open(base_dir, O_RDONLY);
    if (dirfd == -1) {
        return -1;
    }
    
    // 相对打开文件(防止目录遍历攻击)
    int fd = openat(dirfd, filename, O_RDONLY);
    if (fd == -1) {
        close(dirfd);
        return -1;
    }
    
    // 执行安全的文件操作
    // ...
    
    close(fd);
    close(dirfd);
    return 0;
}

场景2:容器环境文件访问

int container_file_access(int root_dirfd, const char *path) {
    // 在容器根目录中安全访问文件
    // 防止跳出容器文件系统
    return openat(root_dirfd, path, O_RDONLY);
}

场景3:批量文件操作

int process_directory_files(const char *dirname) {
    int dirfd = open(dirname, O_RDONLY);
    if (dirfd == -1) return -1;
    
    DIR *dir = fdopendir(dirfd);
    if (!dir) {
        close(dirfd);
        return -1;
    }
    
    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) {
        if (entry->d_name[0] == '.') continue;
        
        // 相对打开每个文件
        int fd = openat(dirfd, entry->d_name, O_RDONLY);
        if (fd != -1) {
            // 处理文件...
            process_file(fd);
            close(fd);
        }
    }
    
    closedir(dir);
    return 0;
}

11. 注意事项

使用 openat 时需要注意:

  1. 目录文件描述符dirfd 必须是有效的目录文件描述符
  2. 路径安全: 防止目录遍历攻击(如 ../
  3. 资源管理: 及时关闭文件描述符避免资源泄漏
  4. 错误处理: 仔细处理各种可能的错误情况
  5. 权限检查: 确保有足够的权限访问目标文件
  6. 竞态条件: 在多线程环境中注意同步

12. 系统配置检查

# 查看系统支持的文件操作特性
grep -i openat /proc/self/maps

# 查看文件系统信息
df -T

# 查看进程打开的文件描述符
ls -la /proc/self/fd/

# 检查系统调用表
ausyscall openat

# 查看系统限制
ulimit -n

总结

openat 是现代 Linux 系统中推荐使用的文件打开函数:

关键特性:
1. 相对路径支持: 相对于目录文件描述符打开文件
2. 安全性提升: 避免路径解析竞态条件
3. 灵活性增强: 支持多种操作模式
4. 容器友好: 在受限环境中更安全

主要应用:
1. 安全的文件操作程序
2. 容器和虚拟化环境
3. 系统管理和监控工具
4. 需要避免竞态条件的应用

使用要点:
1. 理解 dirfd 参数的含义和使用
2. 正确处理相对路径和绝对路径
3. 注意资源管理和错误处理
4. 利用安全性优势防止攻击

openat 为现代 Linux 应用程序提供了更安全、更灵活的文件操作方式,是系统编程的重要工具。

发表在 linux文章 | 留下评论

pause系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 pause 函数,它是一个非常简单的系统调用,功能是使调用它的进程(或线程)进入睡眠(阻塞)状态,直到该进程接收到一个信号(signal)为止。


1. 函数介绍

pause 是一个 Linux 系统调用,它的作用非常直接:挂起调用它的进程,使其进入可中断的睡眠状态(interruptible sleep state)。进程会一直保持睡眠,不消耗 CPU 时间,直到发生以下两种情况之一:

1. 接收到信号: 进程被一个信号中断。这可以是任何信号,例如 SIGINT (Ctrl+C), SIGTERM (终止), SIGUSR1 (用户自定义信号) 等。
2. 进程被杀死: 例如收到 SIGKILL 信号,但这通常不会让 pause 返回,因为进程直接被终止了。
当进程因信号而被唤醒时,pause 调用会返回

pause 通常用于那些需要无限期等待某个外部事件(通过信号来通知)的程序中。它提供了一种简单、高效(不占用 CPU)的等待机制。

你可以把它想象成一个人在等待电话。他什么也不做,只是静静地坐着(睡眠),直到电话铃响(收到信号),他才会起身去接电话(pause 返回)。


2. 函数原型

#include <unistd.h> // 必需

int pause(void);

3. 功能

  • 进入睡眠: 调用 pause 的进程会立即放弃 CPU,并被放入内核的等待队列中。
  • 等待信号: 进程进入睡眠状态,直到有任何信号递达(delivered)到该进程。
  • 被信号中断: 当信号被递达时(并且该信号没有被忽略或导致进程终止),进程会从 pause 调用中返回

4. 参数

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

5. 返回值

  • 总是返回 -1pause 调用永远不会成功返回一个非负值。
  • 总是设置 errno: 当 pause 因接收到信号而返回时,它会将 errno 设置为 EINTR (Interrupted system call)。

重要pause 的返回唯一原因就是被信号中断。因此,检查返回值和 errno 通常是确认 pause 是因信号返回的标准做法。


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

  • sleepnanosleep: 这些函数使进程睡眠指定的时间。pause 是无限期睡眠,直到信号。
  • sigsuspend: 这是一个更高级、更安全的用于等待信号的函数。它允许在等待信号的原子性操作中临时替换进程的信号掩码(blocked signals set)。这可以避免在设置信号掩码和调用 pause 之间收到信号的竞态条件(race condition)。
  • 信号处理函数 (signalsigaction): 用于设置当进程收到特定信号时应执行的操作。
  • sigprocmask: 用于检查或修改进程的信号掩码(哪些信号被阻塞)。
  • waitwaitpid: 使进程等待子进程状态改变(结束、停止等),这也是一种阻塞等待。

7. 示例代码

示例 1:基本的 pause 使用和信号处理

这个例子演示了如何使用 pause 使进程等待信号,并通过信号处理函数来响应信号。

#include <unistd.h>   // pause
#include <stdio.h>    // printf, perror
#include <stdlib.h>   // exit
#include <signal.h>   // signal, SIGINT, SIGTERM
#include <errno.h>    // errno
#include <string.h>   // memset

// 全局标志,用于在信号处理函数和主循环间通信
volatile sig_atomic_t signal_received = 0;
volatile int last_signal = 0;

// 信号处理函数
void signal_handler(int sig) {
    printf("\nSignal handler called for signal %d\n", sig);
    signal_received = 1;
    last_signal = sig;
    // 注意:在信号处理函数中,应只调用异步信号安全的函数
    // printf 通常被认为是安全的,但严格来说不是 100% 可靠
    // 更安全的做法是只设置标志位,然后在主循环中检查
}

int main() {
    printf("Process PID: %d\n", getpid());
    printf("Try sending signals using 'kill %d' or pressing Ctrl+C\n", getpid());
    printf("Send SIGTERM (kill %d) or SIGINT (Ctrl+C) to exit.\n", getpid());

    // 1. 设置信号处理函数
    if (signal(SIGINT, signal_handler) == SIG_ERR) {
        perror("signal SIGINT");
        exit(EXIT_FAILURE);
    }
    if (signal(SIGTERM, signal_handler) == SIG_ERR) {
        perror("signal SIGTERM");
        exit(EXIT_FAILURE);
    }
    // 忽略 SIGUSR1,但它仍然会中断 pause
    if (signal(SIGUSR1, SIG_IGN) == SIG_ERR) {
        perror("signal SIGUSR1");
        exit(EXIT_FAILURE);
    }

    printf("Entering main loop with pause()...\n");

    // 2. 主循环
    while (1) {
        // 3. 调用 pause 进入睡眠
        printf("Going to sleep... (waiting for a signal)\n");
        int result = pause(); // 进程在此处挂起

        // 4. pause 返回(唯一原因是被信号中断)
        if (result == -1 && errno == EINTR) {
            printf("pause() was interrupted by a signal (errno=EINTR).\n");
            
            // 5. 检查是哪个信号
            if (signal_received) {
                printf("Handled signal %d in signal handler.\n", last_signal);
                if (last_signal == SIGINT || last_signal == SIGTERM) {
                    printf("Received exit signal. Cleaning up and exiting.\n");
                    break; // 退出主循环
                }
                // 为下一次循环重置标志
                signal_received = 0;
            }
        } else {
            // 这理论上不应该发生,因为 pause 总是返回 -1 和 EINTR
            printf("Unexpected return from pause(): result=%d, errno=%d (%s)\n",
                   result, errno, strerror(errno));
        }
    }

    printf("Main loop exited. Performing cleanup...\n");
    // 这里可以执行一些清理工作

    printf("Program exiting normally.\n");
    return 0;
}

代码解释:

1. 定义了两个 volatile sig_atomic_t 类型的全局变量 signal_received 和 last_signalvolatile 确保编译器不会优化对它们的访问,sig_atomic_t 是一种推荐用于信号处理函数中修改的整数类型,保证了原子性。
2. 定义了一个信号处理函数 signal_handler。当进程收到 SIGINT (Ctrl+C) 或 SIGTERM 时,该函数会被调用。它打印一条消息,并设置全局标志。
3. 在 main 函数中,使用 signal() 函数为 SIGINT 和 SIGTERM 注册了处理函数。对于 SIGUSR1,设置为忽略 (SIG_IGN),但请注意,即使是被忽略的信号,也能中断 pause
4. 进入一个无限循环 while(1)
5. 在循环内部调用 pause()。进程在此处进入睡眠状态。
6. 当进程收到信号时,pause() 调用返回,并将 errno 设置为 EINTR
7. 检查 pause 的返回值和 errno。如果符合预期(-1 和 EINTR),则继续处理。
8. 检查全局标志 signal_received,确定是哪个信号导致了 pause 返回,并根据信号类型决定是否退出循环。
9. 如果收到 SIGINT 或 SIGTERM,则跳出循环,执行清理工作并退出程序。

编译和运行:

gcc -o pause_example pause_example.c
./pause_example
# 在另一个终端:
# kill -USR1 <PID>  # 发送 SIGUSR1 (会被忽略,但会中断 pause)
# kill <PID>        # 发送 SIGTERM (默认信号,会退出)
# kill -INT <PID>   # 发送 SIGINT (等同于 Ctrl+C)

示例 2:使用 pause 等待子进程结束 (不推荐,仅作演示)

虽然 wait/waitpid 是等待子进程结束的标准方法,但这个例子演示了如何(不推荐地)使用 pause 和 SIGCHLD 信号来实现类似功能。

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>

volatile sig_atomic_t child_done = 0;

void sigchld_handler(int sig) {
    // 在信号处理函数中,通常只应设置标志位
    // 实际的 wait 操作应在主循环中进行,以避免特定的竞争条件
    // 这里简化处理
    printf("SIGCHLD received.\n");
    child_done = 1;
}

int main() {
    pid_t pid;

    // 1. 设置 SIGCHLD 信号处理函数
    // SIGCHLD 在子进程状态改变时发送给父进程
    if (signal(SIGCHLD, sigchld_handler) == SIG_ERR) {
        perror("signal SIGCHLD");
        exit(EXIT_FAILURE);
    }

    // 2. 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        // --- 子进程 ---
        printf("Child process (PID %d) started.\n", getpid());
        sleep(5); // 模拟工作
        printf("Child process (PID %d) finished.\n", getpid());
        _exit(EXIT_SUCCESS);
    } else {
        // --- 父进程 ---
        printf("Parent process (PID %d) created child (PID %d).\n", getpid(), pid);

        // 3. 等待子进程结束
        printf("Parent entering loop with pause() to wait for child...\n");
        while (!child_done) {
            printf("Parent is waiting (paused)...\n");
            pause(); // 等待信号 (期望是 SIGCHLD)
            printf("Parent woke up from pause().\n");
            
            if (child_done) {
                printf("Parent detected child is done via signal flag.\n");
                // 清理僵尸进程
                int status;
                pid_t waited_pid = wait(&status);
                if (waited_pid == -1) {
                    perror("wait");
                } else {
                    printf("Parent reaped child PID %d with status %d.\n", waited_pid, status);
                }
            }
        }
        printf("Parent process finished.\n");
    }

    return 0;
}

代码解释:

  1. 定义了一个全局标志 child_done
  2. 定义了 SIGCHLD 信号的处理函数 sigchld_handler,当子进程结束时,内核会向父进程发送 SIGCHLD 信号,该处理函数会设置 child_done 标志。
  3. 在 main 函数中,为 SIGCHLD 注册处理函数。
  4. 使用 fork 创建子进程。
  5. 子进程: 睡眠 5 秒后退出。
  6. 父进程:
    • 进入一个循环,循环条件是 child_done 为假。
    • 在循环中调用 pause(),使父进程睡眠。
    • 当子进程结束,内核发送 SIGCHLD 信号,sigchld_handler 被调用,设置 child_done 为真。
    • pause() 返回,循环检查 child_done,发现为真,于是调用 wait() 清理子进程(收割僵尸进程)并退出循环。

重要提示与注意事项:

1. sigsuspend vs pause: 直接使用 pause 等待信号时,可能会遇到竞态条件。例如,你可能想在等待信号前先阻塞某些信号。如果在阻塞信号和调用 pause 之间信号到达,信号会被挂起,但 pause 会立即返回(因为信号已挂起)。sigsuspend 可以原子性地设置新的信号掩码并挂起进程,避免了这种竞态条件,是更推荐的方式。
2. 信号安全: 在信号处理函数中,应只调用异步信号安全(async-signal-safe)的函数。printfwrite (到 stderr) 通常被认为是安全的,但最好还是限制在修改 volatile sig_atomic_t 变量等简单操作。
3. SIGCHLD 处理: 示例 2 中的 SIGCHLD 处理方式是简化的。在实际应用中,一个信号处理函数可能需要处理多个子进程的退出,且 wait 可能需要在一个循环中调用直到没有更多子进程结束。使用 waitpid 通常更精确。
4. pause 的局限性pause 只能等待任何信号。如果你只想等待特定信号,pause 本身无法做到,需要结合信号处理函数和全局标志来间接实现。

总结:

pause 是一个简单但重要的系统调用,用于使进程高效地(不消耗 CPU)等待信号。理解其工作原理以及与信号处理机制的结合使用是掌握 Linux 进程控制和同步的基础。在需要等待异步事件时,它是一个非常有用的工具,尽管在某些复杂场景下,sigsuspend 可能是更安全的选择。

发表在 linux文章 | 留下评论

preadv1系统调用及示例

preadv 函数

preadv 是 pread 的分散读取版本,它允许一次性从文件的指定位置读取数据到多个不连续的缓冲区中。这是分散/聚集I/O操作的一部分。(https://www.calcguide.tech/2025/08/16/preadv1%e7%b3%bb%e7%bb%9f%e8%b0%83%e7%94%a8%e5%8f%8a%e7%a4%ba%e4%be%8b/)

1. 函数介绍

preadv 是 pread 的分散读取版本,它允许一次性从文件的指定位置读取数据到多个不连续的缓冲区中。这是分散/聚集I/O操作的一部分。

2. 函数原型

#define _GNU_SOURCE
#include <sys/uio.h>
ssize_t preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);

3. 功能

从文件描述符 fd 指定的文件中,从 offset 位置开始读取数据到由 iov 描述的多个缓冲区中。该操作不会改变文件的当前读写位置。

4. 参数

  • int fd: 文件描述符,必须是已打开的文件
  • *const struct iovec iov: iovec结构体数组,描述多个缓冲区
  • int iovcnt: iov数组中的元素个数
  • off_t offset: 文件中的偏移量(从文件开始处计算)

5. 返回值

  • 成功: 返回实际读取的总字节数
  • 文件末尾: 返回0
  • 失败: 返回-1,并设置errno

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

  • readv: 基本的分散读取函数
  • pread: 单缓冲区定位读取函数
  • pwritev: 对应的写入函数

7. 示例代码

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

/**
 * 使用preadv进行分散读取
 */
int demo_preadv_basic() {
    int fd;
    struct iovec iov[3];
    char buf1[20], buf2[15], buf3[30];
    ssize_t total_bytes;
    
    // 创建测试文件
    fd = open("test_preadv.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    // 写入测试数据
    const char *test_data = "This is a long test string for preadv demonstration purposes.";
    write(fd, test_data, strlen(test_data));
    close(fd);
    
    // 打开文件进行分散读取
    fd = open("test_preadv.txt", O_RDONLY);
    if (fd == -1) {
        perror("打开文件失败");
        return -1;
    }
    
    printf("=== preadv 基本使用示例 ===\n");
    printf("测试数据: %s\n", test_data);
    printf("数据长度: %zu 字节\n\n", strlen(test_data));
    
    // 设置iovec数组
    iov[0].iov_base = buf1;
    iov[0].iov_len = sizeof(buf1) - 1;
    iov[1].iov_base = buf2;
    iov[1].iov_len = sizeof(buf2) - 1;
    iov[2].iov_base = buf3;
    iov[2].iov_len = sizeof(buf3) - 1;
    
    // 从偏移量0开始分散读取
    total_bytes = preadv(fd, iov, 3, 0);
    if (total_bytes == -1) {
        perror("preadv 失败");
        close(fd);
        return -1;
    }
    
    printf("preadv 读取了 %zd 字节到3个缓冲区:\n", total_bytes);
    
    // 添加字符串结束符并显示结果
    buf1[iov[0].iov_len] = '\0';
    buf2[iov[1].iov_len] = '\0';
    buf3[iov[2].iov_len] = '\0';
    
    printf("缓冲区1 (%zu字节): %s\n", iov[0].iov_len, buf1);
    printf("缓冲区2 (%zu字节): %s\n", iov[1].iov_len, buf2);
    printf("缓冲区3 (%zu字节): %s\n", iov[2].iov_len, buf3);
    
    close(fd);
    unlink("test_preadv.txt");
    return 0;
}

/**
 * 演示preadv读取结构体数据
 */
struct Person {
    int id;
    char name[20];
    float score;
};

int demo_preadv_struct() {
    int fd;
    struct iovec iov[3];
    struct Person person = {1001, "Alice Johnson", 95.5};
    int read_id;
    char read_name[20];
    float read_score;
    
    printf("\n=== preadv 读取结构体数据示例 ===\n");
    
    // 创建包含结构体数据的文件
    fd = open("person_data.bin", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建数据文件失败");
        return -1;
    }
    
    write(fd, &person, sizeof(person));
    close(fd);
    
    // 使用preadv分别读取结构体的各个字段
    fd = open("person_data.bin", O_RDONLY);
    if (fd == -1) {
        perror("打开数据文件失败");
        return -1;
    }
    
    // 设置iovec读取结构体的各个部分
    iov[0].iov_base = &read_id;
    iov[0].iov_len = sizeof(read_id);
    iov[1].iov_base = read_name;
    iov[1].iov_len = sizeof(read_name);
    iov[2].iov_base = &read_score;
    iov[2].iov_len = sizeof(read_score);
    
    ssize_t bytes_read = preadv(fd, iov, 3, 0);
    if (bytes_read == -1) {
        perror("preadv 读取结构体失败");
        close(fd);
        return -1;
    }
    
    printf("读取了 %zd 字节的结构体数据:\n", bytes_read);
    printf("ID: %d\n", read_id);
    printf("Name: %s\n", read_name);
    printf("Score: %.1f\n", read_score);
    
    close(fd);
    unlink("person_data.bin");
    return 0;
}

int main() {
    if (demo_preadv_basic() == 0) {
        demo_preadv_struct();
        printf("\n=== preadv 使用总结 ===\n");
        printf("优点:一次系统调用读取多个缓冲区,减少系统调用开销\n");
        printf("适用场景:读取结构化数据,协议解析,网络数据包处理\n");
    }
    return 0;
}
发表在 linux文章 | 留下评论

preadv2系统调用及示例

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


函数 1: preadv2

1. 函数介绍

preadv2 (pread vector 2) 是 preadv 系统调用的扩展版本。它结合了 pread(带偏移量读取)和 readv(分散读取)的优点,并引入了一个新的 flags 参数,提供了更灵活的 I/O 控制选项。

简单来说,preadv2 允许你从文件的指定偏移量开始,将数据分散读入到多个不连续的缓冲区中,同时还能指定一些高级 I/O 行为(通过 flags)。

2. 函数原型

#define _GNU_SOURCE // 必须定义以使用 preadv2
#include <sys/uio.h> // struct iovec
#include <unistd.h>  // ssize_t

ssize_t preadv2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);

3. 功能

  • 从文件描述符 fd 指定的文件中,从绝对偏移量 offset 开始读取数据。
  • 将读取的数据分散存储到由 iov 和 iovcnt 指定的多个缓冲区中。
  • 不修改文件的当前读写位置指针(lseek 位置)。
  • 根据 flags 参数执行特定的 I/O 操作。

4. 参数

  • int fd: 有效的文件描述符。
  • const struct iovec *iov: 指向 struct iovec 数组的指针,描述了多个分散的缓冲区。
  • int iovcntiov 数组中元素的个数。
  • off_t offset: 在文件中开始读取的绝对偏移量(以字节为单位)。必须是非负数
  • int flags: 控制 I/O 行为的标志。可以是以下值的按位或组合:
    • 0: 默认行为,等同于 preadv
    • RWF_HIPRI: 尝试使用高优先级/实时 I/O(如果内核和设备支持)。
    • RWF_DSYNC: 要求 I/O 操作具有数据同步持久性(类似于 O_DSYNC)。
    • RWF_SYNC: 要求 I/O 操作具有文件同步持久性(类似于 O_SYNC)。
    • RWF_NOWAIT非阻塞。如果 I/O 无法立即完成(例如,需要从磁盘读取而数据不在页缓存中),则不等待,立即返回错误 EAGAIN。这需要内核和文件系统支持。
    • RWF_APPEND: 强制将写入追加到文件末尾(仅对 pwritev2 有效)。

5. 返回值

  • 成功时: 返回实际读取的总字节数(0 表示 EOF)。
  • 失败时: 返回 -1,并设置 errno

函数 2: pwritev2

1. 函数介绍

pwritev2 (pwrite vector 2) 是 pwritev 系统调用的扩展版本。它结合了 pwrite(带偏移量写入)和 writev(集中写入)的优点,并同样引入了 flags 参数。

简单来说,pwritev2 允许你从多个不连续的缓冲区收集数据,并将其写入到文件的指定偏移量处,同时还能指定一些高级 I/O 行为(通过 flags)。

2. 函数原型

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

ssize_t pwritev2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);

3. 功能

  • 从由 iov 和 iovcnt 指定的多个缓冲区中收集数据。
  • 将收集到的数据写入到文件描述符 fd 指定的文件中,从绝对偏移量 offset 开始写入。
  • 不修改文件的当前读写位置指针(lseek 位置)。
  • 根据 flags 参数执行特定的 I/O 操作。

4. 参数

  • int fd: 有效的文件描述符。
  • const struct iovec *iov: 指向 struct iovec 数组的指针,描述了多个包含数据的缓冲区。
  • int iovcntiov 数组中元素的个数。
  • off_t offset: 在文件中开始写入的绝对偏移量(以字节为单位)。必须是非负数
    • 如果文件以 O_APPEND 模式打开,或者 flags 中设置了 RWF_APPEND,则 offset 参数会被忽略,数据总是被写入到文件末尾。
  • int flags: 控制 I/O 行为的标志。可以是以下值的按位或组合:
    • 0: 默认行为,等同于 pwritev
    • RWF_HIPRI: 尝试使用高优先级/实时 I/O。
    • RWF_DSYNC: 要求数据同步持久性。
    • RWF_SYNC: 要求文件同步持久性。
    • RWF_NOWAIT非阻塞。如果 I/O 无法立即完成,立即返回错误 EAGAIN
    • RWF_APPEND: 强制将写入追加到文件末尾,即使文件没有以 O_APPEND 打开。

5. 返回值

  • 成功时: 返回实际写入的总字节数
  • 失败时: 返回 -1,并设置 errno

函数 3: pkey_mprotect

1. 函数介绍

pkey_mprotect 是 mprotect 系统调用的扩展,用于将一个内存区域与一个特定的内存保护键(Protection Key, pkey)相关联。

回忆一下 pkey_alloc/free:它们用于获取和释放 pkey 编号。pkey_mprotect 则是将这个编号应用到具体的内存区域上。

一旦内存区域通过 pkey_mprotect 与一个 pkey 关联,对该区域的访问权限就不仅受传统的 PROT_READ/PROT_WRITE/PROT_EXEC 控制,还受该 pkey 在 CPU 的 PKRU(Protection Key Rights User)寄存器中设置的权限控制。加粗样式

2. 函数原型

#define _GNU_SOURCE
#include <sys/mman.h> // 包含 MPK 相关常量

int pkey_mprotect(void *addr, size_t len, int prot, int pkey);

3. 功能

  • 修改从地址 addr 开始、长度为 len 字节的内存区域的访问权限。
  • 将该内存区域与保护键 pkey(由 pkey_alloc 获得)进行关联
  • 设置该区域的基本权限为 protPROT_READPROT_WRITEPROT_EXEC 的组合)。

4. 参数

  • void *addr: 要修改的内存区域的起始地址。必须是页对齐的
  • size_t len: 内存区域的长度(以字节为单位)。会向上舍入到最近的页边界。
  • int prot: 新的内存保护标志。可以是 PROT_NONEPROT_READPROT_WRITEPROT_EXEC 及其按位或组合。
  • int pkey: 通过 pkey_alloc 获得的保护键编号(0-15)。

5. 返回值

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

示例代码

示例 1:preadv2 和 pwritev2 的基本使用

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

#define FILENAME "test_piov2.txt"

int main() {
    int fd;
    char buf1[20], buf2[30], buf3[50];
    struct iovec iov_w[2], iov_r[3];
    ssize_t bytes_written, bytes_read;

    // 1. 创建并写入测试文件 (使用传统 write)
    fd = open(FILENAME, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open for write");
        exit(EXIT_FAILURE);
    }

    const char *data1 = "Part One: Hello, ";
    const char *data2 = "preadv2 and pwritev2 World!\n";
    iov_w[0].iov_base = (void*)data1;
    iov_w[0].iov_len = strlen(data1);
    iov_w[1].iov_base = (void*)data2;
    iov_w[1].iov_len = strlen(data2);

    bytes_written = writev(fd, iov_w, 2);
    if (bytes_written == -1) {
        perror("writev");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("Written %zd bytes using writev.\n", bytes_written);
    close(fd);

    // 2. 使用 preadv2 读取
    fd = open(FILENAME, O_RDONLY);
    if (fd == -1) {
        perror("open for read");
        exit(EXIT_FAILURE);
    }

    // 初始化读取缓冲区
    memset(buf1, '.', sizeof(buf1) - 1); buf1[sizeof(buf1)-1] = '\0';
    memset(buf2, '.', sizeof(buf2) - 1); buf2[sizeof(buf2)-1] = '\0';
    memset(buf3, '.', sizeof(buf3) - 1); buf3[sizeof(buf3)-1] = '\0';

    iov_r[0].iov_base = buf1;
    iov_r[0].iov_len = sizeof(buf1) - 1;
    iov_r[1].iov_base = buf2;
    iov_r[1].iov_len = sizeof(buf2) - 1;
    iov_r[2].iov_base = buf3;
    iov_r[2].iov_len = sizeof(buf3) - 1;

    // 从偏移量 0 开始读取,使用默认标志
    bytes_read = preadv2(fd, iov_r, 3, 0, 0);
    if (bytes_read == -1) {
        perror("preadv2");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("\nRead %zd bytes using preadv2 from offset 0:\n", bytes_read);
    printf("Buffer 1: '%s'\n", buf1);
    printf("Buffer 2: '%s'\n", buf2);
    printf("Buffer 3: '%s'\n", buf3);

    close(fd);

    // 3. 使用 pwritev2 追加写入
    fd = open(FILENAME, O_WRONLY); // 不用 O_APPEND
    if (fd == -1) {
        perror("open for write (again)");
        exit(EXIT_FAILURE);
    }

    const char *append1 = "Appended via ";
    const char *append2 = "pwritev2 with RWF_APPEND flag.\n";
    struct iovec iov_a[2];
    iov_a[0].iov_base = (void*)append1;
    iov_a[0].iov_len = strlen(append1);
    iov_a[1].iov_base = (void*)append2;
    iov_a[1].iov_len = strlen(append2);

    // 使用 RWF_APPEND 标志强制追加,忽略 offset
    bytes_written = pwritev2(fd, iov_a, 2, 0, RWF_APPEND);
    if (bytes_written == -1) {
        perror("pwritev2 with RWF_APPEND");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("\nAppended %zd bytes using pwritev2 with RWF_APPEND.\n", bytes_written);

    close(fd);

    // 4. 验证文件内容
    printf("\n--- Final file content ---\n");
    fd = open(FILENAME, O_RDONLY);
    if (fd != -1) {
        char final_buf[200];
        ssize_t n = read(fd, final_buf, sizeof(final_buf) - 1);
        if (n > 0) {
            final_buf[n] = '\0';
            printf("%s", final_buf);
        }
        close(fd);
    }

    // unlink(FILENAME); // 可选:清理文件
    return 0;
}

代码解释:

  1. 创建一个测试文件,并使用 writev 写入一些初始内容。
  2. 重新打开文件进行读取。
  3. 使用 preadv2(fd, iov_r, 3, 0, 0) 从文件偏移量 0 开始,将数据分散读入三个缓冲区。flags 为 0,表示默认行为。
  4. 打开文件进行写入(非 O_APPEND 模式)。
  5. 使用 pwritev2(fd, iov_a, 2, 0, RWF_APPEND) 将数据写入文件。尽管 offset 是 0,但由于使用了 RWF_APPEND 标志,数据被追加到了文件末尾。
  6. 重新读取并打印文件内容以验证操作结果。

示例 2:pkey_mprotect 结合 pkey_alloc/free 使用

// pkey_mprotect_example.c
#define _GNU_SOURCE
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>

static jmp_buf jmp_env;
static volatile sig_atomic_t sigsegv_caught = 0;

void sigsegv_handler(int sig) {
    sigsegv_caught = 1;
    longjmp(jmp_env, 1);
}

// Conceptual PKRU manipulation (requires inline assembly in real code)
// For demonstration, we'll just print what would happen.
void set_pkey_access(int pkey, int disable_access) {
    printf("  [Concept] Modifying PKRU for pkey %d: %s\n",
           pkey, disable_access ? "DISABLE access" : "ENABLE access");
    // Real code would involve inline assembly to write to PKRU register
}

int main() {
    // Check for MPK support conceptually
    if (sysconf(_SC_MPKEY) <= 0) {
        fprintf(stderr, "MPK not supported by sysconf.\n");
        exit(EXIT_FAILURE);
    }

    struct sigaction sa;
    sa.sa_handler = sigsegv_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGSEGV, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    size_t page_size = getpagesize();
    size_t len = page_size;
    void *addr;

    // 1. Allocate memory
    addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (addr == MAP_FAILED) {
        perror("mmap");
        exit(EXIT_FAILURE);
    }
    printf("Allocated %zu bytes at %p\n", len, addr);

    // 2. Write some data
    strcpy((char*)addr, "This memory is protected by a pkey.");
    printf("Written data: %s\n", (char*)addr);

    // 3. Allocate a protection key
    int pkey = pkey_alloc(0, 0);
    if (pkey == -1) {
        if (errno == EOPNOTSUPP) {
            printf("MPK not supported on this hardware/kernel.\n");
            munmap(addr, len);
            exit(EXIT_FAILURE);
        } else {
            perror("pkey_alloc");
            munmap(addr, len);
            exit(EXIT_FAILURE);
        }
    }
    printf("Allocated pkey: %d\n", pkey);

    // 4. Associate memory with the pkey using pkey_mprotect
    printf("\n--- Associating memory with pkey %d ---\n", pkey);
    if (pkey_mprotect(addr, len, PROT_READ | PROT_WRITE, pkey) == -1) {
        perror("pkey_mprotect");
        pkey_free(pkey);
        munmap(addr, len);
        exit(EXIT_FAILURE);
    }
    printf("Memory successfully associated with pkey %d.\n", pkey);

    // 5. Disable access via PKRU (conceptual)
    printf("\n--- Disabling access to pkey %d via PKRU ---\n", pkey);
    set_pkey_access(pkey, 1); // Conceptual call

    // 6. Try to access protected memory (should trigger SIGSEGV)
    printf("\n--- Attempting to READ from protected memory ---\n");
    sigsegv_caught = 0;

    if (setjmp(jmp_env) == 0) {
        printf("  Trying to read from %p...\n", addr);
        volatile char first_char = *((char*)addr);
        printf("  ERROR: Read succeeded (first char: %c). This should not happen!\n", first_char);
    } else {
        if (sigsegv_caught) {
            printf("  SUCCESS: SIGSEGV caught. Access correctly denied by pkey.\n");
        } else {
            printf("  Unexpected longjmp.\n");
        }
    }

    // 7. Re-enable access
    printf("\n--- Re-enabling access to pkey %d via PKRU ---\n", pkey);
    set_pkey_access(pkey, 0); // Conceptual call

    // 8. Try to access memory again (should succeed)
    printf("\n--- Attempting to access memory again (should succeed now) ---\n");
    printf("  Reading from %p: %.50s\n", addr, (char*)addr);

    // 9. Cleanup
    if (pkey_free(pkey) == -1) {
        perror("pkey_free");
    }
    if (munmap(addr, len) == -1) {
        perror("munmap");
    }

    printf("\nPkey_mprotect example finished.\n");
    return 0;
}

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

  1. 设置信号处理和 setjmp/longjmp 用于捕获 SIGSEGV
  2. 使用 mmap 分配一页内存。
  3. 写入一些测试数据。
  4. 调用 pkey_alloc(0, 0) 获取一个 pkey。
  5. 关键步骤: 调用 pkey_mprotect(addr, len, PROT_READ | PROT_WRITE, pkey) 将分配的内存区域与获取的 pkey 关联起来。
  6. 概念性操作: 模拟通过修改 PKRU 寄存器来禁用对这个 pkey 的访问。
  7. 尝试读取受保护的内存,预期会触发 SIGSEGV
  8. 概念性操作: 模拟重新启用对这个 pkey 的访问。
  9. 再次尝试读取,这次应该成功。
  10. 清理资源(释放 pkey 和内存)。

重要提示与注意事项:

  1. 内核版本:
    • preadv2/pwritev2: Linux 内核 4.6+。
    • pkey_mprotect/pkey_alloc/pkey_free: Linux 内核 4.9+ (MPK)。
  2. glibc 版本: 需要 glibc 2.27+ 才能直接使用这些函数。
  3. 硬件支持pkey_* 函数需要 CPU 支持(如 Intel x86_64 Skylake 及更新架构)。
  4. _GNU_SOURCE: 必须定义此宏才能使用这些扩展函数。
  5. flags 参数preadv2/pwritev2 的 flags 提供了强大的 I/O 控制能力,特别是 RWF_NOWAIT(非阻塞)和 RWF_APPEND
  6. pkey_mprotect 是核心: 它是将 pkey 机制应用到实际内存区域的关键步骤。仅仅 pkey_alloc 是不够的。
  7. PKRU 操作: 真正控制 pkey 权限需要直接操作 CPU 的 PKRU 寄存器,这通常需要内联汇编,比较复杂。
  8. 错误处理: 始终检查返回值,特别是 pkey_* 函数可能返回 EOPNOTSUPP

总结:

preadv2 和 pwritev2 是对现有 I/O 系统调用的有力增强,通过引入 flags 参数,提供了更细粒度的控制,如非阻塞 I/O 和强制追加写入。

pkey_mprotect 是内存保护键(MPK)技术的核心 API 之一,它允许将特定的内存区域与一个 pkey 绑定,从而实现比传统 mprotect 更快速、更灵活的内存访问控制。结合 pkey_alloc/free 和对 PKRU 寄存器的操作,可以构建出高性能的内存安全机制。

这三个函数都代表了 Linux 系统编程向更高性能、更细粒度控制发展的趋势。

preadv2 函数

1. 函数介绍

preadv2 是 preadv 的增强版本,支持额外的标志参数,提供更多的控制选项。它是Linux 4.6引入的新特性。

2. 函数原型

#define _GNU_SOURCE
#include <sys/uio.h>
ssize_t preadv2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);

3. 功能

与 preadv 类似,但从指定位置读取数据到多个缓冲区,并支持额外的控制标志。

4. 参数

  • int fd: 文件描述符
  • *const struct iovec iov: iovec结构体数组
  • int iovcnt: iov数组元素个数
  • off_t offset: 文件偏移量
  • int flags: 控制标志(如RWF_HIPRI, RWF_DSYNC等)

5. 返回值

  • 成功: 返回实际读取的总字节数
  • 失败: 返回-1,并设置errno

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

  • preadv: 基本版本
  • pwritev2: 对应的写入函数
  • read: 基本读取函数

7. 示例代码

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

/**
 * 演示preadv2的基本使用
 * 注意:需要Linux 4.6+内核支持
 */
int demo_preadv2_basic() {
    int fd;
    struct iovec iov[2];
    char buf1[30], buf2[20];
    ssize_t bytes_read;
    
    printf("=== preadv2 基本使用示例 ===\n");
    
    // 创建测试文件
    fd = open("test_preadv2.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    const char *test_data = "This is test data for preadv2 function demonstration.";
    write(fd, test_data, strlen(test_data));
    close(fd);
    
    // 打开文件进行读取
    fd = open("test_preadv2.txt", O_RDONLY);
    if (fd == -1) {
        perror("打开文件失败");
        return -1;
    }
    
    // 设置iovec数组
    iov[0].iov_base = buf1;
    iov[0].iov_len = sizeof(buf1) - 1;
    iov[1].iov_base = buf2;
    iov[1].iov_len = sizeof(buf2) - 1;
    
    // 使用preadv2读取数据(flags设为0表示默认行为)
    bytes_read = preadv2(fd, iov, 2, 0, 0);
    if (bytes_read == -1) {
        if (errno == ENOSYS) {
            printf("系统不支持 preadv2 函数\n");
            close(fd);
            unlink("test_preadv2.txt");
            return 0;
        }
        perror("preadv2 失败");
        close(fd);
        unlink("test_preadv2.txt");
        return -1;
    }
    
    printf("preadv2 成功读取 %zd 字节\n", bytes_read);
    
    // 添加字符串结束符并显示结果
    buf1[iov[0].iov_len] = '\0';
    buf2[iov[1].iov_len] = '\0';
    
    printf("缓冲区1: %s\n", buf1);
    printf("缓冲区2: %s\n", buf2);
    
    close(fd);
    unlink("test_preadv2.txt");
    return 0;
}

/**
 * 演示preadv2的高级特性(如果系统支持)
 */
int demo_preadv2_advanced() {
    int fd;
    struct iovec iov[1];
    char buffer[100];
    ssize_t bytes_read;
    
    printf("\n=== preadv2 高级特性示例 ===\n");
    printf("preadv2 支持的标志包括:\n");
    printf("  RWF_HIPRI: 高优先级I/O\n");
    printf("  RWF_DSYNC: 数据同步写入\n");
    printf("  RWF_SYNC:  同步写入\n");
    printf("  RWF_NOWAIT: 非阻塞操作\n");
    printf("  RWF_APPEND: 追加模式写入\n");
    
    // 创建测试文件
    fd = open("advanced_test.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    const char *test_data = "Advanced preadv2 test data for feature demonstration.";
    write(fd, test_data, strlen(test_data));
    close(fd);
    
    fd = open("advanced_test.txt", O_RDONLY);
    if (fd == -1) {
        perror("打开文件失败");
        return -1;
    }
    
    // 设置iovec
    iov[0].iov_base = buffer;
    iov[0].iov_len = sizeof(buffer) - 1;
    
    // 尝试使用RWF_NOWAIT标志(非阻塞读取)
    bytes_read = preadv2(fd, iov, 1, 0, RWF_NOWAIT);
    if (bytes_read == -1) {
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            printf("非阻塞操作:数据暂时不可用\n");
        } else if (errno == ENOSYS) {
            printf("系统不支持 RWF_NOWAIT 标志\n");
        } else {
            printf("preadv2 with RWF_NOWAIT 失败: %s\n", strerror(errno));
        }
    } else {
        buffer[bytes_read] = '\0';
        printf("非阻塞读取成功: %s\n", buffer);
    }
    
    close(fd);
    unlink("advanced_test.txt");
    return 0;
}

int main() {
    printf("preadv2 需要 Linux 4.6+ 内核支持\n");
    
    if (demo_preadv2_basic() == 0) {
        demo_preadv2_advanced();
        printf("\n=== preadv2 使用总结 ===\n");
        printf("优点:支持额外控制标志,更灵活的I/O控制\n");
        printf("注意:需要较新内核版本支持\n");
    }
    return 0;
}
发表在 linux文章 | 留下评论

pread系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 pread 和 pwrite 函数,它们是 read 和 write 系统调用的增强版本,允许在单次调用中指定文件偏移量,而不会改变文件的当前读写位置指针。


1. 函数介绍

pread (Positioned Read) 和 pwrite (Positioned Write) 是 Linux 系统调用,它们结合了 read/write 的数据传输功能和 lseek 的定位功能。

pread: 从文件描述符 fd 关联的文件中,从指定的偏移量 offset 处开始读取 count 个字节的数据,并将其存储到缓冲区 buf 中。关键点:此操作不会修改文件的当前读写位置指针(即调用 lseek(fd, 0, SEEK_CUR) 返回的值保持不变)。

pwrite: 将 count 个字节的数据从缓冲区 buf 写入到文件描述符 fd 关联的文件中,从指定的偏移量 offset 处开始写入。关键点:此操作也不会修改文件的当前读写位置指针。

你可以把它们想象成 lseek + read 或 lseek + write 的原子性组合,但又不影响文件的“书签”(当前文件偏移量)。


2. 函数原型

#include <unistd.h> // 必需

// 从指定偏移量读取
ssize_t pread(int fd, void *buf, size_t count, off_t offset);

// 向指定偏移量写入
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);

3. 功能

  • pread(fd, buf, count, offset):
    • 将文件 fd 的读取位置临时设置到 offset
    • 从该位置读取最多 count 个字节到 buf
    • 读取完成后,文件的全局读写位置指针保持不变
  • pwrite(fd, buf, count, offset):
    • 将文件 fd 的写入位置临时设置到 offset
    • 从 buf 写入 count 个字节到该位置。
    • 写入完成后,文件的全局读写位置指针保持不变

这种“原子性定位并操作”的特性在多线程环境中特别有用,可以避免多个线程同时操作同一个文件描述符的当前偏移量而导致的竞争条件(race condition)。


4. 参数

这两个函数的参数非常相似:

  • int fd: 有效的文件描述符。
  • void *buf (pread) / const void *buf (pwrite): 指向数据缓冲区的指针。
  • size_t count: 要读取/写入的字节数。
  • off_t offset: 在文件中进行读取/写入操作的绝对偏移量(从文件开头算起的字节数)。

5. 返回值

  • 成功时:
    • 返回实际读取/写入的字节数。这个数可能小于请求的 count(例如,在读取时接近文件末尾,或在写入时遇到磁盘空间不足)。
    • 对于 pread,如果返回 0,通常表示偏移量已在文件末尾或没有更多数据。
  • 失败时:
    • 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EBADF fd 无效,EINVAL offset 无效,EIO I/O 错误等)。

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

  • readwrite: 基础的读写函数,它们的操作基于并会修改文件的当前偏移量。
  • lseek: 用于显式地移动文件的当前读写位置指针。pread/pwrite 内部可能使用了类似 lseek 的机制,但对用户是透明的,且不影响全局偏移量。
  • mmap: 另一种访问文件内容的方式,通过内存映射将文件内容映射到进程地址空间。

7. 示例代码

示例 1:基本 pread 和 pwrite 使用

这个例子演示了如何使用 pread 从文件的不同位置读取数据,以及使用 pwrite 向文件的不同位置写入数据,同时文件的当前偏移量保持不变。

#include <unistd.h>  // pread, pwrite, open, close, lseek
#include <fcntl.h>   // O_RDWR, O_CREAT
#include <stdio.h>   // perror, printf
#include <stdlib.h>  // exit
#include <string.h>  // strlen

int main() {
    int fd;
    const char *filename = "pread_pwrite_example.txt";
    const char *initial_data = "This is the initial content of the file.\nIt spans multiple lines.\n";
    const char *write_data1 = "[OVERWRITTEN_PART_1]";
    const char *write_data2 = "[OVERWRITTEN_PART_2]";
    char read_buffer[100];
    ssize_t bytes_rw;
    off_t current_offset;

    // 1. 创建并写入初始数据
    fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open for creation");
        exit(EXIT_FAILURE);
    }

    if (write(fd, initial_data, strlen(initial_data)) == -1) {
        perror("write initial data");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("Created file '%s' and wrote initial data.\n", filename);

    // 2. 获取并打印当前文件偏移量 (应在文件末尾)
    current_offset = lseek(fd, 0, SEEK_CUR);
    if (current_offset == -1) {
        perror("lseek to get current offset");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("Current file offset after initial write: %ld\n", (long)current_offset);

    // --- 使用 pwrite 进行写入 ---
    printf("\n--- Using pwrite ---\n");
    // 在偏移量 5 处写入数据
    bytes_rw = pwrite(fd, write_data1, strlen(write_data1), 5);
    if (bytes_rw == -1) {
        perror("pwrite 1");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("pwrite 1: Wrote %zd bytes at offset 5.\n", bytes_rw);

    // 在偏移量 30 处写入另一部分数据
    bytes_rw = pwrite(fd, write_data2, strlen(write_data2), 30);
    if (bytes_rw == -1) {
        perror("pwrite 2");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("pwrite 2: Wrote %zd bytes at offset 30.\n", bytes_rw);

    // 3. 再次检查当前文件偏移量 (应该没有改变)
    off_t offset_after_pwrite = lseek(fd, 0, SEEK_CUR);
    if (offset_after_pwrite == -1) {
        perror("lseek after pwrite");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("Current file offset after pwrite calls: %ld (Should be same as before)\n",
           (long)offset_after_pwrite);

    // --- 使用 pread 进行读取 ---
    printf("\n--- Using pread ---\n");
    // 从偏移量 0 开始读取 20 个字节
    bytes_rw = pread(fd, read_buffer, 20, 0);
    if (bytes_rw == -1) {
        perror("pread 1");
        close(fd);
        exit(EXIT_FAILURE);
    }
    read_buffer[bytes_rw] = '\0'; // 确保字符串结束
    printf("pread 1: Read %zd bytes from offset 0: '%s'\n", bytes_rw, read_buffer);

    // 从偏移量 15 开始读取 25 个字节
    bytes_rw = pread(fd, read_buffer, 25, 15);
    if (bytes_rw == -1) {
        perror("pread 2");
        close(fd);
        exit(EXIT_FAILURE);
    }
    read_buffer[bytes_rw] = '\0';
    printf("pread 2: Read %zd bytes from offset 15: '%s'\n", bytes_rw, read_buffer);

    // 从偏移量 50 开始读取 10 个字节 (可能读到文件末尾)
    bytes_rw = pread(fd, read_buffer, 10, 50);
    if (bytes_rw == -1) {
        perror("pread 3");
        close(fd);
        exit(EXIT_FAILURE);
    } else if (bytes_rw == 0) {
        printf("pread 3: Read %zd bytes from offset 50 (likely EOF).\n", bytes_rw);
    } else {
        read_buffer[bytes_rw] = '\0';
        printf("pread 3: Read %zd bytes from offset 50: '%s'\n", bytes_rw, read_buffer);
    }

    // 4. 最后再次确认文件偏移量未变
    off_t final_offset = lseek(fd, 0, SEEK_CUR);
    if (final_offset == -1) {
        perror("lseek to get final offset");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("\nFinal file offset: %ld (Should still be the same)\n", (long)final_offset);

    if (close(fd) == -1) {
        perror("close");
        exit(EXIT_FAILURE);
    }

    printf("File operations completed. Check the file content.\n");
    return 0;
}

代码解释:

  1. 创建一个文件并写入一些初始数据。
  2. 使用 lseek(fd, 0, SEEK_CUR) 获取并打印当前文件偏移量(应该在文件末尾)。
  3. pwrite 操作:
    • 调用 pwrite 两次,分别在偏移量 5 和 30 处写入数据。
    • 每次调用后,再次使用 lseek 检查文件偏移量,确认它没有改变。
  4. pread 操作:
    • 调用 pread 三次,分别从偏移量 0、15 和 50 处读取数据。
    • 打印读取到的内容。
  5. 最后再次检查文件偏移量,确认在整个过程中它始终保持不变。
  6. 关闭文件。

示例 2:多线程环境中的 pread/pwrite

这个例子(概念性地)说明了 pread/pwrite 在多线程场景下的优势。虽然完整的多线程代码比较复杂,但我们可以通过伪代码和解释来理解。

// 假想的多线程程序片段

#include <pthread.h> // POSIX 线程
#include <unistd.h>  // pread, pwrite
// ... 其他包含 ...

int shared_file_fd; // 所有线程共享的文件描述符

// 线程函数 1: 读取文件头部
void* thread_read_header(void *arg) {
    char header_buf[HEADER_SIZE];
    ssize_t bytes_read;

    // 线程 1 总是从偏移量 0 读取头部
    // 使用 pread 确保不影响其他线程的文件位置
    bytes_read = pread(shared_file_fd, header_buf, HEADER_SIZE, 0);
    if (bytes_read > 0) {
        // 处理头部数据...
        process_header(header_buf, bytes_read);
    }
    return NULL;
}

// 线程函数 2: 读取文件尾部
void* thread_read_footer(void *arg) {
    char footer_buf[FOOTER_SIZE];
    ssize_t bytes_read;
    off_t file_size;

    // 获取文件大小 (可能需要预先获取或用 fstat)
    file_size = get_file_size_somehow(shared_file_fd);

    // 线程 2 总是从文件末尾倒数的位置读取尾部
    // 使用 pread 确保不影响其他线程的文件位置
    bytes_read = pread(shared_file_fd, footer_buf, FOOTER_SIZE, file_size - FOOTER_SIZE);
    if (bytes_read > 0) {
        // 处理尾部数据...
        process_footer(footer_buf, bytes_read);
    }
    return NULL;
}

// 线程函数 3: 在文件中间某处写入日志
void* thread_write_log(void *arg) {
    const char *log_msg = "Log entry from thread 3\n";
    off_t write_offset = (off_t)arg; // 假设偏移量通过 arg 传入

    // 线程 3 在指定位置写入日志
    // 使用 pwrite 确保不影响其他线程的文件位置
    ssize_t bytes_written = pwrite(shared_file_fd, log_msg, strlen(log_msg), write_offset);
    if (bytes_written == -1) {
        perror("pwrite in thread 3");
    } else {
        printf("Thread 3 wrote %zd bytes at offset %ld\n", bytes_written, (long)write_offset);
    }
    return NULL;
}

// 主函数 (概念性)
int main() {
    // ... 打开文件 shared_file_fd ...

    pthread_t t1, t2, t3;

    // 创建线程
    pthread_create(&t1, NULL, thread_read_header, NULL);
    pthread_create(&t2, NULL, thread_read_footer, NULL);
    pthread_create(&t3, NULL, thread_write_log, (void*)MIDDLE_OFFSET); // 传递写入偏移量

    // 等待线程完成
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);

    // ... 关闭文件 ...
    return 0;
}

代码解释 (概念性):

1. 假设有多个线程共享同一个文件描述符 shared_file_fd
2. 线程 1 (thread_read_header): 需要读取文件头部。它使用 pread(fd, buf, size, 0),明确指定从偏移量 0 开始读取。这不会影响文件的全局偏移量,因此其他线程可以同时进行其他操作。
3. 线程 2 (thread_read_footer): 需要读取文件尾部。它使用 pread(fd, buf, size, file_size - size),明确指定从文件末尾开始读取。同样,不影响全局偏移量。
4. 线程 3 (thread_write_log): 需要在文件中间写入日志。它使用 pwrite(fd, buf, size, offset),明确指定写入位置。不影响全局偏移量。
5. 如果使用传统的 lseek + read/write,线程在 lseek 和 read/write 之间可能会被切换,导致线程间相互干扰文件偏移量,产生不可预测的结果。pread/pwrite 的原子性定位和操作避免了这个问题。

总结:

pread 和 pwrite 是非常实用的系统调用,特别是在需要随机访问文件或在多线程环境中操作文件时。它们通过将定位和 I/O 操作合并为一个原子步骤,并且不修改文件的全局状态,简化了编程并提高了安全性。理解它们的关键在于掌握它们与传统 read/write + lseek 组合的区别。

发表在 linux文章 | 留下评论

prlimit64系统调用及示例

prlimit64 函数

1. 函数介绍

prlimit64 用于获取和设置进程的资源限制。它是 getrlimit 和 setrlimit 的增强版本,支持64位资源限制值。

2. 函数原型

#define _GNU_SOURCE
#include <sys/resource.h>
int prlimit64(pid_t pid, int resource, const struct rlimit64 *new_limit, struct rlimit64 *old_limit);

3. 功能

获取和/或设置指定进程的资源限制。可以用于控制进程使用的各种系统资源,如内存、文件描述符、CPU时间等。

4. 参数

  • pid_t pid: 目标进程ID(0表示当前进程)
  • int resource: 资源类型(如RLIMIT_AS, RLIMIT_NOFILE等)
  • *const struct rlimit64 new_limit: 新的资源限制(NULL表示不设置)
  • *struct rlimit64 old_limit: 保存旧的资源限制(NULL表示不获取)

5. 返回值

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

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

  • getrlimit/setrlimit: 32位版本
  • ulimit: shell内置命令
  • getrusage: 获取资源使用情况

7. 示例代码

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

/**
 * 显示资源限制信息
 */
void display_resource_limit(int resource, const char *name) {
    struct rlimit64 limit;
    
    if (prlimit64(0, resource, NULL, &limit) == 0) {
        printf("%-20s: ", name);
        if (limit.rlim_cur == RLIM64_INFINITY) {
            printf("无限制");
        } else {
            printf("软限制=%lld", (long long)limit.rlim_cur);
        }
        
        if (limit.rlim_max == RLIM64_INFINITY) {
            printf(", 硬限制=无限制\n");
        } else {
            printf(", 硬限制=%lld\n", (long long)limit.rlim_max);
        }
    } else {
        printf("%-20s: 获取失败 (%s)\n", name, strerror(errno));
    }
}

/**
 * 演示prlimit64获取资源限制
 */
int demo_prlimit64_get() {
    printf("=== prlimit64 获取资源限制示例 ===\n");
    
    // 显示常见的资源限制
    display_resource_limit(RLIMIT_AS, "虚拟内存");
    display_resource_limit(RLIMIT_CORE, "核心文件大小");
    display_resource_limit(RLIMIT_CPU, "CPU时间");
    display_resource_limit(RLIMIT_DATA, "数据段大小");
    display_resource_limit(RLIMIT_FSIZE, "文件大小");
    display_resource_limit(RLIMIT_NOFILE, "文件描述符数");
    display_resource_limit(RLIMIT_NPROC, "进程数");
    display_resource_limit(RLIMIT_STACK, "栈大小");
    
    return 0;
}

/**
 * 演示prlimit64设置资源限制
 */
int demo_prlimit64_set() {
    struct rlimit64 old_limit, new_limit;
    int result;
    
    printf("\n=== prlimit64 设置资源限制示例 ===\n");
    
    // 获取当前文件描述符限制
    if (prlimit64(0, RLIMIT_NOFILE, NULL, &old_limit) == 0) {
        printf("当前文件描述符限制: 软限制=%lld, 硬限制=%lld\n",
               (long long)old_limit.rlim_cur, (long long)old_limit.rlim_max);
    }
    
    // 尝试设置新的文件描述符限制
    new_limit.rlim_cur = 1024;  // 软限制
    new_limit.rlim_max = 2048;  // 硬限制
    
    result = prlimit64(0, RLIMIT_NOFILE, &new_limit, NULL);
    if (result == 0) {
        printf("成功设置新的文件描述符限制\n");
        
        // 验证设置结果
        if (prlimit64(0, RLIMIT_NOFILE, NULL, &old_limit) == 0) {
            printf("设置后文件描述符限制: 软限制=%lld, 硬限制=%lld\n",
                   (long long)old_limit.rlim_cur, (long long)old_limit.rlim_max);
        }
    } else {
        printf("设置文件描述符限制失败: %s\n", strerror(errno));
        printf("注意:可能需要root权限或在硬限制范围内调整\n");
    }
    
    // 演示内存限制设置
    printf("\n尝试设置虚拟内存限制:\n");
    new_limit.rlim_cur = 100 * 1024 * 1024;  // 100MB
    new_limit.rlim_max = 200 * 1024 * 1024;  // 200MB
    
    result = prlimit64(0, RLIMIT_AS, &new_limit, NULL);
    if (result == 0) {
        printf("成功设置虚拟内存限制为100MB\n");
    } else {
        printf("设置虚拟内存限制失败: %s\n", strerror(errno));
    }
    
    return 0;
}

/**
 * 演示资源限制的实际影响
 */
int demo_resource_limit_effect() {
    struct rlimit64 limit;
    int fd_array[100];
    int i, fd_count = 0;
    
    printf("\n=== 资源限制实际影响演示 ===\n");
    
    // 获取当前文件描述符限制
    if (prlimit64(0, RLIMIT_NOFILE, NULL, &limit) == 0) {
        printf("当前文件描述符软限制: %lld\n", (long long)limit.rlim_cur);
    }
    
    // 尝试打开大量文件来测试限制
    printf("尝试打开文件...\n");
    for (i = 0; i < 100; i++) {
        char filename[32];
        snprintf(filename, sizeof(filename), "test_file_%d.txt", i);
        
        int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
        if (fd == -1) {
            printf("在打开第 %d 个文件时失败: %s\n", i, strerror(errno));
            printf("当前已打开 %d 个文件\n", fd_count);
            break;
        }
        
        fd_array[fd_count++] = fd;
        
        if (i % 20 == 0) {
            printf("已成功打开 %d 个文件\n", fd_count);
        }
    }
    
    // 关闭所有打开的文件
    for (i = 0; i < fd_count; i++) {
        close(fd_array[i]);
        char filename[32];
        snprintf(filename, sizeof(filename), "test_file_%d.txt", i);
        unlink(filename);
    }
    
    printf("清理完成\n");
    return 0;
}

int main() {
    if (demo_prlimit64_get() == 0) {
        demo_prlimit64_set();
        demo_resource_limit_effect();
        printf("\n=== prlimit64 使用总结 ===\n");
        printf("用途:控制进程资源使用,防止资源耗尽\n");
        printf("注意:某些限制需要特权权限才能修改\n");
        printf("常见资源类型:内存、文件描述符、CPU时间、进程数等\n");
    }
    return 0;
}

总结

这些系统调用提供了强大的文件I/O和资源管理功能:

文件I/O函数特点:

1. pread/pwrite: 原子性的定位读写,线程安全
2. preadv/pwritev: 分散/聚集I/O,一次系统调用处理多个缓冲区
3. preadv2/pwritev2: 增强版本,支持额外控制标志

资源管理函数:

1. prlimit64: 现代化的资源限制管理,支持64位值

使用建议:

1. 选择合适的函数: 根据具体需求选择单缓冲区或多缓冲区版本
2. 注意权限要求: 某些操作需要相应权限
3. 错误处理: 始终检查返回值并处理错误
4. 性能考虑: 合理使用分散/聚集I/O减少系统调用次数

发表在 linux文章 | 留下评论

process_vm_readv系统调用及示例

process_vm_readv/process_vm_writev 函数详解

1. 函数介绍

process_vm_readv 和 process_vm_writev 是Linux系统提供的两个系统调用,用于在不同进程之间直接传输数据。它们允许一个进程直接读取或写入另一个进程的内存空间,而无需通过传统的管道、套接字等IPC机制。这种直接内存访问方式提供了更高的性能,特别适用于调试工具、进程监控、内存分析等场景。

2. 函数原型

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

ssize_t process_vm_readv(pid_t pid,
                        const struct iovec *local_iov,
                        unsigned long liovcnt,
                        const struct iovec *remote_iov,
                        unsigned long riovcnt,
                        unsigned long flags);

ssize_t process_vm_writev(pid_t pid,
                         const struct iovec *local_iov,
                         unsigned long liovcnt,
                         const struct iovec *remote_iov,
                         unsigned long riovcnt,
                         unsigned long flags);

3. 功能

4. 参数

共同参数说明:

  • pid_t pid: 目标进程的进程ID
    • 必须是正在运行的进程
    • 调用进程必须有权限访问该进程(相同用户或具有CAP_SYS_PTRACE权限)
  • *const struct iovec local_iov: 本地内存区域描述符数组
    • 描述当前进程中用于读写操作的内存缓冲区
    • 每个iovec结构包含基地址和长度
  • unsigned long liovcnt: 本地iovec数组的元素个数
    • 指定local_iov数组中有效元素的数量
  • *const struct iovec remote_iov: 远程内存区域描述符数组
    • 描述目标进程中用于读写操作的内存地址
    • 每个iovec结构包含基地址和长度
  • unsigned long riovcnt: 远程iovec数组的元素个数
    • 指定remote_iov数组中有效元素的数量
  • unsigned long flags: 标志位(保留字段)
    • 当前必须设置为0

iovec结构体定义:

struct iovec {
    void  *iov_base;    // 内存区域的起始地址
    size_t iov_len;     // 内存区域的长度(字节数)
};

5. 返回值

成功时:

  • 返回实际传输的字节数
  • 可能小于请求的总字节数(部分传输)

失败时:

  • 返回-1,并设置errno错误码

常见错误码:

  • EACCES: 没有权限访问目标进程内存
  • EFAULT: 指定的内存地址范围无效
  • EINVAL: 参数无效(如flags非0,iovcnt过大等)
  • ENOMEM: 内存不足
  • EPERM: 没有权限操作目标进程
  • ESRCH: 目标进程不存在或已终止

6. 相似函数或关联函数

相似函数:

  • readv/writev: 在单个进程内进行分散/聚集I/O操作
  • preadv/pwritev: 带偏移量的分散/聚集I/O操作
  • ptrace: 更通用的进程调试和控制接口

关联函数:

  • kill: 向进程发送信号
  • wait/waitpid: 等待子进程状态变化
  • getpid/getppid: 获取进程ID信息

7. 示例代码

示例1:基础内存读取示例

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

/**
 * 读取目标进程内存的简单示例
 * 注意:需要知道目标进程的确切内存地址
 */
int read_target_process_memory(pid_t target_pid, void *remote_addr, size_t size) {
    char *local_buffer;
    struct iovec local_iov[1];
    struct iovec remote_iov[1];
    ssize_t bytes_transferred;
    
    // 分配本地缓冲区
    local_buffer = malloc(size);
    if (!local_buffer) {
        perror("malloc failed");
        return -1;
    }
    
    // 设置本地iovec(当前进程的缓冲区)
    local_iov[0].iov_base = local_buffer;
    local_iov[0].iov_len = size;
    
    // 设置远程iovec(目标进程的内存地址)
    remote_iov[0].iov_base = remote_addr;
    remote_iov[0].iov_len = size;
    
    // 执行读取操作
    bytes_transferred = process_vm_readv(target_pid,
                                        local_iov, 1,      // 本地1个iovec
                                        remote_iov, 1,     // 远程1个iovec
                                        0);                // flags必须为0
    
    if (bytes_transferred == -1) {
        printf("process_vm_readv failed: %s\n", strerror(errno));
        free(local_buffer);
        return -1;
    }
    
    printf("成功读取 %zd 字节数据\n", bytes_transferred);
    printf("读取的数据内容: ");
    for (size_t i = 0; i < bytes_transferred && i < 50; i++) {
        if (local_buffer[i] >= 32 && local_buffer[i] <= 126) {
            putchar(local_buffer[i]);
        } else {
            printf("\\x%02x", (unsigned char)local_buffer[i]);
        }
    }
    printf("\n");
    
    free(local_buffer);
    return 0;
}

int main(int argc, char *argv[]) {
    if (argc != 4) {
        printf("使用方法: %s <目标进程PID> <内存地址> <读取大小>\n", argv[0]);
        printf("示例: %s 1234 0x601040 100\n", argv[0]);
        return 1;
    }
    
    pid_t target_pid = atoi(argv[1]);
    unsigned long addr = strtoul(argv[2], NULL, 0);
    size_t size = strtoul(argv[3], NULL, 0);
    
    printf("尝试读取进程 %d 的内存地址 0x%lx,大小 %zu 字节\n", 
           target_pid, addr, size);
    
    return read_target_process_memory(target_pid, (void*)addr, size);
}

示例2:批量内存操作示例

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

#define MAX_REGIONS 10

/**
 * 批量读取多个内存区域
 */
typedef struct {
    void *remote_addr;    // 远程地址
    size_t size;          // 区域大小
    char *data;           // 读取到的数据
} memory_region_t;

int batch_read_memory_regions(pid_t target_pid, memory_region_t *regions, int count) {
    struct iovec local_iov[MAX_REGIONS];
    struct iovec remote_iov[MAX_REGIONS];
    ssize_t total_bytes;
    int i;
    
    // 参数检查
    if (count > MAX_REGIONS) {
        printf("区域数量超过最大限制 %d\n", MAX_REGIONS);
        return -1;
    }
    
    // 为每个区域分配本地缓冲区并设置iovec
    for (i = 0; i < count; i++) {
        regions[i].data = malloc(regions[i].size);
        if (!regions[i].data) {
            printf("为区域 %d 分配内存失败\n", i);
            // 释放已分配的内存
            for (int j = 0; j < i; j++) {
                free(regions[j].data);
            }
            return -1;
        }
        
        // 设置本地iovec
        local_iov[i].iov_base = regions[i].data;
        local_iov[i].iov_len = regions[i].size;
        
        // 设置远程iovec
        remote_iov[i].iov_base = regions[i].remote_addr;
        remote_iov[i].iov_len = regions[i].size;
    }
    
    // 执行批量读取
    total_bytes = process_vm_readv(target_pid,
                                  local_iov, count,
                                  remote_iov, count,
                                  0);
    
    if (total_bytes == -1) {
        printf("批量读取失败: %s\n", strerror(errno));
        // 释放所有缓冲区
        for (i = 0; i < count; i++) {
            free(regions[i].data);
        }
        return -1;
    }
    
    printf("批量读取成功,总共传输 %zd 字节\n", total_bytes);
    
    // 显示每个区域的数据
    for (i = 0; i < count; i++) {
        printf("区域 %d (地址 0x%lx, 大小 %zu): ", 
               i, (unsigned long)regions[i].remote_addr, regions[i].size);
        for (size_t j = 0; j < regions[i].size && j < 20; j++) {
            if (regions[i].data[j] >= 32 && regions[i].data[j] <= 126) {
                putchar(regions[i].data[j]);
            } else {
                printf(".");
            }
        }
        printf("\n");
    }
    
    return 0;
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("使用方法: %s <目标进程PID>\n", argv[0]);
        return 1;
    }
    
    pid_t target_pid = atoi(argv[1]);
    memory_region_t regions[3];
    
    // 设置要读取的内存区域(实际使用时需要根据目标进程调整地址)
    regions[0].remote_addr = (void*)0x601000;  // 示例地址1
    regions[0].size = 32;
    regions[1].remote_addr = (void*)0x601020;  // 示例地址2
    regions[1].size = 16;
    regions[2].remote_addr = (void*)0x601030;  // 示例地址3
    regions[2].size = 8;
    
    printf("批量读取进程 %d 的多个内存区域\n", target_pid);
    
    if (batch_read_memory_regions(target_pid, regions, 3) == 0) {
        printf("批量读取操作完成\n");
        // 释放内存
        for (int i = 0; i < 3; i++) {
            free(regions[i].data);
        }
    } else {
        printf("批量读取操作失败\n");
    }
    
    return 0;
}

示例3:内存写入和修改示例

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

/**
 * 写入数据到目标进程内存
 */
int write_to_target_memory(pid_t target_pid, void *remote_addr, 
                          const void *data, size_t size) {
    struct iovec local_iov[1];
    struct iovec remote_iov[1];
    ssize_t bytes_written;
    
    // 设置本地iovec(要写入的数据)
    local_iov[0].iov_base = (void*)data;  // 强制转换,实际不会修改
    local_iov[0].iov_len = size;
    
    // 设置远程iovec(目标地址)
    remote_iov[0].iov_base = remote_addr;
    remote_iov[0].iov_len = size;
    
    // 执行写入操作
    bytes_written = process_vm_writev(target_pid,
                                     local_iov, 1,
                                     remote_iov, 1,
                                     0);
    
    if (bytes_written == -1) {
        printf("process_vm_writev 失败: %s\n", strerror(errno));
        return -1;
    }
    
    printf("成功写入 %zd 字节到进程 %d 的地址 0x%lx\n", 
           bytes_written, target_pid, (unsigned long)remote_addr);
    
    return 0;
}

/**
 * 发送信号给目标进程
 */
int send_signal_to_process(pid_t target_pid, int signal) {
    if (kill(target_pid, signal) == -1) {
        printf("发送信号失败: %s\n", strerror(errno));
        return -1;
    }
    printf("成功发送信号 %d 到进程 %d\n", signal, target_pid);
    return 0;
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("使用方法: %s <目标进程PID>\n", argv[0]);
        return 1;
    }
    
    pid_t target_pid = atoi(argv[1]);
    char new_message[] = "Hello from process_vm_writev!";
    int new_value = 999999;
    
    printf("准备修改进程 %d 的内存\n", target_pid);
    
    // 写入字符串数据(需要知道目标进程的确切地址)
    if (write_to_target_memory(target_pid, (void*)0x601060, 
                              new_message, strlen(new_message) + 1) == 0) {
        printf("字符串写入成功\n");
    }
    
    // 写入整数数据
    if (write_to_target_memory(target_pid, (void*)0x601040, 
                              &new_value, sizeof(new_value)) == 0) {
        printf("整数写入成功\n");
    }
    
    // 发送信号让目标进程重新显示数据
    send_signal_to_process(target_pid, SIGUSR1);
    
    return 0;
}

示例4:完整的测试目标进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>

// 这些变量的地址将被其他进程读取和修改
int shared_counter = 42;
char shared_message[256] = "Original message from target process";
double shared_double = 3.14159;

volatile int running = 1;

void signal_handler(int sig) {
    if (sig == SIGUSR1) {
        printf("收到SIGUSR1信号,显示当前数据:\n");
        printf("  Counter: %d\n", shared_counter);
        printf("  Message: %s\n", shared_message);
        printf("  Double:  %.5f\n", shared_double);
    } else if (sig == SIGINT) {
        printf("收到终止信号\n");
        running = 0;
    }
}

int main() {
    printf("=== 目标测试进程 ===\n");
    printf("进程PID: %d\n", getpid());
    printf("变量地址信息:\n");
    printf("  shared_counter 地址: %p\n", &shared_counter);
    printf("  shared_message 地址: %p\n", shared_message);
    printf("  shared_double  地址: %p\n", &shared_double);
    printf("\n");
    printf("初始数据:\n");
    printf("  Counter: %d\n", shared_counter);
    printf("  Message: %s\n", shared_message);
    printf("  Double:  %.5f\n", shared_double);
    printf("\n");
    printf("按 Ctrl+C 退出,或等待其他进程发送 SIGUSR1 信号\n");
    
    // 注册信号处理程序
    signal(SIGUSR1, signal_handler);
    signal(SIGINT, signal_handler);
    
    // 主循环
    while (running) {
        sleep(1);
    }
    
    printf("目标进程退出\n");
    return 0;
}

使用注意事项

权限要求:

1. 调用进程和目标进程必须具有相同的用户ID
2. 或者调用进程必须具有 CAP_SYS_PTRACE 权限
3. 目标进程必须正在运行(不能是僵尸进程)

地址获取:

1. 需要知道目标进程的确切内存地址
2. 可以通过调试器、符号表或/proc/[pid]/maps文件获取
3. 地址必须在目标进程的有效地址空间内

错误处理:

1. 始终检查返回值
2. 处理部分传输的情况
3. 确保本地缓冲区足够大

安全考虑:

1. 这些函数可能被恶意程序用于内存篡改
2. 在生产环境中应谨慎使用
3. 系统管理员可以通过安全策略限制使用

性能特点:

1. 比传统的IPC机制更高效
2. 避免了内核缓冲区复制
3. 适合大量数据传输场景

发表在 linux文章 | 标签为 | 留下评论

process_vm_writev系统调用及示例

process_vm_readv/process_vm_writev 函数详解

1. 函数介绍

process_vm_readv 和 process_vm_writev 是Linux系统提供的两个系统调用,用于在不同进程之间直接传输数据。它们允许一个进程直接读取或写入另一个进程的内存空间,而无需通过传统的管道、套接字等IPC机制。这种直接内存访问方式提供了更高的性能,特别适用于调试工具、进程监控、内存分析等场景。

2. 函数原型

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

ssize_t process_vm_readv(pid_t pid,
                        const struct iovec *local_iov,
                        unsigned long liovcnt,
                        const struct iovec *remote_iov,
                        unsigned long riovcnt,
                        unsigned long flags);

ssize_t process_vm_writev(pid_t pid,
                         const struct iovec *local_iov,
                         unsigned long liovcnt,
                         const struct iovec *remote_iov,
                         unsigned long riovcnt,
                         unsigned long flags);

3. 功能

4. 参数

共同参数说明:

  • pid_t pid: 目标进程的进程ID
    • 必须是正在运行的进程
    • 调用进程必须有权限访问该进程(相同用户或具有CAP_SYS_PTRACE权限)
  • *const struct iovec local_iov: 本地内存区域描述符数组
    • 描述当前进程中用于读写操作的内存缓冲区
    • 每个iovec结构包含基地址和长度
  • unsigned long liovcnt: 本地iovec数组的元素个数
    • 指定local_iov数组中有效元素的数量
  • *const struct iovec remote_iov: 远程内存区域描述符数组
    • 描述目标进程中用于读写操作的内存地址
    • 每个iovec结构包含基地址和长度
  • unsigned long riovcnt: 远程iovec数组的元素个数
    • 指定remote_iov数组中有效元素的数量
  • unsigned long flags: 标志位(保留字段)
    • 当前必须设置为0

iovec结构体定义:

struct iovec {
    void  *iov_base;    // 内存区域的起始地址
    size_t iov_len;     // 内存区域的长度(字节数)
};

5. 返回值

成功时:

  • 返回实际传输的字节数
  • 可能小于请求的总字节数(部分传输)

失败时:

  • 返回-1,并设置errno错误码

常见错误码:

  • EACCES: 没有权限访问目标进程内存
  • EFAULT: 指定的内存地址范围无效
  • EINVAL: 参数无效(如flags非0,iovcnt过大等)
  • ENOMEM: 内存不足
  • EPERM: 没有权限操作目标进程
  • ESRCH: 目标进程不存在或已终止

6. 相似函数或关联函数

相似函数:

  • readv/writev: 在单个进程内进行分散/聚集I/O操作
  • preadv/pwritev: 带偏移量的分散/聚集I/O操作
  • ptrace: 更通用的进程调试和控制接口

关联函数:

  • kill: 向进程发送信号
  • wait/waitpid: 等待子进程状态变化
  • getpid/getppid: 获取进程ID信息

7. 示例代码

示例1:基础内存读取示例

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

/**
 * 读取目标进程内存的简单示例
 * 注意:需要知道目标进程的确切内存地址
 */
int read_target_process_memory(pid_t target_pid, void *remote_addr, size_t size) {
    char *local_buffer;
    struct iovec local_iov[1];
    struct iovec remote_iov[1];
    ssize_t bytes_transferred;
    
    // 分配本地缓冲区
    local_buffer = malloc(size);
    if (!local_buffer) {
        perror("malloc failed");
        return -1;
    }
    
    // 设置本地iovec(当前进程的缓冲区)
    local_iov[0].iov_base = local_buffer;
    local_iov[0].iov_len = size;
    
    // 设置远程iovec(目标进程的内存地址)
    remote_iov[0].iov_base = remote_addr;
    remote_iov[0].iov_len = size;
    
    // 执行读取操作
    bytes_transferred = process_vm_readv(target_pid,
                                        local_iov, 1,      // 本地1个iovec
                                        remote_iov, 1,     // 远程1个iovec
                                        0);                // flags必须为0
    
    if (bytes_transferred == -1) {
        printf("process_vm_readv failed: %s\n", strerror(errno));
        free(local_buffer);
        return -1;
    }
    
    printf("成功读取 %zd 字节数据\n", bytes_transferred);
    printf("读取的数据内容: ");
    for (size_t i = 0; i < bytes_transferred && i < 50; i++) {
        if (local_buffer[i] >= 32 && local_buffer[i] <= 126) {
            putchar(local_buffer[i]);
        } else {
            printf("\\x%02x", (unsigned char)local_buffer[i]);
        }
    }
    printf("\n");
    
    free(local_buffer);
    return 0;
}

int main(int argc, char *argv[]) {
    if (argc != 4) {
        printf("使用方法: %s <目标进程PID> <内存地址> <读取大小>\n", argv[0]);
        printf("示例: %s 1234 0x601040 100\n", argv[0]);
        return 1;
    }
    
    pid_t target_pid = atoi(argv[1]);
    unsigned long addr = strtoul(argv[2], NULL, 0);
    size_t size = strtoul(argv[3], NULL, 0);
    
    printf("尝试读取进程 %d 的内存地址 0x%lx,大小 %zu 字节\n", 
           target_pid, addr, size);
    
    return read_target_process_memory(target_pid, (void*)addr, size);
}

示例2:批量内存操作示例

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

#define MAX_REGIONS 10

/**
 * 批量读取多个内存区域
 */
typedef struct {
    void *remote_addr;    // 远程地址
    size_t size;          // 区域大小
    char *data;           // 读取到的数据
} memory_region_t;

int batch_read_memory_regions(pid_t target_pid, memory_region_t *regions, int count) {
    struct iovec local_iov[MAX_REGIONS];
    struct iovec remote_iov[MAX_REGIONS];
    ssize_t total_bytes;
    int i;
    
    // 参数检查
    if (count > MAX_REGIONS) {
        printf("区域数量超过最大限制 %d\n", MAX_REGIONS);
        return -1;
    }
    
    // 为每个区域分配本地缓冲区并设置iovec
    for (i = 0; i < count; i++) {
        regions[i].data = malloc(regions[i].size);
        if (!regions[i].data) {
            printf("为区域 %d 分配内存失败\n", i);
            // 释放已分配的内存
            for (int j = 0; j < i; j++) {
                free(regions[j].data);
            }
            return -1;
        }
        
        // 设置本地iovec
        local_iov[i].iov_base = regions[i].data;
        local_iov[i].iov_len = regions[i].size;
        
        // 设置远程iovec
        remote_iov[i].iov_base = regions[i].remote_addr;
        remote_iov[i].iov_len = regions[i].size;
    }
    
    // 执行批量读取
    total_bytes = process_vm_readv(target_pid,
                                  local_iov, count,
                                  remote_iov, count,
                                  0);
    
    if (total_bytes == -1) {
        printf("批量读取失败: %s\n", strerror(errno));
        // 释放所有缓冲区
        for (i = 0; i < count; i++) {
            free(regions[i].data);
        }
        return -1;
    }
    
    printf("批量读取成功,总共传输 %zd 字节\n", total_bytes);
    
    // 显示每个区域的数据
    for (i = 0; i < count; i++) {
        printf("区域 %d (地址 0x%lx, 大小 %zu): ", 
               i, (unsigned long)regions[i].remote_addr, regions[i].size);
        for (size_t j = 0; j < regions[i].size && j < 20; j++) {
            if (regions[i].data[j] >= 32 && regions[i].data[j] <= 126) {
                putchar(regions[i].data[j]);
            } else {
                printf(".");
            }
        }
        printf("\n");
    }
    
    return 0;
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("使用方法: %s <目标进程PID>\n", argv[0]);
        return 1;
    }
    
    pid_t target_pid = atoi(argv[1]);
    memory_region_t regions[3];
    
    // 设置要读取的内存区域(实际使用时需要根据目标进程调整地址)
    regions[0].remote_addr = (void*)0x601000;  // 示例地址1
    regions[0].size = 32;
    regions[1].remote_addr = (void*)0x601020;  // 示例地址2
    regions[1].size = 16;
    regions[2].remote_addr = (void*)0x601030;  // 示例地址3
    regions[2].size = 8;
    
    printf("批量读取进程 %d 的多个内存区域\n", target_pid);
    
    if (batch_read_memory_regions(target_pid, regions, 3) == 0) {
        printf("批量读取操作完成\n");
        // 释放内存
        for (int i = 0; i < 3; i++) {
            free(regions[i].data);
        }
    } else {
        printf("批量读取操作失败\n");
    }
    
    return 0;
}

示例3:内存写入和修改示例

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

/**
 * 写入数据到目标进程内存
 */
int write_to_target_memory(pid_t target_pid, void *remote_addr, 
                          const void *data, size_t size) {
    struct iovec local_iov[1];
    struct iovec remote_iov[1];
    ssize_t bytes_written;
    
    // 设置本地iovec(要写入的数据)
    local_iov[0].iov_base = (void*)data;  // 强制转换,实际不会修改
    local_iov[0].iov_len = size;
    
    // 设置远程iovec(目标地址)
    remote_iov[0].iov_base = remote_addr;
    remote_iov[0].iov_len = size;
    
    // 执行写入操作
    bytes_written = process_vm_writev(target_pid,
                                     local_iov, 1,
                                     remote_iov, 1,
                                     0);
    
    if (bytes_written == -1) {
        printf("process_vm_writev 失败: %s\n", strerror(errno));
        return -1;
    }
    
    printf("成功写入 %zd 字节到进程 %d 的地址 0x%lx\n", 
           bytes_written, target_pid, (unsigned long)remote_addr);
    
    return 0;
}

/**
 * 发送信号给目标进程
 */
int send_signal_to_process(pid_t target_pid, int signal) {
    if (kill(target_pid, signal) == -1) {
        printf("发送信号失败: %s\n", strerror(errno));
        return -1;
    }
    printf("成功发送信号 %d 到进程 %d\n", signal, target_pid);
    return 0;
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("使用方法: %s <目标进程PID>\n", argv[0]);
        return 1;
    }
    
    pid_t target_pid = atoi(argv[1]);
    char new_message[] = "Hello from process_vm_writev!";
    int new_value = 999999;
    
    printf("准备修改进程 %d 的内存\n", target_pid);
    
    // 写入字符串数据(需要知道目标进程的确切地址)
    if (write_to_target_memory(target_pid, (void*)0x601060, 
                              new_message, strlen(new_message) + 1) == 0) {
        printf("字符串写入成功\n");
    }
    
    // 写入整数数据
    if (write_to_target_memory(target_pid, (void*)0x601040, 
                              &new_value, sizeof(new_value)) == 0) {
        printf("整数写入成功\n");
    }
    
    // 发送信号让目标进程重新显示数据
    send_signal_to_process(target_pid, SIGUSR1);
    
    return 0;
}

示例4:完整的测试目标进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>

// 这些变量的地址将被其他进程读取和修改
int shared_counter = 42;
char shared_message[256] = "Original message from target process";
double shared_double = 3.14159;

volatile int running = 1;

void signal_handler(int sig) {
    if (sig == SIGUSR1) {
        printf("收到SIGUSR1信号,显示当前数据:\n");
        printf("  Counter: %d\n", shared_counter);
        printf("  Message: %s\n", shared_message);
        printf("  Double:  %.5f\n", shared_double);
    } else if (sig == SIGINT) {
        printf("收到终止信号\n");
        running = 0;
    }
}

int main() {
    printf("=== 目标测试进程 ===\n");
    printf("进程PID: %d\n", getpid());
    printf("变量地址信息:\n");
    printf("  shared_counter 地址: %p\n", &shared_counter);
    printf("  shared_message 地址: %p\n", shared_message);
    printf("  shared_double  地址: %p\n", &shared_double);
    printf("\n");
    printf("初始数据:\n");
    printf("  Counter: %d\n", shared_counter);
    printf("  Message: %s\n", shared_message);
    printf("  Double:  %.5f\n", shared_double);
    printf("\n");
    printf("按 Ctrl+C 退出,或等待其他进程发送 SIGUSR1 信号\n");
    
    // 注册信号处理程序
    signal(SIGUSR1, signal_handler);
    signal(SIGINT, signal_handler);
    
    // 主循环
    while (running) {
        sleep(1);
    }
    
    printf("目标进程退出\n");
    return 0;
}

使用注意事项

权限要求:

1. 调用进程和目标进程必须具有相同的用户ID
2. 或者调用进程必须具有 CAP_SYS_PTRACE 权限
3. 目标进程必须正在运行(不能是僵尸进程)

地址获取:

1. 需要知道目标进程的确切内存地址
2. 可以通过调试器、符号表或/proc/[pid]/maps文件获取
3. 地址必须在目标进程的有效地址空间内

错误处理:

1. 始终检查返回值
2. 处理部分传输的情况
3. 确保本地缓冲区足够大

安全考虑:

1. 这些函数可能被恶意程序用于内存篡改
2. 在生产环境中应谨慎使用
3. 系统管理员可以通过安全策略限制使用

性能特点:

1. 比传统的IPC机制更高效
2. 避免了内核缓冲区复制
3. 适合大量数据传输场景

https://blog.csdn.net/timberwolf007/article/details/150415662?spm=1011.2415.3001.5331

发表在 linux文章 | 留下评论

setrlimit系统调用及示例

我们来深入学习 setrlimit 系统调用

1. 函数介绍

在 Linux 系统中,为了保证系统的稳定性和公平性,防止某个程序因为 bug 或恶意行为而耗尽系统资源(如内存、CPU 时间、打开的文件数量等),内核提供了一种资源限制 (Resource Limits) 机制。

setrlimit (Set Resource Limit) 系统调用的作用就是设置调用进程(及其未来创建的子进程)对某一类系统资源的使用上限

你可以把它想象成你给一个程序分配一个“资源使用预算”或“配额”。比如,你可以告诉内核:“这个程序最多只能使用 100MB 的内存”、“最多只能打开 10 个文件”、“最多只能运行 10 秒钟”等等。当程序试图超出这个限制时,内核会根据资源类型采取不同措施,通常是拒绝其请求(例如 malloc 失败)或发送一个信号(例如 SIGXCPU)来终止它。

简单来说,setrlimit 就是让你用程序来给另一个程序(或自己)“上规矩”,限制它能用多少系统资源。

2. 函数原型

#include <sys/resource.h> // 包含系统调用声明和常量

int setrlimit(int resource, const struct rlimit *rlim);

3. 功能

为调用进程设置指定资源 resource 的软限制 (soft limit) 和硬限制 (hard limit)。

4. 参数

  • resource:
    • int 类型。
    • 指定要设置限制的资源类型。常见的资源类型定义在 <sys/resource.h> 中,例如:
      • RLIMIT_AS: 进程虚拟地址空间的最大总大小(字节)。限制进程能分配的总内存。
      • RLIMIT_CORE: 程序崩溃时创建的核心转储文件 (core dump) 的最大字节数。设置为 0 可以禁用 core dump。
      • RLIMIT_CPU: 进程可以使用的 CPU 时间(秒)。达到软限制会收到 SIGXCPU 信号,达到硬限制会被 SIGKILL 终止。
      • RLIMIT_DATA: 进程数据段的最大字节大小(通过 brk/sbrk 分配的内存)。
      • RLIMIT_FSIZE: 进程可以创建的文件的最大字节大小。超出限制时写操作会失败,并可能收到 SIGXFSZ 信号。
      • RLIMIT_NOFILE: 进程可以同时打开的文件描述符(File Descriptor)的最大数量。
      • RLIMIT_NPROC: 调用用户 ID (Real User ID) 可以拥有的最大进程/线程数量。
      • RLIMIT_STACK: 进程栈的最大字节大小。
      • RLIMIT_MEMLOCK: 可以使用 mlock 锁定在内存中的最大字节数。
      • RLIMIT_RSS: 进程在物理内存中驻留的最大字节数(Resident Set Size)。(在 Linux 上可能不强制执行)。
      • RLIMIT_NICE: nice 值的上限(影响进程调度优先级)。
      • … 还有其他一些资源类型。
  • rlim:
    • const struct rlimit * 类型。
    • 一个指向 rlimit 结构体的指针,该结构体定义了资源的限制。rlimit 结构体定义如下:struct rlimit { rlim_t rlim_cur; // Soft limit (软限制) rlim_t rlim_max; // Hard limit (硬限制) };
      • rlim_cur (软限制):
        • 这是内核实际执行强制限制的值。
        • 进程可以随时将其修改为小于或等于当前硬限制 (rlim_max) 的任何值。
        • 超过软限制通常会导致内核发送一个信号(如 SIGXCPU)来警告进程。
      • rlim_max (硬限制):
        • 这是软限制可以被设置的上限
        • 普通用户只能降低硬限制,不能提高它
        • 只有特权用户 (root) 才能提高硬限制。
        • 进程可以在任何时候将硬限制降低到等于或低于当前硬限制的值。

5. 返回值

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

6. 错误码 (errno)

  • EFAULTrlim 指向了调用进程无法访问的内存地址。
  • EINVALresource 参数无效,或者指定的限制值无效(例如,负数,或者对于某些资源类型不合适)。
  • EPERM: 调用进程没有权限设置指定的限制。最常见的原因是普通用户试图提高硬限制 (rlim_max)。

7. 相似函数或关联函数

  • getrlimit: 用于获取当前进程对某类资源的限制设置。
  • prlimit: 一个更现代的系统调用,可以同时设置和获取任意进程的资源限制(需要 CAP_SYS_RESOURCE 能力)。
  • ulimit: 命令行工具(在 shell 中),用于设置当前 shell 及其子进程的资源限制。它在底层调用 setrlimit 和 getrlimit
  • struct rlimit: 定义限制值的数据结构。

8. 示例代码

下面的示例演示了如何使用 setrlimit 来设置几种常见的资源限制。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/resource.h> // 包含 setrlimit, getrlimit, struct rlimit
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/time.h> // 包含 timeval, 用于 CPU 时间限制
#include <fcntl.h>   // 包含 open

// 信号处理函数,用于捕获因资源限制而产生的信号
void signal_handler(int sig) {
    printf("\nCaught signal %d\n", sig);
    if (sig == SIGXCPU) {
        printf("CPU time limit (soft) reached. Exiting gracefully.\n");
        exit(EXIT_FAILURE);
    } else if (sig == SIGXFSZ) {
        printf("File size limit reached. Write operation failed.\n");
        // 可以选择继续运行或退出
    }
}

// 打印特定资源的当前限制
void print_resource_limit(const char* resource_name, int resource) {
    struct rlimit rl;
    if (getrlimit(resource, &rl) == 0) {
        printf("%-15s: Soft = ", resource_name);
        if (rl.rlim_cur == RLIM_INFINITY) printf("unlimited");
        else printf("%ld", (long)rl.rlim_cur);

        printf(", Hard = ");
        if (rl.rlim_max == RLIM_INFINITY) printf("unlimited");
        else printf("%ld", (long)rl.rlim_max);
        printf("\n");
    } else {
        perror("getrlimit");
    }
}

int main() {
    struct rlimit rl;
    struct sigaction sa;

    printf("--- Demonstrating setrlimit ---\n");
    printf("PID: %d\n", getpid());

    // 1. 显示初始的一些资源限制
    printf("\n--- Initial Resource Limits ---\n");
    print_resource_limit("CPU Time", RLIMIT_CPU);
    print_resource_limit("File Size", RLIMIT_FSIZE);
    print_resource_limit("Data Segment", RLIMIT_DATA);
    print_resource_limit("Stack Size", RLIMIT_STACK);
    print_resource_limit("Virtual Memory", RLIMIT_AS);
    print_resource_limit("Open Files", RLIMIT_NOFILE);
    print_resource_limit("Max Processes", RLIMIT_NPROC);
    print_resource_limit("Core File Size", RLIMIT_CORE);

    // 2. 设置 CPU 时间限制
    printf("\n--- Setting CPU Time Limit ---\n");
    rl.rlim_cur = 5;  // 软限制:5 秒
    rl.rlim_max = 10; // 硬限制:10 秒
    if (setrlimit(RLIMIT_CPU, &rl) == 0) {
        printf("Set CPU time limit: Soft = %lds, Hard = %lds\n", (long)rl.rlim_cur, (long)rl.rlim_max);
        // 设置信号处理函数以捕获 SIGXCPU
        memset(&sa, 0, sizeof(sa));
        sa.sa_handler = signal_handler;
        sigemptyset(&sa.sa_mask);
        if (sigaction(SIGXCPU, &sa, NULL) == -1) {
            perror("sigaction SIGXCPU");
        }
    } else {
        perror("setrlimit RLIMIT_CPU");
    }

    // 3. 设置最大打开文件数限制
    printf("\n--- Setting Open File Descriptor Limit ---\n");
    rl.rlim_cur = 10; // 软限制:最多 10 个文件描述符
    rl.rlim_max = 20; // 硬限制:最多 20 个文件描述符
    if (setrlimit(RLIMIT_NOFILE, &rl) == 0) {
        printf("Set open file limit: Soft = %ld, Hard = %ld\n", (long)rl.rlim_cur, (long)rl.rlim_max);
    } else {
        perror("setrlimit RLIMIT_NOFILE");
    }

    // 4. 设置最大文件大小限制
    printf("\n--- Setting File Size Limit ---\n");
    rl.rlim_cur = 1024 * 1024; // 软限制:1MB
    rl.rlim_max = 2 * 1024 * 1024; // 硬限制:2MB
    if (setrlimit(RLIMIT_FSIZE, &rl) == 0) {
        printf("Set file size limit: Soft = %ld bytes, Hard = %ld bytes\n", (long)rl.rlim_cur, (long)rl.rlim_max);
        // 设置信号处理函数以捕获 SIGXFSZ
        sa.sa_handler = signal_handler;
        if (sigaction(SIGXFSZ, &sa, NULL) == -1) {
            perror("sigaction SIGXFSZ");
        }
    } else {
        perror("setrlimit RLIMIT_FSIZE");
    }

    // 5. 设置虚拟内存限制 (RLIMIT_AS)
    printf("\n--- Setting Virtual Memory Limit ---\n");
    rl.rlim_cur = 50 * 1024 * 1024; // 软限制:50 MB
    rl.rlim_max = 100 * 1024 * 1024; // 硬限制:100 MB
    if (setrlimit(RLIMIT_AS, &rl) == 0) {
        printf("Set virtual memory limit: Soft = %ld bytes (%.2f MB), Hard = %ld bytes (%.2f MB)\n",
               (long)rl.rlim_cur, (double)rl.rlim_cur / (1024*1024),
               (long)rl.rlim_max, (double)rl.rlim_max / (1024*1024));
    } else {
        perror("setrlimit RLIMIT_AS");
    }

    // 6. 设置 core dump 大小为 0,禁用它
    printf("\n--- Disabling Core Dump ---\n");
    rl.rlim_cur = 0;
    rl.rlim_max = 0;
    if (setrlimit(RLIMIT_CORE, &rl) == 0) {
        printf("Disabled core dump generation.\n");
    } else {
        perror("setrlimit RLIMIT_CORE");
    }

    // 7. 验证设置后的限制
    printf("\n--- Resource Limits After setrlimit ---\n");
    print_resource_limit("CPU Time", RLIMIT_CPU);
    print_resource_limit("File Size", RLIMIT_FSIZE);
    print_resource_limit("Virtual Memory", RLIMIT_AS);
    print_resource_limit("Open Files", RLIMIT_NOFILE);
    print_resource_limit("Core File Size", RLIMIT_CORE);

    // 8. 演示资源限制的效果

    // --- 演示 RLIMIT_FSIZE ---
    printf("\n--- Testing RLIMIT_FSIZE (File Size Limit) ---\n");
    const char* test_filename = "test_limited_file.txt";
    int fd = open(test_filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open test file");
    } else {
        char data[1024];
        memset(data, 'A', sizeof(data));
        ssize_t written;
        long total_written = 0;
        // 尝试写入超过 1MB 的数据
        while (total_written < 2 * 1024 * 1024) {
            written = write(fd, data, sizeof(data));
            if (written == -1) {
                perror("write");
                printf("Write failed after writing approximately %ld bytes. File size limit likely reached.\n", total_written);
                break;
            }
            total_written += written;
        }
        close(fd);
        printf("Finished writing (or failed) to file.\n");
        // 清理测试文件
        unlink(test_filename);
    }

    // --- 演示 RLIMIT_AS ---
    printf("\n--- Testing RLIMIT_AS (Virtual Memory Limit) ---\n");
    printf("Attempting to allocate large chunks of memory until limit is hit...\n");
    size_t chunk_size = 10 * 1024 * 1024; // 10MB
    long allocated_mb = 0;
    char *ptr;
    while (1) {
        ptr = malloc(chunk_size);
        if (ptr == NULL) {
            printf("malloc failed after allocating approximately %ld MB. Memory limit likely reached.\n", allocated_mb);
            break;
        }
        // Touch the memory to ensure it's actually allocated
        memset(ptr, 0, chunk_size);
        allocated_mb += chunk_size / (1024 * 1024);
        printf("Allocated %ld MB so far...\n", allocated_mb);
        // 添加一点延迟,方便观察
        sleep(1);
    }

    // --- 演示 RLIMIT_CPU (放在最后,因为它会终止程序) ---
    printf("\n--- Testing RLIMIT_CPU (CPU Time Limit) ---\n");
    printf("Entering infinite loop. Should be killed by SIGKILL after 10 seconds (hard limit).\n");
    printf("You might see 'CPU time limit (soft) reached' message first (after 5s), then termination.\n");
    while(1) {
        // 空循环,消耗 CPU 时间
    }

    // 程序通常不会执行到这里,因为 RLIMIT_CPU 会终止它
    printf("Program finished normally (unexpected).\n");
    return 0;
}

9. 编译和运行

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

# 运行程序
./setrlimit_example

10. 预期输出 (片段)

--- Demonstrating setrlimit ---
PID: 12345

--- Initial Resource Limits ---
CPU Time       : Soft = unlimited, Hard = unlimited
File Size      : Soft = unlimited, Hard = unlimited
Data Segment   : Soft = unlimited, Hard = unlimited
Stack Size     : Soft = 8388608, Hard = unlimited
Virtual Memory : Soft = unlimited, Hard = unlimited
Open Files     : Soft = 1024, Hard = 1048576
Max Processes  : Soft = 62545, Hard = 62545
Core File Size : Soft = 0, Hard = unlimited

--- Setting CPU Time Limit ---
Set CPU time limit: Soft = 5s, Hard = 10s

--- Setting Open File Descriptor Limit ---
Set open file limit: Soft = 10, Hard = 20

--- Setting File Size Limit ---
Set file size limit: Soft = 1048576 bytes, Hard = 2097152 bytes

--- Setting Virtual Memory Limit ---
Set virtual memory limit: Soft = 52428800 bytes (50.00 MB), Hard = 104857600 bytes (100.00 MB)

--- Disabling Core Dump ---
Disabled core dump generation.

--- Resource Limits After setrlimit ---
CPU Time       : Soft = 5, Hard = 10
File Size      : Soft = 1048576, Hard = 2097152
Virtual Memory : Soft = 52428800, Hard = 104857600
Open Files     : Soft = 10, Hard = 20
Core File Size : Soft = 0, Hard = 0

--- Testing RLIMIT_FSIZE (File Size Limit) ---
Finished writing (or failed) to file.

--- Testing RLIMIT_AS (Virtual Memory Limit) ---
Attempting to allocate large chunks of memory until limit is hit...
Allocated 10 MB so far...
Allocated 20 MB so far...
...
Allocated 50 MB so far...
malloc failed after allocating approximately 50 MB. Memory limit likely reached.

--- Testing RLIMIT_CPU (CPU Time Limit) ---
Entering infinite loop. Should be killed by SIGKILL after 10 seconds (hard limit).
You might see 'CPU time limit (soft) reached' message first (after 5s), then termination.

Caught signal 24
CPU time limit (soft) reached. Exiting gracefully.
[程序被终止]

11. 总结

setrlimit 是一个非常有用的系统调用,用于管理和控制进程对系统资源的消耗。它对于编写健壮、安全的系统程序和服务至关重要,可以防止资源耗尽导致的系统不稳定。理解软限制和硬限制的区别,以及不同资源类型的行为,是掌握 Linux 进程管理的基础。

发表在 linux文章 | 标签为 | 留下评论

set内置命令的 -e选项详解

set内置命令的 -e选项详解

探索Shell脚本中set内置命令的-e选项如何提升代码质量与安全性,掌握其工作原理及实用示例,成为脚本高手。

在 Shell 脚本中,set -e 是一个非常重要的命令选项,用于让脚本在遇到错误时立即终止执行(即“出错即停止”)。它通过监控每个命令的退出状态码(Exit Status)来实现这一功能,是编写健壮脚本的关键工具之一。

核心作用:出错即终止

当 set -e 生效时,Shell 脚本中任何返回非零退出状态码(表示执行失败)的命令都会触发脚本立即终止,避免后续命令继续执行可能导致的连锁错误(例如删除错误文件、覆盖重要数据等)。

工作机制:退出状态码

Shell 中每个命令执行后会返回一个退出状态码($? 可获取):
• 0:命令成功执行。

• 非 0(如 1, 2 等):命令执行失败(例如文件不存在、权限不足等)。

set -e 的逻辑是:当脚本中某条命令的退出状态码非 0 时,立即终止整个脚本,并将该状态码作为脚本的最终退出码。

典型使用场景

  1. 自动化/部署脚本:确保每一步操作(如安装依赖、配置文件生成)都成功,避免因局部错误导致整体失败。
  2. 严格校验的脚本:在需要“零容忍错误”的场景(如测试脚本、数据处理流水线)中,强制终止可快速暴露问题。
  3. 调试脚本:配合其他选项(如 set -x)快速定位错误位置。

关键细节与例外

set -e 并非绝对严格,以下场景中命令失败不会触发脚本终止:

  1. 条件语句中的命令

在 if、while、&&、|| 等逻辑结构中,命令的失败会被视为条件判断的一部分,而非直接触发终止。
示例:
#!/bin/bash
set -e

条件判断中的命令失败不会终止脚本

if ! ls non_existent_file.txt; then
echo “文件不存在,跳过”
fi

后续命令仍会执行

echo “脚本继续运行”

输出:

ls: cannot access ‘non_existent_file.txt’: No such file or directory
文件不存在,跳过
脚本继续运行

  1. 子 Shell 中的命令

在子 Shell(( … ))中执行的命令失败,不会影响父 Shell 的 set -e 状态。
示例:
#!/bin/bash
set -e

子 Shell 中的命令失败不会终止父脚本

( ls non_existent_file.txt )

echo “父脚本继续运行”

输出:

ls: cannot access ‘non_existent_file.txt’: No such file or directory
父脚本继续运行

  1. 显式忽略错误的命令

通过 || true 或 || false 显式声明忽略错误时,命令的失败不会触发终止。
示例:
#!/bin/bash
set -e

显式忽略错误(命令失败后返回状态码 0)

ls non_existent_file.txt || true

echo “脚本继续运行”

输出:

ls: cannot access ‘non_existent_file.txt’: No such file or directory
脚本继续运行

  1. 管道中的部分失败(需配合 pipefail)

默认情况下,set -e 不处理管道(|)中部分命令的失败。例如 cmd1 | cmd2 中,若 cmd1 失败但 cmd2 成功,脚本不会终止。
若需严格处理管道错误,需配合 set -o pipefail(Bash 支持):
#!/bin/bash
set -eo pipefail # 同时启用 -e 和 pipefail

管道中任意命令失败都会触发终止

ls non_existent_file.txt | wc -l

echo “脚本继续运行”

输出:

ls: cannot access ‘non_existent_file.txt’: No such file or directory

常用组合选项

set -e 常与其他选项配合使用,进一步提升脚本健壮性:

选项 作用

set -u 遇到未定义的变量时报错并终止(避免空变量导致的隐含错误)。

set -o pipefail 管道中任意命令失败时,整个管道返回非零状态码(配合 -e 严格处理管道错误)。

set -x 打印执行的命令(调试用,可定位哪一步出错)。

推荐组合(严格模式):
#!/bin/bash
set -euo pipefail # 启用所有严格检查

临时禁用 set -e

若需在脚本中临时忽略错误(仅针对部分命令),可使用 set +e 关闭 set -e,之后用 set -e 重新启用。
示例:
#!/bin/bash
set -e

echo “开始执行”

临时禁用 -e

set +e
ls non_existent_file.txt # 此命令失败不会终止脚本
set -e # 重新启用 -e

echo “脚本继续运行”

示例对比

未启用 set -e 的脚本:

#!/bin/bash

命令失败(文件不存在)

ls non_existent_file.txt

后续命令仍会执行

echo “脚本继续运行”

输出:

ls: cannot access ‘non_existent_file.txt’: No such file or directory
脚本继续运行 # 错误未被终止

启用 set -e 的脚本:

#!/bin/bash
set -e

命令失败(文件不存在)

ls non_existent_file.txt

后续命令不会执行

echo “脚本继续运行”

输出:

ls: cannot access ‘non_existent_file.txt’: No such file or directory

脚本在此处终止,后续命令未执行

注意事项

• Shell 兼容性:set -e 在大多数现代 Shell(如 Bash、Zsh)中有效,但部分旧版或特殊 Shell(如 dash)可能有差异(例如 dash 对 set -e 的处理更严格)。

• 调试辅助:若脚本意外终止,可通过 echo $? 查看最后一条命令的退出状态码,定位具体错误。

• 谨慎使用:在需要严格控制的场景中使用 set -e,但避免过度依赖(例如某些命令允许失败时,应显式处理而非依赖 set -e)。

通过合理使用 set -e(及相关选项),可以显著提升 Shell 脚本的可靠性和可维护性,避免因小错误导致整体崩溃。

Shell内置命令

Shell脚本中IFS指南

发表在 linux文章 | 标签为 | 留下评论

Shell内置命令

在 Shell(如 Bash)中,内置命令(Built-in Commands) 是由 Shell 解释器直接执行的命令,无需启动外部程序(如 /bin/ls 这样的独立可执行文件)。它们的执行效率更高,且通常用于控制 Shell 自身的行为(如变量管理、流程控制、进程管理等)。

主流 Shell(如 Bash)的内置命令数量通常在 几十个(而非 80 个)。
以下以最常用的 Bash 为例,分类整理其核心内置命令,并说明功能。

**NAME

   bash,  :,  .,  [, alias, bg, bind, break, builtin, caller, cd, command, compgen,
   complete, compopt, continue, declare, dirs, disown, echo, enable, eval, 
   exec, exit, export, false, fc, fg, getopts,hash, help, history, jobs, kill,
   let, local, logout, mapfile, popd, printf, pushd, pwd, read, readonly, 
   return, set, shift, shopt, source, suspend, test, times, trap, true, 
   type, typeset,  ulimit,umask, unalias, unset, wait - bash built-in commands, 
   see bash(1)

一、Bash 内置命令分类及详解

Bash 的内置命令可分为以下几类,覆盖 Shell 自身管理、流程控制、变量操作、进程管理等场景。

1. Shell 自身管理与元命令

这类命令用于控制 Shell 的运行状态、解析规则或调试。

命令功能描述
:空命令(Null Command),仅返回状态码 0(常用于条件语句占位或循环体)。
示例:while :; do sleep 1; done(无限循环)。
.(或 source在当前 Shell 环境中执行指定脚本文件(而非子 Shell),脚本中对变量/函数的修改会影响当前 Shell。
示例:. ./config.sh 或 source ./config.sh
alias定义或查看别名(Alias)。
示例:alias ll='ls -l'(定义别名);alias(查看所有别名)。
unalias删除已定义的别名。
示例:unalias ll
bash启动一个新的 Bash 子 Shell(可通过参数调整行为,如 bash -c 'echo hello')。
exit终止当前 Shell 或子 Shell,并返回指定的退出状态码(默认 0)。
示例:exit 1(终止并返回错误码 1)。
exec用指定命令替换当前 Shell 进程(不创建新进程)。
示例:exec ./app(当前 Shell 变为 app 进程)。
getopts解析命令行选项(用于脚本处理参数)。
示例:while getopts "a:b:" opt; do ... done(处理 -a val -b val 格式参数)。
hash管理 Shell 的命令路径缓存(加速命令查找)。
示例:hash(查看缓存);hash -r(清空缓存)。
pwd打印当前工作目录(Print Working Directory)。
示例:pwd(输出 /home/user)。
times显示 Shell 及其子进程的累计执行时间。
示例:times(输出用户时间和系统时间)。
trap设置信号捕获(当 Shell 收到指定信号时执行自定义命令)。
示例:trap 'echo 退出' EXIT(Shell 退出时执行 echo 退出)。
umask设置或查看文件权限掩码(控制新建文件的默认权限)。
示例:umask 022(默认文件权限 644,目录 755)。
unset删除变量或函数。
示例:unset var(删除变量 var);unset -f func(删除函数 func)。

2. 流程控制与结构化命令

用于实现条件判断、循环等程序逻辑。

命令功能描述
if条件判断(配合 then/elif/else/fi)。
示例:
if [ $a -gt 10 ]; then echo "大"; elif [ $a -eq 10 ]; then echo "等于"; else echo "小"; fi
case多分支匹配(类似 switch-case)。
示例:
case $var in<br> "start") echo "启动" ;;<br> "stop") echo "停止" ;;<br> *) echo "未知" ;;<br>esac
for循环遍历列表(或命令输出)。
示例:
for i in {1..5}; do echo $i; done(遍历 1-5);
for file in *.txt; do cat $file; done(遍历所有 .txt 文件)。
while当条件为真时循环执行。
示例:
while read line; do echo $line; done < file.txt(逐行读取文件并输出)。
until当条件为假时循环执行(与 while 相反)。
示例:
until [ $count -ge 5 ]; do echo $count; ((count++)); done(输出 0-4)。
select生成交互式菜单(从列表中选择选项)。
示例:
select opt in "苹果" "香蕉" "退出"; do<br> if [ "$opt" = "退出" ]; then break; fi<br> echo "选择了:$opt"<br>done
break终止当前循环(for/while/until)。
示例:for i in {1..10}; do if [ $i -eq 5 ]; then break; fi; echo $i; done(输出 1-4)。
continue跳过当前循环的剩余代码,进入下一次迭代。
示例:for i in {1..5}; do if [ $i -eq 3 ]; then continue; fi; echo $i; done(输出 1-2,4-5)。

3. 变量与参数操作

用于管理 Shell 变量、位置参数(如 $1$@)或特殊参数(如 $#)。

命令功能描述
declare声明变量类型(如整数、只读、数组等)。
示例:
declare -i num=10(声明整数变量);
declare -r const=5(声明只读变量);
declare -a arr=(1 2 3)(声明数组)。
typesetdeclare 的同义词(兼容旧版本)。
export将变量导出为环境变量(子进程可继承)。
示例:export PATH=$PATH:/usr/local/bin(将路径加入环境变量)。
local在函数内部声明局部变量(避免污染全局作用域)。
示例:
func() { local x=10; echo $x; }(函数内 $x 不影响外部)。
readonly声明只读变量(不可修改或取消)。
示例:readonly PI=3.14(尝试修改 PI=3 会报错)。
test测试条件表达式(返回状态码 0 或 1)。
示例:
test -f file.txt(判断是否为普通文件);
test $a -gt $b(判断 $a 是否大于 $b)。
[ ]test 的同义词(需注意方括号内的空格)。
示例:[ -z "$var" ](判断变量是否为空)。
[[ ]]增强版条件测试(支持正则匹配、逻辑运算符 &&/`

4. 输入输出与重定向

用于控制数据输入来源或输出目标(部分命令需配合重定向符号 >/< 使用)。

命令功能描述
read从标准输入读取一行数据到变量。
示例:
read name(读取用户输入到 name);
read -t 5 -p "请输入:" input(5 秒超时提示输入)。
printf格式化输出(类似 C 语言的 printf)。
示例:
`printf “姓名:%s,年龄:%d
” “张三” 20(输出 姓名:张三,年龄:20`)。
echo输出字符串(支持转义字符)。
示例:`echo “Hello
World”(输出两行);echo -e “启用转义:\
\t””-e启用转义,输出启用转义:\ “`)。

5. 进程与作业管理

用于控制后台任务、前台任务切换或进程终止。

命令功能描述
bg将暂停的作业(Job)放到后台继续运行。
示例:bg %1(将作业 1 后台运行)。
fg将后台作业切换到前台运行。
示例:fg %2(将作业 2 前台运行)。
jobs查看当前 Shell 中的后台作业状态。
示例:jobs -l(显示作业编号、PID、状态)。
kill终止指定进程或作业。
示例:kill 1234(终止 PID 为 1234 的进程);kill %1(终止作业 1)。
wait等待所有或指定后台作业完成。
示例:wait %1(等待作业 1 完成);wait(等待所有后台作业)。

二、Bash 内置命令的数量统计

Bash 的内置命令数量会因版本不同略有差异(如 Bash 5.x 比 4.x 新增了部分命令),但核心内置命令通常在 60-70 个 左右(包括上述分类中的命令)。可以通过 compgen -b 命令在终端中查看当前 Bash 环境的所有内置命令:

compgen -b | wc -l  # 统计内置命令数量(输出约 60-70)
root# compgen  -b|wc -l
61

三、内置命令 vs 外部命令

总结

实际主流 Shell(如 Bash)的内置命令数量约为 60-70 个,覆盖 Shell 自身管理、流程控制、变量操作等核心场景。掌握这些内置命令是编写高效 Shell 脚本的基础。

发表在 linux文章 | 标签为 | 留下评论

Vim 中删除特殊字符总结,如\r\n

在 Vim 中删除 \r(回车符,Carriage Return)通常是由于处理跨平台文件(如 Windows 生成的文本文件,换行符为 \r\n,而 Unix/Linux 为 \n)时的常见问题。以下是具体方法和操作说明:

场景说明

当在 Unix/Linux 系统的 Vim 中打开 Windows 格式的文件时,行尾的 \r 会被显示为 ^M(需开启显示不可见字符)。例如:

line1^M
line2^M

此时需要删除这些多余的 \r 字符。

方法 1:通过文件格式转换(推荐)

Vim 支持自动识别文件格式(fileformat,简称 ff),Windows 文件格式为 dos(换行符 \r\n),Unix 格式为 unix(换行符 \n)。通过转换文件格式,Vim 会自动删除行尾的 \r

步骤:

  1. 查看当前文件格式
    在 Vim 命令模式输入 :set ff?,输出可能是 fileformat=dos(表示含 \r)。
  2. 转换为 Unix 格式
    输入 :set ff=unix,Vim 会自动将行尾的 \r\n 替换为 \n,删除所有 \r
  3. 保存文件
    输入 :w 保存,此时文件中的 \r 已被删除。

方法 2:手动替换 \r(精确控制)

如果需要手动删除特定位置的 \r(例如非行尾的 \r),可以使用 Vim 的替换命令。

步骤:

  1. 显示不可见字符(可选)
    输入 :set list\r 会显示为 ^M(行尾),方便确认位置。
    (完成后用 :set nolist 恢复默认显示)
  2. 执行替换命令
    在命令模式输入以下命令,删除所有 \r:%s/\r//g
    • %:表示操作整个文件(等价于 1,$)。
    • \r:匹配 \r 字符(在 Vim 正则中,\r 代表回车符)。
    • //:将匹配内容替换为空(即删除)。
    • g:全局替换(每行所有匹配项都删除,否则仅替换每行第一个)。

注意事项

  1. 输入 \r 的技巧
    如果在命令中直接输入 \r 不生效(例如某些旧版本 Vim),可以用 Ctrl+V + Ctrl+M 输入实际的 \r 字符(先按 Ctrl+V,再按 Ctrl+M,Vim 会显示为 ^M)。此时替换命令为::%s/^M//g
  2. 混合格式文件
    如果文件中同时存在 \r\n(Windows)和 \n(Unix)换行符,:%s/\r//g 会删除所有 \r,无论其位置。
  3. 备份文件(重要)
    操作前建议备份文件(如 :w backup.txt),避免误删关键字符。

示例验证

假设文件 test.txt 内容(含 \r):

hello^M
world^M

^M 为 \r 的显示)

执行 :%s/\r//g 或 :set ff=unix 后,内容变为:

hello
world

通过以上方法,可以高效删除 Vim 中的 \r 字符。推荐优先使用方法 1(转换文件格式),因为它能自动处理所有行尾的 \r,无需手动干预。

发表在 linux文章, linux命令 | 标签为 | 留下评论

Shell脚本中IFS指南

Shell脚本中IFS指南,在 Shell 脚本中,IFS(Internal Field Separator,内部字段分隔符) 是一个关键的变量,用于控制 Shell 如何将字符串分割为多个“字段”(Field)。它的核心作用是定义一组字符,Shell 在处理字符串时会根据这些字符自动分割内容。理解 IFS 是处理文本分割、循环遍历、输入读取等操作的基础。

一、IFS 的默认值

IFS 的默认值由 Shell 环境决定,通常是 空格(Space)、制表符(Tab)、换行符(Newline) 三个字符的组合,记为 <space><tab><newline>
可以通过 echo "$IFS" 查看当前 IFS 的值(输出可能显示为空白,因为这三个字符本身不可见)。

二、IFS 的核心作用场景

IFS 主要影响以下 Shell 操作的分割逻辑:

1. for 循环的字段分割

在 for 循环中,Shell 会默认按 IFS 分割字符串,逐个处理每个字段。
示例

str="apple,banana,orange"
IFS=','  # 临时将 IFS 设为逗号
for fruit in $str; do
  echo "水果:$fruit"
done

输出:

水果:apple
水果:banana
水果:orange
  • 若不设置 IFS=',',Shell 会按默认的 空格/制表符/换行符 分割,但原字符串中无这些字符,因此 str 会被视为单个字段,循环仅执行一次。

2. read 命令的输入分割

read 命令用于从标准输入读取一行,并按 IFS 分割为多个变量(默认最多赋值给前 N 个变量,剩余内容存入最后一个变量)。
示例

# 输入行:"user1  25  admin"(假设两个空格分隔)
IFS=' '  # 默认即为 空格/制表符/换行符(此处显式设置)
read username age role <<< "user1  25  admin"
echo "用户名:$username,年龄:$age,角色:$role"

输出:

用户名:user1,年龄:25,角色:admin
  • 若 IFS 设为其他字符(如 :),则按冒号分割:IFS=':' read user pass uid <<< "root:123456:0" echo "用户:$user,密码:$pass,UID:$uid" # 输出:用户:root,密码:123456,UID:0

3. 字符串赋值时的“修剪”(Trimming)

当将字符串赋值给变量时,Shell 会自动删除变量值开头和结尾与 IFS 匹配的字符(称为“前导/尾随修剪”)。
示例

str="  hello world  "  # 首尾有空格
IFS=' '  # 默认修剪空格
trimmed="$str"         # 实际存储:"hello world"(首尾空格被删除)
echo "[$trimmed]"      # 输出:[hello world]
  • 若 IFS 设为空(IFS=),则不会修剪任何字符:IFS= trimmed2="$str" # 存储原始值:" hello world " echo "[$trimmed2]" # 输出:[ hello world ]

4. 影响其他工具的行为(间接作用)

部分命令或工具(如 sortuniqjoin 等)会依赖环境变量中的 IFS 来分割字段,但更常见的是通过显式参数(如 -t)指定分隔符。

三、IFS 的高级用法

1. 临时修改 IFS(局部生效)

为了避免修改全局 IFS 导致后续代码异常,通常会在需要的代码块中临时修改 IFS,并在结束后恢复原值。
示例

old_ifs="$IFS"       # 保存原 IFS
IFS=','              # 临时设为逗号
# 执行需要分割的操作(如读取 CSV)
IFS="$old_ifs"       # 恢复原 IFS

2. 空 IFS(IFS=)的特殊用途

将 IFS 设为空字符串(IFS=)时,Shell 会禁用所有字段分割,常用于以下场景:

  • 保留字符串首尾空格:如读取包含前导/尾随空格的输入。IFS= read line <<< " 保留空格 " echo "[$line]" # 输出:[ 保留空格 ]
  • 精确分割单字符字段:例如按换行符分割时,避免被其他空白字符干扰。

3. 多字符 IFS(非标准但可用)

虽然 IFS 通常由单个字符组成(如 ,;),但理论上可以是任意字符组合。此时 Shell 会将任意一个字符作为分隔符。
示例

IFS='+-'  # 用 + 或 - 作为分隔符
str="10+20-30"
for part in $str; do
  echo "部分:$part"  # 输出:10, 20, 30
done

四、常见误区与注意事项

  1. 双引号中的 IFS 不生效
    当字符串被双引号包裹时(如 "$str"),IFS 的分割逻辑会被抑制,字符串会被视为单个字段。
    示例:str="a b c" IFS=' ' for part in "$str"; do echo "$part"; done # 输出:a b c(未分割)
  2. 子 Shell 中的 IFS 不影响父 Shell
    在子 Shell(如 ( ... ) 或 bash -c '...')中修改 IFS,不会改变父 Shell 的 IFS 值。
  3. 与 read -r 的配合
    使用 read 时建议添加 -r 选项(禁止反斜杠转义),避免意外行为:IFS= read -r line <<< "line with \\backslash" # 正确读取反斜杠

总结

IFS 是 Shell 控制字符串分割的核心变量,主要影响 for 循环、read 命令和字符串赋值的字段分割逻辑。通过灵活设置 IFS(如临时修改、设为空或特定分隔符),可以高效处理各种文本分割场景(如 CSV、日志解析等)。掌握 IFS 的关键是理解其对“字段分割”和“首尾修剪”的控制规则,并避免常见误区(如双引号抑制分割)。

https://blog.csdn.net/timberwolf007/article/details/150415662?spm=1011.2415.3001.5331

发表在 linux文章, linux命令 | 标签为 | 留下评论

Linux 系统中安装中文man手册

Linux 系统中安装中文man手册

轻松掌握Linux系统中中文man手册的安装方法,快速获取帮助文档。适合所有Linux用户学习参考,立即访问了解更多!

在 Linux 系统中获取中文man手册的最合适方法如下:


一、主流发行版安装方法

1. Debian/Ubuntu 及衍生系统

# 更新软件源
sudo apt update
# 安装中文 man 手册包
sudo apt install manpages-zh

安装后,中文手册默认位于 /usr/share/man/zh_CN/

2. RHEL/CentOS 及衍生系统

# 安装 EPEL 源(CentOS 7 及更早)
sudo yum install epel-release
# 安装中文 man 手册包
sudo yum install manpages-zh

安装后,中文手册路径为 /usr/share/man/zh_CN/

3. Arch Linux 及衍生系统

# 直接安装预编译包
sudo pacman -S man-pages-zh_cn

安装路径为 /usr/share/man/zh_CN/


二、配置中文手册别名

安装完成后,建议设置命令别名以区分中英文手册:

# 编辑 ~/.bashrc 或 ~/.zshrc
echo "alias cman='man -M /usr/share/man/zh_CN'" >> ~/.bashrc
# 生效配置
source ~/.bashrc

之后通过 cman ls 即可查看中文帮助网页。


三、注意事项

  1. 语言环境设置
    确保系统语言环境为中文(如 zh_CN.UTF-8),否则可能显示乱码:export LANG=zh_CN.UTF-8 可将此行加入 ~/.bashrc 永久生效。
  2. 依赖问题
    • Arch Linux 需提前安装 automake 和 opencc 等依赖。
    • CentOS 若安装失败,尝试更新系统并安装 langpacks-zh_CN
  3. 乱码处理
    若出现乱码,安装中文字体(如 fonts-wqy-microhei):sudo apt install fonts-wqy-microhei # Debian/Ubuntu

四、手动编译安装(备用方案)

若官方源无中文手册包,可手动编译:

# 下载源码(以 manpages-zh 1.5.1 为例)
wget https://src.fedoraproject.org/repo/pkgs/manpages-zh-CN/manpages-zh-1.5.1.tar.gz
tar -zxvf manpages-zh-1.5.1.tar.gz
cd manpages-zh-1.5.1
# 编译安装
./configure --prefix=/usr/local/zhman
make && sudo make install

安装后需设置 MANPATH 环境变量。


五、验证安装

# 查看中文手册路径
man -w | grep zh_CN
# 测试中文手册
cman ls  # 应显示中文帮助

总结

  • 推荐方法:通过包管理器直接安装(如 manpages-zh),简单高效。
  • 兼容性:部分命令可能无中文翻译,需结合英文手册使用。
  • 扩展性:通过别名 cman 可无缝切换中英文手册,提升使用效率。

系统的区域设置(Locale)未正确配置,导致 man 命令无法正确识别中文手册路径。以下是详细的解决步骤:

问题原因

man -w 命令用于查询 man 手册的搜索路径,但它依赖系统的区域设置(LANG 和 LC_* 环境变量)来定位多语言手册(如中文)。如果区域设置未正确配置为中文(如 zh_CN.UTF-8),man 会因无法识别语言环境而报错。

解决步骤

1. 临时设置正确的区域设置

首先临时设置 LANG 和 LC_ALL 为中文 UTF-8 编码,验证是否能解决问题:

# 设置环境变量(临时生效)
export LANG=zh_CN.UTF-8
export LC_ALL=zh_CN.UTF-8

# 再次执行 man -w 查看路径
man -w | grep zh_CN

如果输出类似 /usr/share/man/zh_CN/man1 的路径,说明区域设置已生效,问题解决。

2. 永久设置区域设置(推荐)

临时设置会在终端关闭后失效,需永久生效需修改系统区域配置。

方法 1:通过 dpkg-reconfigure(Debian/Ubuntu)
# 安装语言包(如果未安装)
sudo apt install language-pack-zh-hans language-pack-zh-hans-base

# 重新配置区域设置(选择 zh_CN.UTF-8)
sudo dpkg-reconfigure locales

在弹出的界面中,用空格选中 zh_CN.UTF-8,回车确认。

方法 2:手动修改配置文件(通用)

编辑 /etc/locale.conf(CentOS/RHEL)或 /etc/default/locale(Debian/Ubuntu),添加以下内容:

# 对于 CentOS/RHEL
echo 'LANG="zh_CN.UTF-8"' | sudo tee /etc/locale.conf
echo 'LC_ALL="zh_CN.UTF-8"' | sudo tee -a /etc/locale.conf

# 对于 Debian/Ubuntu
echo 'LANG="zh_CN.UTF-8"' | sudo tee /etc/default/locale
echo 'LC_ALL="zh_CN.UTF-8"' | sudo tee -a /etc/default/locale

保存后重启终端或执行 source /etc/locale.conf(或 source /etc/default/locale)生效。

3. 验证区域设置是否生效

执行以下命令检查当前区域设置:

locale

输出应包含 LANG=zh_CN.UTF-8 和 LC_ALL=zh_CN.UTF-8(或类似中文编码)。

4. 确认中文手册已正确安装

如果区域设置正确但仍无法找到中文手册,检查 manpages-zh 是否已安装(以 Debian/Ubuntu 为例):

# 检查是否安装
dpkg -l | grep manpages-zh

# 未安装则重新安装
sudo apt reinstall manpages-zh

5. 手动添加中文手册路径(备用)

如果 man 仍无法自动识别中文路径,可手动编辑 man 的配置文件,添加中文手册路径:

# 编辑 man 配置文件
sudo vim /etc/manpath.config

# 在文件末尾添加以下内容(根据实际安装路径调整)
MANPATH_MAP /usr/local/bin  /usr/share/man/zh_CN
MANPATH_MAP /usr/bin        /usr/share/man/zh_CN
MANPATH_MAP /bin            /usr/share/man/zh_CN

总结

核心问题是系统区域设置未正确配置为中文(zh_CN.UTF-8)。通过临时或永久设置 LANG 和 LC_ALL 环境变量,并确保 manpages-zh 包已安装,即可解决 man -w 报错问题,正常使用中文手册。

系统级中文 Man 手册问题解决方案(整合版)

针对用户遇到的 LC_ALL 设置失败但 man ls 显示中文的问题,结合历史操作和系统特性,整理以下跨发行版通用解决方案,覆盖区域设置、手册安装、路径映射、缓存生成等核心环节。

一、问题根源分析

用户现象:LC_ALL=zh_CN.UTF-8 设置失败(报错 No such file or directory),但 LANG=zh_CN.UTF-8 man ls 能显示中文。
核心原因:
1. 区域设置未完全支持:系统未正确生成或安装 zh_CN.UTF-8 区域配置,导致 LC_ALL(最高优先级)无法生效。
2. 语言环境回退机制LANG(次优先级)作为默认语言环境,系统可能通过其他方式(如 manpages-zh 包的默认映射)找到了中文手册。
3. 手册路径未完全映射:部分命令(如 bash)的中文手册未被 MANPATH_MAP 正确关联,导致仅部分命令显示中文。

二、分步解决方案(按发行版区分)

(一)Debian/Ubuntu 系统

1. 安装/修复中文语言包

确保系统支持 zh_CN.UTF-8 区域设置:

# 更新软件源
sudo apt update

# 安装简体中文语言包(含 UTF-8 支持)
sudo apt install -y language-pack-zh-hans language-pack-zh-hans-base

# 安装 man 手册的中文翻译(覆盖所有核心命令)
sudo apt install -y manpages-zh
2. 强制生成区域设置

即使 LC_ALL 设置失败,仍需确保 zh_CN.UTF-8 被系统识别:

# 生成 zh_CN.UTF-8 区域配置(关键!)
sudo locale-gen zh_CN.UTF-8

# 编辑区域配置文件(覆盖默认设置)
sudo vim /etc/default/locale

修改为以下内容(强制中文优先):

LANG="zh_CN.UTF-8"
LC_ALL="zh_CN.UTF-8"  # 即使报错,仍保留此配置(系统会自动回退到 LANG)
LANGUAGE="zh_CN:zh"   # 语言优先级:中文 > 英文
3. 配置 Man 手册路径映射

编辑 /etc/man_db.conf,确保所有核心命令路径映射到中文手册:

sudo vim /etc/man_db.conf

在 MANPATH_MAP 部分添加(放在现有条目最前面,确保优先级):

# 覆盖所有常用二进制目录(包括 bash、ls 等核心命令)
MANPATH_MAP	/bin			/usr/share/man/zh_CN
MANPATH_MAP	/usr/bin		/usr/share/man/zh_CN
MANPATH_MAP	/sbin			/usr/share/man/zh_CN
MANPATH_MAP	/usr/sbin		/usr/share/man/zh_CN
MANPATH_MAP	/usr/local/bin	/usr/share/man/zh_CN
MANPATH_MAP	/usr/local/sbin	/usr/share/man/zh_CN
4. 重新生成 Man 数据库缓存

修改配置后,强制刷新 man 的手册索引:

sudo mandb -c  # 清除旧缓存并重新生成
5. 验证效果
# 不设置 LANG 直接查看(应自动使用中文)
man ls
man bash

# 显式指定中文环境(确保生效)
LANG=zh_CN.UTF-8 man man  # 应显示中文
(二)RHEL/CentOS 系统
1. 安装中文语言包与 Man 手册
# 启用 EPEL 源(若未启用)
sudo yum install -y epel-release

# 安装简体中文语言包
sudo yum install -y glibc-langpack-zh

# 安装 Man 手册的中文翻译
sudo yum install -y man-pages-zh-CN
2. 配置区域设置

编辑 /etc/locale.conf,设置中文环境:

sudo vim /etc/locale.conf

添加以下内容:

LANG="zh_CN.UTF-8"
LC_ALL="zh_CN.UTF-8"  # 保留配置(系统会自动回退到 LANG)
3. 手动生成区域设置(可选)

若 zh_CN.UTF-8 仍未被识别,手动生成:

# 查看当前支持的 region
locale -a

# 若缺失 zh_CN.UTF-8,手动生成(需 root)
sudo localectl set-locale LANG=zh_CN.UTF-8
4. 配置 Man 手册路径映射

编辑 /etc/man_db.conf(路径可能与 Debian 不同,通常为 /etc/man.config 或 /etc/man_db.conf),添加:

MANPATH_MAP	/bin			/usr/share/man/zh_CN
MANPATH_MAP	/usr/bin		/usr/share/man/zh_CN
MANPATH_MAP	/sbin			/usr/share/man/zh_CN
MANPATH_MAP	/usr/sbin		/usr/share/man/zh_CN
5. 刷新 Man 缓存
sudo mandb -c
6. 验证效果
man ls
man bash

三、通用优化技巧

1. 临时强制中文手册(无需修改环境变量)
LANG=zh_CN.UTF-8 man <命令>  # 临时指定中文环境
2. 检查中文手册是否存在

若 man <命令> 仍显示英文,直接检查手册文件是否存在:

# 示例:检查 bash 的中文手册(man1 目录)
ls -l /usr/share/man/zh_CN/man1/bash.1.gz

# 若不存在,重新安装 manpages-zh 包
3. 解决 LC_ALL 设置失败问题

LC_ALL 报错 No such file or directory 通常是因为系统未生成该区域配置。即使无法修复,通过 LANG 变量仍可实现中文显示(系统会自动回退到 LANG)。

四、最终验证

完成以上步骤后,执行以下命令确认所有核心命令的中文手册均可显示:

man ls      # 应显示中文
man bash    # 应显示中文
man man     # 应显示中文

总结

通过安装中文语言包、强制生成区域配置、完善 MANPATH_MAP 映射并刷新缓存,即可解决大部分中文 Man 手册显示问题。即使 LC_ALL 设置失败,通过 LANG 变量和正确的路径映射,仍可实现中文手册的显示。

https://www.calcguide.tech/2025/08/15/linux%e7%b3%bb%e7%bb%9f%e4%b8%ad%e5%ae%89%e8%a3%85%e4%b8%ad%e6%96%87man-%e6%89%8b%e5%86%8c

Linux 开发者终极资源导航:全球镜像站 + 核心开发手册(2025 国际中文版)

发表在 linux文章, linux命令 | 留下评论

sed 初级学习手册

sed 初级学习手册(修订版)

一、认识 sed

  1. 什么是 sed
    • 全称:Stream Editor(流编辑器)
    • 功能:非交互式命令行文本编辑器,用于对输入流进行文本转换和编辑
    • 特点:批量处理、支持正则表达式、强大的文本处理能力
  2. 安装与验证# 检查是否安装 sed --version # 安装命令 # Ubuntu/Debian: apt install sed # CentOS/RHEL: yum install sed # 基本测试 echo "Hello World" | sed 's/World/sed/'
  3. 基本概念
    • 地址:指定要处理的行
    • 命令:指定要执行的操作
    • 脚本:命令的组合
    • 模式空间:sed内部用于存储当前处理行的缓冲区

二、基本语法与选项

1. 基本用法格式

# 格式1:直接指定脚本
sed [选项] '脚本' 文件名...

# 格式2:从文件读取脚本
sed [选项] -f 脚本文件 文件名...

# 格式3:管道使用
命令 | sed [选项] '脚本'

# 格式4:标准输入
sed [选项] '脚本'

2. 常用选项详解(基于 sed –help)

选项长选项说明
-n--quiet, --silent抑制默认输出(只输出明确指定的内容)
-e--expression=脚本添加脚本到命令中(可多次使用)
-f--file=脚本文件从文件读取脚本
-i--in-place[=后缀]原地编辑文件(提供后缀则备份原文件)
-r--regexp-extended使用扩展正则表达式(同-E)
-E--regexp-extended使用扩展正则表达式(推荐使用)
-s--separate将多个文件分别处理,而非作为连续流
-u--unbuffered减少I/O缓冲,及时刷新输出
-z--null-data使用NUL字符作为行分隔符
-b--binary二进制模式(Windows兼容)
--posix禁用所有GNU扩展,使用POSIX兼容模式
--sandbox沙盒模式(禁用e/r/w命令)
--follow-symlinks原地编辑时跟随符号链接
-c--copy使用复制而非重命名方式处理文件(-i模式)
-l--line-length=N指定’l’命令的行包装长度

三、基本命令详解

1. 替换命令 (s)

# 基本替换语法:s/原字符串/新字符串/标志
sed 's/old/new/' file.txt           # 只替换每行第一次出现
sed 's/old/new/g' file.txt          # 全局替换(所有出现)
sed 's/old/new/i' file.txt          # 忽略大小写替换
sed 's/old/new/p' file.txt          # 替换并打印匹配行
sed 's/old/new/gp' file.txt         # 全局替换并打印匹配行
sed 's/old/new/2' file.txt          # 替换每行第2次出现
sed 's/old/new/gw output.txt' file.txt  # 替换结果写入指定文件

# 使用不同分隔符(避免转义斜杠)
sed 's|/old/path|/new/path|' file.txt
sed 's_old_new_' file.txt
sed 's#http://#https://#' file.txt

2. 删除命令 (d)

# 删除指定行
sed '3d' file.txt                   # 删除第3行
sed '3,5d' file.txt                 # 删除第3到第5行
sed '3,$d' file.txt                 # 删除第3行到文件末尾
sed '1~2d' file.txt                 # 删除奇数行
sed '2~2d' file.txt                 # 删除偶数行

# 删除匹配模式的行
sed '/pattern/d' file.txt           # 删除包含pattern的行
sed '/^$/d' file.txt                # 删除空行
sed '/^[[:space:]]*$/d' file.txt    # 删除空白行
sed '/^#/d' file.txt                # 删除注释行

3. 打印命令 §

# 需要配合-n选项使用
sed -n '3p' file.txt                # 打印第3行
sed -n '3,5p' file.txt              # 打印第3到第5行
sed -n '/pattern/p' file.txt        # 打印包含pattern的行
sed -n '1~2p' file.txt              # 打印奇数行
sed -n '2~2p' file.txt              # 打印偶数行

4. 插入命令 (i, a, c)

# 插入命令
sed '3i\新行内容' file.txt          # 在第3行前插入
sed '/pattern/i\新行内容' file.txt  # 在匹配行前插入

# 追加命令
sed '3a\新行内容' file.txt          # 在第3行后追加
sed '/pattern/a\新行内容' file.txt  # 在匹配行后追加

# 替换命令
sed '3c\新行内容' file.txt          # 替换第3行
sed '/pattern/c\新行内容' file.txt  # 替换匹配行

5. 其他常用命令

# 行号显示 (=)
sed '=' file.txt                    # 显示所有行号
sed '/pattern/=' file.txt           # 显示匹配行的行号

# 列出行内容 (l)
sed -n 'l' file.txt                 # 显示所有行(转义特殊字符)
sed -n '/pattern/l' file.txt        # 显示匹配行(转义特殊字符)

# 读取文件 (r)
sed '/pattern/r insert.txt' file.txt  # 在匹配行后读入文件内容

# 写入文件 (w)
sed -n '/pattern/w output.txt' file.txt  # 将匹配行写入文件

# 退出命令 (q)
sed '10q' file.txt                  # 处理10行后退出
sed '/pattern/q' file.txt           # 匹配到pattern后退出

四、地址范围

1. 行号地址

# 单行地址
sed '5命令' file.txt                # 处理第5行

# 行号范围
sed '5,10命令' file.txt             # 处理第5到第10行
sed '5,$命令' file.txt              # 处理第5行到文件末尾
sed '1,3!命令' file.txt             # 处理除第1到第3行外的所有行

# 步进地址
sed '1~2命令' file.txt              # 处理奇数行(1,3,5...)
sed '2~2命令' file.txt              # 处理偶数行(2,4,6...)

2. 模式地址

# 单模式地址
sed '/pattern/命令' file.txt        # 处理包含pattern的行

# 模式范围
sed '/start/,/end/命令' file.txt    # 处理从包含start到包含end的行
sed '/pattern/,+3命令' file.txt     # 处理包含pattern的行及其后3行
sed '/pattern/,$命令' file.txt      # 处理包含pattern的行到文件末尾

# 模式与行号组合
sed '/pattern/,10命令' file.txt     # 处理包含pattern的行到第10行

3. 地址否定

# 否定地址(处理不匹配的行)
sed '3!命令' file.txt               # 处理除第3行外的所有行
sed '/pattern/!命令' file.txt       # 处理不包含pattern的行

五、正则表达式详解

1. 基本正则表达式(默认)

# 字符匹配
sed 's/a/b/' file.txt               # 匹配字符a
sed 's/./b/' file.txt               # 匹配任意字符
sed 's/[abc]/X/' file.txt           # 匹配a、b或c
sed 's/[^abc]/X/' file.txt          # 匹配除a、b、c外的字符

# 重复匹配
sed 's/a*/X/' file.txt              # 匹配0个或多个a
sed 's/a\+/X/' file.txt             # 匹配1个或多个a(需要转义)
sed 's/a\{2,4\}/X/' file.txt        # 匹配2到4个a(需要转义)

# 位置匹配
sed 's/^/X/' file.txt               # 匹配行首
sed 's/$/X/' file.txt               # 匹配行尾
sed 's/\<word\>/X/' file.txt        # 匹配单词边界

2. 扩展正则表达式(-E选项)

# 使用-E选项启用扩展正则表达式
sed -E 's/a+/X/' file.txt           # 匹配1个或多个a
sed -E 's/a{2,4}/X/' file.txt       # 匹配2到4个a
sed -E 's/(group)+/X/' file.txt     # 分组匹配
sed -E 's/pattern1|pattern2/X/' file.txt  # OR操作
sed -E 's/(..)(..)/\2\1/' file.txt  # 捕获组交换

3. 捕获组和引用

# 基本正则中的捕获组
sed 's/\(pattern\)/[\1]/' file.txt  # 使用\1引用第一个捕获组
sed 's/\(first\)\(second\)/\2\1/' file.txt  # 交换两个捕获组

# 扩展正则中的捕获组
sed -E 's/(pattern)/[\1]/' file.txt  # 使用\1引用第一个捕获组
sed -E 's/(first)(second)/\2\1/' file.txt  # 交换两个捕获组

六、实用示例

1. 文本替换

# 简单替换
sed 's/old/new/' file.txt

# 全局替换
sed 's/old/new/g' file.txt

# 忽略大小写替换
sed 's/old/new/gi' file.txt

# 使用捕获组
sed 's/\(pattern\)/[\1]/' file.txt

# 删除行首空白
sed 's/^[ \t]*//' file.txt

# 删除行尾空白
sed 's/[ \t]*$//' file.txt

# 替换多个空格为单个空格
sed 's/  */ /g' file.txt

# 删除所有空白字符
sed 's/[[:space:]]//g' file.txt

2. 文件处理

# 原地编辑文件
sed -i 's/old/new/g' file.txt

# 原地编辑并备份
sed -i.bak 's/old/new/g' file.txt

# 处理多个文件
sed 's/old/new/g' file1.txt file2.txt

# 从标准输入处理
cat file.txt | sed 's/old/new/g'

3. 复杂操作

# 多命令组合(使用分号)
sed 's/old/new/g; s/abc/xyz/g' file.txt

# 多命令组合(使用-e选项)
sed -e 's/old/new/g' -e 's/abc/xyz/g' file.txt

# 使用脚本文件
echo -e 's/old/new/g\ns/abc/xyz/g' > script.sed
sed -f script.sed file.txt

# 条件处理
sed '/pattern/{s/old/new/;p}' file.txt

# 多行处理
sed '/start/,/end/{s/old/new/g}' file.txt

4. 实际应用场景

# 配置文件处理
sed 's/#.*$//' file.conf | sed '/^$/d'  # 删除注释和空行

# 日志处理
sed -n '/ERROR/p' application.log       # 提取错误日志
sed '/DEBUG/d' application.log          # 过滤调试日志

# CSV处理
sed 's/,/|/g' data.csv                  # 替换分隔符

# 格式转换
sed 's/\t/    /g' file.txt              # Tab转空格

七、高级功能

1. 保持空间操作

# h - 将模式空间复制到保持空间
# H - 将模式空间追加到保持空间
# g - 将保持空间复制到模式空间
# G - 将保持空间追加到模式空间
# x - 交换模式空间和保持空间

# 示例:在文件末尾添加文件头
sed '1h; 1d; $G' file.txt

# 示例:反转文件行顺序
sed -n '1!G; h; $p' file.txt

2. 分支和测试命令

# b - 分支到标签(无标签则跳到脚本末尾)
# t - 测试(如果上次替换成功则分支)
# T - 测试(如果上次替换失败则分支)

# 示例:替换成功后退出
sed '/pattern/{s/old/new/; t; d}' file.txt

3. 原地编辑高级用法

# 创建带时间戳的备份
sed -i.$(date +%Y%m%d) 's/old/new/g' file.txt

# 处理符号链接
sed --follow-symlinks -i 's/old/new/g' symlink.txt

# 使用复制模式
sed -c -i 's/old/new/g' file.txt

八、常见问题解决

1. 特殊字符处理

# 处理包含斜杠的路径
sed 's|/old/path|/new/path|' file.txt
sed 's#/old/path#/new/path#' file.txt

# 处理特殊字符
sed 's/\*/STAR/g' file.txt              # 替换星号
sed 's/\$/DOLLAR/g' file.txt            # 替换美元符号
sed 's/\\/BACKSLASH/g' file.txt         # 替换反斜杠

2. 性能优化

# 对大文件只处理前N行
sed '1000q; s/old/new/g' largefile.txt

# 使用地址范围限制处理范围
sed '100,200s/old/new/g' file.txt

# 避免不必要的全局替换
sed 's/old/new/' file.txt               # 只替换第一次出现

3. 跨平台兼容性

# 使用POSIX模式
sed --posix 's/old/new/g' file.txt

# Windows兼容模式
sed -b 's/old/new/g' file.txt

# 使用扩展正则表达式的可移植性
sed -E 's/pattern1|pattern2/replacement/' file.txt  # GNU/Linux
sed -r 's/pattern1|pattern2/replacement/' file.txt  # 旧版本GNU sed

九、快速参考表

基本命令速查

s/old/new/    替换(第一次出现)
s/old/new/g   全局替换
d             删除行
p             打印行(需-n选项)
i\text        在前插入
a\text        在后追加
c\text        替换行
=             显示行号
l             列出行内容
q             退出

地址范围速查

5             第5行
5,10          第5到第10行
5,$           第5行到末尾
/pattern/     包含pattern的行
/pattern/,/end/  从pattern到end的行
1~2           奇数行
2~2           偶数行

常用正则表达式

^             行首
$             行尾
.             任意字符
*             前一字符0次或多次
\+            前一字符1次或多次
\{n,m\}       前一字符n到m次
[abc]         字符类
[^abc]        非字符类
\(abc\)       捕获组(基本正则)
(abc)         捕获组(扩展正则)

实用组合示例

# 删除空行和注释行
sed '/^$/d; /^#/d' file.txt

# 提取文件头和尾各5行
sed -n '1,5p; $-4,$p' file.txt

# 给文件添加行号
sed '=' file.txt | sed 'N; s/\n/\t/'

# 合并连续的空行为单个空行
sed '/^$/N; /\n$/D' file.txt

# 在每个匹配行后添加标记
sed '/pattern/a\--- MATCH ---' file.txt

退出状态码

0   成功
1   无效命令或语法错误
2   无法访问文件或I/O错误
4   GNU sed内部错误

这个修订版手册基于sed --help的完整输出,涵盖了所有主要选项和功能。掌握这些内容后,你可以高效地进行各种文本处理工作。

发表在 linux命令, 未分类 | 留下评论

grep 命令初学者指南:从基础到精通 – LinuxGuide

grep 初级学习手册(修订版)

一、认识 grep

  1. 什么是 grep
    • 全称:Global Regular Expression Print(全局正则表达式打印)
    • 功能:在文件中搜索指定模式的文本行
    • 特点:快速、高效、支持多种正则表达式
  2. 安装与验证# 检查是否安装 grep --version # 安装命令 # Ubuntu/Debian: apt install grep # CentOS/RHEL: yum install grep # 基本测试 echo "Hello World" | grep "Hello"
  3. 基本概念
    • 模式(Pattern):要搜索的文本或正则表达式
    • 输入源:可以是文件、标准输入或管道
    • 输出:匹配的行或相关信息

二、基本语法与选项

1. 基本用法格式

# 格式1:搜索文件
grep [选项] 模式 文件名...

# 格式2:管道使用
命令 | grep [选项] 模式

# 格式3:标准输入
grep [选项] 模式

2. 模式选择和解释选项

选项长选项说明
-E--extended-regexp使用扩展正则表达式
-F--fixed-strings将模式视为固定字符串(不使用正则表达式)
-G--basic-regexp使用基本正则表达式(默认)
-P--perl-regexp使用Perl兼容正则表达式
-e--regexp=PATTERN指定模式进行匹配
-f--file=FILE从文件中读取模式
-i--ignore-case忽略大小写
-w--word-regexp强制模式只匹配整个单词
-x--line-regexp强制模式只匹配整行
-z--null-data数据行以0字节结尾,而非换行符

3. 输出控制选项

选项长选项说明
-m--max-count=NUM匹配NUM行后停止
-b--byte-offset显示匹配行的字节偏移量
-n--line-number显示行号
-H--with-filename显示文件名
-h--no-filename不显示文件名前缀
-o--only-matching只显示匹配的部分
-q--quiet, --silent静默模式,不输出正常结果
-L--files-without-match只显示不包含匹配的文件名
-l--files-with-matches只显示包含匹配的文件名
-c--count只显示每个文件的匹配行数
-T--initial-tab使制表符对齐
-Z--null在文件名后输出0字节

4. 上下文控制选项

选项长选项说明
-B--before-context=NUM显示匹配行前NUM行
-A--after-context=NUM显示匹配行后NUM行
-C--context=NUM显示匹配行前后NUM行
-NUM等同于--context=NUM

5. 杂项选项

选项长选项说明
-s--no-messages抑制错误消息
-v--invert-match反向匹配(显示不匹配的行)
-V--version显示版本信息
--help显示帮助信息
-a--text等同于--binary-files=text
-I等同于--binary-files=without-match
-r--recursive递归搜索目录
-R--dereference-recursive递归搜索并跟随所有符号链接
-U--binary不去除行尾的CR字符(MSDOS/Windows)

三、基本搜索操作

1. 简单文本搜索

# 搜索单个文件
grep "error" application.log

# 搜索多个文件
grep "warning" *.log

# 搜索目录下所有文件
grep -r "exception" /var/log/

# 从标准输入搜索
cat file.txt | grep "pattern"

2. 大小写处理

# 区分大小写(默认)
grep "Error" log.txt

# 忽略大小写
grep -i "error" log.txt

# 完全匹配大小写
grep -w "ERROR" log.txt

3. 行号和上下文

# 显示行号
grep -n "error" log.txt

# 显示匹配行的前后几行
grep -A 3 "error" log.txt    # 显示匹配行及其后3行
grep -B 3 "error" log.txt    # 显示匹配行及其前3行
grep -C 3 "error" log.txt    # 显示匹配行前后各3行

# 使用数字简写
grep -3 "error" log.txt      # 等同于-C 3

四、正则表达式详解

1. 基本正则表达式(BRE)

# 行首和行尾
grep "^start" file.txt       # 以start开头的行
grep "end$" file.txt         # 以end结尾的行

# 任意字符
grep "a.b" file.txt          # a和b之间有一个字符

# 重复字符
grep "a*" file.txt           # 匹配0个或多个a
grep "a\+" file.txt          # 匹配1个或多个a(需要转义)
grep "a\{2,4\}" file.txt     # 匹配2到4个a

# 字符类
grep "[abc]" file.txt        # 包含a、b或c的行
grep "[0-9]" file.txt        # 包含数字的行
grep "[a-z]" file.txt        # 包含小写字母的行

2. 扩展正则表达式(ERE)

# 使用-E选项启用扩展正则表达式
grep -E "pattern1|pattern2" file.txt    # OR操作
grep -E "a+" file.txt                   # 匹配1个或多个a
grep -E "a{2,4}" file.txt               # 匹配2到4个a
grep -E "(group)+" file.txt             # 分组匹配

3. Perl兼容正则表达式(PCRE)

# 使用-P选项启用Perl正则表达式
grep -P "\d+" file.txt                  # 匹配数字
grep -P "\w+" file.txt                  # 匹配单词字符
grep -P "(?<=pattern).*" file.txt       # 正向后瞻断言

4. 字符类和特殊字符

# 预定义字符类
grep "[[:digit:]]" file.txt     # 数字
grep "[[:alpha:]]" file.txt     # 字母
grep "[[:alnum:]]" file.txt     # 字母和数字
grep "[[:space:]]" file.txt     # 空白字符
grep "[[:upper:]]" file.txt     # 大写字母
grep "[[:lower:]]" file.txt     # 小写字母

# 特殊字符(需要转义)
grep "\." file.txt              # 匹配点号
grep "\*" file.txt              # 匹配星号
grep "\\" file.txt              # 匹配反斜杠

五、实用搜索技巧

1. 多模式搜索

# OR 操作(使用扩展正则)
grep -E "error|warning|critical" log.txt

# 使用多个-e选项
grep -e "error" -e "warning" log.txt

# 从文件读取模式
echo -e "error\nwarning\ncritical" > patterns.txt
grep -f patterns.txt log.txt

# AND 操作(管道组合)
grep "error" log.txt | grep "database"

# NOT 操作
grep -v "debug" log.txt

2. 精确匹配

# 整词匹配
grep -w "cat" file.txt          # 不匹配"catch"中的"cat"

# 整行匹配
grep -x "exact line" file.txt   # 完全匹配整行

# 固定字符串匹配
grep -F ".*[0-9].*" file.txt    # 将特殊字符视为普通字符

3. 文件和目录操作

# 搜索特定类型的文件
grep -r --include="*.log" "error" /var/log/

# 排除特定文件
grep -r --exclude="*.tmp" "pattern" /path/

# 排除目录
grep -r --exclude-dir="backup" "pattern" /home/

# 只显示文件名
grep -l "pattern" *.txt

# 只显示不包含匹配的文件名
grep -L "pattern" *.txt

# 处理二进制文件
grep -a "text" binary_file      # 将二进制文件视为文本
grep -I "pattern" *             # 跳过二进制文件

六、输出控制选项详解

1. 计数和限制

# 只显示匹配行数
grep -c "error" log.txt

# 显示前N个匹配
grep -m 5 "error" log.txt

# 静默模式(只返回状态码)
grep -q "pattern" file.txt && echo "Found"

# 显示字节偏移
grep -b "pattern" file.txt

2. 颜色和格式

# 彩色显示匹配项
grep --color=always "error" log.txt
grep --color=auto "error" log.txt    # 自动检测终端
grep --color=never "error" log.txt   # 不使用颜色

# 只显示匹配的部分
grep -o "error" log.txt

# 不显示文件名
grep -h "pattern" file1.txt file2.txt

# 显示文件名
grep -H "pattern" single.txt

3. 分组和分隔符

# 设置组分隔符
grep -A 2 -B 2 --group-separator="---" "error" log.txt

# 不使用组分隔符
grep -A 2 -B 2 --no-group-separator "error" log.txt

七、高级文件处理

1. 目录和文件类型处理

# 递归搜索
grep -r "pattern" /path/

# 递归搜索并跟随符号链接
grep -R "pattern" /path/

# 处理目录的方式
grep -d read "pattern" directory/      # 读取目录(通常失败)
grep -d skip "pattern" directory/      # 跳过目录
grep -d recurse "pattern" directory/   # 递归搜索

# 处理设备文件
grep -D read "pattern" /dev/sda        # 读取设备文件
grep -D skip "pattern" /dev/sda        # 跳过设备文件

2. 二进制文件处理

# 处理二进制文件的方式
grep --binary-files=text "pattern" file    # 视为文本文件
grep --binary-files=binary "pattern" file  # 视为二进制文件
grep --binary-files=without-match "pattern" file  # 跳过匹配

# 快捷选项
grep -a "pattern" binary_file              # 等同于--binary-files=text
grep -I "pattern" *                        # 等同于--binary-files=without-match

3. 特殊输入处理

# 处理空字节结尾的数据
grep -z "pattern" null_terminated_file

# 处理Windows格式文件
grep -U "pattern" windows_file         # 不去除CR字符

八、常见使用场景

1. 日志分析

# 查找错误信息
grep "ERROR" application.log

# 查找最近的错误
grep -A 5 "ERROR" application.log

# 统计错误数量
grep -c "ERROR" application.log

# 查找特定时间段的日志
grep "2024-01-15" access.log

# 查找IP地址
grep -E "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" access.log

2. 系统管理

# 查找进程
ps aux | grep "nginx"

# 查找配置项
grep -n "port" /etc/nginx/nginx.conf

# 查找用户
grep "username" /etc/passwd

# 查找服务
systemctl list-units | grep "running"

# 查找监听端口
netstat -tuln | grep ":80"

3. 代码搜索

# 在源代码中搜索函数
grep -r "function_name" /path/to/source/

# 查找TODO注释
grep -r "TODO" /path/to/project/

# 查找特定文件类型中的内容
grep -r --include="*.py" "import" /path/to/python/code/

# 查找函数定义
grep -r "^def " --include="*.py" /path/to/project/

4. 网络和安全

# 查找失败的登录尝试
grep "Failed password" /var/log/auth.log

# 查找可疑IP
grep -E "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" firewall.log

# 查找安全事件
grep -i "security\|attack\|breach" /var/log/messages

九、实用示例集

1. 文件内容搜索

# 搜索包含特定单词的行
grep "important" document.txt

# 搜索多个文件中的内容
grep "configuration" *.conf

# 递归搜索整个目录
grep -r "database" /etc/

# 忽略大小写搜索
grep -i "Error" log.txt

# 显示匹配行的前后内容
grep -B 2 -A 2 "ERROR" application.log

2. 行处理技巧

# 显示空行
grep "^$" file.txt

# 显示非空行
grep -v "^$" file.txt

# 显示以特定字符开头的行
grep "^[A-Z]" file.txt

# 显示包含数字的行
grep "[0-9]" file.txt

# 显示邮箱地址
grep -E "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" file.txt

3. 组合使用

# 统计匹配行数并显示
grep -c "error" log.txt && grep "error" log.txt

# 查找并显示上下文
grep -B 2 -A 2 "exception" application.log

# 多条件过滤
cat data.txt | grep "pattern1" | grep "pattern2"

# 排除多个模式
grep -v -E "(debug|info|trace)" log.txt

4. 高级搜索技巧

# 查找重复行
grep -v "^$" file.txt | sort | uniq -d

# 查找最长的行
grep -o ".*" file.txt | awk '{print length, $0}' | sort -nr | head -1

# 查找包含特定字符数的行
grep -E "^.{10,50}$" file.txt  # 10到50个字符的行

# 查找URL
grep -E "https?://[^\s\"]+" file.txt

十、常见问题解决

1. 特殊字符处理

# 搜索包含特殊字符的文本
grep "file\.txt" documents.txt      # 搜索"file.txt"
grep "price\$" products.txt         # 搜索"price$"
grep "a\*b" patterns.txt            # 搜索"a*b"

# 使用固定字符串避免转义
grep -F "file.txt" documents.txt    # 不需要转义

2. 性能优化

# 大文件搜索优化
grep -m 100 "pattern" largefile.txt  # 限制匹配数量

# 使用固定字符串提高速度
grep -F "literal_string" file.txt    # 不使用正则表达式

# 指定文件类型减少搜索范围
grep -r --include="*.log" "error" /var/

# 并行搜索大文件
grep -m 1 "pattern" largefile.txt    # 找到第一个匹配就停止

3. 编码和格式问题

# 处理不同编码文件
grep -a "pattern" file.txt           # 将文件视为文本

# 处理Windows格式文件
grep -U "pattern" windows_file       # 保留CR字符

# 处理空字节结尾的文件
grep -z "pattern" null_terminated_file

4. 错误处理

# 抑制错误消息
grep -s "pattern" *.txt              # 忽略无法读取的文件错误

# 处理权限问题
grep -r --exclude-dir="/proc" "pattern" /  # 排除无权限目录

十一、快速参考表

基本选项速查

-i    --ignore-case         忽略大小写
-v    --invert-match        反向匹配
-n    --line-number         显示行号
-c    --count               只显示匹配行数
-l    --files-with-matches  只显示文件名
-L    --files-without-match 只显示不匹配的文件名
-r    --recursive           递归搜索
-E    --extended-regexp     扩展正则表达式
-F    --fixed-strings       固定字符串匹配
-w    --word-regexp         整词匹配
-x    --line-regexp         整行匹配

常用正则表达式

^pattern    行首匹配
pattern$    行尾匹配
.           任意单字符
*           前一字符0次或多次
\+          前一字符1次或多次
\{n,m\}     前一字符n到m次
[abc]       字符类
[^abc]      非字符类
(pattern1|pattern2)  OR操作(扩展正则)

实用组合示例

# 查找错误并显示上下文
grep -n -A 3 -B 3 "ERROR" application.log

# 递归搜索并只显示文件名
grep -r -l "TODO" /path/to/project/

# 统计各类错误数量
grep -c -E "ERROR|WARNING|CRITICAL" log.txt

# 查找并高亮显示
grep --color=always "pattern" file.txt

# 处理大文件的优化搜索
grep -m 10 -i "error" largefile.log

# 查找邮箱地址
grep -E -o "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" file.txt

退出状态码

0   找到匹配的行
1   未找到匹配的行
2   发生错误(除非使用-q选项)

这个修订版手册基于grep --help的完整输出,涵盖了所有主要选项和功能。掌握这些内容后,你可以高效地进行各种文本搜索和处理工作。

发表在 linux文章, linux命令 | 留下评论

AWK 复杂应用场景总结

AWK 复杂应用场景总结

一、大数据处理与分析

1. 流式日志分析系统

# 实时分析Web服务器日志,生成实时报表
awk -v interval=60 '
BEGIN {
    start_time = systime()
    OFMT = "%.2f"
}

{
    # 解析Apache日志
    ip = $1
    status = $9
    bytes = ($10 == "-" ? 0 : $10)
    url = $7
    
    # 统计指标
    total_requests++
    total_bytes += bytes
    status_count[status]++
    ip_count[ip]++
    
    # 按URL分类统计
    url_stats[url]["count"]++
    url_stats[url]["bytes"] += bytes
    
    # 每分钟输出统计
    current_time = systime()
    if(current_time - start_time >= interval) {
        generate_report()
        reset_counters()
        start_time = current_time
    }
}

END {
    if(total_requests > 0) {
        generate_report()
    }
}

function generate_report() {
    print strftime("%Y-%m-%d %H:%M:%S"), "=== 1分钟统计报告 ==="
    print "总请求数:", total_requests
    print "总流量:", format_bytes(total_bytes)
    print "平均请求大小:", format_bytes(total_bytes/total_requests)
    print "QPS:", total_requests/interval
    
    # 状态码分布
    print "\n状态码分布:"
    for(status in status_count) {
        print "  " status ":", status_count[status], 
              sprintf("(%.1f%%)", status_count[status]*100/total_requests)
    }
    
    # Top 10 IP
    print "\nTop 10 访问IP:"
    sort_array_by_value(ip_count, "desc")
    for(i = 1; i <= 10 && i <= length(sorted_keys); i++) {
        ip = sorted_keys[i]
        print "  " ip ":", ip_count[ip], 
              sprintf("(%.1f%%)", ip_count[ip]*100/total_requests)
    }
    
    print "========================\n"
}

function reset_counters() {
    delete status_count
    delete ip_count
    delete url_stats
    total_requests = 0
    total_bytes = 0
}

function format_bytes(bytes) {
    units[1] = "B"; units[2] = "KB"; units[3] = "MB"; units[4] = "GB"
    unit = 1
    while(bytes >= 1024 && unit < 4) {
        bytes /= 1024
        unit++
    }
    return sprintf("%.2f %s", bytes, units[unit])
}'

2. 分布式数据聚合

# 处理分布在多个文件中的数据,进行全局统计
awk '
BEGIN {
    # 初始化聚合变量
    global_stats["total_records"] = 0
    global_stats["total_amount"] = 0
    global_stats["max_amount"] = 0
    global_stats["min_amount"] = 999999999
}

# 处理每个文件的数据
FNR == 1 {
    current_file = FILENAME
    file_stats[current_file]["records"] = 0
    file_stats[current_file]["amount"] = 0
}

{
    # 假设第3列是金额
    amount = $3 + 0
    
    # 文件级别统计
    file_stats[current_file]["records"]++
    file_stats[current_file]["amount"] += amount
    
    # 全局统计
    global_stats["total_records"]++
    global_stats["total_amount"] += amount
    if(amount > global_stats["max_amount"]) {
        global_stats["max_amount"] = amount
    }
    if(amount < global_stats["min_amount"]) {
        global_stats["min_amount"] = amount
    }
    
    # 按类别统计(假设第2列是类别)
    category_stats[$2]["count"]++
    category_stats[$2]["amount"] += amount
}

END {
    # 输出全局统计
    print "=== 全局统计 ==="
    print "总记录数:", global_stats["total_records"]
    print "总金额:", global_stats["total_amount"]
    print "平均金额:", global_stats["total_amount"]/global_stats["total_records"]
    print "最大金额:", global_stats["max_amount"]
    print "最小金额:", global_stats["min_amount"]
    
    # 输出文件级别统计
    print "\n=== 各文件统计 ==="
    for(file in file_stats) {
        print file ":"
        print "  记录数:", file_stats[file]["records"]
        print "  金额:", file_stats[file]["amount"]
        print "  平均金额:", file_stats[file]["amount"]/file_stats[file]["records"]
    }
    
    # 输出类别统计
    print "\n=== 类别统计 ==="
    for(category in category_stats) {
        print category ":"
        print "  记录数:", category_stats[category]["count"]
        print "  总金额:", category_stats[category]["amount"]
        print "  平均金额:", category_stats[category]["amount"]/category_stats[category]["count"]
    }
}'

二、系统监控与运维

1. 综合系统监控脚本

# 多维度系统监控工具
awk -v threshold_cpu=80 -v threshold_mem=80 -v threshold_disk=90 '
BEGIN {
    # 收集系统信息
    collect_system_info()
    
    # 分析各项指标
    analyze_cpu()
    analyze_memory()
    analyze_disk()
    analyze_network()
    
    # 生成报告
    generate_system_report()
    
    # 检查告警
    check_alerts()
}

function collect_system_info() {
    # CPU信息
    cmd = "top -bn1 | grep '%Cpu(s)'"
    if((cmd | getline) > 0) {
        system_info["cpu_usage"] = extract_cpu_usage($0)
    }
    close(cmd)
    
    # 内存信息
    cmd = "free | grep Mem"
    if((cmd | getline) > 0) {
        system_info["mem_total"] = $2
        system_info["mem_used"] = $3
        system_info["mem_usage"] = ($3/$2)*100
    }
    close(cmd)
    
    # 磁盘信息
    cmd = "df -h | grep -E '^/dev/'"
    disk_index = 0
    while((cmd | getline) > 0) {
        disk_info[disk_index]["filesystem"] = $1
        disk_info[disk_index]["size"] = $2
        disk_info[disk_index]["used"] = $3
        disk_info[disk_index]["available"] = $4
        disk_info[disk_index]["usage"] = substr($5, 1, length($5)-1) + 0
        disk_info[disk_index]["mount"] = $6
        disk_index++
    }
    close(cmd)
    
    # 网络连接
    cmd = "netstat -an | grep ESTABLISHED | wc -l"
    if((cmd | getline) > 0) {
        system_info["active_connections"] = $0 + 0
    }
    close(cmd)
    
    # 系统负载
    cmd = "uptime"
    if((cmd | getline) > 0) {
        system_info["load_average"] = extract_load_average($0)
    }
    close(cmd)
}

function analyze_cpu() {
    cpu_usage = system_info["cpu_usage"]
    if(cpu_usage > threshold_cpu) {
        alerts["cpu"] = "CPU使用率过高: " cpu_usage "%"
    }
}

function analyze_memory() {
    mem_usage = system_info["mem_usage"]
    if(mem_usage > threshold_mem) {
        alerts["memory"] = "内存使用率过高: " sprintf("%.1f", mem_usage) "%"
    }
}

function analyze_disk() {
    for(i = 0; i < disk_index; i++) {
        usage = disk_info[i]["usage"]
        if(usage > threshold_disk) {
            alerts["disk_" i] = "磁盘 " disk_info[i]["mount"] " 使用率过高: " usage "%"
        }
    }
}

function analyze_network() {
    connections = system_info["active_connections"]
    if(connections > 1000) {  # 假设1000个连接为阈值
        alerts["network"] = "活跃连接数过多: " connections
    }
}

function generate_system_report() {
    print "=== 系统监控报告 ==="
    print "生成时间:", strftime("%Y-%m-%d %H:%M:%S")
    print ""
    
    # CPU信息
    print "CPU使用率:", system_info["cpu_usage"], "%"
    
    # 内存信息
    print "内存使用情况:"
    print "  总量:", format_bytes(system_info["mem_total"]*1024)
    print "  已用:", format_bytes(system_info["mem_used"]*1024)
    print "  使用率:", sprintf("%.1f", system_info["mem_usage"]), "%"
    
    # 磁盘信息
    print "\n磁盘使用情况:"
    for(i = 0; i < disk_index; i++) {
        print "  " disk_info[i]["mount"] ":", 
              disk_info[i]["used"], "/", disk_info[i]["size"],
              "(" disk_info[i]["usage"] "%)"
    }
    
    # 网络信息
    print "\n网络连接:", system_info["active_connections"]
    
    # 系统负载
    print "系统负载:", system_info["load_average"]
}

function check_alerts() {
    if(length(alerts) > 0) {
        print "\n=== 告警信息 ==="
        for(alert_type in alerts) {
            print "⚠️  " alerts[alert_type]
        }
        
        # 可以在这里添加告警通知逻辑
        # send_alert_email(alerts)
    } else {
        print "\n✅ 系统状态正常"
    }
}

function extract_cpu_usage(line) {
    # 从top输出中提取CPU使用率
    if(match(line, /[0-9.]+ id/)) {
        idle = substr(line, RSTART, RLENGTH-3) + 0
        return 100 - idle
    }
    return 0
}

function extract_load_average(line) {
    # 从uptime输出中提取负载平均值
    if(match(line, /load average: [0-9., ]+/)) {
        load_str = substr(line, RSTART+14, RLENGTH-14)
        gsub(/,/, "", load_str)
        return load_str
    }
    return "N/A"
}

function format_bytes(bytes) {
    units[1] = "B"; units[2] = "KB"; units[3] = "MB"; units[4] = "GB"
    unit = 1
    temp_bytes = bytes
    while(temp_bytes >= 1024 && unit < 4) {
        temp_bytes /= 1024
        unit++
    }
    return sprintf("%.1f %s", temp_bytes, units[unit])
}'

三、网络安全与审计

1. 入侵检测系统

# 分析安全日志,检测潜在威胁
awk -f intrusion_detection.awk /var/log/auth.log /var/log/syslog

# intrusion_detection.awk 内容:
BEGIN {
    # 定义威胁模式
    threat_patterns["failed_login"] = "Failed password"
    threat_patterns["invalid_user"] = "Invalid user"
    threat_patterns["break_in"] = "POSSIBLE BREAK-IN ATTEMPT"
    threat_patterns["root_login"] = "Accepted .* for root"
    
    # 初始化统计
    start_time = systime()
    OFMT = "%.0f"
}

{
    # 检查每种威胁模式
    for(threat_type in threat_patterns) {
        pattern = threat_patterns[threat_type]
        if($0 ~ pattern) {
            threats[threat_type]++
            threat_details[threat_type][threats[threat_type]] = $0
            threat_times[threat_type][threats[threat_type]] = FNR
            
            # 记录IP地址(如果存在)
            if(match($0, /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/)) {
                ip = substr($0, RSTART, RLENGTH)
                threat_ips[threat_type][ip]++
            }
        }
    }
    
    # 统计IP访问频率
    if(match($0, /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/)) {
        ip = substr($0, RSTART, RLENGTH)
        ip_activity[ip]++
        
        # 检测异常高频访问
        if(ip_activity[ip] > 100 && (systime() - start_time) < 3600) {
            suspicious_ips[ip] = ip_activity[ip]
        }
    }
}

END {
    # 生成安全报告
    print "=== 安全审计报告 ==="
    print "分析时间:", strftime("%Y-%m-%d %H:%M:%S")
    print "分析文件:", ARGV[1]
    print ""
    
    # 输出威胁统计
    total_threats = 0
    for(threat_type in threats) {
        count = threats[threat_type]
        total_threats += count
        print threat_type ":", count, "次"
        
        # 显示Top 3详细信息
        print "  详细信息 (Top 3):"
        for(i = 1; i <= 3 && i <= count; i++) {
            print "    " threat_details[threat_type][i]
        }
        if(count > 3) {
            print "    ... 还有", count-3, "条记录"
        }
        print ""
    }
    
    # 输出可疑IP
    if(length(suspicious_ips) > 0) {
        print "⚠️  可疑IP地址 (1小时内访问超过100次):"
        for(ip in suspicious_ips) {
            print "  " ip ":", suspicious_ips[ip], "次"
        }
        print ""
    }
    
    # 输出威胁IP统计
    print "威胁源IP统计:"
    for(threat_type in threat_ips) {
        print "  " threat_type ":"
        sort_array_by_count(threat_ips[threat_type])
        for(i = 1; i <= 5 && i <= length(sorted_array); i++) {
            ip = sorted_array[i]
            print "    " ip ":", threat_ips[threat_type][ip], "次"
        }
    }
    
    print "\n总计发现威胁:", total_threats, "次"
    
    # 如果威胁较多,建议采取措施
    if(total_threats > 50) {
        print "\n🚨 高风险: 建议立即检查防火墙规则和系统安全配置"
    }
}

function sort_array_by_count(array) {
    delete sorted_array
    count = 0
    for(key in array) {
        temp_array[++count] = key
    }
    
    # 按值排序
    for(i = 1; i <= count; i++) {
        max_key = temp_array[i]
        max_val = array[temp_array[i]]
        max_index = i
        
        for(j = i+1; j <= count; j++) {
            if(array[temp_array[j]] > max_val) {
                max_val = array[temp_array[j]]
                max_key = temp_array[j]
                max_index = j
            }
        }
        
        # 交换
        temp = temp_array[i]
        temp_array[i] = temp_array[max_index]
        temp_array[max_index] = temp
        
        sorted_array[i] = max_key
    }
}'

四、数据科学与机器学习预处理

1. 特征工程工具

# 数据预处理和特征工程
awk -F',' '
BEGIN {
    # 数据类型检测和处理
    OFS = ","
}

NR == 1 {
    # 处理表头
    header = $0
    print "原始表头:", header > "/dev/stderr"
    
    # 记录字段数量
    field_count = NF
    for(i = 1; i <= NF; i++) {
        field_names[i] = $i
        field_stats[i]["missing"] = 0
        field_stats[i]["type"] = "unknown"
    }
    next
}

{
    # 数据质量检查
    for(i = 1; i <= field_count; i++) {
        value = $i
        
        # 缺失值检查
        if(value == "" || value == "NULL" || value == "N/A") {
            field_stats[i]["missing"]++
            missing_data[NR,i] = 1
        } else {
            # 数据类型推断
            infer_data_type(i, value)
            
            # 统计信息收集
            collect_statistics(i, value)
        }
    }
    
    # 异常值检测
    detect_outliers()
    
    # 数据清洗
    clean_data()
}

END {
    # 生成数据质量报告
    generate_quality_report()
    
    # 输出清洗后的数据
    output_cleaned_data()
}

function infer_data_type(field_index, value) {
    # 数值类型检测
    if(value ~ /^-?[0-9]+\.?[0-9]*$/) {
        if(field_stats[field_index]["type"] == "unknown") {
            field_stats[field_index]["type"] = "numeric"
        } else if(field_stats[field_index]["type"] != "numeric") {
            field_stats[field_index]["type"] = "mixed"
        }
    }
    # 日期类型检测
    else if(value ~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}/) {
        field_stats[field_index]["type"] = "date"
    }
    # 分类类型
    else {
        field_stats[field_index]["type"] = "categorical"
        field_stats[field_index]["categories"][value]++
    }
}

function collect_statistics(field_index, value) {
    if(field_stats[field_index]["type"] == "numeric") {
        num_value = value + 0
        field_stats[field_index]["sum"] += num_value
        field_stats[field_index]["count"]++
        
        if(field_stats[field_index]["min"] == "" || num_value < field_stats[field_index]["min"]) {
            field_stats[field_index]["min"] = num_value
        }
        if(field_stats[field_index]["max"] == "" || num_value > field_stats[field_index]["max"]) {
            field_stats[field_index]["max"] = num_value
        }
        
        # 存储值用于计算方差
        field_stats[field_index]["values"][field_stats[field_index]["count"]] = num_value
    }
}

function detect_outliers() {
    # 使用3σ原则检测异常值
    for(i = 1; i <= field_count; i++) {
        if(field_stats[i]["type"] == "numeric" && field_stats[i]["count"] > 10) {
            mean = field_stats[i]["sum"] / field_stats[i]["count"]
            
            # 计算方差
            variance = 0
            for(j = 1; j <= field_stats[i]["count"]; j++) {
                diff = field_stats[i]["values"][j] - mean
                variance += diff * diff
            }
            variance /= field_stats[i]["count"]
            std_dev = sqrt(variance)
            
            # 检查当前值是否为异常值
            current_value = $i + 0
            if(abs(current_value - mean) > 3 * std_dev) {
                outliers[NR,i] = 1
                field_stats[i]["outliers"]++
            }
        }
    }
}

function clean_data() {
    # 处理缺失值
    for(i = 1; i <= field_count; i++) {
        if((NR,i) in missing_data) {
            # 数值型用均值填充
            if(field_stats[i]["type"] == "numeric") {
                $i = field_stats[i]["sum"] / field_stats[i]["count"]
            }
            # 分类型用众数填充
            else if(field_stats[i]["type"] == "categorical") {
                $i = get_mode(field_stats[i]["categories"])
            }
        }
    }
    
    # 标准化数值型数据
    for(i = 1; i <= field_count; i++) {
        if(field_stats[i]["type"] == "numeric") {
            mean = field_stats[i]["sum"] / field_stats[i]["count"]
            # 简单的Min-Max标准化
            range = field_stats[i]["max"] - field_stats[i]["min"]
            if(range > 0) {
                $i = ($i - field_stats[i]["min"]) / range
            }
        }
    }
}

function generate_quality_report() {
    print "=== 数据质量报告 ===" > "/dev/stderr"
    print "总记录数:", NR-1 > "/dev/stderr"
    print "字段数:", field_count > "/dev/stderr"
    print "" > "/dev/stderr"
    
    for(i = 1; i <= field_count; i++) {
        print "字段", i, "(", field_names[i], "):" > "/dev/stderr"
        print "  数据类型:", field_stats[i]["type"] > "/dev/stderr"
        print "  缺失值:", field_stats[i]["missing"] > "/dev/stderr"
        
        if(field_stats[i]["type"] == "numeric") {
            print "  最小值:", field_stats[i]["min"] > "/dev/stderr"
            print "  最大值:", field_stats[i]["max"] > "/dev/stderr"
            print "  平均值:", field_stats[i]["sum"]/field_stats[i]["count"] > "/dev/stderr"
            if("outliers" in field_stats[i]) {
                print "  异常值数量:", field_stats[i]["outliers"] > "/dev/stderr"
            }
        }
        print "" > "/dev/stderr"
    }
}

function output_cleaned_data() {
    print header  # 输出处理后的表头
    # 实际数据在主处理流程中已经输出
}

function abs(x) {
    return (x < 0) ? -x : x
}

function get_mode(categories) {
    max_count = 0
    mode_value = ""
    for(value in categories) {
        if(categories[value] > max_count) {
            max_count = categories[value]
            mode_value = value
        }
    }
    return mode_value
}'

五、DevOps自动化工具

1. 持续集成/持续部署(CI/CD)流水线工具

# CI/CD 流水线监控和报告生成器
awk '
BEGIN {
    # 流水线配置
    pipeline_stages["build"] = "构建"
    pipeline_stages["test"] = "测试"
    pipeline_stages["deploy"] = "部署"
    pipeline_stages["verify"] = "验证"
    
    current_pipeline = ""
    start_time = systime()
}

# 解析CI/CD日志
/^Pipeline: / {
    current_pipeline = $2
    pipeline_start[current_pipeline] = systime()
    print "🚀 开始执行流水线:", current_pipeline
}

/^Stage: ([a-zA-Z]+) (STARTED|FINISHED|FAILED)/ {
    stage = $2
    status = $3
    timestamp = systime()
    
    if(status == "STARTED") {
        stage_start[current_pipeline,stage] = timestamp
        print "  ⏱️  阶段开始:", pipeline_stages[stage]
    } else if(status == "FINISHED") {
        duration = timestamp - stage_start[current_pipeline,stage]
        stage_duration[current_pipeline,stage] = duration
        print "  ✅ 阶段完成:", pipeline_stages[stage], 
              "(" duration "秒)"
    } else if(status == "FAILED") {
        duration = timestamp - stage_start[current_pipeline,stage]
        stage_duration[current_pipeline,stage] = duration
        stage_failed[current_pipeline,stage] = 1
        print "  ❌ 阶段失败:", pipeline_stages[stage], 
              "(" duration "秒)"
    }
}

/^Artifact: (.+) Size: ([0-9]+) bytes/ {
    artifact_name = $2
    artifact_size = $4
    artifacts[current_pipeline,artifact_name] = artifact_size
    print "  📦 生成制品:", artifact_name, 
          "(" format_bytes(artifact_size) ")"
}

/^Test Results: ([0-9]+) passed, ([0-9]+) failed, ([0-9]+) skipped/ {
    passed = $3
    failed = $5
    skipped = $8
    total_tests = passed + failed + skipped
    
    test_results[current_pipeline,"passed"] = passed
    test_results[current_pipeline,"failed"] = failed
    test_results[current_pipeline,"skipped"] = skipped
    
    success_rate = (total_tests > 0) ? (passed/total_tests)*100 : 0
    
    print "  🧪 测试结果:", passed "通过,", failed "失败,", skipped "跳过"
    print "  📊 成功率:", sprintf("%.1f", success_rate) "%"
}

END {
    # 生成最终报告
    generate_ci_report()
}

function generate_ci_report() {
    print "\n" "=" x 50
    print "CI/CD 流水线执行报告"
    print "=" x 50
    
    for(pipeline in pipeline_start) {
        print "\n📋 流水线:", pipeline
        print "开始时间:", strftime("%Y-%m-%d %H:%M:%S", pipeline_start[pipeline])
        
        total_duration = 0
        all_passed = 1
        
        for(stage in pipeline_stages) {
            if((pipeline,stage) in stage_duration) {
                duration = stage_duration[pipeline,stage]
                total_duration += duration
                
                status_icon = ((pipeline,stage) in stage_failed) ? "❌" : "✅"
                print "  " status_icon, pipeline_stages[stage] ":", duration "秒"
                
                if((pipeline,stage) in stage_failed) {
                    all_passed = 0
                }
            }
        }
        
        print "总耗时:", total_duration "秒"
        print "最终状态:", (all_passed ? "✅ 成功" : "❌ 失败")
        
        # 测试结果
        if((pipeline,"passed") in test_results) {
            passed = test_results[pipeline,"passed"]
            failed = test_results[pipeline,"failed"]
            skipped = test_results[pipeline,"skipped"]
            total = passed + failed + skipped
            
            print "测试统计:", passed "通过,", failed "失败,", skipped "跳过"
            if(total > 0) {
                success_rate = (passed/total)*100
                print "成功率:", sprintf("%.1f", success_rate) "%"
            }
        }
        
        # 制品信息
        artifact_count = 0
        total_size = 0
        for(key in artifacts) {
            split(key, parts, SUBSEP)
            if(parts[1] == pipeline) {
                artifact_count++
                total_size += artifacts[key]
            }
        }
        
        if(artifact_count > 0) {
            print "生成制品:", artifact_count "个, 总大小:", format_bytes(total_size)
        }
    }
    
    print "\n" "=" x 50
    print "报告生成时间:", strftime("%Y-%m-%d %H:%M:%S")
    print "=" x 50
}

function format_bytes(bytes) {
    units[1] = "B"; units[2] = "KB"; units[3] = "MB"; units[4] = "GB"
    unit = 1
    temp_bytes = bytes
    while(temp_bytes >= 1024 && unit < 4) {
        temp_bytes /= 1024
        unit++
    }
    return sprintf("%.1f %s", temp_bytes, units[unit])
}'

六、性能优化最佳实践

1. AWK脚本性能分析工具

# AWK脚本性能分析器
awk -p performance_profile.txt '
BEGIN {
    # 性能监控开始
    start_time = systime()
    start_timestamp = gettimeofday()
    
    # 内存使用监控(通过系统调用)
    initial_memory = get_memory_usage()
}

# 主处理逻辑
{
    # 记录处理速度
    lines_processed++
    
    # 每处理10000行输出一次进度
    if(lines_processed % 10000 == 0) {
        current_time = systime()
        rate = lines_processed / (current_time - start_time + 1)
        print "已处理:", lines_processed, "行, 速度:", rate, "行/秒" > "/dev/stderr"
    }
    
    # 实际数据处理逻辑
    process_data()
}

END {
    # 性能统计
    end_time = systime()
    end_timestamp = gettimeofday()
    
    final_memory = get_memory_usage()
    
    total_time = end_time - start_time
    total_time_precise = end_timestamp - start_timestamp
    
    print "\n=== 性能分析报告 ===" > "/dev/stderr"
    print "总处理行数:", lines_processed > "/dev/stderr"
    print "总耗时:", total_time, "秒 (精确:", sprintf("%.3f", total_time_precise), "秒)" > "/dev/stderr"
    
    if(total_time > 0) {
        print "平均处理速度:", lines_processed/total_time, "行/秒" > "/dev/stderr"
    }
    
    print "内存使用变化:", initial_memory, "->", final_memory, 
          "(", final_memory - initial_memory, ")" > "/dev/stderr"
    
    # 函数调用统计(如果使用了自定义函数)
    if("function_calls" in SYMTAB) {
        print "函数调用次数:", function_calls > "/dev/stderr"
    }
}

function process_data() {
    # 模拟复杂的数据处理
    # 这里应该是实际的业务逻辑
    
    # 性能优化技巧示例:
    
    # 1. 避免重复计算
    # 不好的做法:length($1) 在条件中重复计算
    # 好的做法:
    field1_length = length($1)
    if(field1_length > 10) {
        # 处理逻辑
    }
    
    # 2. 使用哈希表优化查找
    if($2 in lookup_table) {
        # O(1) 查找
    }
    
    # 3. 批量处理减少I/O
    # 收集数据到数组中,最后统一处理
    
    # 4. 避免不必要的字符串操作
    # 预编译正则表达式
    if(!regex_compiled) {
        regex_pattern = "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
        regex_compiled = 1
    }
    
    if($1 ~ regex_pattern) {
        # 处理日期格式
    }
    
    # 5. 使用适当的数值格式
    OFMT = "%.6f"  # 控制浮点数精度
}

function get_memory_usage() {
    # 获取当前进程内存使用情况
    cmd = "ps -o rss= -p " PROCINFO["pid"]
    if((cmd | getline mem_kb) > 0) {
        close(cmd)
        return mem_kb + 0  # 转换为数字
    }
    close(cmd)
    return 0
}

function gettimeofday() {
    # 获取高精度时间戳
    cmd = "date +%s.%N"
    if((cmd | getline timestamp) > 0) {
        close(cmd)
        return timestamp + 0
    }
    close(cmd)
    return systime()
}'

七、综合应用案例

1. 企业级日志分析平台

# 企业级多维度日志分析系统
awk -v company="MyCompany" -v environment="production" '
BEGIN {
    # 系统配置
    config["company"] = company
    config["environment"] = environment
    config["timezone"] = "Asia/Shanghai"
    
    # 初始化各种统计器
    init_analytics()
    
    # 启动时间
    start_timestamp = systime()
    
    print "🚀 启动 " company " " environment " 环境日志分析系统" > "/dev/stderr"
    print "开始时间:", strftime("%Y-%m-%d %H:%M:%S", start_timestamp) > "/dev/stderr"
}

# 多种日志格式处理
{
    # 自动识别日志类型
    log_type = identify_log_type($0)
    
    if(log_type == "apache") {
        process_apache_log()
    } else if(log_type == "nginx") {
        process_nginx_log()
    } else if(log_type == "application") {
        process_app_log()
    } else if(log_type == "security") {
        process_security_log()
    } else {
        process_generic_log()
    }
    
    # 更新全局统计
    update_global_stats()
}

END {
    # 生成综合报告
    generate_comprehensive_report()
    
    # 输出JSON格式的实时数据(用于仪表板)
    output_json_data()
    
    # 发送告警(如果需要)
    send_alerts_if_needed()
}

function init_analytics() {
    # 初始化各种分析模块
    init_performance_analytics()
    init_security_analytics()
    init_business_analytics()
    init_user_behavior_analytics()
}

function identify_log_type(line) {
    # 通过正则表达式识别日志类型
    if(line ~ /^([0-9]{1,3}\.){3}[0-9]{1,3} - - \[/) {
        return "apache"
    } else if(line ~ /^[0-9]{4}\/[0-9]{2}\/[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} \[/) {
        return "nginx"
    } else if(line ~ /ERROR|WARN|INFO|DEBUG/) {
        return "application"
    } else if(line ~ /SECURITY|AUTH|LOGIN|FAILED/) {
        return "security"
    } else {
        return "generic"
    }
}

function process_apache_log() {
    # Apache日志处理逻辑
    ip = $1
    timestamp = $4
    method = $6
    url = $7
    status = $9
    bytes = ($10 == "-" ? 0 : $10)
    user_agent = $12
    
    # 性能统计
    analytics["apache"]["requests"]++
    analytics["apache"]["bytes"] += bytes
    analytics["apache"]["status"][status]++
    
    # 用户行为分析
    analytics["users"][ip]["requests"]++
    analytics["users"][ip]["bytes"] += bytes
    
    # URL分析
    analytics["urls"][url]["count"]++
    analytics["urls"][url]["bytes"] += bytes
    
    # 响应时间分析(如果日志中包含)
    if($NF ~ /([0-9]+)ms/) {
        response_time = substr($NF, 1, length($NF)-2) + 0
        analytics["apache"]["response_times"] += response_time
        analytics["apache"]["response_count"]++
    }
}

function process_nginx_log() {
    # Nginx日志处理逻辑
    # 类似Apache处理,但格式略有不同
    ip = $1
    timestamp = $4
    # ... 处理逻辑
}

function process_app_log() {
    # 应用程序日志处理
    timestamp = $1 " " $2
    level = $3
    message = ""
    for(i = 4; i <= NF; i++) {
        message = message $i " "
    }
    
    # 错误统计
    if(level == "ERROR" || level == "FATAL") {
        analytics["errors"]["count"]++
        analytics["errors"]["by_type"][message]++
        
        # 提取错误类型
        if(match(message, /(Database|Network|Timeout|Permission)/)) {
            error_type = substr(message, RSTART, RLENGTH)
            analytics["errors"]["by_category"][error_type]++
        }
    }
}

function process_security_log() {
    # 安全日志处理
    timestamp = $1 " " $2
    event_type = $3
    details = ""
    for(i = 4; i <= NF; i++) {
        details = details $i " "
    }
    
    # 安全事件统计
    analytics["security"]["events"]++
    analytics["security"]["by_type"][event_type]++
    
    # IP地址分析
    if(match(details, /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/)) {
        ip = substr(details, RSTART, RLENGTH)
        analytics["security"]["suspicious_ips"][ip]++
    }
}

function update_global_stats() {
    # 更新全局统计信息
    global_stats["lines_processed"]++
    
    # 每处理一定数量的行,更新进度
    if(global_stats["lines_processed"] % 50000 == 0) {
        print "处理进度:", global_stats["lines_processed"], "行" > "/dev/stderr"
    }
}

function generate_comprehensive_report() {
    end_timestamp = systime()
    duration = end_timestamp - start_timestamp
    
    print "\n" "=" x 80
    print "📊 " company " " environment " 环境综合分析报告"
    print "=" x 80
    print "分析时间:", strftime("%Y-%m-%d %H:%M:%S", start_timestamp), 
          "至", strftime("%Y-%m-%d %H:%M:%S", end_timestamp)
    print "分析耗时:", duration, "秒"
    print "处理行数:", global_stats["lines_processed"]
    print ""
    
    # Web访问统计
    if("apache" in analytics) {
        print "🌐 Web访问统计:"
        print "  总请求数:", analytics["apache"]["requests"]
        print "  总流量:", format_bytes(analytics["apache"]["bytes"])
        if(analytics["apache"]["requests"] > 0) {
            print "  平均请求大小:", 
                  format_bytes(analytics["apache"]["bytes"]/analytics["apache"]["requests"])
        }
        if(analytics["apache"]["response_count"] > 0) {
            print "  平均响应时间:", 
                  analytics["apache"]["response_times"]/analytics["apache"]["response_count"], "ms"
        }
        print ""
    }
    
    # 错误统计
    if("errors" in analytics && analytics["errors"]["count"] > 0) {
        print "❌ 错误统计:"
        print "  总错误数:", analytics["errors"]["count"]
        print "  错误类型分布:"
        for(error_type in analytics["errors"]["by_type"]) {
            count = analytics["errors"]["by_type"][error_type]
            print "    " error_type ":", count
        }
        print ""
    }
    
    # 安全事件
    if("security" in analytics && analytics["security"]["events"] > 0) {
        print "🛡️  安全事件:"
        print "  总事件数:", analytics["security"]["events"]
        print "  事件类型:"
        for(event_type in analytics["security"]["by_type"]) {
            count = analytics["security"]["by_type"][event_type]
            print "    " event_type ":", count
        }
        print ""
    }
    
    print "=" x 80
}

function output_json_data() {
    # 输出JSON格式数据用于实时监控
    print "{"
    print "  \"company\": \"" company "\","
    print "  \"environment\": \"" environment "\","
    print "  \"timestamp\": \"" strftime("%Y-%m-%d %H:%M:%S") "\","
    print "  \"metrics\": {"
    print "    \"requests_per_second\": " (analytics["apache"]["requests"]/duration) ","
    print "    \"error_rate\": " (analytics["errors"]["count"]*100/global_stats["lines_processed"]) ","
    print "    \"total_bytes\": " analytics["apache"]["bytes"]
    print "  }"
    print "}"
}

function send_alerts_if_needed() {
    # 根据配置发送告警
    error_rate = (analytics["errors"]["count"]*100/global_stats["lines_processed"])
    if(error_rate > 5) {  # 错误率超过5%告警
        print "🚨 高错误率告警: " error_rate "% > 5%" > "/dev/stderr"
        # system("echo '高错误率告警' | mail -s '系统告警' admin@company.com")
    }
}

function format_bytes(bytes) {
    units[1] = "B"; units[2] = "KB"; units[3] = "MB"; units[4] = "GB"
    unit = 1
    temp_bytes = bytes
    while(temp_bytes >= 1024 && unit < 4) {
        temp_bytes /= 1024
        unit++
    }
    return sprintf("%.1f %s", temp_bytes, units[unit])
}'

这些复杂的AWK应用场景展示了AWK在企业级系统中的强大能力。通过合理的设计和优化,AWK可以处理各种复杂的文本处理任务,成为系统管理员、数据分析师和DevOps工程师的重要工具。

发表在 linux文章 | 标签为 | 留下评论

AWK 高级应用手册:网络编程与套接字实战指南

AWK 高级应用手册

深入学习 AWK 的高级用法,包括网络编程、套接字操作及自动化脚本开发,适合进阶 Linux 用户

一、网络编程与套接字

1. TCP 客户端通信

# 简单TCP客户端
awk 'BEGIN {
    # 连接到本地8080端口
    server = "/inet/tcp/0/127.0.0.1/8080"
    
    # 发送数据
    print "Hello Server" |& server
    
    # 接收响应
    if ((server |& getline response) > 0) {
        print "Server response:", response
    }
    
    close(server)
}'

2. TCP 服务器

# 简单TCP服务器(监听8080端口)
awk 'BEGIN {
    server = "/inet/tcp/8080/0/0"
    print "Server listening on port 8080..."
    
    while ((server |& getline) > 0) {
        print "Client connected"
        print "Received:", $0
        
        # 回复客户端
        print "Echo: " $0 |& server
        close(server)
    }
}'

3. HTTP 客户端示例

# 简单HTTP GET请求
awk 'BEGIN {
    host = "www.example.com"
    port = 80
    socket = "/inet/tcp/0/" host "/" port
    
    # 构造HTTP请求
    request = "GET / HTTP/1.1\r\n"
    request = request "Host: " host "\r\n"
    request = request "Connection: close\r\n\r\n"
    
    # 发送请求
    print request |& socket
    
    # 读取响应
    while ((socket |& getline) > 0) {
        print $0
    }
    
    close(socket)
}'

二、进程间通信

1. 管道通信

# 与外部命令交互
awk 'BEGIN {
    cmd = "sort -n"
    
    # 发送数据到sort命令
    print "30" |& cmd
    print "10" |& cmd
    print "20" |& cmd
    
    close(cmd, "to")  # 关闭写入端
    
    # 读取排序结果
    while ((cmd |& getline) > 0) {
        print "Sorted:", $0
    }
    
    close(cmd)
}'

2. 协同进程

# 创建协同进程
awk 'BEGIN {
    coproc = "tr a-z A-Z"  # 创建大写转换进程
    
    # 发送数据
    print "hello world" |& coproc
    
    # 读取结果
    if ((coproc |& getline result) > 0) {
        print "Result:", result
    }
    
    close(coproc)
}'

三、动态数据结构

1. 动态数组管理

# 动态数组类实现
awk '
BEGIN {
    # 初始化数组
    init_array("myarray")
    
    # 添加元素
    array_push("myarray", "first")
    array_push("myarray", "second")
    array_push("myarray", "third")
    
    # 获取元素
    print "Element at index 1:", array_get("myarray", 1)
    
    # 数组长度
    print "Array length:", array_length("myarray")
    
    # 遍历数组
    for(i = 0; i < array_length("myarray"); i++) {
        print "Index", i, ":", array_get("myarray", i)
    }
}

function init_array(name) {
    eval(name "_length = 0")
}

function array_push(name, value) {
    eval(name "[" eval(name "_length") "] = \"" value "\"")
    eval(name "_length++")
}

function array_get(name, index) {
    return eval(name "[" index "]")
}

function array_length(name) {
    return eval(name "_length")
}

function eval(expr) {
    return system("echo " expr " | awk '{print " expr "}'")
}'

2. 栈数据结构

# 栈实现
awk '
BEGIN {
    stack_init("mystack")
    stack_push("mystack", "item1")
    stack_push("mystack", "item2")
    stack_push("mystack", "item3")
    
    while(!stack_empty("mystack")) {
        print "Popped:", stack_pop("mystack")
    }
}

function stack_init(name) {
    eval(name "_top = -1")
}

function stack_push(name, item) {
    top = eval(name "_top")
    top++
    eval(name "[" top "] = \"" item "\"")
    eval(name "_top = " top)
}

function stack_pop(name) {
    top = eval(name "_top")
    if(top >= 0) {
        item = eval(name "[" top "]")
        top--
        eval(name "_top = " top)
        return item
    }
    return ""
}

function stack_empty(name) {
    return (eval(name "_top") < 0)
}'

四、文件系统操作

1. 目录遍历

# 递归遍历目录
awk 'BEGIN {
    traverse_directory(".")
}

function traverse_directory(dir) {
    cmd = "find " dir " -type f"
    while ((cmd | getline file) > 0) {
        print "Found file:", file
        # 处理文件...
    }
    close(cmd)
}'

2. 文件监控

# 监控文件变化
awk 'BEGIN {
    file = "monitor.txt"
    last_size = get_file_size(file)
    
    while(1) {
        current_size = get_file_size(file)
        if(current_size != last_size) {
            print "File changed! New size:", current_size
            last_size = current_size
        }
        system("sleep 1")
    }
}

function get_file_size(filename) {
    cmd = "stat -c %s " filename " 2>/dev/null"
    if((cmd | getline size) > 0) {
        close(cmd)
        return size
    }
    close(cmd)
    return 0
}'

五、数据库接口

1. SQLite 集成

# 通过系统调用与SQLite交互
awk 'BEGIN {
    db_file = "data.db"
    
    # 创建表
    system("sqlite3 " db_file " \"CREATE TABLE IF NOT EXISTS users(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)\"")
    
    # 插入数据
    insert_user(db_file, "Alice", 25)
    insert_user(db_file, "Bob", 30)
    
    # 查询数据
    query_users(db_file)
}

function insert_user(db, name, age) {
    cmd = "sqlite3 " db " \"INSERT INTO users(name, age) VALUES (\"\"" name "\"\", " age ")\""
    system(cmd)
}

function query_users(db) {
    cmd = "sqlite3 " db " \"SELECT * FROM users\""
    while((cmd | getline) > 0) {
        print "User:", $0
    }
    close(cmd)
}'

2. CSV 到数据库转换

# CSV导入数据库
awk -F',' '
NR > 1 {
    cmd = "sqlite3 data.db \"INSERT INTO records(col1, col2, col3) VALUES (\"\"" $1 "\"\", \"\"" $2 "\"\", \"\"" $3 "\"\")\""
    system(cmd)
}
' data.csv

六、并发与并行处理

1. 多进程处理

# 并行处理多个文件
awk 'BEGIN {
    files[1] = "file1.txt"
    files[2] = "file2.txt"
    files[3] = "file3.txt"
    
    # 启动多个进程
    for(i in files) {
        cmd = "wc -l " files[i] " > result_" i ".txt &"
        system(cmd)
    }
    
    # 等待所有进程完成
    system("wait")
    
    # 收集结果
    for(i in files) {
        cmd = "cat result_" i ".txt"
        if((cmd | getline) > 0) {
            print "File", files[i], ":", $1, "lines"
        }
        close(cmd)
        system("rm result_" i ".txt")
    }
}'

2. 线程池模拟

# 简单任务队列
awk 'BEGIN {
    max_workers = 3
    current_workers = 0
    
    # 模拟任务队列
    tasks[1] = "task1.sh"
    tasks[2] = "task2.sh"
    tasks[3] = "task3.sh"
    tasks[4] = "task4.sh"
    tasks[5] = "task5.sh"
    
    for(i in tasks) {
        while(current_workers >= max_workers) {
            system("sleep 0.1")  # 等待有空闲工作线程
        }
        
        # 启动任务
        cmd = tasks[i] " &"
        system(cmd)
        current_workers++
        print "Started task:", tasks[i], "(Workers:", current_workers, ")"
    }
    
    # 等待所有任务完成
    system("wait")
    print "All tasks completed"
}'

七、高级正则表达式

1. 复杂模式匹配

# 多行模式匹配
awk '
BEGIN {
    RS = ""  # 空行分隔记录
}
/ERROR/ && /critical/ {
    print "Critical error found:"
    print $0
    print "---"
}
' system.log

# 嵌套结构解析
awk '
BEGIN {
    pattern = "<([a-zA-Z][a-zA-Z0-9]*)[^>]*>([^<]*)</\\1>"
}
{
    while(match($0, pattern, arr)) {
        tag = arr[1]
        content = arr[2]
        print "Tag:", tag, "Content:", content
        $0 = substr($0, RSTART + RLENGTH)
    }
}
' html_file.txt

2. 正则捕获组

# GNU awk 的正则捕获组
awk '{
    if(match($0, /([0-9]{4})-([0-9]{2})-([0-9]{2})/, date_parts)) {
        year = date_parts[1]
        month = date_parts[2]
        day = date_parts[3]
        print "Year:", year, "Month:", month, "Day:", day
    }
}' dates.txt

八、性能分析与优化

1. 性能监控

# 性能分析脚本
awk -p profile.out '
{
    # 实际处理逻辑
    sum += $1
    count++
}
END {
    if(count > 0) {
        print "Average:", sum/count
    }
}
' large_data.txt

# 查看profile.out文件分析性能

2. 内存优化

# 大文件处理优化
awk '
BEGIN {
    # 设置缓冲区大小
    BINMODE = 3
}
{
    # 分批处理,避免内存溢出
    if(NR % 10000 == 0) {
        print "Processed", NR, "lines" > "/dev/stderr"
    }
    
    # 实际处理逻辑
    # ...
}
END {
    print "Total processed:", NR, "lines"
}' huge_file.txt

九、安全编程实践

1. 输入验证

# 安全的输入处理
awk '
function safe_input(str) {
    # 移除危险字符
    gsub(/[;&|$`]/, "", str)
    return str
}

function validate_number(str) {
    return (str ~ /^[0-9]+$/)
}

{
    user_input = safe_input($1)
    if(validate_number(user_input)) {
        print "Valid number:", user_input
    } else {
        print "Invalid input:", user_input > "/dev/stderr"
    }
}' input.txt

2. 沙盒模式

# 使用沙盒模式运行
# gawk -S script.awk data.txt
# 禁止system(), getline等危险函数

awk -S '
BEGIN {
    # 在沙盒模式下,这些操作会被禁止
    # system("rm -rf /")  # 这会失败
    # cmd = "/bin/sh"     # 这也会失败
    print "Running in sandbox mode"
}
{
    print $0
}' data.txt

十、高级实用示例

1. 实时日志监控系统

#!/usr/bin/awk -f
# 实时日志分析器
BEGIN {
    # 配置
    ERROR_THRESHOLD = 10
    WINDOW_SIZE = 60  # 60秒窗口
    
    # 初始化统计
    start_time = systime()
}

{
    # 解析日志行
    timestamp = substr($0, 1, 19)  # 假设前19个字符是时间戳
    if($0 ~ /ERROR|FATAL/) {
        error_count++
        errors[error_count] = $0
    }
    
    # 每分钟报告一次
    current_time = systime()
    if(current_time - start_time >= 60) {
        report_stats()
        start_time = current_time
        delete errors  # 清空窗口数据
        error_count = 0
    }
}

function report_stats() {
    print strftime("%Y-%m-%d %H:%M:%S"), "- Errors in last minute:", error_count
    if(error_count > ERROR_THRESHOLD) {
        print "ALERT: Error threshold exceeded!"
        # 可以发送告警邮件等
    }
}

2. 数据可视化工具

# 简单的文本图表生成器
awk '
BEGIN {
    max_value = 0
}

{
    data[NR] = $1
    if($1 > max_value) max_value = $1
}

END {
    print "Data Visualization:"
    print "=================="
    
    scale = 50 / max_value  # 缩放到50个字符宽度
    
    for(i = 1; i <= NR; i++) {
        bar_length = int(data[i] * scale)
        printf "%3d |", data[i]
        for(j = 1; j <= bar_length; j++) {
            printf "*"
        }
        print ""
    }
}' numbers.txt

3. 配置管理器

# 高级配置文件解析器
awk '
BEGIN {
    # 支持多种配置格式
    current_section = "global"
}

# 处理注释
/^ *#/ { next }

# 处理空行
/^ *$/ { next }

# 处理节头 [section]
/^\[.*\]$/ {
    sub(/^\[/, "")
    sub(/\]$/, "")
    current_section = $0
    next
}

# 处理键值对
/^[a-zA-Z_]/ && /=/ {
    # 支持变量替换
    gsub(/\${([^}]+)}/, "\\$" var_map[substr($0, RSTART+2, RLENGTH-3)])
    
    split($0, parts, "=")
    key = trim(parts[1])
    value = trim(parts[2])
    
    # 存储配置
    config[current_section "/" key] = value
    var_map[key] = value
}

END {
    # 输出所有配置
    for(key in config) {
        print key " = " config[key]
    }
}

function trim(str) {
    gsub(/^ +| +$/, "", str)
    return str
}' config.ini

这些高级应用展示了 AWK 在系统编程、网络通信、数据库集成等方面的强大能力。掌握这些技能后,你可以用 AWK 构建复杂的系统工具和自动化脚本。

AWK 高级应用手册, AWK 网络编程教程, TCP 客户端通信 awk, AWK 套接字编程指南, 高级 AWK 技术详解, AWK 实战案例分析, 网络编程中的 AWK 应用, AWK 高级技巧与实践, Linux AWK 网络开发, AWK 通信程序开发教程

发表在 linux文章, linux命令 | 标签为 | 留下评论

AWK 中级学习手册

一、内置函数详解

1. 数值函数

# 常用数值函数
awk 'BEGIN {
    print sqrt(16)        # 平方根:4
    print int(3.7)        # 取整:3
    print rand()          # 随机数:0-1之间
    print srand()         # 设置随机种子
    print log(10)         # 自然对数
    print exp(2)          # e的2次方
}'

2. 字符串函数

# 字符串处理函数
awk 'BEGIN {
    str = "Hello World"
    
    # 长度
    print length(str)           # 11
    
    # 子串
    print substr(str, 1, 5)     # Hello
    print substr(str, 7)        # World
    
    # 查找
    print index(str, "World")   # 7 (位置)
    print match(str, /W[a-z]+/) # 7 (正则匹配位置)
    
    # 替换
    print sub(/World/, "AWK", str)  # 替换第一个匹配项
    print gsub(/l/, "L", str)       # 替换所有匹配项
    
    # 分割
    split("a,b,c", arr, ",")
    for(i in arr) print arr[i]      # a b c
}'

3. 时间函数

# 时间相关函数
awk 'BEGIN {
    print systime()             # 当前时间戳
    print strftime("%Y-%m-%d")  # 格式化当前时间
    print strftime("%H:%M:%S", systime())  # 指定时间戳格式化
}'

4. 其他实用函数

# 系统函数
awk 'BEGIN {
    print system("echo Hello")  # 执行系统命令
    print getline               # 从输入读取下一行
}'

二、自定义函数

1. 基本语法

function 函数名(参数列表) {
    函数体
    return 返回值
}

2. 实际示例

# 计算平均值函数
awk '
function avg(arr, n) {
    sum = 0
    for(i = 1; i <= n; i++) {
        sum += arr[i]
    }
    return sum / n
}

{
    scores[NR] = $2
}
END {
    print "平均分:", avg(scores, NR)
}' students.txt
# 字符串处理函数
awk '
function format_name(first, last) {
    return toupper(substr(first,1,1)) substr(first,2) " " toupper(last)
}

{
    print format_name($1, $2)
}' names.txt

三、正则表达式进阶

1. 正则表达式操作符

# 基本正则
awk '/^[0-9]+$/ {print "纯数字:", $0}' file.txt    # 行首到行尾都是数字
awk '/\.[a-zA-Z]{3,4}$/ {print "文件:", $0}' file.txt  # 匹配文件扩展名

# 正则替换
awk '{gsub(/[0-9]+/, "NUM"); print}' file.txt     # 替换所有数字为NUM
awk '{sub(/^.*@/, ""); print}' emails.txt         # 去掉邮箱前缀

2. match函数的高级用法

# 提取匹配内容
awk '{
    if(match($0, /[0-9]{4}-[0-9]{2}-[0-9]{2}/)) {
        date = substr($0, RSTART, RLENGTH)
        print "找到日期:", date
    }
}' logs.txt

# 使用捕获组(GNU awk)
awk '{
    if(match($0, /([0-9]{4})-([0-9]{2})-([0-9]{2})/, arr)) {
        print "年:", arr[1], "月:", arr[2], "日:", arr[3]
    }
}' logs.txt

四、多维数组处理

1. 模拟多维数组

# 使用分隔符模拟二维数组
awk '{
    matrix[NR,1] = $1
    matrix[NR,2] = $2
    matrix[NR,3] = $3
}
END {
    for(row = 1; row <= NR; row++) {
        for(col = 1; col <= 3; col++) {
            printf "%s\t", matrix[row,col]
        }
        print ""
    }
}' data.txt

2. 关联数组的复杂应用

# 统计每个用户在不同日期的操作次数
awk '{
    user = $1
    date = $2
    count[user "," date]++
}
END {
    for(key in count) {
        split(key, parts, ",")
        print parts[1], parts[2], count[key]
    }
}' user_log.txt

五、getline的高级用法

1. 从文件读取

# 读取配置文件
awk '
BEGIN {
    while((getline line < "config.txt") > 0) {
        if(line ~ /=/) {
            split(line, kv, "=")
            config[kv[1]] = kv[2]
        }
    }
    close("config.txt")
}
{
    print $1, config["prefix"] $2
}' data.txt

2. 从管道读取

# 执行命令并读取输出
awk '
BEGIN {
    cmd = "ls -l"
    while((cmd | getline) > 0) {
        if(NF > 8) files[$NF] = $5  # 文件名和大小
    }
    close(cmd)
}
{
    if($1 in files) {
        print $1, files[$1]
    }
}' file_list.txt

六、输入输出控制

1. 输出重定向

# 输出到不同文件
awk '{
    if($2 >= 60) {
        print $0 > "pass.txt"
    } else {
        print $0 > "fail.txt"
    }
}
END {
    close("pass.txt")
    close("fail.txt")
}' scores.txt

2. 格式化输出控制

# 控制输出格式
awk 'BEGIN {
    OFS = "\t"      # 输出字段分隔符
    ORS = "\n---\n" # 输出记录分隔符
}
{
    print $1, $2, $3
}' data.txt

# printf的高级用法
awk '{
    printf "%-10s %8.2f %5d\n", $1, $2, $3
}' sales.txt

七、错误处理与调试

1. 错误检查

# 检查getline返回值
awk '{
    if((getline line) <= 0) {
        print "读取失败或文件结束" > "/dev/stderr"
        exit 1
    }
    print line
}'

2. 调试技巧

# 使用print调试
awk '{
    print "DEBUG: processing line", NR, "with", NF, "fields" > "/dev/stderr"
    # 实际处理逻辑
}' data.txt

# 使用dump变量
awk -d 'BEGIN {var=1; print var}'  # 会输出变量信息到文件

八、性能优化技巧

1. 避免重复计算

# 不好的做法
awk '{
    if(length($1) > 10) {  # 每次都计算长度
        print $1
    }
}'

# 好的做法
awk '{
    len = length($1)       # 只计算一次
    if(len > 10) {
        print $1
    }
}'

2. 合理使用正则

# 避免复杂正则
awk '$0 ~ /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/'

# 简化为字符串比较(如果可能)
awk 'substr($0,1,19) ~ /^[0-9 :-]+$/'

九、实用中级示例

1. 日志分析脚本

#!/usr/bin/awk -f
# 分析Apache访问日志
BEGIN {
    FS = " "
    print "IP地址\t\t访问次数\t流量总计"
}
{
    ip = $1
    bytes = ($10 == "-" ? 0 : $10)
    count[ip]++
    total_bytes[ip] += bytes
}
END {
    for(ip in count) {
        printf "%-15s\t%8d\t%10d\n", ip, count[ip], total_bytes[ip]
    }
}

2. 数据透视表

# 生成销售数据透视表
awk '
BEGIN {
    FS = ","
    print "产品\t一月\t二月\t三月\t总计"
}
NR > 1 {
    product = $1
    month = $2
    sales = $3
    monthly[product,month] += sales
    total[product] += sales
}
END {
    months["一月"] = "一月"; months["二月"] = "二月"; months["三月"] = "三月"
    for(product in total) {
        printf "%s", product
        for(month in months) {
            printf "\t%d", monthly[product,month]
        }
        printf "\t%d\n", total[product]
    }
}' sales.csv

3. 配置文件解析器

# 解析INI格式配置文件
awk '
BEGIN { section = "global" }
/^\[.*\]$/ { 
    sub(/^\[/, ""); 
    sub(/\]$/, ""); 
    section = $0; 
    next 
}
/^[a-zA-Z]/ && /=/ { 
    split($0, kv, "="); 
    config[section "/" kv[1]] = kv[2] 
}
END {
    for(key in config) {
        print key " = " config[key]
    }
}' config.ini

十、GNU AWK 扩展功能

1. 高精度计算

# 使用 -M 选项进行高精度计算
awk -M 'BEGIN {
    print 1/3            # 精确到小数点后多位
    print 2^100          # 大数计算
}'

2. 包含文件

# functions.awk
function square(x) { return x * x }
function cube(x) { return x * x * x }

# main.awk
@include "functions.awk"
BEGIN {
    print square(5)  # 25
    print cube(3)    # 27
}

3. 扩展库使用

# 使用JSON库(如果安装)
awk -l json 'BEGIN {
    # JSON处理功能
}'

这些中级内容涵盖了 AWK 的核心中高级功能,掌握后可以处理更复杂的文本处理任务。

发表在 linux命令, linux文章 | 标签为 | 留下评论

AWK 初级学习手册(修订版)


AWK 初级学习手册(修订版)

一、认识 AWK

  1. 什么是 AWK
  • 一款强大的文本处理工具,特别适合分析结构化文本(如日志、表格数据等)
  • 名称来源于三位开发者:Alfred Aho、Peter Weinberger、Brian Kernighan
  • 核心能力:按行读取、按字段分割、支持条件判断和计算
  1. 安装与验证
  • 检查是否预装:awk --version(Linux 通常自带)
  • 安装命令:Ubuntu/Debian 用 apt install gawk,CentOS 用 yum install gawk
  • 查看帮助:awk -hawk --help(获取完整选项说明)
  • 入门测试:awk 'BEGIN {print "Hello AWK"}'(输出欢迎语)

二、AWK 基本语法与选项

  1. 核心用法格式
   # 方式1:直接指定处理程序
   awk [选项] '处理程序' 文件名...

   # 方式2:从文件读取处理程序
   awk [选项] -f 程序文件.awk 文件名...
  1. 常用选项说明(基于 awk -h 输出)
选项长选项说明
-f progfile--file=progfile从文件中读取 AWK 程序
-F fs--field-separator=fs设置字段分隔符(替代默认空格或制表符)
-v var=val--assign=var=val在 AWK 程序开始前设置变量值
-e 'program'--source='program'直接提供 AWK 程序代码(可多次使用)
-h--help显示帮助信息
-V--version显示版本信息
-i includefile--include=includefile包含其他 AWK 文件(GNU 扩展)
-l library--load=library加载扩展库(GNU 扩展)
-M--bignum使用任意精度算术(GNU 扩展)
-P--posix启用 POSIX 兼容模式
-r--re-interval允许正则表达式中的区间操作符(GNU 扩展)
-S--sandbox沙盒模式,禁止系统调用(安全增强)
-d[file]--dump-variables[=file]将所有变量及其值写入文件(调试用途)
-D[file]--debug[=file]启动调试器(GNU 扩展)
-o[file]--pretty-print[=file]格式化打印 AWK 程序源码(GNU 扩展)
  1. 示例:选项使用
# 使用 -v 设置变量
awk -v threshold=50 '$2 > threshold {print $1}' data.txt

# 多段程序组合(使用 -e)
awk -e 'BEGIN {print "Start"}' -e '{print NR, $0}' file.txt

# 包含外部 AWK 文件
awk -i mathlib.awk '{print sqrt($1)}' numbers.txt

三、行与字段处理

  1. 行处理基础
  • 逐行读取:AWK 默认按行处理文件
  • 行号变量 NR:表示当前行号
    示例:awk '{print NR, $0}' file.txt 打印行号+整行
  1. 字段分割与访问
  • 默认分隔符:空格或制表符
  • 字段变量:$0(整行)、$1(第1字段)、$2(第2字段)…$NF(最后字段)
  • 字段总数 NF:示例:awk '{print "共", NF, "个字段,最后字段:"$NF}' file.txt
  1. 自定义分隔符(-F 选项)
  • 示例:awk -F ',' '{print $1, $3}' data.csv(按逗号分割CSV)
  • 多分隔符:awk -F '[: ]' '{print $1}' /etc/passwd(按冒号或空格分割)

四、模式(筛选行的条件)

  1. 正则表达式模式
  • 格式:/正则内容/(匹配包含该内容的行)
  • 示例:
    • 匹配 “success”:awk '/success/ {print}' log.txt
    • 排除 “error”:awk '!/error/ {print}' log.txt
  1. 比较表达式模式
  • 运算符:==!=>>=<<=
  • 示例:
    • 第2字段>100:awk '$2 > 100 {print}' data.txt
    • 第1字段是”admin”:awk '$1 == "admin" {print}' user.txt
  1. 行号范围模式
  • NR 指定:awk 'NR >=5 && NR <=10 {print}' file.txt(打印5-10行)
  1. BEGIN 与 END 块
  • BEGIN:处理文件前执行(如打印表头)
  • END:处理文件后执行(如汇总结果)
  • 示例:awk 'BEGIN {print "开始"} {print} END {print "共处理", NR, "行"}' file.txt

五、动作(处理行的操作)

  1. 打印操作
  • print:直接输出(默认换行),如 awk '{print $1, $3}' file.txt
  • printf:格式化输出,如 awk '{printf "姓名:%s,年龄:%d\n", $1, $2}' student.txt
    • 格式符:%s(字符串)、%d(整数)、%f(浮点数)
  1. 变量与运算
  • 自定义变量:直接赋值(无需声明类型),如 sum = $2 + $3
  • 常用运算:+ - * /(加减乘除)、+=(累加)
  • 示例:计算平均值:awk 'BEGIN {total=0} {total+=$2} END {print total/NR}' scores.txt
  1. 条件判断
  • 语法:if (条件) {动作} else {动作}
  • 示例:awk '{if ($2>=60) print $1,"及格"; else print $1,"不及格"}' scores.txt

六、数组基础

  1. 关联数组特性
  • 键可以是字符串或数字,无需声明长度
  • 赋值:数组名[键] = 值,如 awk 'BEGIN {arr["name"]="Tom"; print arr["name"]}'
  1. 数组遍历
  • 语法:for (键 in 数组) {处理}
  • 示例:awk 'BEGIN {arr[1]="a"; arr[2]="b"; for(i in arr) print i, arr[i]}'
  1. 简单统计
  • 示例:统计单词出现次数:echo "a b a c" | awk '{for(i=1;i<=NF;i++) count[$i]++} END {for(w in count) print w, count[w]}'

七、官方示例解析

根据 awk -h 提供的示例,理解核心用法:

  1. 求和示例
   gawk '{ sum += $1 }; END { print sum }' file
  • 功能:计算文件中第1列所有数值的总和
  • 解析:每行执行 sum += $1(累加第1字段),最后在 END 块打印总和
  1. 提取用户名示例
   gawk -F: '{ print $1 }' /etc/passwd
  • 功能:从系统用户文件中提取所有用户名
  • 解析:用 -F: 指定冒号为分隔符,打印每行第1字段(用户名)

八、初级实战示例

  1. 内容提取
  • 提取日志IP:awk '{print $1}' access.log
  • 提取CSV列:awk -F ',' '{print $2, $4}' data.csv
  1. 数据统计
  • 求和:awk '{sum+=$3} END {print sum}' numbers.txt
  • 统计行数:awk 'END {print NR}' file.txt
  1. 格式转换
  • 替换分隔符:awk 'BEGIN {OFS=","} {print $1,$2,$3}' space.txt(空格转逗号)

九、常见问题与解决

  1. 字段索引错误:区分 $0(整行)和 $1(第1字段)
  2. 分隔符问题:用 -F 明确指定分隔符(如处理CSV必须指定 ,
  3. 变量未初始化:计算前用 BEGIN 初始化变量(如 BEGIN {sum=0}
  4. 过滤空行awk 'NF>0' file.txtNF>0 表示非空行)

十、GNU 扩展功能简述(进阶方向)

以下是一些 GNU AWK 的扩展功能,适用于复杂场景:

选项功能描述
-M支持高精度运算(如大数计算)
-S沙盒模式,禁止危险操作(如 system()
-D启动调试器(类似 gdb)
-d导出变量信息(用于调试)
-o美化输出 AWK 程序源码
-i包含其他 AWK 脚本文件
-l加载扩展库(如网络、数据库接口)

十一、推荐练习资源


  • GNU AWK Manual
  • LeetCode AWK 编程题(适合进阶)
  • 实战项目:日志分析、CSV 处理、报表生成等

发表在 linux命令 | 留下评论

AWK 初级学习手册-终极基础版

AWK 初级学习手册-终极基础版

一、认识 AWK

  1. 什么是 AWK
    • 一款文本处理工具,擅长分析结构化文本(日志、表格等)
    • 名称源自三位开发者(Aho、Weinberger、Kernighan)的首字母
    • 核心能力:按行读取、按字段分割、支持条件判断和计算
  2. 安装与验证
    • 检查是否预装:awk --version(Linux 通常自带)
    • 安装命令:Ubuntu/Debian 用 apt install gawk,CentOS 用 yum install gawk
    • 查看帮助:awk -h 或 awk --help(获取完整选项说明)
    • 入门测试:awk 'BEGIN {print "Hello AWK"}'(输出欢迎语)

二、AWK 基本语法与选项

  1. 核心用法格式# 方式1:直接指定处理程序 awk [选项] '处理程序' 文件名... # 方式2:从文件读取处理程序 awk [选项] -f 程序文件.awk 文件名...
  2. 常用选项说明选项长选项说明-f progfile--file=progfile从文件读取 AWK 程序-F fs--field-separator=fs指定字段分隔符(替代默认空格)-v var=val--assign=var=val定义变量并赋值(处理前生效)-e 'program'--source='program'直接指定 AWK 程序文本-h--help显示帮助信息-V--version显示版本信息
  3. 示例:选项使用# 用 -v 定义变量,计算时使用 awk -v base=10 '{print $1 + base}' numbers.txt # 用 -e 直接指定程序(适合短逻辑) awk -e '{print NR, $0}' file.txt # 用 -f 从脚本文件读取程序 awk -f process.awk data.txt

三、行与字段处理

  1. 行处理基础
    • 逐行读取:AWK 默认按行处理文件
    • 行号变量 NR:表示当前行号(示例:awk '{print NR, $0}' file.txt 打印行号+整行)
  2. 字段分割与访问
    • 默认分隔符:空格或制表符
    • 字段变量:$0(整行)、$1(第1字段)、$2(第2字段)…$NF(最后字段)
    • 字段总数 NF:示例:awk '{print "共", NF, "个字段,最后字段:"$NF}' file.txt
  3. 自定义分隔符(-F 选项)
    • 示例:awk -F ',' '{print $1, $3}' data.csv(按逗号分割CSV)
    • 多分隔符:awk -F '[: ]' '{print $1}' /etc/passwd(按冒号或空格分割)

四、模式(筛选行的条件)

  1. 正则表达式模式
    • 格式:/正则内容/(匹配包含该内容的行)
    • 示例:
      • 匹配 “success”:awk '/success/ {print}' log.txt
      • 排除 “error”:awk '!/error/ {print}' log.txt
  2. 比较表达式模式
    • 运算符:==(等于)、!=(不等于)、>(大于)等
    • 示例:
      • 第2字段>100:awk '$2 > 100 {print}' data.txt
      • 第1字段是”admin”:awk '$1 == "admin" {print}' user.txt
  3. 行号范围模式
    • 用 NR 指定:awk 'NR >=5 && NR <=10 {print}' file.txt(打印5-10行)
  4. BEGIN 与 END 块
    • BEGIN:处理文件前执行(如打印表头)
    • END:处理文件后执行(如汇总结果)
    • 示例:awk 'BEGIN {print "开始"} {print} END {print "共处理", NR, "行"}' file.txt

五、动作(处理行的操作)

  1. 打印操作
    • print:直接输出(默认换行),如 awk '{print $1, $3}' file.txt
    • printf:格式化输出,如 awk '{printf "姓名:%s,年龄:%d\n", $1, $2}' student.txt
      • 格式符:%s(字符串)、%d(整数)、%f(浮点数)
  2. 变量与运算
    • 自定义变量:直接赋值(无需声明类型),如 sum = $2 + $3
    • 常用运算:+ - * /(加减乘除)、+=(累加)
    • 示例:计算平均值:awk 'BEGIN {total=0} {total+=$2} END {print total/NR}' scores.txt
  3. 条件判断
    • 语法:if (条件) {动作} else {动作}
    • 示例:awk '{if ($2>=60) print $1,"及格"; else print $1,"不及格"}' scores.txt

六、数组基础

  1. 关联数组特性
    • 键可以是字符串或数字,无需声明长度
    • 赋值:数组名[键] = 值,如 awk 'BEGIN {arr["name"]="Tom"; print arr["name"]}'
  2. 数组遍历
    • 语法:for (键 in 数组) {处理}
    • 示例:awk 'BEGIN {arr[1]="a"; arr[2]="b"; for(i in arr) print i, arr[i]}'
  3. 简单统计
    • 示例:统计单词出现次数:echo "a b a c" | awk '{for(i=1;i<=NF;i++) count[$i]++} END {for(w in count) print w, count[w]}'

七、官方示例解析

根据 awk -h 提供的示例,理解核心用法:

  1. 求和示例gawk '{ sum += $1 }; END { print sum }' file
    • 功能:计算文件中第1列所有数值的总和
    • 解析:每行执行 sum += $1(累加第1字段),最后在 END 块打印总和
  2. 提取用户名示例gawk -F: '{ print $1 }' /etc/passwd
    • 功能:从系统用户文件中提取所有用户名
    • 解析:用 -F: 指定冒号为分隔符,打印每行第1字段(用户名)

八、初级实战示例

  1. 内容提取
    • 提取日志IP:awk '{print $1}' access.log
    • 提取CSV列:awk -F ',' '{print $2, $4}' data.csv
  2. 数据统计
    • 求和:awk '{sum+=$3} END {print sum}' numbers.txt
    • 统计行数:awk 'END {print NR}' file.txt
  3. 格式转换
    • 替换分隔符:awk 'BEGIN {OFS=","} {print $1,$2,$3}' space.txt(空格转逗号)

九、常见问题与解决

  1. 字段索引错误:区分 $0(整行)和 $1(第1字段)
  2. 分隔符问题:用 -F 明确指定分隔符(如处理CSV必须指定 ,
  3. 变量未初始化:计算前用 BEGIN 初始化变量(如 BEGIN {sum=0}
  4. 过滤空行:awk 'NF>0' file.txtNF>0 表示非空行)
发表在 linux文章, linux命令 | 标签为 | 留下评论

grep输出显示文件名的方法总结

grep输出显示文件名的方法总结,使用grep查找时,如何在输出中显示文件名?详解grep显示文件名的方法。在不同场景下的文件名添加方法;单文件、多文件、目录等场景;

grep 可以在输出的匹配行前加上文件名。这是 grep 的默认行为之一,具体取决于使用方式:

  1. 当搜索多个文件时grep 会自动在匹配行前加上文件名:# 搜索多个文件时,默认显示文件名 grep "pattern" file1.txt file2.txt file3.txt 输出会类似:file1.txt:这是包含pattern的行 file2.txt:这是另一个包含pattern的行
  2. 当只搜索单个文件时,默认不显示文件名,可通过 -H 选项强制显示:# 单个文件搜索时,强制显示文件名 grep -H "pattern" file.txt 输出会显示:file.txt:这是包含pattern的行
  3. 当搜索目录下所有文件时(结合 -r 递归选项),也会自动显示文件名:# 递归搜索目录,显示文件名 grep -r "pattern" ./directory/

相关常用选项:

  • -H:总是显示文件名(即使只搜索一个文件)
  • -h:从不显示文件名(即使搜索多个文件)
  • -n:同时显示行号(可与文件名一起显示)

示例(同时显示文件名和行号):

grep -Hn "error" /var/log/*.log

输出会类似:

/var/log/syslog:123:2023-08-11 10:00:00 [error] 系统错误信息
/var/log/auth.log:45:2023-08-11 10:05:00 [error
发表在 linux文章 | 标签为 | 留下评论

如何用taskset绑定CPU核心以提升性能

如何用taskset绑定CPU核心以提升性能

相关文章:​​taskset 命令CPU 核绑定

使用taskset绑定CPU核心可显著提升性能,优化系统资源分配,提高程序运行效率。要将shell命令绑定到特定CPU核心运行,可以使用taskset命令。taskset允许你设置或检索进程的CPU亲和性,即限制进程只能在指定的CPU核心上运行。

使用方法如下:

  1. 直接运行命令并绑定到特定CPU:
# 将命令绑定到CPU 0运行(CPU编号从0开始)
taskset -c 0 你的命令

# 例如,将ls命令绑定到CPU 1运行
taskset -c 1 ls

# 将命令绑定到多个CPU,比如CPU 0和1
taskset -c 0,1 你的命令

# 将命令绑定到一个CPU范围,比如CPU 0到3
taskset -c 0-3 你的命令
  1. 为已运行的进程设置CPU亲和性:
# 首先找到进程ID(PID)
ps -ef | grep 进程名

# 然后将进程绑定到特定CPU
taskset -c 0 -p 进程ID

示例:将ping命令绑定到CPU 2运行:

taskset -c 2 ping www.example.com

这在需要控制进程资源使用、进行性能测试或避免特定进程之间资源竞争时非常有用。不同CPU核心之间可能存在性能差异,合理的CPU绑定可以优化系统整体性能。

发表在 linux文章, linux命令 | 标签为 | 留下评论

fchown系统调用及示例

fchown – 修改文件所有者(通过文件描述符)

fchown系统调用及示例:通过文件描述符修改文件所有者,包含使用示例与参数说明。fchown 系统调用, fchown 函数用法, 修改文件所有者系统调用, fchown 示例代码, Linux fchown 命令, 系统调用 fchown 详解, 如何使用 fchown, fchown 和 chown 区别, Linux 文件权限修改, 系统调用函数 fchown

1. 函数介绍

fchown 是一个 Linux 系统调用,用于修改已打开文件的所有者(owner)和所属组(group)。与 chown 不同,fchown 通过文件描述符而不是文件路径来指定要修改的文件,这样可以避免在多线程环境中因文件重命名或删除而导致的竞态条件。

你可以把它想象成通过”门把手”而不是”地址”来找到房子并更换房主,这样即使房子的地址变了,你仍然可以通过门把手找到它。

https://blog.csdn.net/timberwolf007/article/details/150869147?spm=1011.2415.3001.5331

2. 函数原型

#include <unistd.h>

int fchown(int fd, uid_t owner, gid_t group);

3. 功能

修改通过文件描述符指定的文件的所有者和所属组。如果只想修改所有者或所属组中的一个,可以将另一个参数设置为 -1。

4. 参数

  • int fd: 文件描述符,通过 open() 等函数获得
  • uid_t owner: 新的所有者用户 ID
    • 有效的用户 ID:设置为指定用户所有
    • (uid_t)-1:保持当前所有者不变
  • gid_t group: 新的所属组 ID
    • 有效的组 ID:设置为指定组所有
    • (gid_t)-1:保持当前所属组不变

5. 返回值

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

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

  • chown(): 通过文件路径修改文件所有者
  • fchownat(): 相对于目录文件描述符修改文件所有者
  • lchown(): 修改符号链接本身的所有者(而不是链接指向的文件)
  • getuid(): 获取当前用户 ID
  • getgid(): 获取当前组 ID

7. 示例代码

示例1:基本使用 – 修改文件所有者

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

int main() {
    int fd;
    int ret;
    
    // 创建测试文件
    fd = open("test_file.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建文件失败");
        exit(EXIT_FAILURE);
    }
    
    printf("成功创建文件,文件描述符: %d\n", fd);
    
    // 写入一些内容
    const char *content = "这是一个测试文件\n";
    write(fd, content, strlen(content));
    
    // 修改文件所有者为 root (uid=0),组为 root (gid=0)
    // 注意:这需要适当的权限(通常是 root 权限)
    ret = fchown(fd, 0, 0);
    if (ret == -1) {
        if (errno == EPERM) {
            printf("权限不足:需要 root 权限才能将文件所有者改为 root\n");
        } else {
            perror("fchown 调用失败");
        }
    } else {
        printf("成功将文件所有者修改为 root:root\n");
    }
    
    // 只修改所有者,保持组不变
    ret = fchown(fd, getuid(), -1);  // -1 表示不修改组
    if (ret == -1) {
        perror("修改所有者失败");
    } else {
        printf("成功将文件所有者修改为当前用户\n");
    }
    
    // 只修改组,保持所有者不变
    ret = fchown(fd, -1, getgid());  // -1 表示不修改所有者
    if (ret == -1) {
        perror("修改组失败");
    } else {
        printf("成功将文件组修改为当前组\n");
    }
    
    close(fd);
    printf("文件已关闭\n");
    
    return 0;
}

示例2:检查权限并安全修改所有者

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

int main() {
    int fd;
    int ret;
    struct stat file_info;
    
    // 打开系统文件进行测试
    fd = open("/etc/passwd", O_RDONLY);
    if (fd == -1) {
        perror("打开 /etc/passwd 失败");
        exit(EXIT_FAILURE);
    }
    
    printf("成功打开文件,文件描述符: %d\n", fd);
    
    // 获取文件当前信息
    if (fstat(fd, &file_info) == -1) {
        perror("获取文件信息失败");
        close(fd);
        exit(EXIT_FAILURE);
    }
    
    printf("文件当前信息:\n");
    printf("  所有者 UID: %d\n", file_info.st_uid);
    printf("  所属组 GID: %d\n", file_info.st_gid);
    printf("  文件大小: %ld 字节\n", file_info.st_size);
    
    // 尝试修改所有者(这通常会失败,除非我们有适当权限)
    ret = fchown(fd, 1000, 1000);  // 尝试修改为 UID 1000, GID 1000
    if (ret == -1) {
        switch (errno) {
            case EPERM:
                printf("权限拒绝:修改文件所有者需要适当权限\n");
                break;
            case EBADF:
                printf("无效的文件描述符\n");
                break;
            case EROFS:
                printf("文件位于只读文件系统上\n");
                break;
            case EIO:
                printf("I/O 错误\n");
                break;
            default:
                printf("其他错误: %s\n", strerror(errno));
                break;
        }
    } else {
        printf("成功修改文件所有者\n");
    }
    
    close(fd);
    printf("文件已关闭\n");
    
    return 0;
}
发表在 linux文章 | 留下评论

fadvise64系统调用及示例

fadvise64 – 文件访问建议

函数介绍

fadvise64是一个Linux系统调用,用于向内核提供关于文件访问模式的建议。它帮助内核优化文件I/O操作,提高性能。fadvise64系统调用详解,提供文件访问建议函数示例与使用方法,优化Linux性能必备。

关键词:fadvise64 系统调用, fadvise64 函数用法, linux fadvise64 详解, fadvise64 示例代码, linux 系统调用 fadvise64, fadvise64 文件访问建议, 如何使用 fadvise64, fadvise64 系统调用介绍, linux fadvise64 函数说明, fadvise64 作用与用法

函数原型

#include <fcntl.h>
#include <sys/syscall.h>
#include <unistd.h>

int fadvise64(int fd, off_t offset, off_t len, int advice);

功能

向内核提供文件访问模式建议,帮助内核优化缓存和预读策略。

参数

  • int fd: 文件描述符
  • off_t offset: 建议适用的文件起始偏移量
  • off_t len: 建议适用的文件长度(0表示到文件末尾)
  • int advice: 访问建议类型
    • POSIX_FADV_NORMAL: 普通访问模式(默认)
    • POSIX_FADV_SEQUENTIAL: 顺序访问
    • POSIX_FADV_RANDOM: 随机访问
    • POSIX_FADV_NOREUSE: 数据只访问一次
    • POSIX_FADV_WILLNEED: 数据即将被访问
    • POSIX_FADV_DONTNEED: 数据不再需要

返回值

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

特殊限制

  • 需要Linux 2.5.60以上内核支持
  • 某些文件系统可能不完全支持
  • 建议只是提示,内核可能忽略

相似函数

  • madvise(): 内存访问建议
  • readahead(): 文件预读
  • posix_fadvise(): POSIX标准版本

示例代码

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

// 系统调用包装
static int fadvise64_wrapper(int fd, off_t offset, off_t len, int advice) {
    return syscall(__NR_fadvise64, fd, offset, len, advice);
}

// 创建测试文件
int create_test_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) {
        memset(buffer, 'A', 4096);
        for (size_t i = 0; i < size; i += 4096) {
            size_t write_size = (size - i > 4096) ? 4096 : (size - i);
            write(fd, buffer, write_size);
        }
        free(buffer);
    }
    
    return fd;
}

int main() {
    int fd;
    int result;
    
    printf("=== Fadvise64 函数示例 ===\n");
    
    // 示例1: 基本使用
    printf("\n示例1: 基本使用\n");
    
    // 创建大文件用于测试
    fd = create_test_file("test_fadvise64.dat", 1024 * 1024); // 1MB
    if (fd == -1) {
        exit(EXIT_FAILURE);
    }
    printf("创建测试文件: test_fadvise64.dat (1MB)\n");
    
    close(fd);
    
    // 重新打开文件进行测试
    fd = open("test_fadvise64.dat", O_RDONLY);
    if (fd == -1) {
        perror("打开测试文件失败");
        unlink("test_fadvise64.dat");
        exit(EXIT_FAILURE);
    }
    printf("打开测试文件进行fadvise64测试\n");
    
    // 示例2: 不同的访问建议
    printf("\n示例2: 不同的访问建议\n");
    
    // POSIX_FADV_NORMAL - 普通访问模式
    result = fadvise64_wrapper(fd, 0, 0, POSIX_FADV_NORMAL);
    if (result == 0) {
        printf("设置POSIX_FADV_NORMAL成功\n");
    } else {
        printf("设置POSIX_FADV_NORMAL失败: %s\n", strerror(errno));
    }
    
    // POSIX_FADV_SEQUENTIAL - 顺序访问
    result = fadvise64_wrapper(fd, 0, 1024*1024, POSIX_FADV_SEQUENTIAL);
    if (result == 0) {
        printf("设置POSIX_FADV_SEQUENTIAL成功\n");
        printf("提示内核将进行顺序访问,优化预读策略\n");
    }
    
    // POSIX_FADV_RANDOM - 随机访问
    result = fadvise64_wrapper(fd, 0, 1024*1024, POSIX_FADV_RANDOM);
    if (result == 0) {
        printf("设置POSIX_FADV_RANDOM成功\n");
        printf("提示内核将进行随机访问,减少预读\n");
    }
    
    // POSIX_FADV_WILLNEED - 数据即将被访问
    result = fadvise64_wrapper(fd, 0, 64*1024, POSIX_FADV_WILLNEED);
    if (result == 0) {
        printf("设置POSIX_FADV_WILLNEED成功\n");
        printf("提示内核预读前64KB数据\n");
    }
    
    // POSIX_FADV_DONTNEED - 数据不再需要
    result = fadvise64_wrapper(fd, 0, 64*1024, POSIX_FADV_DONTNEED);
    if (result == 0) {
        printf("设置POSIX_FADV_DONTNEED成功\n");
        printf("提示内核可以丢弃前64KB数据的缓存\n");
    }
    
    // POSIX_FADV_NOREUSE - 数据只访问一次
    result = fadvise64_wrapper(fd, 64*1024, 64*1024, POSIX_FADV_NOREUSE);
    if (result == 0) {
        printf("设置POSIX_FADV_NOREUSE成功\n");
        printf("提示内核64KB-128KB范围的数据只访问一次\n");
    }
    
    // 示例3: 错误处理演示
    printf("\n示例3: 错误处理演示\n");
    
    // 使用无效的文件描述符
    result = fadvise64_wrapper(999, 0, 1024, POSIX_FADV_NORMAL);
    if (result == -1) {
        if (errno == EBADF) {
            printf("无效文件描述符错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 使用无效的建议类型
    result = fadvise64_wrapper(fd, 0, 1024, 999);
    if (result == -1) {
        if (errno == EINVAL) {
            printf("无效建议类型错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 使用负的偏移量
    result = fadvise64_wrapper(fd, -1024, 1024, POSIX_FADV_NORMAL);
    if (result == -1) {
        printf("负偏移量处理: %s\n", strerror(errno));
    }
    
    // 示例4: 实际使用场景演示
    printf("\n示例4: 实际使用场景演示\n");
    
    // 场景1: 大文件顺序读取
    printf("场景1: 大文件顺序读取优化\n");
    printf("处理大日志文件的代码示例:\n");
    printf("int process_large_log(const char* filename) {\n");
    printf("    int fd = open(filename, O_RDONLY);\n");
    printf("    if (fd == -1) return -1;\n");
    printf("    \n");
    printf("    // 提示内核将顺序访问整个文件\n");
    printf("    posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);\n");
    printf("    \n");
    printf("    // 读取处理文件...\n");
    printf("    char buffer[8192];\n");
    printf("    ssize_t bytes;\n");
    printf("    while ((bytes = read(fd, buffer, sizeof(buffer))) > 0) {\n");
    printf("        // 处理数据\n");
    printf("    }\n");
    printf("    \n");
    printf("    close(fd);\n");
    printf("    return 0;\n");
    printf("}\n\n");
    
    // 场景2: 随机访问数据库文件
    printf("场景2: 随机访问数据库文件\n");
    printf("数据库文件访问优化:\n");
    printf("int access_database_file(const char* filename) {\n");
    printf("    int fd = open(filename, O_RDWR);\n");
    printf("    if (fd == -1) return -1;\n");
    printf("    \n");
    printf("    // 提示内核将随机访问文件\n");
    printf("    posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM);\n");
    printf("    \n");
    printf("    // 根据需要预读特定区域\n");
    printf("    posix_fadvise(fd, index_offset, index_size, POSIX_FADV_WILLNEED);\n");
    printf("    \n");
    printf("    // 访问完成后释放不需要的缓存\n");
    printf("    posix_fadvise(fd, old_data_offset, old_data_size, POSIX_FADV_DONTNEED);\n");
    printf("    \n");
    printf("    close(fd);\n");
    printf("    return 0;\n");
    printf("}\n\n");
    
    // 示例5: 不同建议类型的效果说明
    printf("示例5: 不同建议类型的效果说明\n");
    
    printf("POSIX_FADV_NORMAL:\n");
    printf("  - 默认访问模式\n");
    printf("  - 使用系统默认的预读和缓存策略\n");
    printf("  - 适用于一般情况\n\n");
    
    printf("POSIX_FADV_SEQUENTIAL:\n");
    printf("  - 优化顺序访问\n");
    printf("  - 增加预读量\n");
    printf("  - 适用于大文件顺序读取\n");
    printf("  - 提高顺序读取性能\n\n");
    
    printf("POSIX_FADV_RANDOM:\n");
    printf("  - 优化随机访问\n");
    printf("  - 减少或禁用预读\n");
    printf("  - 适用于数据库、索引文件\n");
    printf("  - 减少不必要的内存占用\n\n");
    
    printf("POSIX_FADV_NOREUSE:\n");
    printf("  - 数据只访问一次\n");
    printf("  - 访问后尽快释放缓存\n");
    printf("  - 适用于一次性处理的大文件\n");
    printf("  - 节省内存资源\n\n");
    
    printf("POSIX_FADV_WILLNEED:\n");
    printf("  - 数据即将被访问\n");
    printf("  - 提前预读数据到缓存\n");
    printf("  - 适用于已知访问模式的场景\n");
    printf("  - 减少实际访问时的等待\n\n");
    
    printf("POSIX_FADV_DONTNEED:\n");
    printf("  - 数据不再需要\n");
    printf("  - 尽快释放缓存空间\n");
    printf("  - 适用于处理完成的数据\n");
    printf("  - 释放系统资源\n\n");
    
    // 示例6: 性能测试演示
    printf("示例6: 性能影响演示\n");
    
    printf("fadvise64对性能的影响:\n");
    printf("1. 正确使用可显著提高I/O性能\n");
    printf("2. 错误使用可能导致性能下降\n");
    printf("3. 效果因文件系统和硬件而异\n");
    printf("4. 大文件效果更明显\n");
    printf("5. 需要根据实际访问模式选择\n\n");
    
    // 示例7: 实际应用建议
    printf("示例7: 实际应用建议\n");
    
    printf("使用fadvise64的最佳实践:\n");
    printf("1. 在文件打开后尽早设置建议\n");
    printf("2. 根据实际访问模式选择合适的建议\n");
    printf("3. 对于大文件效果更明显\n");
    printf("4. 不要过度使用,避免增加系统负担\n");
    printf("5. 在长时间运行的应用中适时调整\n");
    printf("6. 测试不同建议对性能的影响\n\n");
    
    printf("常见应用场景:\n");
    printf("- 大文件处理和分析\n");
    printf("- 数据库系统\n");
    printf("- 日志处理系统\n");
    printf("- 备份和归档工具\n");
    printf("- 媒体播放器\n");
    printf("- 科学计算应用\n\n");
    
    // 示例8: 与相关函数的对比
    printf("示例8: 与相关函数的对比\n");
    
    printf("fadvise64 vs madvise:\n");
    printf("fadvise64:\n");
    printf("  - 针对文件I/O\n");
    printf("  - 影响文件缓存策略\n");
    printf("  - 在文件描述符上操作\n\n");
    
    printf("madvise:\n");
    printf("  - 针对内存映射\n");
    printf("  - 影响内存管理策略\n");
    printf("  - 在内存地址上操作\n\n");
    
    printf("fadvise64 vs readahead:\n");
    printf("fadvise64:\n");
    printf("  - 更通用的建议机制\n");
    printf("  - 支持多种访问模式\n");
    printf("  - 可以指定文件区域\n\n");
    
    printf("readahead:\n");
    printf("  - 专门用于预读\n");
    printf("  - 立即执行预读操作\n");
    printf("  - 较为直接但不够灵活\n\n");
    
    // 清理资源
    close(fd);
    unlink("test_fadvise64.dat");
    
    printf("总结:\n");
    printf("fadvise64是Linux提供的文件访问优化机制\n");
    printf("通过向内核提供访问建议来优化性能\n");
    printf("支持多种访问模式的优化\n");
    printf("是处理大文件和特定访问模式的重要工具\n");
    printf("需要根据实际应用场景合理使用\n");
    
    return 0;
}
发表在 linux文章 | 留下评论

setpgid系统调用及示例

setpgid 函数详解

setpgid 是Linux系统调用,用于设置进程的进程组ID(Process Group ID)。进程组是进程的集合,用于信号分发和作业控制。通过设置进程组,可以将相关进程组织在一起,便于统一管理和控制。

1. 函数介绍

setpgid 是Linux系统调用,用于设置进程的进程组ID(Process Group ID)。进程组是进程的集合,用于信号分发和作业控制。通过设置进程组,可以将相关进程组织在一起,便于统一管理和控制。

setpgid系统调用LinuxGuide

setpgid系统调用及示例_setpgid函数作用-CSDN博客

2. 函数原型

#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);

3. 功能

setpgid 将指定进程(由pid标识)加入到指定的进程组(由pgid标识)。如果进程组不存在,则创建新的进程组。这个函数主要用于作业控制和信号管理。

4. 参数

  • pid_t pid: 目标进程ID(0表示当前进程)
  • pid_t pgid: 进程组ID(0表示使用pid作为进程组ID)

5. 返回值

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

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

  • getpgid: 获取进程的进程组ID
  • setpgrp: 设置进程组(等同于setpgid(0,0))
  • getsid: 获取会话ID
  • setsid: 创建新会话

7. 示例代码

示例1:基础setpgid使用

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

/**
 * 显示进程组信息
 */
void show_process_group_info() {
    pid_t pid = getpid();
    pid_t pgid = getpgid(0);
    pid_t ppid = getppid();
    
    printf("进程ID: %d\n", pid);
    printf("父进程ID: %d\n", ppid);
    printf("进程组ID: %d\n", pgid);
    printf("会话ID: %d\n", getsid(0));
    printf("\n");
}

/**
 * 演示基础setpgid使用方法
 */
int demo_setpgid_basic() {
    pid_t original_pgid, new_pgid;
    int result;
    
    printf("=== 基础setpgid使用示例 ===\n");
    
    // 显示原始进程组信息
    printf("1. 原始进程组信息:\n");
    show_process_group_info();
    original_pgid = getpgid(0);
    
    // 将当前进程移动到新的进程组
    printf("2. 创建新的进程组:\n");
    pid_t new_group_id = getpid();  // 使用当前进程ID作为新进程组ID
    
    printf("   尝试将进程 %d 移动到进程组 %d\n", getpid(), new_group_id);
    result = setpgid(0, new_group_id);
    
    if (result == 0) {
        printf("   ✓ 成功创建并加入新进程组\n");
        
        // 验证设置结果
        new_pgid = getpgid(0);
        printf("   新的进程组ID: %d\n", new_pgid);
        
        if (new_pgid == new_group_id) {
            printf("   ✓ 进程组设置正确\n");
        } else {
            printf("   ✗ 进程组设置可能有问题\n");
        }
        
        show_process_group_info();
    } else {
        printf("   ✗ 创建新进程组失败: %s\n", strerror(errno));
        if (errno == EACCES) {
            printf("   原因:不允许从会话领导进程更改进程组\n");
        } else if (errno == EINVAL) {
            printf("   原因:进程ID或进程组ID无效\n");
        } else if (errno == EPERM) {
            printf("   原因:权限不足\n");
        } else if (errno == ESRCH) {
            printf("   原因:指定的进程不存在\n");
        }
    }
    
    return 0;
}

int main() {
    return demo_setpgid_basic();
}

示例2:父子进程组管理

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

/**
 * 子进程函数
 */
void child_process(int child_id) {
    printf("子进程 %d 启动\n", getpid());
    printf("  子进程 %d 的原始进程组: %d\n", getpid(), getpgid(0));
    
    // 子进程创建自己的进程组
    pid_t new_pgid = getpid();
    printf("  子进程 %d 尝试创建新进程组: %d\n", getpid(), new_pgid);
    
    int result = setpgid(0, new_pgid);
    if (result == 0) {
        printf("  ✓ 子进程 %d 成功创建新进程组\n", getpid());
        printf("  子进程 %d 当前进程组: %d\n", getpid(), getpgid(0));
    } else {
        printf("  ✗ 子进程 %d 创建新进程组失败: %s\n", getpid(), strerror(errno));
    }
    
    // 模拟一些工作
    sleep(3);
    printf("子进程 %d 结束\n", getpid());
    exit(0);
}

/**
 * 演示父子进程组管理
 */
int demo_parent_child_groups() {
    pid_t child1_pid, child2_pid;
    pid_t original_pgid;
    
    printf("=== 父子进程组管理演示 ===\n");
    
    // 显示父进程信息
    printf("父进程信息:\n");
    printf("  进程ID: %d\n", getpid());
    printf("  原始进程组: %d\n", getpgid(0));
    printf("  会话ID: %d\n", getsid(0));
    printf("\n");
    
    // 创建第一个子进程
    printf("1. 创建第一个子进程:\n");
    child1_pid = fork();
    if (child1_pid == 0) {
        child_process(1);
    } else if (child1_pid > 0) {
        printf("  父进程创建子进程1: PID=%d\n", child1_pid);
        printf("  子进程1的进程组: %d\n", getpgid(child1_pid));
    } else {
        perror("创建子进程1失败");
        return -1;
    }
    
    // 创建第二个子进程
    printf("\n2. 创建第二个子进程:\n");
    child2_pid = fork();
    if (child2_pid == 0) {
        child_process(2);
    } else if (child2_pid > 0) {
        printf("  父进程创建子进程2: PID=%d\n", child2_pid);
        printf("  子进程2的进程组: %d\n", getpgid(child2_pid));
    } else {
        perror("创建子进程2失败");
        // 清理已创建的子进程
        kill(child1_pid, SIGKILL);
        return -1;
    }
    
    // 父进程也创建新进程组
    printf("\n3. 父进程创建新进程组:\n");
    original_pgid = getpgid(0);
    pid_t parent_pgid = getpid();
    
    printf("  父进程尝试创建新进程组: %d\n", parent_pgid);
    int result = setpgid(0, parent_pgid);
    if (result == 0) {
        printf("  ✓ 父进程成功创建新进程组\n");
        printf("  父进程当前进程组: %d\n", getpgid(0));
    } else {
        printf("  ✗ 父进程创建新进程组失败: %s\n", strerror(errno));
    }
    
    // 等待子进程结束
    printf("\n4. 等待子进程结束:\n");
    int status;
    pid_t finished_pid;
    
    while ((finished_pid = wait(&status)) > 0) {
        printf("  子进程 %d 已结束,退出状态: %d\n", finished_pid, WEXITSTATUS(status));
    }
    
    // 恢复父进程原始进程组
    printf("\n5. 恢复父进程原始进程组:\n");
    result = setpgid(0, original_pgid);
    if (result == 0) {
        printf("  ✓ 父进程成功恢复原始进程组: %d\n", getpgid(0));
    } else {
        printf("  ✗ 父进程恢复原始进程组失败: %s\n", strerror(errno));
    }
    
    return 0;
}

int main() {
    return demo_parent_child_groups();
}

示例3:进程组信号处理

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <signal.h>

/**
 * 信号处理函数
 */
void signal_handler(int sig) {
    printf("进程 %d 接收到信号 %d (%s)\n", getpid(), sig, strsignal(sig));
}

/**
 * 工作进程函数
 */
void worker_process(int worker_id) {
    // 设置信号处理
    signal(SIGTERM, signal_handler);
    signal(SIGINT, signal_handler);
    signal(SIGUSR1, signal_handler);
    
    printf("工作进程 %d 启动,进程组: %d\n", getpid(), getpgid(0));
    
    // 创建自己的进程组
    pid_t new_pgid = getpid();
    if (setpgid(0, new_pgid) == 0) {
        printf("工作进程 %d 成功创建进程组: %d\n", getpid(), new_pgid);
    }
    
    // 模拟长时间工作
    for (int i = 0; i < 10; i++) {
        printf("工作进程 %d 正在工作... (%d/10)\n", getpid(), i + 1);
        sleep(2);
    }
    
    printf("工作进程 %d 结束\n", getpid());
    exit(0);
}

/**
 * 演示进程组信号处理
 */
int demo_process_group_signaling() {
    pid_t workers[3];
    pid_t original_pgid;
    
    printf("=== 进程组信号处理演示 ===\n");
    
    // 保存原始进程组
    original_pgid = getpgid(0);
    printf("主进程原始进程组: %d\n", original_pgid);
    
    // 创建工作进程组
    printf("\n1. 创建工作进程组:\n");
    for (int i = 0; i < 3; i++) {
        workers[i] = fork();
        if (workers[i] == 0) {
            worker_process(i + 1);
        } else if (workers[i] > 0) {
            printf("  创建工作进程 %d: PID=%d\n", i + 1, workers[i]);
            
            // 将子进程加入同一进程组
            pid_t group_id = workers[0];  // 使用第一个子进程的PID作为组ID
            if (setpgid(workers[i], group_id) == 0) {
                printf("  工作进程 %d 加入进程组: %d\n", workers[i], group_id);
            } else {
                printf("  工作进程 %d 加入进程组失败: %s\n", workers[i], strerror(errno));
            }
        } else {
            perror("创建工作进程失败");
            // 清理已创建的进程
            for (int j = 0; j < i; j++) {
                kill(workers[j], SIGKILL);
            }
            return -1;
        }
    }
    
    // 显示进程组信息
    printf("\n2. 进程组信息:\n");
    for (int i = 0; i < 3; i++) {
        pid_t pgid = getpgid(workers[i]);
        printf("  工作进程 %d (PID=%d) 进程组: %d\n", i + 1, workers[i], pgid);
    }
    
    // 等待一段时间后发送信号
    printf("\n3. 等待5秒后发送信号到进程组...\n");
    sleep(5);
    
    // 向进程组发送信号(发送给组内所有进程)
    pid_t target_group = getpgid(workers[0]);
    printf("向进程组 %d 发送 SIGUSR1 信号\n", target_group);
    
    if (kill(-target_group, SIGUSR1) == 0) {  // 负数表示发送给整个进程组
        printf("✓ 成功发送信号到进程组\n");
    } else {
        printf("✗ 发送信号失败: %s\n", strerror(errno));
    }
    
    // 等待一段时间观察信号处理
    sleep(3);
    
    // 发送终止信号
    printf("\n4. 发送终止信号:\n");
    printf("向进程组 %d 发送 SIGTERM 信号\n", target_group);
    
    if (kill(-target_group, SIGTERM) == 0) {
        printf("✓ 成功发送终止信号\n");
    } else {
        printf("✗ 发送终止信号失败: %s\n", strerror(errno));
    }
    
    // 等待所有工作进程结束
    printf("\n5. 等待工作进程结束:\n");
    int status;
    pid_t finished_pid;
    int finished_count = 0;
    
    while (finished_count < 3) {
        finished_pid = wait(&status);
        if (finished_pid > 0) {
            finished_count++;
            printf("  工作进程 %d 已结束,退出状态: %d\n", 
                   finished_pid, WEXITSTATUS(status));
        } else {
            break;
        }
    }
    
    printf("所有工作进程已完成\n");
    return 0;
}

int main() {
    return demo_process_group_signaling();
}

示例4:会话和进程组管理

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <signal.h>

/**
 * 显示会话和进程组信息
 */
void show_session_info() {
    printf("=== 会话和进程组信息 ===\n");
    printf("进程ID: %d\n", getpid());
    printf("父进程ID: %d\n", getppid());
    printf("进程组ID: %d\n", getpgid(0));
    printf("会话ID: %d\n", getsid(0));
    printf("前台进程组: %d\n", tcgetpgrp(STDIN_FILENO));
    printf("\n");
}

/**
 * 会话领导进程
 */
void session_leader() {
    printf("会话领导进程启动 (PID: %d)\n", getpid());
    show_session_info();
    
    // 创建新会话
    pid_t sid = setsid();
    if (sid != -1) {
        printf("✓ 成功创建新会话: %d\n", sid);
        show_session_info();
    } else {
        printf("✗ 创建新会话失败: %s\n", strerror(errno));
        exit(1);
    }
    
    // 创建子进程组
    for (int i = 0; i < 2; i++) {
        pid_t child_pid = fork();
        if (child_pid == 0) {
            // 子进程
            printf("子进程 %d 启动\n", getpid());
            
            // 创建自己的进程组
            pid_t new_pgid = getpid();
            if (setpgid(0, new_pgid) == 0) {
                printf("子进程 %d 成功创建进程组: %d\n", getpid(), new_pgid);
            }
            
            // 模拟工作
            for (int j = 0; j < 5; j++) {
                printf("子进程 %d 工作中... (%d/5)\n", getpid(), j + 1);
                sleep(2);
            }
            
            printf("子进程 %d 结束\n", getpid());
            exit(0);
        } else if (child_pid > 0) {
            printf("会话领导进程创建子进程: %d\n", child_pid);
            
            // 将子进程加入特定进程组
            if (setpgid(child_pid, child_pid) == 0) {
                printf("子进程 %d 加入进程组成功\n", child_pid);
            }
        }
    }
    
    // 等待子进程结束
    int status;
    pid_t finished_pid;
    while ((finished_pid = wait(&status)) > 0) {
        printf("子进程 %d 已结束\n", finished_pid);
    }
    
    printf("会话领导进程结束\n");
    exit(0);
}

/**
 * 演示会话和进程组管理
 */
int demo_session_management() {
    pid_t session_leader_pid;
    
    printf("=== 会话和进程组管理演示 ===\n");
    
    // 显示初始信息
    printf("1. 初始进程信息:\n");
    show_session_info();
    
    // 创建会话领导进程
    printf("2. 创建会话领导进程:\n");
    session_leader_pid = fork();
    if (session_leader_pid == 0) {
        session_leader();
    } else if (session_leader_pid > 0) {
        printf("主进程创建会话领导进程: %d\n", session_leader_pid);
        
        // 等待会话领导进程结束
        int status;
        waitpid(session_leader_pid, &status, 0);
        printf("会话领导进程已结束\n");
    } else {
        perror("创建会话领导进程失败");
        return -1;
    }
    
    // 显示最终信息
    printf("\n3. 最终进程信息:\n");
    show_session_info();
    
    return 0;
}

int main() {
    return demo_session_management();
}

示例5:作业控制演示

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <signal.h>

/**
 * 后台作业进程
 */
void background_job(int job_id) {
    printf("后台作业 %d 启动 (PID: %d)\n", job_id, getpid());
    
    // 创建独立的进程组
    if (setpgid(0, 0) == 0) {
        printf("后台作业 %d 成功创建进程组\n", job_id);
    } else {
        printf("后台作业 %d 创建进程组失败: %s\n", job_id, strerror(errno));
    }
    
    // 模拟长时间运行的作业
    for (int i = 0; i < 20; i++) {
        printf("后台作业 %d 运行中... (%d/20)\n", job_id, i + 1);
        sleep(1);
        
        // 检查是否收到终止信号
        if (i == 10) {
            // 模拟作业暂停和恢复
            printf("后台作业 %d 暂停工作\n", job_id);
            sleep(2);
            printf("后台作业 %d 恢复工作\n", job_id);
        }
    }
    
    printf("后台作业 %d 完成\n", job_id);
    exit(0);
}

/**
 * 作业控制管理器
 */
typedef struct {
    pid_t pid;
    int job_id;
    int status;  // 0:运行, 1:暂停, 2:完成
    char command[256];
} job_t;

/**
 * 显示作业列表
 */
void show_job_list(job_t *jobs, int count) {
    printf("\n=== 作业列表 ===\n");
    printf("%-5s %-8s %-10s %s\n", "作业ID", "进程ID", "状态", "命令");
    printf("----------------------------------------\n");
    
    for (int i = 0; i < count; i++) {
        const char *status_str;
        switch (jobs[i].status) {
            case 0: status_str = "运行"; break;
            case 1: status_str = "暂停"; break;
            case 2: status_str = "完成"; break;
            default: status_str = "未知"; break;
        }
        
        printf("%-5d %-8d %-10s %s\n", 
               jobs[i].job_id, jobs[i].pid, status_str, jobs[i].command);
    }
    printf("\n");
}

/**
 * 演示作业控制
 */
int demo_job_control() {
    job_t jobs[3];
    int job_count = 0;
    
    printf("=== 作业控制演示 ===\n");
    
    // 创建后台作业
    printf("1. 创建后台作业:\n");
    for (int i = 0; i < 3; i++) {
        pid_t job_pid = fork();
        if (job_pid == 0) {
            background_job(i + 1);
        } else if (job_pid > 0) {
            // 父进程记录作业信息
            jobs[job_count].pid = job_pid;
            jobs[job_count].job_id = i + 1;
            jobs[job_count].status = 0;  // 运行中
            snprintf(jobs[job_count].command, sizeof(jobs[job_count].command), 
                     "background_job_%d", i + 1);
            job_count++;
            
            printf("  创建后台作业 %d: PID=%d\n", i + 1, job_pid);
            
            // 将子进程加入独立进程组
            if (setpgid(job_pid, job_pid) == 0) {
                printf("  作业 %d 成功创建独立进程组\n", i + 1);
            }
        } else {
            perror("创建后台作业失败");
        }
    }
    
    // 显示初始作业列表
    show_job_list(jobs, job_count);
    
    // 演示向特定作业发送信号
    printf("2. 向作业发送信号:\n");
    if (job_count > 0) {
        printf("  向作业 1 (PID=%d) 发送 SIGUSR1 信号\n", jobs[0].pid);
        if (kill(jobs[0].pid, SIGUSR1) == 0) {
            printf("  ✓ 信号发送成功\n");
        } else {
            printf("  ✗ 信号发送失败: %s\n", strerror(errno));
        }
    }
    
    // 等待一段时间
    printf("\n3. 等待作业运行...\n");
    sleep(5);
    
    // 显示当前作业状态
    show_job_list(jobs, job_count);
    
    // 演示作业控制操作
    printf("4. 作业控制操作:\n");
    
    // 暂停一个作业
    if (job_count > 1) {
        printf("  暂停作业 2 (PID=%d)\n", jobs[1].pid);
        if (kill(-getpgid(jobs[1].pid), SIGSTOP) == 0) {  // 发送给整个进程组
            jobs[1].status = 1;  // 暂停
            printf("  ✓ 作业 2 已暂停\n");
        } else {
            printf("  ✗ 暂停作业 2 失败: %s\n", strerror(errno));
        }
    }
    
    // 显示更新后的作业列表
    show_job_list(jobs, job_count);
    
    // 恢复暂停的作业
    if (job_count > 1) {
        printf("  恢复作业 2 (PID=%d)\n", jobs[1].pid);
        if (kill(-getpgid(jobs[1].pid), SIGCONT) == 0) {
            jobs[1].status = 0;  // 运行中
            printf("  ✓ 作业 2 已恢复\n");
        } else {
            printf("  ✗ 恢复作业 2 失败: %s\n", strerror(errno));
        }
    }
    
    // 等待所有作业完成
    printf("\n5. 等待所有作业完成:\n");
    int completed_jobs = 0;
    while (completed_jobs < job_count) {
        int status;
        pid_t finished_pid = waitpid(-1, &status, WNOHANG);
        if (finished_pid > 0) {
            // 找到完成的作业并更新状态
            for (int i = 0; i < job_count; i++) {
                if (jobs[i].pid == finished_pid) {
                    jobs[i].status = 2;  // 完成
                    printf("  作业 %d (PID=%d) 已完成\n", jobs[i].job_id, finished_pid);
                    completed_jobs++;
                    break;
                }
            }
        } else if (finished_pid == 0) {
            // 没有作业完成,短暂等待
            usleep(100000);  // 100ms
        } else {
            // 错误
            if (errno != ECHILD) {
                perror("等待作业完成时出错");
            }
            break;
        }
    }
    
    // 显示最终作业列表
    show_job_list(jobs, job_count);
    
    printf("作业控制演示完成\n");
    return 0;
}

int main() {
    return demo_job_control();
}

setpgid 使用注意事项

系统要求:

  1. 内核版本: 支持进程组管理的Linux内核
  2. 权限要求: 通常不需要特殊权限
  3. 架构支持: 支持所有主流架构

参数规则:

  1. pid为0: 表示当前进程
  2. pgid为0: 表示使用pid作为进程组ID
  3. 进程限制: 只能操作当前会话中的进程
  4. 会话限制: 不能将进程移到不同会话的进程组

错误处理:

  1. EACCES: 不允许从会话领导进程更改进程组
  2. EINVAL: 进程ID或进程组ID无效
  3. EPERM: 权限不足
  4. ESRCH: 指定的进程不存在

性能考虑:

  1. 系统调用开销: 频繁调用有性能开销
  2. 内核数据结构: 需要更新内核中的进程组信息
  3. 同步操作: 涉及内核数据结构的同步更新

安全考虑:

  1. 进程控制: 可能影响其他进程的行为
  2. 信号传播: 进程组信号会影响组内所有进程
  3. 会话管理: 不当的会话操作可能影响终端控制

最佳实践:

  1. 时机选择: 在进程创建后尽早设置进程组
  2. 错误处理: 妥善处理各种错误情况
  3. 状态检查: 验证设置结果的正确性
  4. 资源清理: 及时清理不需要的进程组
  5. 文档记录: 记录进程组管理策略

进程组和会话概念

进程组(Process Group):

  • 定义: 一组相关进程的集合
  • 用途: 信号分发、作业控制
  • 特点: 同一进程组的进程可以被统一控制

会话(Session):

  • 定义: 一个或多个进程组的集合
  • 用途: 终端控制、登录会话管理
  • 特点: 会话领导进程控制整个会话

关系层次:

会话 (Session)
└── 进程组 (Process Group)
    └── 进程 (Process)

常见使用场景

1. Shell作业控制:

// 创建后台作业的独立进程组
setpgid(child_pid, child_pid);

2. 守护进程:

// 创建独立会话,脱离控制终端
setsid();

3. 多进程应用:

// 将相关进程组织到同一进程组
setpgid(worker_pid, group_leader_pid);

信号与进程组

组信号发送:

// 向整个进程组发送信号
kill(-pgid, SIGTERM);

前台进程组:

// 设置前台进程组(控制终端)
tcsetpgrp(STDIN_FILENO, pgid);

总结

setpgid 是Linux系统中重要的进程管理函数,提供了:

  1. 进程组控制: 灵活的进程组管理能力
  2. 作业控制: 支持shell风格的作业控制
  3. 信号管理: 便于向进程组发送信号
  4. 会话管理: 支持会话和终端控制

通过合理使用 setpgid,可以构建更加灵活和可控的多进程应用程序。在实际应用中,需要注意进程组的生命周期管理、错误处理和信号控制等关键问题。

发表在 linux文章 | 留下评论

setpriority系统调用及示例

setpriority 函数详解

1. 函数介绍

setpriority 是Linux系统调用,用于设置进程、进程组或用户的调度优先级(nice值)。它允许调整进程的CPU调度优先级,从而影响进程获得CPU时间的相对比例。较低的nice值表示更高的优先级,较高的nice值表示更低的优先级。

2. 函数原型

#include <sys/resource.h>
int setpriority(int which, id_t who, int prio);

3. 功能

setpriority 设置指定进程、进程组或用户的调度优先级(nice值)。这个函数主要用于进程调度优化,通过调整优先级来平衡系统资源分配。

4. 参数

  • int which: 指定操作类型(PRIO_PROCESS, PRIO_PGRP, PRIO_USER)
  • id_t who: 根据which参数指定的ID(进程ID、进程组ID或用户ID)
  • int prio: 新的优先级值(nice值,范围-20到19)

5. 返回值

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

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

  • getpriority: 获取优先级
  • nice: 调整当前进程的nice值
  • sched_setscheduler: 设置进程调度策略
  • sched_setparam: 设置进程调度参数

7. 示例代码

示例1:基础setpriority使用

#include <sys/resource.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>

/**
 * 显示进程优先级信息
 */
void show_process_priority() {
    pid_t pid = getpid();
    uid_t uid = getuid();
    int prio;
    
    printf("=== 进程优先级信息 ===\n");
    printf("进程ID: %d\n", pid);
    printf("用户ID: %d\n", uid);
    
    // 获取当前进程优先级
    errno = 0;
    prio = getpriority(PRIO_PROCESS, 0);
    if (prio == -1 && errno != 0) {
        printf("获取进程优先级失败: %s\n", strerror(errno));
    } else {
        printf("当前进程优先级 (nice值): %d\n", prio);
    }
    
    printf("\n");
}

/**
 * 演示基础setpriority使用方法
 */
int demo_setpriority_basic() {
    int original_prio, new_prio;
    int result;
    
    printf("=== 基础setpriority使用示例 ===\n");
    
    // 显示原始优先级信息
    show_process_priority();
    original_prio = getpriority(PRIO_PROCESS, 0);
    printf("原始优先级: %d\n", original_prio);
    
    // 提高进程优先级(降低nice值)
    printf("1. 提高进程优先级 (nice值从 %d 降到 %d):\n", original_prio, original_prio - 5);
    result = setpriority(PRIO_PROCESS, 0, original_prio - 5);
    
    if (result == 0) {
        printf("   ✓ 成功提高优先级\n");
        
        // 验证设置结果
        new_prio = getpriority(PRIO_PROCESS, 0);
        printf("   新优先级: %d\n", new_prio);
        
        if (new_prio == original_prio - 5) {
            printf("   ✓ 优先级设置正确\n");
        } else {
            printf("   ✗ 优先级设置可能有问题\n");
        }
    } else {
        printf("   ✗ 提高优先级失败: %s\n", strerror(errno));
        if (errno == EACCES) {
            printf("   原因:需要root权限或CAP_SYS_NICE能力来提高优先级\n");
        } else if (errno == EINVAL) {
            printf("   原因:优先级值超出有效范围\n");
        } else if (errno == ESRCH) {
            printf("   原因:指定的进程不存在\n");
        }
    }
    
    // 降低进程优先级(提高nice值)
    printf("\n2. 降低进程优先级 (nice值从 %d 升到 %d):\n", new_prio, new_prio + 10);
    result = setpriority(PRIO_PROCESS, 0, new_prio + 10);
    
    if (result == 0) {
        printf("   ✓ 成功降低优先级\n");
        
        // 验证设置结果
        new_prio = getpriority(PRIO_PROCESS, 0);
        printf("   新优先级: %d\n", new_prio);
    } else {
        printf("   ✗ 降低优先级失败: %s\n", strerror(errno));
    }
    
    // 恢复原始优先级
    printf("\n3. 恢复原始优先级 (%d):\n", original_prio);
    result = setpriority(PRIO_PROCESS, 0, original_prio);
    
    if (result == 0) {
        printf("   ✓ 成功恢复原始优先级\n");
        new_prio = getpriority(PRIO_PROCESS, 0);
        printf("   最终优先级: %d\n", new_prio);
    } else {
        printf("   ✗ 恢复原始优先级失败: %s\n", strerror(errno));
    }
    
    return 0;
}

int main() {
    return demo_setpriority_basic();
}

示例2:多进程优先级管理

#include <sys/resource.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <signal.h>

/**
 * 工作进程函数
 */
void worker_process(int worker_id, int priority) {
    pid_t pid = getpid();
    
    printf("工作进程 %d 启动 (PID: %d)\n", worker_id, pid);
    
    // 设置进程优先级
    if (setpriority(PRIO_PROCESS, 0, priority) == 0) {
        printf("工作进程 %d 优先级设置为 %d\n", worker_id, priority);
    } else {
        printf("工作进程 %d 优先级设置失败: %s\n", worker_id, strerror(errno));
    }
    
    // 显示当前优先级
    int current_prio = getpriority(PRIO_PROCESS, 0);
    printf("工作进程 %d 当前优先级: %d\n", worker_id, current_prio);
    
    // 模拟工作负载
    volatile long sum = 0;
    for (long i = 0; i < 1000000000L; i++) {
        sum += i;
        if (i % 100000000 == 0) {
            printf("工作进程 %d: 进度 %ld%%\n", worker_id, i / 10000000);
        }
    }
    
    printf("工作进程 %d 完成,计算结果: %ld\n", worker_id, sum);
    exit(0);
}

/**
 * 演示多进程优先级管理
 */
int demo_multiprocess_priority() {
    pid_t workers[3];
    int priorities[] = {-5, 0, 10};  // 高、中、低优先级
    int worker_count = 3;
    
    printf("=== 多进程优先级管理演示 ===\n");
    
    // 显示父进程信息
    printf("父进程信息:\n");
    printf("  进程ID: %d\n", getpid());
    printf("  当前优先级: %d\n", getpriority(PRIO_PROCESS, 0));
    printf("\n");
    
    // 创建工作进程
    printf("创建 %d 个工作进程:\n", worker_count);
    for (int i = 0; i < worker_count; i++) {
        workers[i] = fork();
        if (workers[i] == 0) {
            // 子进程
            worker_process(i + 1, priorities[i]);
        } else if (workers[i] > 0) {
            // 父进程
            printf("  创建工作进程 %d: PID=%d, 优先级=%d\n", 
                   i + 1, workers[i], priorities[i]);
        } else {
            perror("创建工作进程失败");
            // 清理已创建的进程
            for (int j = 0; j < i; j++) {
                kill(workers[j], SIGKILL);
            }
            return -1;
        }
    }
    
    // 显示所有进程的优先级
    printf("\n所有进程优先级信息:\n");
    for (int i = 0; i < worker_count; i++) {
        errno = 0;
        int prio = getpriority(PRIO_PROCESS, workers[i]);
        if (prio != -1 || errno == 0) {
            printf("  进程 %d (PID=%d): 优先级=%d\n", i + 1, workers[i], prio);
        } else {
            printf("  进程 %d (PID=%d): 无法获取优先级 (%s)\n", 
                   i + 1, workers[i], strerror(errno));
        }
    }
    
    // 等待所有工作进程完成
    printf("\n等待工作进程完成:\n");
    int status;
    pid_t finished_pid;
    int finished_count = 0;
    
    while (finished_count < worker_count) {
        finished_pid = wait(&status);
        if (finished_pid > 0) {
            finished_count++;
            printf("  工作进程 (PID=%d) 已完成,退出状态: %d\n", 
                   finished_pid, WEXITSTATUS(status));
        } else {
            break;
        }
    }
    
    printf("所有工作进程已完成\n");
    return 0;
}

int main() {
    return demo_multiprocess_priority();
}

示例3:进程组优先级设置

#include <sys/resource.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <signal.h>

/**
 * 显示进程组信息
 */
void show_process_group_info(pid_t pgid) {
    printf("进程组 %d 信息:\n", pgid);
    
    // 显示进程组中所有进程的优先级
    // 注意:这里简化处理,实际应用中需要遍历/proc或使用其他方法
    int current_prio = getpriority(PRIO_PGRP, pgid);
    if (current_prio != -1 || errno == 0) {
        printf("  进程组优先级: %d\n", current_prio);
    } else {
        printf("  无法获取进程组优先级: %s\n", strerror(errno));
    }
}

/**
 * 工作进程函数(进程组成员)
 */
void group_worker(int worker_id, pid_t group_leader) {
    pid_t pid = getpid();
    
    printf("组工作进程 %d 启动 (PID: %d)\n", worker_id, pid);
    
    // 加入指定的进程组
    if (setpgid(0, group_leader) == 0) {
        printf("组工作进程 %d 成功加入进程组 %d\n", worker_id, group_leader);
    } else {
        printf("组工作进程 %d 加入进程组失败: %s\n", worker_id, strerror(errno));
    }
    
    // 显示当前进程信息
    printf("组工作进程 %d 当前优先级: %d\n", worker_id, getpriority(PRIO_PROCESS, 0));
    printf("组工作进程 %d 所属进程组: %d\n", worker_id, getpgid(0));
    
    // 模拟工作
    for (int i = 0; i < 10; i++) {
        printf("组工作进程 %d 工作中... (%d/10)\n", worker_id, i + 1);
        sleep(1);
    }
    
    printf("组工作进程 %d 完成\n", worker_id);
    exit(0);
}

/**
 * 演示进程组优先级设置
 */
int demo_process_group_priority() {
    pid_t group_leader_pid;
    pid_t workers[2];
    pid_t group_id;
    
    printf("=== 进程组优先级设置演示 ===\n");
    
    // 创建进程组领导进程
    group_leader_pid = fork();
    if (group_leader_pid == 0) {
        // 进程组领导进程
        printf("进程组领导进程启动 (PID: %d)\n", getpid());
        
        // 创建自己的进程组
        if (setpgid(0, 0) == 0) {
            group_id = getpid();
            printf("成功创建进程组: %d\n", group_id);
        } else {
            printf("创建进程组失败: %s\n", strerror(errno));
            exit(1);
        }
        
        // 显示初始优先级
        printf("进程组领导进程初始优先级: %d\n", getpriority(PRIO_PROCESS, 0));
        
        // 创建组内工作进程
        for (int i = 0; i < 2; i++) {
            workers[i] = fork();
            if (workers[i] == 0) {
                group_worker(i + 1, group_id);
            } else if (workers[i] > 0) {
                printf("创建组工作进程 %d: PID=%d\n", i + 1, workers[i]);
            }
        }
        
        // 等待子进程启动
        sleep(1);
        
        // 设置整个进程组的优先级
        printf("\n设置进程组 %d 的优先级:\n", group_id);
        show_process_group_info(group_id);
        
        // 尝试设置进程组优先级
        printf("尝试将进程组 %d 的优先级设置为 5:\n", group_id);
        int result = setpriority(PRIO_PGRP, group_id, 5);
        if (result == 0) {
            printf("✓ 成功设置进程组优先级\n");
        } else {
            printf("✗ 设置进程组优先级失败: %s\n", strerror(errno));
            if (errno == EACCES) {
                printf("  需要适当权限来设置优先级\n");
            }
        }
        
        // 验证设置结果
        show_process_group_info(group_id);
        
        // 等待组内工作进程完成
        for (int i = 0; i < 2; i++) {
            int status;
            waitpid(workers[i], &status, 0);
            printf("组工作进程 %d 已完成\n", i + 1);
        }
        
        printf("进程组领导进程结束\n");
        exit(0);
    } else if (group_leader_pid > 0) {
        printf("主进程创建进程组领导进程: %d\n", group_leader_pid);
        
        // 等待进程组领导进程完成
        int status;
        waitpid(group_leader_pid, &status, 0);
        printf("进程组领导进程已结束\n");
    } else {
        perror("创建进程组领导进程失败");
        return -1;
    }
    
    return 0;
}

int main() {
    return demo_process_group_priority();
}

示例4:用户优先级管理

#include <sys/resource.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>

/**
 * 显示用户优先级信息
 */
void show_user_priority(uid_t uid) {
    struct passwd *pwd = getpwuid(uid);
    if (pwd) {
        printf("用户 %s (UID: %d) 信息:\n", pwd->pw_name, uid);
    } else {
        printf("用户 (UID: %d) 信息:\n", uid);
    }
    
    // 获取用户进程的优先级(注意:这会返回其中一个进程的优先级)
    errno = 0;
    int prio = getpriority(PRIO_USER, uid);
    if (prio != -1 || errno == 0) {
        printf("  用户进程优先级: %d\n", prio);
    } else {
        printf("  无法获取用户优先级: %s\n", strerror(errno));
        if (errno == ESRCH) {
            printf("  该用户可能没有运行中的进程\n");
        }
    }
}

/**
 * 演示用户优先级管理
 */
int demo_user_priority_management() {
    uid_t current_uid = getuid();
    uid_t effective_uid = geteuid();
    
    printf("=== 用户优先级管理演示 ===\n");
    
    // 显示当前用户信息
    printf("1. 当前用户信息:\n");
    printf("   真实用户ID: %d\n", current_uid);
    printf("   有效用户ID: %d\n", effective_uid);
    
    show_user_priority(current_uid);
    
    // 检查权限
    printf("\n2. 权限检查:\n");
    if (current_uid == 0 || effective_uid == 0) {
        printf("   ✓ 当前具有root权限\n");
    } else {
        printf("   ✗ 当前没有root权限\n");
        printf("   设置其他用户优先级需要root权限\n");
    }
    
    // 尝试设置当前用户优先级
    printf("\n3. 设置当前用户优先级:\n");
    int original_prio = getpriority(PRIO_USER, current_uid);
    if (original_prio == -1 && errno != 0) {
        original_prio = 0;  // 默认值
    }
    
    printf("   当前用户优先级: %d\n", original_prio);
    printf("   尝试设置为: %d\n", original_prio + 2);
    
    int result = setpriority(PRIO_USER, current_uid, original_prio + 2);
    if (result == 0) {
        printf("   ✓ 成功设置用户优先级\n");
        
        // 验证设置结果
        int new_prio = getpriority(PRIO_USER, current_uid);
        printf("   新用户优先级: %d\n", new_prio);
    } else {
        printf("   ✗ 设置用户优先级失败: %s\n", strerror(errno));
        if (errno == EACCES) {
            printf("   需要root权限来设置用户优先级\n");
        } else if (errno == EPERM) {
            printf("   权限不足\n");
        }
    }
    
    // 显示系统中其他用户(如果有权限)
    printf("\n4. 系统用户优先级信息:\n");
    struct passwd *pwd;
    int user_count = 0;
    
    // 重置密码文件指针
    setpwent();
    
    while ((pwd = getpwent()) != NULL && user_count < 5) {
        // 只显示一些系统用户
        if (pwd->pw_uid < 1000) {  // 系统用户通常UID较小
            show_user_priority(pwd->pw_uid);
            user_count++;
        }
    }
    
    endpwent();
    
    // 恢复原始优先级
    printf("\n5. 恢复原始优先级:\n");
    result = setpriority(PRIO_USER, current_uid, original_prio);
    if (result == 0) {
        printf("   ✓ 成功恢复原始优先级\n");
    } else {
        printf("   ✗ 恢复原始优先级失败: %s\n", strerror(errno));
    }
    
    return 0;
}

int main() {
    return demo_user_priority_management();
}

示例5:优先级调度演示

#include <sys/resource.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <time.h>

/**
 * CPU密集型工作函数
 */
void cpu_intensive_work(int worker_id, int priority, int duration_seconds) {
    pid_t pid = getpid();
    clock_t start_time = clock();
    clock_t work_duration = duration_seconds * CLOCKS_PER_SEC;
    
    printf("CPU密集型工作进程 %d 启动 (PID: %d, 优先级: %d)\n", 
           worker_id, pid, priority);
    
    // 设置进程优先级
    if (setpriority(PRIO_PROCESS, 0, priority) != 0) {
        printf("工作进程 %d 设置优先级失败: %s\n", worker_id, strerror(errno));
    }
    
    // 执行CPU密集型工作
    volatile long sum = 0;
    long iterations = 0;
    
    while ((clock() - start_time) < work_duration) {
        sum += iterations;
        iterations++;
        
        // 定期报告进度
        if (iterations % 10000000 == 0) {
            double elapsed = (double)(clock() - start_time) / CLOCKS_PER_SEC;
            printf("工作进程 %d: 迭代 %ld 次, 耗时 %.2f 秒\n", 
                   worker_id, iterations, elapsed);
        }
    }
    
    double total_time = (double)(clock() - start_time) / CLOCKS_PER_SEC;
    printf("工作进程 %d 完成:\n", worker_id);
    printf("  总迭代次数: %ld\n", iterations);
    printf("  总耗时: %.2f 秒\n", total_time);
    printf("  平均迭代速度: %.0f 次/秒\n", iterations / total_time);
    
    exit(0);
}

/**
 * 演示优先级调度效果
 */
int demo_priority_scheduling() {
    pid_t workers[3];
    int priorities[] = {-10, 0, 10};  // 高、中、低优先级
    const char *priority_names[] = {"高优先级", "中优先级", "低优先级"};
    int worker_count = 3;
    int work_duration = 10;  // 工作10秒
    
    printf("=== 优先级调度效果演示 ===\n");
    printf("工作时长: %d 秒\n", work_duration);
    printf("优先级设置: 高(-10), 中(0), 低(10)\n\n");
    
    // 检查权限
    uid_t uid = getuid();
    printf("权限检查:\n");
    if (uid == 0) {
        printf("  ✓ 具有root权限,可以设置所有优先级\n");
    } else {
        printf("  ✗ 没有root权限,可能无法设置负优先级\n");
    }
    printf("\n");
    
    // 创建工作进程
    printf("创建 %d 个工作进程:\n", worker_count);
    for (int i = 0; i < worker_count; i++) {
        workers[i] = fork();
        if (workers[i] == 0) {
            // 子进程
            cpu_intensive_work(i + 1, priorities[i], work_duration);
        } else if (workers[i] > 0) {
            // 父进程
            printf("  %s工作进程 %d: PID=%d, 优先级=%d\n", 
                   priority_names[i], i + 1, workers[i], priorities[i]);
        } else {
            perror("创建工作进程失败");
            // 清理已创建的进程
            for (int j = 0; j < i; j++) {
                kill(workers[j], SIGKILL);
            }
            return -1;
        }
    }
    
    // 显示所有进程的初始优先级
    printf("\n所有进程初始优先级:\n");
    for (int i = 0; i < worker_count; i++) {
        errno = 0;
        int prio = getpriority(PRIO_PROCESS, workers[i]);
        if (prio != -1 || errno == 0) {
            printf("  进程 %d (PID=%d): 优先级=%d\n", i + 1, workers[i], prio);
        } else {
            printf("  进程 %d (PID=%d): 获取优先级失败 (%s)\n", 
                   i + 1, workers[i], strerror(errno));
        }
    }
    
    // 等待所有工作进程完成
    printf("\n等待工作进程完成 (约 %d 秒):\n", work_duration);
    int status;
    pid_t finished_pid;
    int finished_count = 0;
    time_t start_wait = time(NULL);
    
    while (finished_count < worker_count) {
        finished_pid = wait(&status);
        if (finished_pid > 0) {
            finished_count++;
            time_t elapsed = time(NULL) - start_wait;
            printf("  工作进程 (PID=%d) 已完成,耗时 %ld 秒,退出状态: %d\n", 
                   finished_pid, elapsed, WEXITSTATUS(status));
        } else if (finished_pid == -1) {
            if (errno == EINTR) {
                continue;  // 被信号中断,继续等待
            } else {
                break;
            }
        }
    }
    
    // 分析调度效果
    printf("\n=== 调度效果分析 ===\n");
    printf("高优先级进程应该获得更多的CPU时间\n");
    printf("低优先级进程应该获得较少的CPU时间\n");
    printf("实际效果取决于系统负载和调度器实现\n");
    
    // 显示优先级相关建议
    printf("\n=== 优先级使用建议 ===\n");
    printf("1. 优先级范围: -20 (最高) 到 19 (最低)\n");
    printf("2. root权限可以设置负优先级\n");
    printf("3. 普通用户只能设置0到19的优先级\n");
    printf("4. 过高的优先级可能影响系统稳定性\n");
    printf("5. 合理使用优先级平衡系统资源\n");
    
    return 0;
}

int main() {
    return demo_priority_scheduling();
}

setpriority 使用注意事项

系统要求:

  1. 内核版本: 支持进程优先级管理的Linux内核
  2. 权限要求: 设置负优先级需要root权限或CAP_SYS_NICE能力
  3. 架构支持: 支持所有主流架构

优先级范围:

  1. 有效范围: -20 到 19
  2. 默认值: 通常为0
  3. 负值: 更高优先级(需要特权)
  4. 正值: 更低优先级(普通用户可设置)

which参数类型:

  1. PRIO_PROCESS: 指定进程
  2. PRIO_PGRP: 指定进程组
  3. PRIO_USER: 指定用户

错误处理:

  1. EACCES: 权限不足(设置负优先级时)
  2. EINVAL: 优先级值超出有效范围
  3. EPERM: 权限不足
  4. ESRCH: 指定的进程/进程组/用户不存在

性能考虑:

  1. 调度开销: 频繁改变优先级有性能开销
  2. 系统影响: 不当的优先级设置可能影响系统稳定性
  3. 公平性: 避免过度偏向某些进程

安全考虑:

  1. 权限提升: 不当使用可能导致权限提升
  2. 系统稳定性: 过高的优先级可能影响系统响应
  3. 资源竞争: 合理分配优先级避免资源竞争

最佳实践:

  1. 权限检查: 执行前检查是否具有足够权限
  2. 范围验证: 确保优先级值在有效范围内
  3. 错误处理: 妥善处理各种错误情况
  4. 适度使用: 避免过度调整优先级
  5. 监控效果: 监控优先级调整的效果

优先级值说明

Nice值范围:

  • -20: 最高优先级
  • -10: 高优先级
  • 0: 默认优先级
  • 10: 低优先级
  • 19: 最低优先级

权限要求:

// 普通用户可以设置的范围
if (prio >= 0 && prio <= 19) {
    // 可以设置
}

// 设置负优先级需要特权
if (prio < 0) {
    // 需要root权限或CAP_SYS_NICE能力
}

相关系统调用

1. getpriority:

int getpriority(int which, id_t who);

2. nice:

int nice(int inc);

3. sched_setscheduler:

int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param);

常见使用场景

1. 系统服务管理:

// 降低后台服务优先级
setpriority(PRIO_PROCESS, service_pid, 10);

2. 批处理作业:

// 为批处理作业设置较低优先级
setpriority(PRIO_PROCESS, batch_pid, 15);

3. 实时应用:

// 提高关键应用优先级(需要root权限)
setpriority(PRIO_PROCESS, critical_pid, -5);

优先级调度策略

1. 时间片分配:

  • 高优先级进程获得更多CPU时间片
  • 低优先级进程获得较少CPU时间片

2. 调度顺序:

  • 优先调度高优先级进程
  • 低优先级进程可能被延迟执行

3. I/O优先级:

  • 优先级也影响I/O调度
  • 高优先级进程获得更好的I/O性能

总结

setpriority 是Linux系统中重要的进程调度管理函数,提供了:

  1. 优先级控制: 灵活的进程优先级管理
  2. 多级调度: 支持进程、进程组、用户级别的优先级设置
  3. 权限管理: 通过权限控制保证系统安全
  4. 性能优化: 帮助优化系统资源分配

通过合理使用 setpriority,可以构建更加高效和可控的多进程应用程序。在实际应用中,需要注意权限要求、错误处理和系统稳定性等关键问题。

发表在 linux文章 | 留下评论

setregid系统调用及示例

setregid 函数详解

1. 函数介绍

setregid系统调用详解及示例,掌握Linux进程权限管理,了解函数功能与使用方法,提升系统编程技能。setregid 是Linux系统调用,用于同时设置进程的真实组ID(real group ID)和有效组ID(effective group ID)。它是组ID管理的重要函数,允许进程在不同组权限之间切换,实现灵活的权限控制。

2. 函数原型

#include <unistd.h>
int setregid(gid_t rgid, gid_t egid);

3. 功能

setregid 同时设置进程的真实组ID和有效组ID。真实组ID标识进程的实际所有者,有效组ID决定进程当前的权限。这个函数主要用于权限切换和安全控制。

4. 参数

  • gid_t rgid: 新的真实组ID(-1表示不改变)
  • gid_t egid: 新的有效组ID(-1表示不改变)

5. 返回值

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

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

  • setgid: 仅设置有效组ID
  • setegid: 仅设置有效组ID
  • setrgid: 仅设置真实组ID
  • getgid: 获取真实组ID
  • getegid: 获取有效组ID

7. 示例代码

示例1:基础setregid使用

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <grp.h>

/**
 * 显示当前组ID信息
 */
void show_group_ids() {
    gid_t real_gid = getgid();
    gid_t effective_gid = getegid();
    
    printf("=== 当前组ID信息 ===\n");
    printf("真实组ID: %d", real_gid);
    struct group *real_grp = getgrgid(real_gid);
    if (real_grp) {
        printf(" (%s)", real_grp->gr_name);
    }
    printf("\n");
    
    printf("有效组ID: %d", effective_gid);
    struct group *effective_grp = getgrgid(effective_gid);
    if (effective_grp) {
        printf(" (%s)", effective_grp->gr_name);
    }
    printf("\n\n");
}

/**
 * 演示基础setregid使用方法
 */
int demo_setregid_basic() {
    gid_t original_real_gid, original_effective_gid;
    gid_t test_gid = 1000;  // 测试组ID
    int result;
    
    printf("=== 基础setregid使用示例 ===\n");
    
    // 显示原始组ID信息
    printf("1. 原始组ID信息:\n");
    show_group_ids();
    
    original_real_gid = getgid();
    original_effective_gid = getegid();
    
    // 尝试设置组ID(需要适当权限)
    printf("2. 尝试设置组ID:\n");
    printf("   尝试将真实组ID设置为: %d\n", test_gid);
    printf("   尝试将有效组ID设置为: %d\n", test_gid);
    
    result = setregid(test_gid, test_gid);
    if (result == 0) {
        printf("   ✓ 成功设置组ID\n");
        show_group_ids();
        
        // 验证设置结果
        if (getgid() == test_gid && getegid() == test_gid) {
            printf("   ✓ 组ID设置正确\n");
        } else {
            printf("   ✗ 组ID设置可能有问题\n");
        }
    } else {
        printf("   ✗ 设置组ID失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("   原因:权限不足,需要root权限或适当的组权限\n");
        } else if (errno == EINVAL) {
            printf("   原因:组ID无效\n");
        }
        
        printf("   注意:普通用户通常无法随意更改组ID\n");
    }
    
    // 尝试部分设置(使用-1)
    printf("\n3. 尝试部分设置组ID:\n");
    printf("   保持真实组ID不变,仅设置有效组ID\n");
    
    result = setregid(-1, original_effective_gid);
    if (result == 0) {
        printf("   ✓ 部分设置成功\n");
        show_group_ids();
    } else {
        printf("   ✗ 部分设置失败: %s\n", strerror(errno));
    }
    
    // 恢复原始组ID
    printf("\n4. 恢复原始组ID:\n");
    result = setregid(original_real_gid, original_effective_gid);
    if (result == 0) {
        printf("   ✓ 成功恢复原始组ID\n");
        show_group_ids();
    } else {
        printf("   ✗ 恢复原始组ID失败: %s\n", strerror(errno));
    }
    
    return 0;
}

int main() {
    return demo_setregid_basic();
}

示例2:权限检查和组管理

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <grp.h>
#include <pwd.h>

/**
 * 检查当前权限
 */
void check_permissions() {
    uid_t uid = getuid();
    uid_t euid = geteuid();
    gid_t gid = getgid();
    gid_t egid = getegid();
    
    printf("=== 权限检查 ===\n");
    printf("真实用户ID: %d", uid);
    struct passwd *pwd = getpwuid(uid);
    if (pwd) {
        printf(" (%s)", pwd->pw_name);
    }
    printf("\n");
    
    printf("有效用户ID: %d", euid);
    pwd = getpwuid(euid);
    if (pwd) {
        printf(" (%s)", pwd->pw_name);
    }
    printf("\n");
    
    printf("真实组ID: %d", gid);
    struct group *grp = getgrgid(gid);
    if (grp) {
        printf(" (%s)", grp->gr_name);
    }
    printf("\n");
    
    printf("有效组ID: %d", egid);
    grp = getgrgid(egid);
    if (grp) {
        printf(" (%s)", grp->gr_name);
    }
    printf("\n");
    
    if (uid == 0 || euid == 0) {
        printf("✓ 当前具有root权限\n");
    } else {
        printf("✗ 当前没有root权限\n");
        printf("  提示:修改组ID通常需要root权限\n");
    }
    printf("\n");
}

/**
 * 查找系统中的组
 */
void find_system_groups() {
    struct group *grp;
    int group_count = 0;
    
    printf("=== 系统中的部分组信息 ===\n");
    
    // 查找一些常见的组
    const char *common_groups[] = {"root", "daemon", "sys", "adm", "tty", "disk", NULL};
    
    for (int i = 0; common_groups[i]; i++) {
        grp = getgrnam(common_groups[i]);
        if (grp) {
            printf("  组名: %-10s ID: %d\n", grp->gr_name, grp->gr_gid);
            group_count++;
        }
    }
    
    printf("  找到 %d 个常用组\n\n", group_count);
}

/**
 * 演示权限检查和组管理
 */
int demo_permission_check() {
    gid_t test_groups[3];
    int num_groups = 0;
    
    printf("=== 权限检查和组管理演示 ===\n");
    
    // 检查当前权限
    check_permissions();
    
    // 查找系统组
    find_system_groups();
    
    // 准备测试组列表
    struct group *grp;
    
    // 尝试获取一些系统组ID
    grp = getgrnam("daemon");
    if (grp) {
        test_groups[num_groups++] = grp->gr_gid;
        printf("添加组 daemon (ID: %d) 到测试列表\n", grp->gr_gid);
    }
    
    grp = getgrnam("sys");
    if (grp) {
        test_groups[num_groups++] = grp->gr_gid;
        printf("添加组 sys (ID: %d) 到测试列表\n", grp->gr_gid);
    }
    
    grp = getgrnam("adm");
    if (grp) {
        test_groups[num_groups++] = grp->gr_gid;
        printf("添加组 adm (ID: %d) 到测试列表\n", grp->gr_gid);
    }
    
    if (num_groups == 0) {
        // 如果找不到系统组,使用示例ID
        test_groups[0] = 100;
        test_groups[1] = 200;
        test_groups[2] = 300;
        num_groups = 3;
        printf("使用示例组ID: 100, 200, 300\n");
    }
    
    // 显示当前组ID
    printf("\n当前组ID信息:\n");
    show_group_ids();
    
    // 尝试设置组ID
    printf("\n尝试设置组ID:\n");
    printf("  目标真实组ID: %d\n", test_groups[0]);
    printf("  目标有效组ID: %d\n", test_groups[1]);
    
    int result = setregid(test_groups[0], test_groups[1]);
    if (result == 0) {
        printf("✓ 组ID设置成功\n");
        show_group_ids();
    } else {
        printf("✗ 组ID设置失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("  需要root权限\n");
        }
    }
    
    // 演示部分设置
    printf("\n演示部分设置 (仅改变有效组ID):\n");
    printf("  保持真实组ID: %d\n", getgid());
    printf("  新的有效组ID: %d\n", test_groups[2]);
    
    result = setregid(-1, test_groups[2]);
    if (result == 0) {
        printf("✓ 部分设置成功\n");
        show_group_ids();
    } else {
        printf("✗ 部分设置失败: %s\n", strerror(errno));
    }
    
    return 0;
}

// 辅助函数声明
void show_group_ids();

int main() {
    return demo_permission_check();
}

示例3:组ID切换演示

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

/**
 * 安全组切换演示
 */
int demo_secure_group_switching() {
    gid_t original_real_gid, original_effective_gid;
    gid_t test_gid = 1000;  // 测试组ID
    
    printf("=== 安全组切换演示 ===\n");
    
    // 保存原始组ID
    original_real_gid = getgid();
    original_effective_gid = getegid();
    
    printf("原始组ID信息:\n");
    printf("  真实组ID: %d\n", original_real_gid);
    printf("  有效组ID: %d\n", original_effective_gid);
    
    // 演示组切换的安全性考虑
    printf("\n1. 组切换安全考虑:\n");
    printf("   ✓ 始终保存原始组ID状态\n");
    printf("   ✓ 在使用特殊权限后及时恢复\n");
    printf("   ✓ 检查组切换操作的返回值\n");
    printf("   ✓ 记录组切换日志\n");
    printf("   ✓ 最小权限原则:只请求必需的权限\n");
    
    // 演示临时组权限使用
    printf("\n2. 模拟使用临时组权限:\n");
    
    // 尝试切换到测试组
    int result = setregid(test_gid, test_gid);
    if (result == 0) {
        printf("   ✓ 成功切换到组 %d\n", test_gid);
        show_group_ids();
        
        // 模拟使用组权限的操作
        printf("   使用新组权限进行操作...\n");
        // 这里可以进行需要特定组权限的操作
        
        // 恢复原始组ID
        result = setregid(original_real_gid, original_effective_gid);
        if (result == 0) {
            printf("   ✓ 成功恢复原始组ID\n");
        } else {
            printf("   ✗ 恢复原始组ID失败: %s\n", strerror(errno));
        }
    } else {
        printf("   ✗ 切换到组 %d 失败: %s\n", test_gid, strerror(errno));
        if (errno == EPERM) {
            printf("   需要root权限或适当的组权限\n");
        }
    }
    
    show_group_ids();
    
    // 演示权限提升检测
    printf("\n3. 权限提升检测:\n");
    gid_t current_real_gid = getgid();
    gid_t current_effective_gid = getegid();
    
    if (current_real_gid != original_real_gid || 
        current_effective_gid != original_effective_gid) {
        printf("   警告:组ID状态未正确恢复\n");
    } else {
        printf("   ✓ 组ID状态已正确恢复\n");
    }
    
    // 显示组安全相关信息
    printf("\n4. 组安全相关信息:\n");
    printf("   组ID切换需要CAP_SETGID能力\n");
    printf("   真实组ID只能向有效组ID降低\n");
    printf("   避免在生产环境中频繁切换组权限\n");
    
    return 0;
}

// 辅助函数声明
void show_group_ids();

int main() {
    return demo_secure_group_switching();
}

示例4:进程组权限管理

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <sys/wait.h>

/**
 * 子进程函数
 */
void child_process(int child_id) {
    printf("子进程 %d 启动 (PID: %d)\n", child_id, getpid());
    
    // 显示子进程初始组ID
    printf("  子进程 %d 初始组ID:\n", child_id);
    printf("    真实组ID: %d\n", getgid());
    printf("    有效组ID: %d\n", getegid());
    
    // 尝试修改组ID
    printf("  子进程 %d 尝试修改组ID:\n", child_id);
    
    gid_t new_gid = 1000;  // 测试组ID
    int result = setregid(new_gid, new_gid);
    if (result == 0) {
        printf("    ✓ 子进程 %d 成功修改组ID\n", child_id);
        printf("    新真实组ID: %d\n", getgid());
        printf("    新有效组ID: %d\n", getegid());
    } else {
        printf("    ✗ 子进程 %d 修改组ID失败: %s\n", child_id, strerror(errno));
    }
    
    // 模拟一些工作
    sleep(3);
    printf("子进程 %d 结束\n", child_id);
    exit(0);
}

/**
 * 演示进程组权限管理
 */
int demo_process_group_permissions() {
    pid_t child1_pid, child2_pid;
    gid_t original_real_gid, original_effective_gid;
    
    printf("=== 进程组权限管理演示 ===\n");
    
    // 保存父进程原始组ID
    original_real_gid = getgid();
    original_effective_gid = getegid();
    
    printf("父进程原始组ID信息:\n");
    printf("  真实组ID: %d\n", original_real_gid);
    printf("  有效组ID: %d\n", original_effective_gid);
    
    // 创建第一个子进程
    printf("\n1. 创建第一个子进程:\n");
    child1_pid = fork();
    if (child1_pid == 0) {
        child_process(1);
    } else if (child1_pid > 0) {
        printf("  父进程创建子进程1: PID=%d\n", child1_pid);
    } else {
        perror("创建子进程1失败");
        return -1;
    }
    
    // 创建第二个子进程
    printf("\n2. 创建第二个子进程:\n");
    child2_pid = fork();
    if (child2_pid == 0) {
        child_process(2);
    } else if (child2_pid > 0) {
        printf("  父进程创建子进程2: PID=%d\n", child2_pid);
    } else {
        perror("创建子进程2失败");
        // 清理已创建的子进程
        kill(child1_pid, SIGKILL);
        return -1;
    }
    
    // 父进程也尝试修改组ID
    printf("\n3. 父进程尝试修改组ID:\n");
    gid_t parent_new_gid = 2000;  // 测试组ID
    int result = setregid(parent_new_gid, parent_new_gid);
    if (result == 0) {
        printf("  ✓ 父进程成功修改组ID\n");
        printf("  新真实组ID: %d\n", getgid());
        printf("  新有效组ID: %d\n", getegid());
    } else {
        printf("  ✗ 父进程修改组ID失败: %s\n", strerror(errno));
    }
    
    // 等待子进程结束
    printf("\n4. 等待子进程结束:\n");
    int status;
    pid_t finished_pid;
    
    while ((finished_pid = wait(&status)) > 0) {
        printf("  子进程 %d 已结束,退出状态: %d\n", finished_pid, WEXITSTATUS(status));
    }
    
    // 恢复父进程原始组ID
    printf("\n5. 恢复父进程原始组ID:\n");
    result = setregid(original_real_gid, original_effective_gid);
    if (result == 0) {
        printf("  ✓ 父进程成功恢复原始组ID\n");
        printf("  最终真实组ID: %d\n", getgid());
        printf("  最终有效组ID: %d\n", getegid());
    } else {
        printf("  ✗ 父进程恢复原始组ID失败: %s\n", strerror(errno));
    }
    
    return 0;
}

int main() {
    return demo_process_group_permissions();
}

示例5:组权限安全测试

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <grp.h>
#include <pwd.h>

/**
 * 组权限安全测试
 */
int demo_group_security_testing() {
    gid_t original_real_gid, original_effective_gid;
    gid_t test_gid = 1000;  // 测试组ID
    
    printf("=== 组权限安全测试 ===\n");
    
    // 保存原始状态
    original_real_gid = getgid();
    original_effective_gid = getegid();
    
    printf("测试开始 - 原始组ID状态:\n");
    printf("  真实组ID: %d\n", original_real_gid);
    printf("  有效组ID: %d\n", original_effective_gid);
    
    // 测试1: 正常组切换
    printf("\n=== 测试1: 正常组切换 ===\n");
    int result = setregid(test_gid, test_gid);
    if (result == 0) {
        printf("✓ 正常组切换成功\n");
        printf("  当前真实组ID: %d\n", getgid());
        printf("  当前有效组ID: %d\n", getegid());
        
        // 恢复
        result = setregid(original_real_gid, original_effective_gid);
        if (result == 0) {
            printf("✓ 成功恢复原始状态\n");
        } else {
            printf("✗ 恢复原始状态失败: %s\n", strerror(errno));
        }
    } else {
        printf("✗ 正常组切换失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("  预期结果:没有足够权限\n");
        }
    }
    
    // 测试2: 部分组切换
    printf("\n=== 测试2: 部分组切换 ===\n");
    result = setregid(-1, test_gid);
    if (result == 0) {
        printf("✓ 部分组切换成功 (仅改变有效组ID)\n");
        printf("  当前真实组ID: %d\n", getgid());
        printf("  当前有效组ID: %d\n", getegid());
        
        // 恢复有效组ID
        result = setregid(-1, original_effective_gid);
        if (result == 0) {
            printf("✓ 成功恢复有效组ID\n");
        } else {
            printf("✗ 恢复有效组ID失败: %s\n", strerror(errno));
        }
    } else {
        printf("✗ 部分组切换失败: %s\n", strerror(errno));
    }
    
    // 测试3: 无效组ID
    printf("\n=== 测试3: 无效组ID ===\n");
    gid_t invalid_gid = 999999;  // 很可能不存在的组ID
    result = setregid(invalid_gid, invalid_gid);
    if (result == 0) {
        printf("✗ 无效组ID设置成功 (这不应该发生)\n");
    } else {
        printf("✓ 无效组ID设置失败: %s\n", strerror(errno));
        if (errno == EINVAL) {
            printf("  正确:组ID无效\n");
        }
    }
    
    // 测试4: 权限验证
    printf("\n=== 测试4: 权限验证 ===\n");
    uid_t uid = getuid();
    uid_t euid = geteuid();
    
    printf("当前用户权限状态:\n");
    printf("  真实用户ID: %d", uid);
    struct passwd *pwd = getpwuid(uid);
    if (pwd) {
        printf(" (%s)", pwd->pw_name);
    }
    printf("\n");
    
    printf("  有效用户ID: %d", euid);
    pwd = getpwuid(euid);
    if (pwd) {
        printf(" (%s)", pwd->pw_name);
    }
    printf("\n");
    
    if (uid == 0 || euid == 0) {
        printf("✓ 具有root权限,可以进行组切换测试\n");
    } else {
        printf("✗ 没有root权限,组切换可能受限\n");
    }
    
    // 测试5: 状态一致性检查
    printf("\n=== 测试5: 状态一致性检查 ===\n");
    gid_t final_real_gid = getgid();
    gid_t final_effective_gid = getegid();
    
    printf("最终组ID状态:\n");
    printf("  真实组ID: %d\n", final_real_gid);
    printf("  有效组ID: %d\n", final_effective_gid);
    
    if (final_real_gid == original_real_gid && 
        final_effective_gid == original_effective_gid) {
        printf("✓ 组ID状态一致性检查通过\n");
    } else {
        printf("✗ 组ID状态不一致,可能存在安全风险\n");
    }
    
    // 显示安全建议
    printf("\n=== 安全建议 ===\n");
    printf("1. 始终验证setregid的返回值\n");
    printf("2. 保存原始组ID状态以便恢复\n");
    printf("3. 使用最小权限原则\n");
    printf("4. 记录组权限变更日志\n");
    printf("5. 避免频繁的组权限切换\n");
    printf("6. 在特权操作后及时降权\n");
    
    return 0;
}

int main() {
    return demo_group_security_testing();
}

setregid 使用注意事项

系统要求:

  1. 内核版本: 支持组ID管理的Linux内核
  2. 权限要求: 需要CAP_SETGID能力或root权限
  3. 架构支持: 支持所有主流架构

参数规则:

  1. -1表示不改变: rgid或egid为-1时保持原值
  2. 权限限制: 普通用户只能在允许范围内切换
  3. 真实组ID限制: 真实组ID只能向有效组ID降低

错误处理:

  1. EPERM: 权限不足(需要CAP_SETGID或root权限)
  2. EINVAL: 组ID无效
  3. EAGAIN: 资源暂时不可用
  4. EFAULT: 指针参数无效

安全考虑:

  1. 权限提升: 可能导致权限提升风险
  2. 审计日志: 建议记录权限变更操作
  3. 最小权限: 遵循最小权限原则
  4. 状态恢复: 及时恢复原始权限状态

最佳实践:

  1. 权限检查: 执行前检查是否具有足够权限
  2. 参数验证: 验证参数的有效性和安全性
  3. 错误处理: 妥善处理各种错误情况
  4. 状态保存: 保存原始状态以便恢复
  5. 日志记录: 记录权限变更操作
  6. 及时恢复: 使用完特殊权限后及时恢复

组ID类型说明

真实组ID (Real Group ID):

  • 含义: 进程的实际所有者组ID
  • 用途: 标识进程的真正归属
  • 修改限制: 只能向有效组ID降低

有效组ID (Effective Group ID):

  • 含义: 当前用于权限检查的组ID
  • 用途: 决定进程当前的权限
  • 修改限制: 可以设置为真实组ID或保存的组ID

保存的组ID (Saved Group ID):

  • 含义: 进程启动时的有效组ID副本
  • 用途: 用于权限恢复
  • 修改时机: 通常在exec时设置

常见使用场景

1. 服务权限管理:

// 服务启动时降低权限
setregid(service_gid, service_gid);

2. 安全沙箱:

// 创建受限环境时设置适当的组权限
setregid(restricted_gid, restricted_gid);

3. 权限切换:

// 临时获取特定组权限
setregid(-1, target_gid);  // 仅改变有效组ID
// 执行需要权限的操作
setregid(-1, original_gid);  // 恢复有效组ID

权限检查规则

setregid权限要求:

  1. 超级用户: 可以设置任意组ID
  2. 普通用户: 只能设置为以下值之一:
    • 当前真实组ID
    • 当前有效组ID
    • 保存的组ID

安全限制:

  1. 真实组ID: 只能向有效组ID降低
  2. 有效组ID: 可以自由设置(在允许范围内)
  3. 审计: 系统通常会记录权限变更

总结

setregid 是Linux系统中重要的组权限管理函数,提供了:

  1. 组权限控制: 精确控制进程的组权限
  2. 灵活配置: 支持同时设置真实和有效组ID
  3. 安全机制: 通过权限检查保证系统安全
  4. 标准兼容: 符合POSIX标准

通过合理使用 setregid,可以实现细粒度的权限控制,构建更加安全可靠的系统应用。在实际应用中,需要注意权限要求、错误处理和安全最佳实践。

发表在 linux文章 | 留下评论

setresgid系统调用及示例setresgid/setresuid/setreuid系统调用及示例

setresgid/setresuid/setreuid 函数详解

1. 函数介绍

这三个函数是Linux系统调用,用于设置进程的用户ID和组ID。它们提供了更灵活的权限控制机制,允许同时设置真实ID、有效ID和保存的ID。了解setresgid/setresuid/setreuid系统调用及示例,掌握Linux进程权限管理技巧。

关键词:setresgid setresuid setreuid 系统调用详解, setresgid setresuid setreuid 函数用法, linux setresgid setresuid setreuid 示例, 系统调用 setresgid setresuid setreuid 说明, setresgid setresuid setreuid 功能解释, linux 系统调用 setresgid setresuid setreuid, setresgid setresuid setreuid 使用教程, setresgid setresuid setreuid 参数说明, linux 中 setresgid setresuid setreuid 应用, 系统调用 setresgid setresuid setreuid 示例代码

  • setresuid: 设置真实用户ID、有效用户ID和保存的用户ID
  • setresgid: 设置真实组ID、有效组ID和保存的组ID
  • setreuid: 设置真实用户ID和有效用户ID(简化版本)

2. 函数原型

#include <unistd.h>

// 设置用户ID
int setresuid(uid_t ruid, uid_t euid, uid_t suid);
int setreuid(uid_t ruid, uid_t euid);

// 设置组ID
int setresgid(gid_t rgid, gid_t egid, gid_t sgid);
int setregid(gid_t rgid, gid_t egid);

3. 功能

这些函数允许进程精确控制其用户和组权限,支持复杂的权限切换场景,是构建安全应用程序的重要工具。

4. 参数

  • ruid/rgid: 真实用户ID/组ID(-1表示不改变)
  • euid/egid: 有效用户ID/组ID(-1表示不改变)
  • suid/sgid: 保存的用户ID/组ID(-1表示不改变,仅setresuid/setresgid)

5. 返回值

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

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

  • setuid/setgid: 设置有效ID
  • seteuid/setegid: 设置有效ID
  • getuid/getgid: 获取真实ID
  • geteuid/getegid: 获取有效ID

7. 示例代码

示例1:基础setresuid/setresgid使用

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>

/**
 * 显示用户和组ID信息
 */
void show_user_group_ids() {
    uid_t ruid = getuid();
    uid_t euid = geteuid();
    gid_t rgid = getgid();
    gid_t egid = getegid();
    
    printf("=== 用户和组ID信息 ===\n");
    printf("真实用户ID: %d", ruid);
    struct passwd *pwd = getpwuid(ruid);
    if (pwd) printf(" (%s)", pwd->pw_name);
    printf("\n");
    
    printf("有效用户ID: %d", euid);
    pwd = getpwuid(euid);
    if (pwd) printf(" (%s)", pwd->pw_name);
    printf("\n");
    
    printf("真实组ID: %d", rgid);
    struct group *grp = getgrgid(rgid);
    if (grp) printf(" (%s)", grp->gr_name);
    printf("\n");
    
    printf("有效组ID: %d", egid);
    grp = getgrgid(egid);
    if (grp) printf(" (%s)", grp->gr_name);
    printf("\n\n");
}

/**
 * 演示基础setresuid/setresgid使用
 */
int demo_basic_usage() {
    uid_t orig_ruid, orig_euid;
    gid_t orig_rgid, orig_egid;
    uid_t test_uid = 1000;
    gid_t test_gid = 1000;
    
    printf("=== 基础setresuid/setresgid使用示例 ===\n");
    
    // 保存原始ID
    orig_ruid = getuid();
    orig_euid = geteuid();
    orig_rgid = getgid();
    orig_egid = getegid();
    
    printf("1. 原始ID信息:\n");
    show_user_group_ids();
    
    // 演示setresuid
    printf("2. 演示setresuid:\n");
    printf("   尝试设置真实用户ID=%d, 有效用户ID=%d, 保存用户ID=%d\n", 
           test_uid, test_uid, test_uid);
    
    int result = setresuid(test_uid, test_uid, test_uid);
    if (result == 0) {
        printf("   ✓ setresuid成功\n");
        printf("   新用户ID信息:\n");
        show_user_group_ids();
    } else {
        printf("   ✗ setresuid失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("   原因:需要root权限\n");
        }
    }
    
    // 演示setresgid
    printf("3. 演示setresgid:\n");
    printf("   尝试设置真实组ID=%d, 有效组ID=%d, 保存组ID=%d\n", 
           test_gid, test_gid, test_gid);
    
    result = setresgid(test_gid, test_gid, test_gid);
    if (result == 0) {
        printf("   ✓ setresgid成功\n");
        printf("   新组ID信息:\n");
        show_user_group_ids();
    } else {
        printf("   ✗ setresgid失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("   原因:需要root权限\n");
        }
    }
    
    // 恢复原始ID
    printf("4. 恢复原始ID:\n");
    result = setresuid(orig_ruid, orig_euid, orig_ruid);
    if (result == 0) {
        printf("   ✓ 恢复用户ID成功\n");
    } else {
        printf("   ✗ 恢复用户ID失败: %s\n", strerror(errno));
    }
    
    result = setresgid(orig_rgid, orig_egid, orig_rgid);
    if (result == 0) {
        printf("   ✓ 恢复组ID成功\n");
    } else {
        printf("   ✗ 恢复组ID失败: %s\n", strerror(errno));
    }
    
    printf("   最终ID信息:\n");
    show_user_group_ids();
    
    return 0;
}

int main() {
    return demo_basic_usage();
}

示例2:权限切换和恢复

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

/**
 * 安全权限切换类
 */
typedef struct {
    uid_t saved_ruid;
    uid_t saved_euid;
    uid_t saved_suid;
    gid_t saved_rgid;
    gid_t saved_egid;
    gid_t saved_sgid;
    int valid;
} security_context_t;

/**
 * 保存当前安全上下文
 */
int save_security_context(security_context_t *ctx) {
    ctx->saved_ruid = getuid();
    ctx->saved_euid = geteuid();
    ctx->saved_rgid = getgid();
    ctx->saved_egid = getegid();
    
    // 保存的ID通常需要通过其他方式获取
    ctx->saved_suid = ctx->saved_euid;
    ctx->saved_sgid = ctx->saved_egid;
    ctx->valid = 1;
    
    printf("保存安全上下文:\n");
    printf("  真实UID: %d, 有效UID: %d, 保存UID: %d\n", 
           ctx->saved_ruid, ctx->saved_euid, ctx->saved_suid);
    printf("  真实GID: %d, 有效GID: %d, 保存GID: %d\n", 
           ctx->saved_rgid, ctx->saved_egid, ctx->saved_sgid);
    
    return 0;
}

/**
 * 恢复安全上下文
 */
int restore_security_context(security_context_t *ctx) {
    if (!ctx->valid) {
        printf("无效的安全上下文\n");
        return -1;
    }
    
    printf("恢复安全上下文:\n");
    printf("  目标UID: %d/%d/%d\n", ctx->saved_ruid, ctx->saved_euid, ctx->saved_suid);
    printf("  目标GID: %d/%d/%d\n", ctx->saved_rgid, ctx->saved_egid, ctx->saved_sgid);
    
    int result = setresuid(ctx->saved_ruid, ctx->saved_euid, ctx->saved_suid);
    if (result != 0) {
        printf("恢复UID失败: %s\n", strerror(errno));
        return -1;
    }
    
    result = setresgid(ctx->saved_rgid, ctx->saved_egid, ctx->saved_sgid);
    if (result != 0) {
        printf("恢复GID失败: %s\n", strerror(errno));
        return -1;
    }
    
    printf("安全上下文恢复成功\n");
    return 0;
}

/**
 * 演示权限切换和恢复
 */
int demo_privilege_switching() {
    security_context_t ctx = {0};
    uid_t current_uid = getuid();
    
    printf("=== 权限切换和恢复演示 ===\n");
    
    // 保存当前安全上下文
    if (save_security_context(&ctx) != 0) {
        printf("保存安全上下文失败\n");
        return -1;
    }
    
    // 显示当前状态
    printf("\n当前用户权限状态:\n");
    show_user_group_ids();
    
    // 检查权限
    if (current_uid == 0) {
        printf("✓ 当前具有root权限,可以进行权限切换测试\n");
    } else {
        printf("✗ 当前没有root权限,权限切换可能受限\n");
    }
    
    // 演示临时权限提升
    printf("\n=== 临时权限提升演示 ===\n");
    if (current_uid == 0) {
        // 如果已经是root,演示降权
        uid_t target_uid = 1000;  // 普通用户ID
        gid_t target_gid = 1000;  // 普通组ID
        
        printf("尝试降权到用户 %d, 组 %d\n", target_uid, target_gid);
        
        int result = setresuid(target_uid, target_uid, target_uid);
        if (result == 0) {
            printf("✓ 用户权限降级成功\n");
            result = setresgid(target_gid, target_gid, target_gid);
            if (result == 0) {
                printf("✓ 组权限降级成功\n");
                show_user_group_ids();
                
                // 模拟降权后的工作
                printf("执行降权后的操作...\n");
                sleep(2);
                
                // 恢复权限
                printf("恢复原始权限:\n");
                if (restore_security_context(&ctx) == 0) {
                    show_user_group_ids();
                    printf("✓ 权限成功恢复\n");
                } else {
                    printf("✗ 权限恢复失败\n");
                }
            } else {
                printf("✗ 组权限降级失败: %s\n", strerror(errno));
            }
        } else {
            printf("✗ 用户权限降级失败: %s\n", strerror(errno));
        }
    } else {
        printf("非root用户权限切换演示:\n");
        
        // 演示部分权限切换(仅改变有效ID)
        printf("尝试仅改变有效用户ID:\n");
        int result = setreuid(-1, current_uid);  // -1表示不改变真实ID
        if (result == 0) {
            printf("✓ 有效用户ID设置成功\n");
        } else {
            printf("✗ 有效用户ID设置失败: %s\n", strerror(errno));
        }
        
        show_user_group_ids();
    }
    
    // 验证最终状态
    printf("\n=== 最终状态验证 ===\n");
    uid_t final_ruid = getuid();
    uid_t final_euid = geteuid();
    gid_t final_rgid = getgid();
    gid_t final_egid = getegid();
    
    printf("最终ID状态:\n");
    printf("  真实UID: %d (原始: %d)\n", final_ruid, ctx.saved_ruid);
    printf("  有效UID: %d (原始: %d)\n", final_euid, ctx.saved_euid);
    printf("  真实GID: %d (原始: %d)\n", final_rgid, ctx.saved_rgid);
    printf("  有效GID: %d (原始: %d)\n", final_egid, ctx.saved_egid);
    
    if (final_ruid == ctx.saved_ruid && final_euid == ctx.saved_euid &&
        final_rgid == ctx.saved_rgid && final_egid == ctx.saved_egid) {
        printf("✓ ID状态验证通过\n");
    } else {
        printf("✗ ID状态验证失败\n");
    }
    
    return 0;
}

// 辅助函数声明
void show_user_group_ids();

int main() {
    return demo_privilege_switching();
}

示例3:setreuid/setregid使用

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <sys/wait.h>

/**
 * 演示setreuid/setregid使用
 */
int demo_simplified_functions() {
    uid_t orig_ruid, orig_euid;
    gid_t orig_rgid, orig_egid;
    uid_t test_uid = 1000;
    gid_t test_gid = 1000;
    
    printf("=== setreuid/setregid 使用演示 ===\n");
    
    // 保存原始ID
    orig_ruid = getuid();
    orig_euid = geteuid();
    orig_rgid = getgid();
    orig_egid = getegid();
    
    printf("原始ID信息:\n");
    show_user_group_ids();
    
    // 演示setreuid(仅设置真实和有效用户ID)
    printf("\n1. 演示setreuid:\n");
    printf("   尝试设置真实用户ID=%d, 有效用户ID=%d\n", test_uid, test_uid);
    
    int result = setreuid(test_uid, test_uid);
    if (result == 0) {
        printf("   ✓ setreuid成功\n");
        printf("   新用户ID信息:\n");
        show_user_group_ids();
    } else {
        printf("   ✗ setreuid失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("   原因:需要适当权限\n");
        }
    }
    
    // 演示setregid(仅设置真实和有效组ID)
    printf("\n2. 演示setregid:\n");
    printf("   尝试设置真实组ID=%d, 有效组ID=%d\n", test_gid, test_gid);
    
    result = setregid(test_gid, test_gid);
    if (result == 0) {
        printf("   ✓ setregid成功\n");
        printf("   新组ID信息:\n");
        show_user_group_ids();
    } else {
        printf("   ✗ setregid失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("   原因:需要适当权限\n");
        }
    }
    
    // 演示部分设置(使用-1)
    printf("\n3. 演示部分设置:\n");
    printf("   保持真实用户ID不变,仅设置有效用户ID\n");
    
    result = setreuid(-1, orig_euid);
    if (result == 0) {
        printf("   ✓ 部分设置成功\n");
        show_user_group_ids();
    } else {
        printf("   ✗ 部分设置失败: %s\n", strerror(errno));
    }
    
    // 恢复原始ID
    printf("\n4. 恢复原始ID:\n");
    result = setreuid(orig_ruid, orig_euid);
    if (result == 0) {
        printf("   ✓ 恢复用户ID成功\n");
    } else {
        printf("   ✗ 恢复用户ID失败: %s\n", strerror(errno));
    }
    
    result = setregid(orig_rgid, orig_egid);
    if (result == 0) {
        printf("   ✓ 恢复组ID成功\n");
    } else {
        printf("   ✗ 恢复组ID失败: %s\n", strerror(errno));
    }
    
    printf("   最终ID信息:\n");
    show_user_group_ids();
    
    return 0;
}

/**
 * 子进程演示
 */
void child_process_demo(int child_id) {
    printf("子进程 %d 启动 (PID: %d)\n", child_id, getpid());
    
    printf("子进程 %d 初始ID信息:\n", child_id);
    show_user_group_ids();
    
    // 子进程尝试修改ID
    uid_t new_uid = 1000;
    gid_t new_gid = 1000;
    
    printf("子进程 %d 尝试修改ID:\n", child_id);
    
    int result = setreuid(new_uid, new_uid);
    if (result == 0) {
        printf("  子进程 %d setreuid成功\n", child_id);
    } else {
        printf("  子进程 %d setreuid失败: %s\n", child_id, strerror(errno));
    }
    
    result = setregid(new_gid, new_gid);
    if (result == 0) {
        printf("  子进程 %d setregid成功\n", child_id);
    } else {
        printf("  子进程 %d setregid失败: %s\n", child_id, strerror(errno));
    }
    
    printf("子进程 %d 最终ID信息:\n", child_id);
    show_user_group_ids();
    
    sleep(2);
    printf("子进程 %d 结束\n", child_id);
    exit(0);
}

/**
 * 多进程ID管理演示
 */
int demo_multiprocess_id_management() {
    pid_t child1_pid, child2_pid;
    
    printf("=== 多进程ID管理演示 ===\n");
    
    printf("父进程初始ID信息:\n");
    show_user_group_ids();
    
    // 创建子进程1
    child1_pid = fork();
    if (child1_pid == 0) {
        child_process_demo(1);
    } else if (child1_pid > 0) {
        printf("创建子进程1: PID=%d\n", child1_pid);
    } else {
        perror("创建子进程1失败");
        return -1;
    }
    
    // 创建子进程2
    child2_pid = fork();
    if (child2_pid == 0) {
        child_process_demo(2);
    } else if (child2_pid > 0) {
        printf("创建子进程2: PID=%d\n", child2_pid);
    } else {
        perror("创建子进程2失败");
        kill(child1_pid, SIGKILL);
        return -1;
    }
    
    // 父进程也尝试修改ID
    printf("\n父进程尝试修改ID:\n");
    uid_t parent_new_uid = 2000;
    gid_t parent_new_gid = 2000;
    
    int result = setreuid(parent_new_uid, parent_new_uid);
    if (result == 0) {
        printf("父进程setreuid成功\n");
    } else {
        printf("父进程setreuid失败: %s\n", strerror(errno));
    }
    
    result = setregid(parent_new_gid, parent_new_gid);
    if (result == 0) {
        printf("父进程setregid成功\n");
    } else {
        printf("父进程setregid失败: %s\n", strerror(errno));
    }
    
    printf("父进程修改后ID信息:\n");
    show_user_group_ids();
    
    // 等待子进程结束
    printf("等待子进程结束:\n");
    int status;
    pid_t finished_pid;
    
    while ((finished_pid = wait(&status)) > 0) {
        printf("子进程 %d 已结束,退出状态: %d\n", finished_pid, WEXITSTATUS(status));
    }
    
    return 0;
}

// 辅助函数声明
void show_user_group_ids();

int main() {
    printf("=== setreuid/setregid vs setresuid/setresgid 比较 ===\n");
    printf("setreuid/setregid: 简化版本,只设置真实和有效ID\n");
    printf("setresuid/setresgid: 完整版本,设置真实、有效和保存的ID\n\n");
    
    // 先运行简化函数演示
    demo_simplified_functions();
    
    printf("\n" "=" * 50 "\n");
    
    // 再运行多进程演示
    demo_multiprocess_id_management();
    
    return 0;
}

示例4:安全权限管理

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>

/**
 * 权限管理器
 */
typedef struct {
    uid_t original_ruid;
    uid_t original_euid;
    uid_t original_suid;
    gid_t original_rgid;
    gid_t original_egid;
    gid_t original_sgid;
    int initialized;
} privilege_manager_t;

/**
 * 初始化权限管理器
 */
int init_privilege_manager(privilege_manager_t *pm) {
    pm->original_ruid = getuid();
    pm->original_euid = geteuid();
    pm->original_rgid = getgid();
    pm->original_egid = getegid();
    
    // 保存的ID通常等于有效ID(在程序启动时)
    pm->original_suid = pm->original_euid;
    pm->original_sgid = pm->original_egid;
    pm->initialized = 1;
    
    printf("权限管理器初始化完成\n");
    printf("原始UID: %d/%d/%d\n", pm->original_ruid, pm->original_euid, pm->original_suid);
    printf("原始GID: %d/%d/%d\n", pm->original_rgid, pm->original_egid, pm->original_sgid);
    
    return 0;
}

/**
 * 临时提升到root权限
 */
int escalate_to_root(privilege_manager_t *pm) {
    if (!pm->initialized) {
        printf("权限管理器未初始化\n");
        return -1;
    }
    
    printf("尝试提升到root权限...\n");
    
    // 使用setresuid设置所有ID为0(root)
    int result = setresuid(0, 0, 0);
    if (result == 0) {
        printf("✓ 成功提升到root权限\n");
        show_user_group_ids();
        
        result = setresgid(0, 0, 0);
        if (result == 0) {
            printf("✓ 成功提升到root组权限\n");
        } else {
            printf("✗ 提升组权限失败: %s\n", strerror(errno));
            // 尝试恢复用户权限
            setresuid(pm->original_ruid, pm->original_euid, pm->original_suid);
        }
    } else {
        printf("✗ 提升用户权限失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("  当前没有足够权限进行提升\n");
        }
    }
    
    return result;
}

/**
 * 降权到指定用户
 */
int drop_to_user(privilege_manager_t *pm, uid_t target_uid, gid_t target_gid) {
    if (!pm->initialized) {
        printf("权限管理器未初始化\n");
        return -1;
    }
    
    printf("尝试降权到用户 %d, 组 %d...\n", target_uid, target_gid);
    
    int result = setresuid(target_uid, target_uid, pm->original_suid);
    if (result == 0) {
        printf("✓ 用户权限降级成功\n");
        
        result = setresgid(target_gid, target_gid, pm->original_sgid);
        if (result == 0) {
            printf("✓ 组权限降级成功\n");
            show_user_group_ids();
        } else {
            printf("✗ 组权限降级失败: %s\n", strerror(errno));
        }
    } else {
        printf("✗ 用户权限降级失败: %s\n", strerror(errno));
    }
    
    return result;
}

/**
 * 恢复原始权限
 */
int restore_original_privileges(privilege_manager_t *pm) {
    if (!pm->initialized) {
        printf("权限管理器未初始化\n");
        return -1;
    }
    
    printf("恢复原始权限...\n");
    
    int result = setresuid(pm->original_ruid, pm->original_euid, pm->original_suid);
    if (result == 0) {
        printf("✓ 用户权限恢复成功\n");
        
        result = setresgid(pm->original_rgid, pm->original_egid, pm->original_sgid);
        if (result == 0) {
            printf("✓ 组权限恢复成功\n");
            show_user_group_ids();
        } else {
            printf("✗ 组权限恢复失败: %s\n", strerror(errno));
        }
    } else {
        printf("✗ 用户权限恢复失败: %s\n", strerror(errno));
    }
    
    return result;
}

/**
 * 演示安全权限管理
 */
int demo_secure_privilege_management() {
    privilege_manager_t pm = {0};
    uid_t current_uid = getuid();
    
    printf("=== 安全权限管理演示 ===\n");
    
    // 初始化权限管理器
    if (init_privilege_manager(&pm) != 0) {
        printf("初始化权限管理器失败\n");
        return -1;
    }
    
    printf("\n当前权限状态:\n");
    show_user_group_ids();
    
    // 根据当前权限级别演示不同场景
    if (current_uid == 0) {
        printf("✓ 当前运行在root权限下\n");
        
        // 演示降权操作
        printf("\n=== 降权操作演示 ===\n");
        uid_t target_uid = 1000;  // 普通用户
        gid_t target_gid = 1000;  // 普通组
        
        if (drop_to_user(&pm, target_uid, target_gid) == 0) {
            printf("降权操作成功\n");
            
            // 模拟降权后的操作
            printf("执行降权后的安全操作...\n");
            sleep(2);
            
            // 恢复原始权限
            printf("\n=== 权限恢复演示 ===\n");
            if (restore_original_privileges(&pm) == 0) {
                printf("权限成功恢复到原始状态\n");
            } else {
                printf("权限恢复失败\n");
            }
        }
    } else {
        printf("✗ 当前运行在普通用户权限下\n");
        
        // 演示权限检查和安全操作
        printf("\n=== 权限检查演示 ===\n");
        
        // 尝试提升权限(应该失败)
        printf("尝试提升权限:\n");
        int result = setresuid(0, 0, 0);
        if (result == 0) {
            printf("✓ 权限提升成功(意外情况)\n");
        } else {
            printf("✗ 权限提升失败: %s\n", strerror(errno));
            printf("  这是预期的行为\n");
        }
        
        // 演示部分权限切换
        printf("\n演示部分权限切换:\n");
        result = setreuid(-1, current_uid);  // 仅改变有效ID
        if (result == 0) {
            printf("✓ 部分权限切换成功\n");
        } else {
            printf("✗ 部分权限切换失败: %s\n", strerror(errno));
        }
    }
    
    // 安全审计
    printf("\n=== 安全审计 ===\n");
    uid_t final_ruid = getuid();
    uid_t final_euid = geteuid();
    gid_t final_rgid = getgid();
    gid_t final_egid = getegid();
    
    printf("最终权限状态:\n");
    printf("  真实UID: %d (原始: %d)\n", final_ruid, pm.original_ruid);
    printf("  有效UID: %d (原始: %d)\n", final_euid, pm.original_euid);
    printf("  真实GID: %d (原始: %d)\n", final_rgid, pm.original_rgid);
    printf("  有效GID: %d (原始: %d)\n", final_egid, pm.original_egid);
    
    if (final_ruid == pm.original_ruid && final_euid == pm.original_euid &&
        final_rgid == pm.original_rgid && final_egid == pm.original_egid) {
        printf("✓ 权限状态审计通过\n");
    } else {
        printf("⚠ 权限状态发生变化,需要关注\n");
    }
    
    return 0;
}

// 辅助函数声明
void show_user_group_ids();

int main() {
    return demo_secure_privilege_management();
}

示例5:权限切换最佳实践

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>

/**
 * 权限切换安全检查
 */
typedef enum {
    PRIV_CHECK_SUCCESS = 0,
    PRIV_CHECK_PERMISSION_DENIED,
    PRIV_CHECK_INVALID_ID,
    PRIV_CHECK_OTHER_ERROR
} privilege_check_result_t;

/**
 * 检查权限切换的可行性
 */
privilege_check_result_t check_privilege_switch_feasibility(uid_t target_uid, gid_t target_gid) {
    uid_t current_ruid = getuid();
    uid_t current_euid = geteuid();
    gid_t current_rgid = getgid();
    gid_t current_egid = getegid();
    
    printf("权限切换可行性检查:\n");
    printf("  当前真实UID: %d, 有效UID: %d\n", current_ruid, current_euid);
    printf("  当前真实GID: %d, 有效GID: %d\n", current_rgid, current_egid);
    printf("  目标UID: %d, 目标GID: %d\n", target_uid, target_gid);
    
    // 检查是否已经是目标权限
    if (current_ruid == target_uid && current_euid == target_uid &&
        current_rgid == target_gid && current_egid == target_gid) {
        printf("  ✓ 已经是目标权限\n");
        return PRIV_CHECK_SUCCESS;
    }
    
    // 检查是否具有足够权限
    if (current_ruid == 0 || current_euid == 0) {
        printf("  ✓ 具有root权限,可以切换到任意权限\n");
        return PRIV_CHECK_SUCCESS;
    }
    
    // 检查普通用户的权限切换限制
    if ((target_uid == current_ruid || target_uid == current_euid) &&
        (target_gid == current_rgid || target_gid == current_egid)) {
        printf("  ✓ 可以切换到目标权限(在允许范围内)\n");
        return PRIV_CHECK_SUCCESS;
    }
    
    printf("  ✗ 权限不足,无法切换到目标权限\n");
    return PRIV_CHECK_PERMISSION_DENIED;
}

/**
 * 安全的权限切换函数
 */
int safe_privilege_switch(uid_t target_uid, gid_t target_gid, int preserve_saved_ids) {
    uid_t current_ruid = getuid();
    uid_t current_euid = geteuid();
    gid_t current_rgid = getgid();
    gid_t current_egid = getegid();
    
    printf("执行安全权限切换:\n");
    printf("  从 UID %d/%d, GID %d/%d\n", 
           current_ruid, current_euid, current_rgid, current_egid);
    printf("  切换到 UID %d, GID %d\n", target_uid, target_gid);
    
    // 使用setresuid/setresgid进行完整切换
    uid_t new_ruid = target_uid;
    uid_t new_euid = target_uid;
    uid_t new_suid = preserve_saved_ids ? -1 : target_uid;
    
    gid_t new_rgid = target_gid;
    gid_t new_egid = target_gid;
    gid_t new_sgid = preserve_saved_ids ? -1 : target_gid;
    
    int result;
    
    // 先切换组ID(通常更安全)
    result = setresgid(new_rgid, new_egid, new_sgid);
    if (result != 0) {
        printf("  ✗ 组权限切换失败: %s\n", strerror(errno));
        return -1;
    }
    
    // 再切换用户ID
    result = setresuid(new_ruid, new_euid, new_suid);
    if (result != 0) {
        printf("  ✗ 用户权限切换失败: %s\n", strerror(errno));
        // 尝试恢复组ID
        setresgid(current_rgid, current_egid, -1);
        return -1;
    }
    
    printf("  ✓ 权限切换成功\n");
    show_user_group_ids();
    
    return 0;
}

/**
 * 权限切换回滚机制
 */
typedef struct {
    uid_t saved_ruid;
    uid_t saved_euid;
    gid_t saved_rgid;
    gid_t saved_egid;
    int valid;
} rollback_info_t;

/**
 * 保存回滚信息
 */
int save_rollback_info(rollback_info_t *rb) {
    rb->saved_ruid = getuid();
    rb->saved_euid = geteuid();
    rb->saved_rgid = getgid();
    rb->saved_egid = getegid();
    rb->valid = 1;
    
    printf("保存回滚信息: UID %d/%d, GID %d/%d\n",
           rb->saved_ruid, rb->saved_euid, rb->saved_rgid, rb->saved_egid);
    
    return 0;
}

/**
 * 执行回滚
 */
int perform_rollback(rollback_info_t *rb) {
    if (!rb->valid) {
        printf("无效的回滚信息\n");
        return -1;
    }
    
    printf("执行权限回滚:\n");
    printf("  目标 UID %d/%d, GID %d/%d\n",
           rb->saved_ruid, rb->saved_euid, rb->saved_rgid, rb->saved_egid);
    
    // 恢复权限
    int result = setresuid(rb->saved_ruid, rb->saved_euid, -1);
    if (result != 0) {
        printf("  ✗ 用户权限回滚失败: %s\n", strerror(errno));
        return -1;
    }
    
    result = setresgid(rb->saved_rgid, rb->saved_egid, -1);
    if (result != 0) {
        printf("  ✗ 组权限回滚失败: %s\n", strerror(errno));
        return -1;
    }
    
    printf("  ✓ 权限回滚成功\n");
    show_user_group_ids();
    
    return 0;
}

/**
 * 演示权限切换最佳实践
 */
int demo_best_practices() {
    rollback_info_t rb = {0};
    uid_t current_uid = getuid();
    
    printf("=== 权限切换最佳实践演示 ===\n");
    
    // 1. 保存当前状态用于回滚
    printf("1. 保存当前权限状态\n");
    if (save_rollback_info(&rb) != 0) {
        printf("保存回滚信息失败\n");
        return -1;
    }
    
    // 2. 检查权限切换可行性
    printf("\n2. 检查权限切换可行性\n");
    privilege_check_result_t check_result = check_privilege_switch_feasibility(1000, 1000);
    if (check_result != PRIV_CHECK_SUCCESS) {
        printf("权限切换不可行,跳过演示\n");
        return 0;
    }
    
    // 3. 执行安全权限切换
    printf("\n3. 执行安全权限切换\n");
    if (current_uid == 0) {
        // 如果是root,演示降权
        printf("当前为root权限,演示安全降权:\n");
        if (safe_privilege_switch(1000, 1000, 1) == 0) {
            printf("✓ 安全降权成功\n");
            
            // 模拟降权后的操作
            printf("执行降权后的安全操作...\n");
            sleep(2);
            
            // 4. 执行权限回滚
            printf("\n4. 执行权限回滚\n");
            if (perform_rollback(&rb) == 0) {
                printf("✓ 权限成功回滚到原始状态\n");
            } else {
                printf("✗ 权限回滚失败\n");
            }
        } else {
            printf("✗ 安全降权失败\n");
        }
    } else {
        // 如果是普通用户,演示权限检查
        printf("当前为普通用户权限,演示权限检查:\n");
        
        // 尝试切换到相同权限(应该成功)
        if (safe_privilege_switch(current_uid, getgid(), 1) == 0) {
            printf("✓ 相同权限切换成功\n");
        }
        
        // 尝试切换到不同权限(应该失败)
        printf("尝试切换到不同权限:\n");
        int result = setresuid(0, 0, 0);  // 尝试切换到root
        if (result == 0) {
            printf("✗ 意外获得root权限\n");
        } else {
            printf("✓ 权限切换被正确拒绝: %s\n", strerror(errno));
        }
    }
    
    // 5. 显示最佳实践总结
    printf("\n=== 权限切换最佳实践总结 ===\n");
    printf("1. ✓ 始终保存原始权限状态用于回滚\n");
    printf("2. ✓ 在切换前检查权限可行性\n");
    printf("3. ✓ 使用setresuid/setresgid进行完整控制\n");
    printf("4. ✓ 先切换组权限,再切换用户权限\n");
    printf("5. ✓ 及时回滚到原始权限状态\n");
    printf("6. ✓ 记录权限切换操作日志\n");
    printf("7. ✓ 遵循最小权限原则\n");
    printf("8. ✓ 在特权操作后立即降权\n");
    
    // 6. 安全审计
    printf("\n=== 最终安全审计 ===\n");
    uid_t final_ruid = getuid();
    uid_t final_euid = geteuid();
    gid_t final_rgid = getgid();
    gid_t final_egid = getegid();
    
    printf("最终权限状态:\n");
    printf("  真实UID: %d (原始: %d)\n", final_ruid, rb.saved_ruid);
    printf("  有效UID: %d (原始: %d)\n", final_euid, rb.saved_euid);
    printf("  真实GID: %d (原始: %d)\n", final_rgid, rb.saved_rgid);
    printf("  有效GID: %d (原始: %d)\n", final_egid, rb.saved_egid);
    
    if (final_ruid == rb.saved_ruid && final_euid == rb.saved_euid &&
        final_rgid == rb.saved_rgid && final_egid == rb.saved_egid) {
        printf("✓ 权限状态已正确恢复\n");
    } else {
        printf("⚠ 权限状态异常,需要手动检查\n");
    }
    
    return 0;
}

// 辅助函数声明
void show_user_group_ids();

int main() {
    return demo_best_practices();
}

使用注意事项

系统要求:

  1. 内核版本: 支持完整权限管理的Linux内核
  2. 权限要求: 需要相应权限才能执行权限切换
  3. 架构支持: 支持所有主流架构

权限规则:

  1. 超级用户: 可以设置任意ID
  2. 普通用户: 只能设置为允许的值
  3. 保存的ID: 通常在exec时设置

错误处理:

  1. EPERM: 权限不足
  2. EINVAL: ID值无效
  3. EAGAIN: 资源暂时不可用

安全考虑:

  1. 权限提升: 可能导致安全风险
  2. 审计日志: 建议记录权限变更
  3. 最小权限: 遵循最小权限原则
  4. 状态恢复: 及时恢复原始状态

最佳实践:

  1. 保存状态: 执行前保存原始权限状态
  2. 检查权限: 验证是否有足够权限
  3. 错误处理: 妥善处理各种错误情况
  4. 及时恢复: 使用完特权后及时恢复
  5. 日志记录: 记录权限变更操作

函数对比

函数参数数量功能适用场景
setreuid2个设置真实和有效用户ID简单权限切换
setregid2个设置真实和有效组ID简单权限切换
setresuid3个设置真实、有效和保存用户ID完整权限控制
setresgid3个设置真实、有效和保存组ID完整权限控制

总结

setresuid/setresgid/setreuid/setregid 提供了灵活的权限管理机制:

  1. 精确控制: 可以精确控制进程的权限状态
  2. 安全机制: 通过权限检查保证系统安全
  3. 灵活使用: 支持部分参数设置(使用-1)
  4. 标准兼容: 符合POSIX标准

在实际应用中,需要:

  • 遵循安全最佳实践
  • 妥善处理错误情况
  • 及时恢复原始权限状态
  • 记录权限变更日志
发表在 linux文章 | 留下评论

setrlimit系统调用及示例

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

1. 函数介绍

在 Linux 系统中,为了保证系统的稳定性和公平性,防止某个程序因为 bug 或恶意行为而耗尽系统资源(如内存、CPU 时间、打开的文件数量等),内核提供了一种资源限制 (Resource Limits) 机制。

setrlimit (Set Resource Limit) 系统调用的作用就是设置调用进程(及其未来创建的子进程)对某一类系统资源的使用上限

你可以把它想象成你给一个程序分配一个“资源使用预算”或“配额”。比如,你可以告诉内核:“这个程序最多只能使用 100MB 的内存”、“最多只能打开 10 个文件”、“最多只能运行 10 秒钟”等等。当程序试图超出这个限制时,内核会根据资源类型采取不同措施,通常是拒绝其请求(例如 malloc 失败)或发送一个信号(例如 SIGXCPU)来终止它。

简单来说,setrlimit 就是让你用程序来给另一个程序(或自己)“上规矩”,限制它能用多少系统资源。

2. 函数原型

#include <sys/resource.h> // 包含系统调用声明和常量

int setrlimit(int resource, const struct rlimit *rlim);

3. 功能

为调用进程设置指定资源 resource 的软限制 (soft limit) 和硬限制 (hard limit)。

4. 参数

  • resource:
    • int 类型。
    • 指定要设置限制的资源类型。常见的资源类型定义在 <sys/resource.h> 中,例如:
      • RLIMIT_AS: 进程虚拟地址空间的最大总大小(字节)。限制进程能分配的总内存。
      • RLIMIT_CORE: 程序崩溃时创建的核心转储文件 (core dump) 的最大字节数。设置为 0 可以禁用 core dump。
      • RLIMIT_CPU: 进程可以使用的 CPU 时间(秒)。达到软限制会收到 SIGXCPU 信号,达到硬限制会被 SIGKILL 终止。
      • RLIMIT_DATA: 进程数据段的最大字节大小(通过 brk/sbrk 分配的内存)。
      • RLIMIT_FSIZE: 进程可以创建的文件的最大字节大小。超出限制时写操作会失败,并可能收到 SIGXFSZ 信号。
      • RLIMIT_NOFILE: 进程可以同时打开的文件描述符(File Descriptor)的最大数量。
      • RLIMIT_NPROC: 调用用户 ID (Real User ID) 可以拥有的最大进程/线程数量。
      • RLIMIT_STACK: 进程栈的最大字节大小。
      • RLIMIT_MEMLOCK: 可以使用 mlock 锁定在内存中的最大字节数。
      • RLIMIT_RSS: 进程在物理内存中驻留的最大字节数(Resident Set Size)。(在 Linux 上可能不强制执行)。
      • RLIMIT_NICE: nice 值的上限(影响进程调度优先级)。
      • … 还有其他一些资源类型。
  • rlim:
    • const struct rlimit * 类型。
    • 一个指向 rlimit 结构体的指针,该结构体定义了资源的限制。rlimit 结构体定义如下:struct rlimit { rlim_t rlim_cur; // Soft limit (软限制) rlim_t rlim_max; // Hard limit (硬限制) };
      • rlim_cur (软限制):
        • 这是内核实际执行强制限制的值。
        • 进程可以随时将其修改为小于或等于当前硬限制 (rlim_max) 的任何值。
        • 超过软限制通常会导致内核发送一个信号(如 SIGXCPU)来警告进程。
      • rlim_max (硬限制):
        • 这是软限制可以被设置的上限
        • 普通用户只能降低硬限制,不能提高它
        • 只有特权用户 (root) 才能提高硬限制。
        • 进程可以在任何时候将硬限制降低到等于或低于当前硬限制的值。

5. 返回值

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

6. 错误码 (errno)

  • EFAULTrlim 指向了调用进程无法访问的内存地址。
  • EINVALresource 参数无效,或者指定的限制值无效(例如,负数,或者对于某些资源类型不合适)。
  • EPERM: 调用进程没有权限设置指定的限制。最常见的原因是普通用户试图提高硬限制 (rlim_max)。

7. 相似函数或关联函数

  • getrlimit: 用于获取当前进程对某类资源的限制设置。
  • prlimit: 一个更现代的系统调用,可以同时设置和获取任意进程的资源限制(需要 CAP_SYS_RESOURCE 能力)。
  • ulimit: 命令行工具(在 shell 中),用于设置当前 shell 及其子进程的资源限制。它在底层调用 setrlimit 和 getrlimit
  • struct rlimit: 定义限制值的数据结构。

8. 示例代码

下面的示例演示了如何使用 setrlimit 来设置几种常见的资源限制。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/resource.h> // 包含 setrlimit, getrlimit, struct rlimit
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/time.h> // 包含 timeval, 用于 CPU 时间限制
#include <fcntl.h>   // 包含 open

// 信号处理函数,用于捕获因资源限制而产生的信号
void signal_handler(int sig) {
    printf("\nCaught signal %d\n", sig);
    if (sig == SIGXCPU) {
        printf("CPU time limit (soft) reached. Exiting gracefully.\n");
        exit(EXIT_FAILURE);
    } else if (sig == SIGXFSZ) {
        printf("File size limit reached. Write operation failed.\n");
        // 可以选择继续运行或退出
    }
}

// 打印特定资源的当前限制
void print_resource_limit(const char* resource_name, int resource) {
    struct rlimit rl;
    if (getrlimit(resource, &rl) == 0) {
        printf("%-15s: Soft = ", resource_name);
        if (rl.rlim_cur == RLIM_INFINITY) printf("unlimited");
        else printf("%ld", (long)rl.rlim_cur);

        printf(", Hard = ");
        if (rl.rlim_max == RLIM_INFINITY) printf("unlimited");
        else printf("%ld", (long)rl.rlim_max);
        printf("\n");
    } else {
        perror("getrlimit");
    }
}

int main() {
    struct rlimit rl;
    struct sigaction sa;

    printf("--- Demonstrating setrlimit ---\n");
    printf("PID: %d\n", getpid());

    // 1. 显示初始的一些资源限制
    printf("\n--- Initial Resource Limits ---\n");
    print_resource_limit("CPU Time", RLIMIT_CPU);
    print_resource_limit("File Size", RLIMIT_FSIZE);
    print_resource_limit("Data Segment", RLIMIT_DATA);
    print_resource_limit("Stack Size", RLIMIT_STACK);
    print_resource_limit("Virtual Memory", RLIMIT_AS);
    print_resource_limit("Open Files", RLIMIT_NOFILE);
    print_resource_limit("Max Processes", RLIMIT_NPROC);
    print_resource_limit("Core File Size", RLIMIT_CORE);

    // 2. 设置 CPU 时间限制
    printf("\n--- Setting CPU Time Limit ---\n");
    rl.rlim_cur = 5;  // 软限制:5 秒
    rl.rlim_max = 10; // 硬限制:10 秒
    if (setrlimit(RLIMIT_CPU, &rl) == 0) {
        printf("Set CPU time limit: Soft = %lds, Hard = %lds\n", (long)rl.rlim_cur, (long)rl.rlim_max);
        // 设置信号处理函数以捕获 SIGXCPU
        memset(&sa, 0, sizeof(sa));
        sa.sa_handler = signal_handler;
        sigemptyset(&sa.sa_mask);
        if (sigaction(SIGXCPU, &sa, NULL) == -1) {
            perror("sigaction SIGXCPU");
        }
    } else {
        perror("setrlimit RLIMIT_CPU");
    }

    // 3. 设置最大打开文件数限制
    printf("\n--- Setting Open File Descriptor Limit ---\n");
    rl.rlim_cur = 10; // 软限制:最多 10 个文件描述符
    rl.rlim_max = 20; // 硬限制:最多 20 个文件描述符
    if (setrlimit(RLIMIT_NOFILE, &rl) == 0) {
        printf("Set open file limit: Soft = %ld, Hard = %ld\n", (long)rl.rlim_cur, (long)rl.rlim_max);
    } else {
        perror("setrlimit RLIMIT_NOFILE");
    }

    // 4. 设置最大文件大小限制
    printf("\n--- Setting File Size Limit ---\n");
    rl.rlim_cur = 1024 * 1024; // 软限制:1MB
    rl.rlim_max = 2 * 1024 * 1024; // 硬限制:2MB
    if (setrlimit(RLIMIT_FSIZE, &rl) == 0) {
        printf("Set file size limit: Soft = %ld bytes, Hard = %ld bytes\n", (long)rl.rlim_cur, (long)rl.rlim_max);
        // 设置信号处理函数以捕获 SIGXFSZ
        sa.sa_handler = signal_handler;
        if (sigaction(SIGXFSZ, &sa, NULL) == -1) {
            perror("sigaction SIGXFSZ");
        }
    } else {
        perror("setrlimit RLIMIT_FSIZE");
    }

    // 5. 设置虚拟内存限制 (RLIMIT_AS)
    printf("\n--- Setting Virtual Memory Limit ---\n");
    rl.rlim_cur = 50 * 1024 * 1024; // 软限制:50 MB
    rl.rlim_max = 100 * 1024 * 1024; // 硬限制:100 MB
    if (setrlimit(RLIMIT_AS, &rl) == 0) {
        printf("Set virtual memory limit: Soft = %ld bytes (%.2f MB), Hard = %ld bytes (%.2f MB)\n",
               (long)rl.rlim_cur, (double)rl.rlim_cur / (1024*1024),
               (long)rl.rlim_max, (double)rl.rlim_max / (1024*1024));
    } else {
        perror("setrlimit RLIMIT_AS");
    }

    // 6. 设置 core dump 大小为 0,禁用它
    printf("\n--- Disabling Core Dump ---\n");
    rl.rlim_cur = 0;
    rl.rlim_max = 0;
    if (setrlimit(RLIMIT_CORE, &rl) == 0) {
        printf("Disabled core dump generation.\n");
    } else {
        perror("setrlimit RLIMIT_CORE");
    }

    // 7. 验证设置后的限制
    printf("\n--- Resource Limits After setrlimit ---\n");
    print_resource_limit("CPU Time", RLIMIT_CPU);
    print_resource_limit("File Size", RLIMIT_FSIZE);
    print_resource_limit("Virtual Memory", RLIMIT_AS);
    print_resource_limit("Open Files", RLIMIT_NOFILE);
    print_resource_limit("Core File Size", RLIMIT_CORE);

    // 8. 演示资源限制的效果

    // --- 演示 RLIMIT_FSIZE ---
    printf("\n--- Testing RLIMIT_FSIZE (File Size Limit) ---\n");
    const char* test_filename = "test_limited_file.txt";
    int fd = open(test_filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open test file");
    } else {
        char data[1024];
        memset(data, 'A', sizeof(data));
        ssize_t written;
        long total_written = 0;
        // 尝试写入超过 1MB 的数据
        while (total_written < 2 * 1024 * 1024) {
            written = write(fd, data, sizeof(data));
            if (written == -1) {
                perror("write");
                printf("Write failed after writing approximately %ld bytes. File size limit likely reached.\n", total_written);
                break;
            }
            total_written += written;
        }
        close(fd);
        printf("Finished writing (or failed) to file.\n");
        // 清理测试文件
        unlink(test_filename);
    }

    // --- 演示 RLIMIT_AS ---
    printf("\n--- Testing RLIMIT_AS (Virtual Memory Limit) ---\n");
    printf("Attempting to allocate large chunks of memory until limit is hit...\n");
    size_t chunk_size = 10 * 1024 * 1024; // 10MB
    long allocated_mb = 0;
    char *ptr;
    while (1) {
        ptr = malloc(chunk_size);
        if (ptr == NULL) {
            printf("malloc failed after allocating approximately %ld MB. Memory limit likely reached.\n", allocated_mb);
            break;
        }
        // Touch the memory to ensure it's actually allocated
        memset(ptr, 0, chunk_size);
        allocated_mb += chunk_size / (1024 * 1024);
        printf("Allocated %ld MB so far...\n", allocated_mb);
        // 添加一点延迟,方便观察
        sleep(1);
    }

    // --- 演示 RLIMIT_CPU (放在最后,因为它会终止程序) ---
    printf("\n--- Testing RLIMIT_CPU (CPU Time Limit) ---\n");
    printf("Entering infinite loop. Should be killed by SIGKILL after 10 seconds (hard limit).\n");
    printf("You might see 'CPU time limit (soft) reached' message first (after 5s), then termination.\n");
    while(1) {
        // 空循环,消耗 CPU 时间
    }

    // 程序通常不会执行到这里,因为 RLIMIT_CPU 会终止它
    printf("Program finished normally (unexpected).\n");
    return 0;
}

9. 编译和运行

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

# 运行程序
./setrlimit_example

10. 预期输出 (片段)

--- Demonstrating setrlimit ---
PID: 12345

--- Initial Resource Limits ---
CPU Time       : Soft = unlimited, Hard = unlimited
File Size      : Soft = unlimited, Hard = unlimited
Data Segment   : Soft = unlimited, Hard = unlimited
Stack Size     : Soft = 8388608, Hard = unlimited
Virtual Memory : Soft = unlimited, Hard = unlimited
Open Files     : Soft = 1024, Hard = 1048576
Max Processes  : Soft = 62545, Hard = 62545
Core File Size : Soft = 0, Hard = unlimited

--- Setting CPU Time Limit ---
Set CPU time limit: Soft = 5s, Hard = 10s

--- Setting Open File Descriptor Limit ---
Set open file limit: Soft = 10, Hard = 20

--- Setting File Size Limit ---
Set file size limit: Soft = 1048576 bytes, Hard = 2097152 bytes

--- Setting Virtual Memory Limit ---
Set virtual memory limit: Soft = 52428800 bytes (50.00 MB), Hard = 104857600 bytes (100.00 MB)

--- Disabling Core Dump ---
Disabled core dump generation.

--- Resource Limits After setrlimit ---
CPU Time       : Soft = 5, Hard = 10
File Size      : Soft = 1048576, Hard = 2097152
Virtual Memory : Soft = 52428800, Hard = 104857600
Open Files     : Soft = 10, Hard = 20
Core File Size : Soft = 0, Hard = 0

--- Testing RLIMIT_FSIZE (File Size Limit) ---
Finished writing (or failed) to file.

--- Testing RLIMIT_AS (Virtual Memory Limit) ---
Attempting to allocate large chunks of memory until limit is hit...
Allocated 10 MB so far...
Allocated 20 MB so far...
...
Allocated 50 MB so far...
malloc failed after allocating approximately 50 MB. Memory limit likely reached.

--- Testing RLIMIT_CPU (CPU Time Limit) ---
Entering infinite loop. Should be killed by SIGKILL after 10 seconds (hard limit).
You might see 'CPU time limit (soft) reached' message first (after 5s), then termination.

Caught signal 24
CPU time limit (soft) reached. Exiting gracefully.
[程序被终止]

11. 总结

setrlimit 是一个非常有用的系统调用,用于管理和控制进程对系统资源的消耗。它对于编写健壮、安全的系统程序和服务至关重要,可以防止资源耗尽导致的系统不稳定。理解软限制和硬限制的区别,以及不同资源类型的行为,是掌握 Linux 进程管理的基础。

发表在 linux文章 | 标签为 | 留下评论

setsockopt系统调用及示例

1. 函数介绍

setsockopt (Set Socket Options) 是一个 Linux 系统调用,用于在已创建的套接字上设置各种选项(options)或参数(parameters)。这些选项控制着套接字的行为和底层协议的特定方面。

你可以把 setsockopt 想象成调整收音机或电视机的设置

  • 你有一个设备(套接字)。
  • 这个设备有很多可以调整的旋钮和开关(选项),比如音量(SO_RCVBUF)、音质(SO_REUSEADDR)、电源(SO_KEEPALIVE)等。
  • setsockopt 就是让你转动这些旋钮、切换这些开关,来改变设备的工作方式。

这些选项可以影响套接字的方方面面,例如:

  • 地址重用: 允许绑定到一个处于 TIME_WAIT 状态的地址(SO_REUSEADDR)。
  • 缓冲区大小: 调整发送和接收缓冲区的大小(SO_SNDBUFSO_RCVBUF)。
  • 连接保活: 启用 TCP 的 keep-alive 机制来检测死连接(SO_KEEPALIVE)。
  • ** linger**: 控制 close 调用在有未发送数据时的行为(SO_LINGER)。
  • 广播: 允许在 UDP 套接字上发送广播数据报(SO_BROADCAST)。
  • 错误: 控制是否接收带外数据的错误指示(SO_OOBINLINE)。
  • 超时: 设置发送和接收的超时时间(SO_SNDTIMEOSO_RCVTIMEO)。
  • 以及其他许多高级选项

2. 函数原型

#include <sys/socket.h> // 必需

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

3. 功能

  • 设置选项: 为套接字 sockfd 设置由 level 和 optname 共同指定的选项。
  • 传递参数: 通过 optval 指针将选项的具体值传递给内核。这个值的类型和含义取决于具体的选项。
  • 指定长度: 通过 optlen 指定 optval 指向的数据的大小(以字节为单位)。

4. 参数

  • int sockfd: 这是一个已创建(通过 socket())的有效套接字文件描述符
  • int level: 指定选项定义的协议层(level)。
    • SOL_SOCKET套接字层本身。这是最常用的级别,用于设置与具体网络协议(如 TCP/IP)无关的通用套接字选项。
    • IPPROTO_IPIP 层。用于设置 IP 协议特有的选项(如 IP_TTL)。
    • IPPROTO_TCPTCP 层。用于设置 TCP 协议特有的选项(如 TCP_NODELAY)。
    • IPPROTO_IPV6IPv6 层。用于设置 IPv6 协议特有的选项。
  • int optname: 指定在 level 层要设置的具体选项名称
    • 例如,在 SOL_SOCKET 级别,常见的 optname 有:
      • SO_REUSEADDR: 允许重用本地地址。
      • SO_KEEPALIVE: 启用 keep-alive 机制。
      • SO_LINGER: 设置 linger 选项。
      • SO_BROADCAST: 允许发送广播数据报。
      • SO_SNDBUF: 设置发送缓冲区大小。
      • SO_RCVBUF: 设置接收缓冲区大小。
      • SO_SNDTIMEO: 设置发送超时。
      • SO_RCVTIMEO: 设置接收超时。
    • 在 IPPROTO_TCP 级别,常见的 optname 有:
      • TCP_NODELAY: 禁用 Nagle 算法(发送小包时立即发送,不等待)。
  • const void *optval: 这是一个指向选项值的指针。
    • 选项值的类型取决于 optname
    • 对于布尔型选项(如 SO_REUSEADDR),通常是一个指向 int 的指针,非 0 表示启用,0 表示禁用。
    • 对于整型选项(如 SO_SNDBUF),通常是一个指向 int 的指针,值为所需的大小。
    • 对于结构体选项(如 SO_LINGER),则是一个指向相应结构体的指针。
  • socklen_t optlen: 指定 optval 指向的数据的大小(以字节为单位)。
    • 例如,如果 optval 指向一个 int,则 optlen 应为 sizeof(int)
    • 如果 optval 指向一个 struct linger,则 optlen 应为 sizeof(struct linger)

5. 返回值

  • 成功时: 返回 0。套接字选项已成功设置。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EBADF sockfd 无效,EINVAL level 或 optname 无效或 optval/optlen 不匹配,ENOPROTOOPT 协议不支持该选项等)。

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

  • getsockopt: 用于获取套接字的当前选项值。与 setsockopt 相对应。
  • socket: 创建套接字,是 setsockopt 操作的对象。
  • bind / listen / connect: 其他套接字操作函数,通常与 setsockopt 结合使用来配置套接字。

7. 示例代码

示例 1:设置 SO_REUSEADDR 选项

这个例子演示了如何在服务器套接字上设置 SO_REUSEADDR 选项,这是服务器编程中的一个常见且重要的实践。

// setsockopt_reuseaddr.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PORT 8095
#define BACKLOG 10

int main() {
    int server_fd;
    struct sockaddr_in address;
    int opt = 1; // 用于 SO_REUSEADDR 的值

    // 1. 创建套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
    printf("Socket created successfully (fd: %d)\n", server_fd);

    // --- 关键: 设置 SO_REUSEADDR 选项 ---
    // 这允许服务器在重启时立即绑定到同一个地址,
    // 即使旧的连接可能处于 TIME_WAIT 状态。
    printf("Setting SO_REUSEADDR option...\n");
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt SO_REUSEADDR failed");
        // 注意:即使失败,程序也可以继续,但这不是好习惯
        // 最佳实践是处理错误并决定是否继续
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    printf("SO_REUSEADDR option set successfully.\n");

    // 2. 配置服务器地址结构
    memset(&address, 0, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 3. 绑定套接字
    printf("Binding socket to port %d...\n", PORT);
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    printf("Socket bound successfully.\n");

    // 4. 监听
    if (listen(server_fd, BACKLOG) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    printf("Server is listening.\n");

    printf("Server setup complete. Press Ctrl+C to exit.\n");
    pause(); // 挂起等待信号

    close(server_fd);
    return 0;
}

代码解释:

  1. 创建 TCP 套接字。
  2. 关键步骤: 调用 setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))
    • server_fd: 要设置选项的套接字。
    • SOL_SOCKET: 选项所在的协议层(套接字层)。
    • SO_REUSEADDR: 要设置的选项名称。
    • &opt: 指向选项值的指针。这里 opt 是一个 int 变量,值为 1,表示启用该选项。
    • sizeof(opt): 选项值的大小。
  3. 如果调用成功,SO_REUSEADDR 选项就被设置为启用了。
  4. 继续进行 bind 和 listen
  5. 设置 SO_REUSEADDR 的好处是,当服务器进程因某种原因(如崩溃或重启)终止后,它之前绑定的地址和端口可能会在内核中处于 TIME_WAIT 状态一段时间。如果没有设置 SO_REUSEADDR,立即重启服务器并尝试绑定同一个地址端口会失败(EADDRINUSE)。设置了这个选项后,就可以立即重用该地址。

示例 2:设置 SO_RCVBUF 和 SO_SNDBUF 选项

这个例子演示了如何调整套接字的接收和发送缓冲区大小。

// setsockopt_buffer.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PORT 8096
#define CUSTOM_BUFFER_SIZE 64 * 1024 // 64 KB

void print_buffer_sizes(int sock, const char* role) {
    int rcv_buf_size, snd_buf_size;
    socklen_t len = sizeof(rcv_buf_size);

    if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcv_buf_size, &len) == 0) {
        printf("%s SO_RCVBUF: %d bytes\n", role, rcv_buf_size);
    } else {
        perror("getsockopt SO_RCVBUF failed");
    }

    len = sizeof(snd_buf_size); // Reset len
    if (getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &snd_buf_size, &len) == 0) {
        printf("%s SO_SNDBUF: %d bytes\n", role, snd_buf_size);
    } else {
        perror("getsockopt SO_SNDBUF failed");
    }
}

int main() {
    int server_fd, client_sock;
    struct sockaddr_in address, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    int opt = 1;

    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 设置 SO_REUSEADDR
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt SO_REUSEADDR failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // --- 设置服务器套接字的缓冲区大小 ---
    printf("--- Before setting buffer sizes ---\n");
    print_buffer_sizes(server_fd, "Server (before)");

    int rcv_buf_size = CUSTOM_BUFFER_SIZE;
    int snd_buf_size = CUSTOM_BUFFER_SIZE;

    printf("\nSetting custom buffer sizes to %d bytes...\n", CUSTOM_BUFFER_SIZE);
    if (setsockopt(server_fd, SOL_SOCKET, SO_RCVBUF, &rcv_buf_size, sizeof(rcv_buf_size))) {
        perror("setsockopt SO_RCVBUF failed");
        // 注意:内核可能会调整这个值到一个合理的范围
    }
    if (setsockopt(server_fd, SOL_SOCKET, SO_SNDBUF, &snd_buf_size, sizeof(snd_buf_size))) {
        perror("setsockopt SO_SNDBUF failed");
    }

    printf("--- After setting buffer sizes ---\n");
    print_buffer_sizes(server_fd, "Server (after)");

    // 绑定和监听
    memset(&address, 0, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    if (listen(server_fd, 5) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("\nServer listening on port %d\n", PORT);

    // --- 客户端连接 ---
    client_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (client_sock < 0) {
        perror("client socket failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if (connect(client_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("client connect failed");
        close(server_fd);
        close(client_sock);
        exit(EXIT_FAILURE);
    }

    // --- 检查客户端套接字的缓冲区大小 ---
    printf("\n--- Client socket buffer sizes ---\n");
    print_buffer_sizes(client_sock, "Client");

    // --- 服务器 accept 连接并检查其套接字 ---
    int accepted_sock = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
    if (accepted_sock < 0) {
        perror("accept failed");
        close(server_fd);
        close(client_sock);
        exit(EXIT_FAILURE);
    }

    printf("\n--- Server accepted socket buffer sizes ---\n");
    print_buffer_sizes(accepted_sock, "Server-Accepted");

    // 简单通信后关闭
    close(client_sock);
    close(accepted_sock);
    close(server_fd);

    return 0;
}

代码解释:

  1. 定义了一个 print_buffer_sizes 函数,它使用 getsockopt 来获取并打印套接字的当前接收和发送缓冲区大小。
  2. 创建服务器套接字。
  3. 设置 SO_REUSEADDR
  4. 关键: 在 bind 之前,调用 setsockopt 来设置服务器套接字的 SO_RCVBUF 和 SO_SNDBUF
    • 传递一个 int 变量的指针和其大小。
  5. 打印设置前后的缓冲区大小。注意,内核可能会将请求的大小调整为一个内部支持的值。
  6. 继续设置服务器监听。
  7. 创建客户端套接字并连接。
  8. 打印客户端套接字的缓冲区大小。
  9. 服务器 accept 连接,得到一个新的已连接套接字。
  10. 打印这个新套接字的缓冲区大小。通常,accept 返回的套接字会继承监听套接字的一些属性,包括缓冲区大小。

示例 3:设置 TCP_NODELAY 选项

这个例子演示了如何在 TCP 套接字上设置 TCP_NODELAY 选项,以禁用 Nagle 算法。

// setsockopt_tcp_nodelay.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h> // For TCP_NODELAY
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PORT 8097

int main() {
    int server_fd, client_sock;
    struct sockaddr_in address, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    int opt = 1;

    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt SO_REUSEADDR failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    memset(&address, 0, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    if (listen(server_fd, 5) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d. Waiting for connection...\n", PORT);

    // --- 客户端连接 ---
    client_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (client_sock < 0) {
        perror("client socket failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if (connect(client_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("client connect failed");
        close(server_fd);
        close(client_sock);
        exit(EXIT_FAILURE);
    }
    printf("Client connected.\n");

    // --- 服务器 accept 连接 ---
    int accepted_sock = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
    if (accepted_sock < 0) {
        perror("accept failed");
        close(server_fd);
        close(client_sock);
        exit(EXIT_FAILURE);
    }
    printf("Server accepted connection.\n");

    // --- 关键: 在已连接的套接字上设置 TCP_NODELAY ---
    // 这会禁用 Nagle 算法,使得小的数据包能够立即发送,而不必等待 ACK 或积累更多数据。
    // 这对于实时性要求高的应用(如游戏、交互式应用)很有用。
    printf("\nSetting TCP_NODELAY on server's accepted socket...\n");
    int flag = 1; // Enable TCP_NODELAY
    if (setsockopt(accepted_sock, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) < 0) {
        perror("setsockopt TCP_NODELAY on server socket failed");
        // 不是致命错误,可以继续
    } else {
        printf("TCP_NODELAY enabled on server's accepted socket (fd: %d).\n", accepted_sock);
    }

    printf("\nSetting TCP_NODELAY on client socket...\n");
    if (setsockopt(client_sock, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) < 0) {
        perror("setsockopt TCP_NODELAY on client socket failed");
    } else {
        printf("TCP_NODELAY enabled on client socket (fd: %d).\n", client_sock);
    }

    // ... 这里可以进行数据传输 ...

    close(client_sock);
    close(accepted_sock);
    close(server_fd);

    printf("Sockets closed.\n");
    return 0;
}

代码解释:

  1. 设置标准的服务器和客户端连接。
  2. 服务器 accept 连接后,得到 accepted_sock
  3. 关键: 在 accepted_sock 上调用 setsockopt(accepted_sock, IPPROTO_TCP, TCP_NODELAY, ...)
    • IPPROTO_TCP: 选项所在的协议层(TCP 层)。
    • TCP_NODELAY: 要设置的选项名称。
    • &flag (flag=1): 选项值,1 表示启用(禁用 Nagle 算法)。
    • sizeof(int): 选项值大小。
  4. 同样地,在客户端套接字 client_sock 上也设置 TCP_NODELAY
  5. 启用 TCP_NODELAY 后,TCP 连接将立即发送小的数据包,而不会等待将多个小包合并成一个更大的包(Nagle 算法的默认行为)或等待前一个包的 ACK。这可以减少延迟,但可能会增加网络上的小包数量。

重要提示与注意事项:

  1. 调用时机setsockopt 可以在 socket() 之后、bind()/connect()/listen() 之前或之后调用,具体取决于选项。有些选项(如 SO_REUSEADDR)必须在 bind 之前设置才有效。
  2. level 和 optname 的匹配: 确保 level 和 optname 正确匹配。例如,TCP_NODELAY 必须在 IPPROTO_TCP 级别设置。
  3. optval 和 optlen: 确保传递给 optval 的数据类型和大小与 optname 要求的完全一致。
  4. 内核调整: 对于某些选项(如缓冲区大小),内核可能会将你请求的值调整为一个它认为更合适的值。
  5. 错误处理: 始终检查返回值。虽然某些选项设置失败可能不会导致程序无法运行,但最好处理错误并了解原因。
  6. 常见用途SO_REUSEADDRTCP_NODELAYSO_KEEPALIVESO_LINGER 是网络编程中非常常见的选项。

总结:

setsockopt 是一个功能强大的函数,允许程序员精细地调整套接字的行为。理解其参数(特别是 level 和 optname 的组合)以及各种常用选项的作用,对于编写高效、健壮的网络应用程序至关重要。它是网络编程中不可或缺的工具之一。

发表在 linux文章 | 留下评论

settimeofday系统调用及示例

settimeofday 函数详解

1. 函数介绍

settimeofday 是Linux系统调用,settimeofday系统调用及示例,用于设置系统的日期和时间。它允许特权进程修改系统时钟,是系统管理和时间同步的重要工具。这个函数通常由系统管理员或时间同步服务(如NTP)使用。

2. 函数原型

#include <sys/time.h>
int settimeofday(const struct timeval *tv, const struct timezone *tz);

3. 功能

settimeofday 设置系统的当前日期和时间。它可以精确到微秒级别,用于系统时钟校准和时间同步。

4. 参数

  • *const struct timeval tv: 指向时间值结构的指针(NULL表示不设置时间)
  • *const struct timezone tz: 指向时区结构的指针(通常为NULL,已被废弃)

5. 返回值

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

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

  • gettimeofday: 获取当前时间
  • clock_settime: 更现代的时间设置函数
  • adjtime: 逐步调整系统时间
  • ntp_adjtime: NTP时间调整

7. 示例代码

示例1:基础settimeofday使用

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

/**
 * 显示当前系统时间
 */
void show_current_time() {
    struct timeval tv;
    struct tm *tm_info;
    char time_str[64];
    
    if (gettimeofday(&tv, NULL) == 0) {
        tm_info = localtime(&tv.tv_sec);
        strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
        printf("当前系统时间: %s.%06ld\n", time_str, tv.tv_usec);
    } else {
        printf("获取当前时间失败: %s\n", strerror(errno));
    }
}

/**
 * 演示基础settimeofday使用方法
 */
int demo_settimeofday_basic() {
    struct timeval current_time, new_time;
    struct timezone tz = {0, 0};
    time_t original_time;
    int result;
    
    printf("=== 基础settimeofday使用示例 ===\n");
    
    // 显示原始时间
    printf("1. 原始系统时间:\n");
    show_current_time();
    
    // 获取当前时间作为参考
    if (gettimeofday(&current_time, NULL) != 0) {
        printf("获取当前时间失败: %s\n", strerror(errno));
        return -1;
    }
    
    original_time = current_time.tv_sec;
    
    // 设置新的时间(当前时间+10秒)
    new_time.tv_sec = current_time.tv_sec + 10;
    new_time.tv_usec = current_time.tv_usec;
    
    printf("\n2. 尝试设置新时间:\n");
    printf("   目标时间: %ld.%06ld\n", new_time.tv_sec, new_time.tv_usec);
    
    // 尝试设置时间(需要root权限)
    result = settimeofday(&new_time, &tz);
    if (result == 0) {
        printf("   ✓ 成功设置系统时间\n");
        
        // 验证设置结果
        printf("   设置后的时间:\n");
        show_current_time();
        
        // 恢复原始时间
        printf("\n3. 恢复原始时间:\n");
        struct timeval restore_time;
        restore_time.tv_sec = original_time;
        restore_time.tv_usec = current_time.tv_usec;
        
        result = settimeofday(&restore_time, &tz);
        if (result == 0) {
            printf("   ✓ 成功恢复原始时间\n");
            show_current_time();
        } else {
            printf("   ✗ 恢复原始时间失败: %s\n", strerror(errno));
            if (errno == EPERM) {
                printf("   原因:需要root权限\n");
            }
        }
    } else {
        printf("   ✗ 设置系统时间失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("   原因:需要root权限来设置系统时间\n");
        } else if (errno == EINVAL) {
            printf("   原因:时间值无效\n");
        }
        
        printf("   注意:普通用户通常无法修改系统时间\n");
    }
    
    return 0;
}

int main() {
    return demo_settimeofday_basic();
}

示例2:时间同步模拟

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

/**
 * 时间同步服务模拟
 */
typedef struct {
    time_t reference_time;
    int sync_interval;
    int max_adjustment;
} time_sync_service_t;

/**
 * 初始化时间同步服务
 */
int init_time_sync_service(time_sync_service_t *service) {
    struct timeval tv;
    
    if (gettimeofday(&tv, NULL) != 0) {
        printf("获取当前时间失败\n");
        return -1;
    }
    
    service->reference_time = tv.tv_sec;
    service->sync_interval = 60;  // 60秒同步间隔
    service->max_adjustment = 30; // 最大调整30秒
    
    printf("时间同步服务初始化完成\n");
    printf("  参考时间: %ld\n", service->reference_time);
    printf("  同步间隔: %d 秒\n", service->sync_interval);
    printf("  最大调整: %d 秒\n", service->max_adjustment);
    
    return 0;
}

/**
 * 计算时间差
 */
long calculate_time_difference(time_t current_time, time_t reference_time) {
    return current_time - reference_time;
}

/**
 * 模拟时间同步
 */
int simulate_time_synchronization(time_sync_service_t *service) {
    struct timeval current_time, adjusted_time;
    struct timezone tz = {0, 0};
    long time_diff;
    int result;
    
    printf("=== 时间同步模拟 ===\n");
    
    // 获取当前时间
    if (gettimeofday(&current_time, NULL) != 0) {
        printf("获取当前时间失败: %s\n", strerror(errno));
        return -1;
    }
    
    printf("当前系统时间: %ld.%06ld\n", current_time.tv_sec, current_time.tv_usec);
    
    // 计算时间差
    time_diff = calculate_time_difference(current_time.tv_sec, service->reference_time);
    printf("与参考时间差: %ld 秒\n", time_diff);
    
    // 检查是否需要调整
    if (labs(time_diff) > service->max_adjustment) {
        printf("时间偏差过大,需要调整\n");
        
        // 计算调整后的时间
        adjusted_time.tv_sec = service->reference_time;
        adjusted_time.tv_usec = current_time.tv_usec;
        
        printf("调整目标时间: %ld.%06ld\n", adjusted_time.tv_sec, adjusted_time.tv_usec);
        
        // 尝试设置时间
        result = settimeofday(&adjusted_time, &tz);
        if (result == 0) {
            printf("✓ 时间同步成功\n");
            show_current_time();
        } else {
            printf("✗ 时间同步失败: %s\n", strerror(errno));
            if (errno == EPERM) {
                printf("  需要root权限来调整系统时间\n");
            }
            return -1;
        }
    } else {
        printf("时间偏差在可接受范围内,无需调整\n");
    }
    
    return 0;
}

/**
 * 演示时间同步
 */
int demo_time_synchronization() {
    time_sync_service_t service = {0};
    
    printf("=== 时间同步演示 ===\n");
    
    // 检查权限
    uid_t uid = getuid();
    printf("当前用户ID: %d\n", uid);
    if (uid == 0) {
        printf("✓ 具有root权限,可以设置系统时间\n");
    } else {
        printf("✗ 没有root权限,时间设置操作将失败\n");
    }
    
    // 显示当前时间
    printf("\n1. 当前系统时间:\n");
    show_current_time();
    
    // 初始化时间同步服务
    if (init_time_sync_service(&service) != 0) {
        printf("初始化时间同步服务失败\n");
        return -1;
    }
    
    // 模拟时间同步
    printf("\n2. 模拟时间同步:\n");
    if (simulate_time_synchronization(&service) != 0) {
        printf("时间同步模拟失败\n");
    }
    
    // 显示同步后的时间
    printf("\n3. 同步后的时间:\n");
    show_current_time();
    
    return 0;
}

// 辅助函数声明
void show_current_time();

int main() {
    return demo_time_synchronization();
}

示例3:逐步时间调整

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

/**
 * 逐步时间调整
 */
int gradual_time_adjustment(time_t target_time, int adjustment_steps) {
    struct timeval current_time, target_tv;
    struct timezone tz = {0, 0};
    time_t current_sec, step_size;
    int result;
    
    printf("=== 逐步时间调整 ===\n");
    printf("目标时间: %ld\n", target_time);
    printf("调整步数: %d\n", adjustment_steps);
    
    // 获取当前时间
    if (gettimeofday(&current_time, NULL) != 0) {
        printf("获取当前时间失败: %s\n", strerror(errno));
        return -1;
    }
    
    current_sec = current_time.tv_sec;
    printf("当前时间: %ld\n", current_sec);
    
    // 计算步长
    step_size = (target_time - current_sec) / adjustment_steps;
    if (step_size == 0) {
        step_size = (target_time > current_sec) ? 1 : -1;
    }
    
    printf("每步调整: %ld 秒\n", step_size);
    
    // 逐步调整时间
    for (int i = 0; i < adjustment_steps; i++) {
        time_t next_time = current_sec + (i + 1) * step_size;
        
        // 确保不超过目标时间
        if ((step_size > 0 && next_time > target_time) ||
            (step_size < 0 && next_time < target_time)) {
            next_time = target_time;
        }
        
        target_tv.tv_sec = next_time;
        target_tv.tv_usec = current_time.tv_usec;
        
        printf("第 %d 步: 设置时间 %ld\n", i + 1, next_time);
        
        result = settimeofday(&target_tv, &tz);
        if (result == 0) {
            printf("  ✓ 设置成功\n");
            show_current_time();
        } else {
            printf("  ✗ 设置失败: %s\n", strerror(errno));
            if (errno == EPERM) {
                printf("  需要root权限\n");
                break;
            }
        }
        
        // 短暂延迟
        sleep(1);
    }
    
    return 0;
}

/**
 * 时间跳跃检测
 */
int detect_time_jumps() {
    struct timeval prev_time, current_time;
    long time_diff;
    static int first_call = 1;
    
    if (gettimeofday(&current_time, NULL) != 0) {
        printf("获取时间失败: %s\n", strerror(errno));
        return -1;
    }
    
    if (first_call) {
        prev_time = current_time;
        first_call = 0;
        return 0;
    }
    
    time_diff = current_time.tv_sec - prev_time.tv_sec;
    
    if (labs(time_diff) > 5) {  // 超过5秒的时间跳跃
        printf("⚠ 检测到时间跳跃: %ld 秒\n", time_diff);
        printf("  之前时间: %ld.%06ld\n", prev_time.tv_sec, prev_time.tv_usec);
        printf("  当前时间: %ld.%06ld\n", current_time.tv_sec, current_time.tv_usec);
    }
    
    prev_time = current_time;
    return 0;
}

/**
 * 演示逐步时间调整
 */
int demo_gradual_adjustment() {
    struct timeval current_time;
    time_t target_time;
    uid_t uid = getuid();
    
    printf("=== 逐步时间调整演示 ===\n");
    
    // 检查权限
    printf("用户权限检查:\n");
    printf("  当前用户ID: %d\n", uid);
    if (uid == 0) {
        printf("  ✓ 具有root权限\n");
    } else {
        printf("  ✗ 没有root权限,时间设置将失败\n");
    }
    
    // 显示当前时间
    printf("\n当前系统时间:\n");
    show_current_time();
    
    // 获取当前时间
    if (gettimeofday(&current_time, NULL) != 0) {
        printf("获取当前时间失败\n");
        return -1;
    }
    
    // 设置目标时间为当前时间+30秒
    target_time = current_time.tv_sec + 30;
    printf("\n目标时间: %ld (当前时间+30秒)\n", target_time);
    
    // 演示逐步调整
    printf("\n执行逐步时间调整:\n");
    if (gradual_time_adjustment(target_time, 5) != 0) {
        printf("逐步时间调整失败\n");
    }
    
    // 演示时间跳跃检测
    printf("\n时间跳跃检测演示:\n");
    for (int i = 0; i < 10; i++) {
        detect_time_jumps();
        sleep(1);
    }
    
    return 0;
}

// 辅助函数声明
void show_current_time();

int main() {
    return demo_gradual_adjustment();
}

示例4:NTP时间同步模拟

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

/**
 * NTP时间同步模拟
 */
typedef struct {
    time_t server_time;
    double offset;
    double delay;
    int stratum;
    char server_name[64];
} ntp_server_info_t;

/**
 * 模拟NTP服务器响应
 */
int simulate_ntp_server_response(ntp_server_info_t *server) {
    struct timeval tv;
    
    // 模拟NTP服务器信息
    strcpy(server->server_name, "ntp.example.com");
    server->stratum = 2;  // 二级时间服务器
    server->delay = 0.05 + (rand() / (double)RAND_MAX) * 0.1;  // 50-150ms延迟
    
    // 获取当前时间作为服务器时间
    if (gettimeofday(&tv, NULL) != 0) {
        return -1;
    }
    
    server->server_time = tv.tv_sec;
    // 模拟时钟偏移(-1到1秒)
    server->offset = (rand() / (double)RAND_MAX) * 2.0 - 1.0;
    
    return 0;
}

/**
 * 计算时间同步质量
 */
double calculate_sync_quality(ntp_server_info_t *server) {
    // 简单的质量计算:基于stratum和offset
    double quality = 1.0;
    
    // stratum越高,质量越低
    quality -= (server->stratum - 1) * 0.1;
    
    // offset越大,质量越低
    quality -= fabs(server->offset) * 0.5;
    
    // delay越大,质量越低
    quality -= server->delay * 2.0;
    
    return (quality > 0) ? quality : 0.0;
}

/**
 * NTP时间同步
 */
int ntp_time_synchronization(ntp_server_info_t *server) {
    struct timeval current_time, sync_time;
    struct timezone tz = {0, 0};
    double quality;
    int result;
    
    printf("=== NTP时间同步 ===\n");
    printf("服务器: %s\n", server->server_name);
    printf("层级: %d\n", server->stratum);
    printf("服务器时间: %ld\n", server->server_time);
    printf("时钟偏移: %.3f 秒\n", server->offset);
    printf("网络延迟: %.3f 秒\n", server->delay);
    
    // 计算同步质量
    quality = calculate_sync_quality(server);
    printf("同步质量: %.2f\n", quality);
    
    if (quality < 0.5) {
        printf("同步质量过低,跳过同步\n");
        return -1;
    }
    
    // 获取当前时间
    if (gettimeofday(&current_time, NULL) != 0) {
        printf("获取当前时间失败: %s\n", strerror(errno));
        return -1;
    }
    
    printf("同步前时间: %ld.%06ld\n", current_time.tv_sec, current_time.tv_usec);
    
    // 计算同步后的时间
    sync_time.tv_sec = server->server_time;
    sync_time.tv_usec = current_time.tv_usec;
    
    // 应用偏移调整
    if (server->offset > 0) {
        sync_time.tv_sec += (time_t)server->offset;
        sync_time.tv_usec += (suseconds_t)((server->offset - (time_t)server->offset) * 1000000);
    } else {
        sync_time.tv_sec += (time_t)server->offset;
        sync_time.tv_usec += (suseconds_t)((server->offset - (time_t)server->offset) * 1000000);
    }
    
    // 确保微秒在有效范围内
    if (sync_time.tv_usec >= 1000000) {
        sync_time.tv_sec += 1;
        sync_time.tv_usec -= 1000000;
    } else if (sync_time.tv_usec < 0) {
        sync_time.tv_sec -= 1;
        sync_time.tv_usec += 1000000;
    }
    
    printf("同步目标时间: %ld.%06ld\n", sync_time.tv_sec, sync_time.tv_usec);
    
    // 执行时间同步
    result = settimeofday(&sync_time, &tz);
    if (result == 0) {
        printf("✓ NTP时间同步成功\n");
        show_current_time();
    } else {
        printf("✗ NTP时间同步失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("  需要root权限来设置系统时间\n");
        }
        return -1;
    }
    
    return 0;
}

/**
 * 演示NTP时间同步
 */
int demo_ntp_synchronization() {
    ntp_server_info_t servers[3];
    double best_quality = 0.0;
    int best_server = -1;
    
    printf("=== NTP时间同步演示 ===\n");
    
    // 检查权限
    uid_t uid = getuid();
    printf("权限检查: ");
    if (uid == 0) {
        printf("✓ 具有root权限\n");
    } else {
        printf("✗ 没有root权限,时间设置将失败\n");
    }
    
    // 显示当前时间
    printf("\n当前系统时间:\n");
    show_current_time();
    
    // 模拟多个NTP服务器
    printf("\n查询NTP服务器:\n");
    srand(time(NULL));
    
    for (int i = 0; i < 3; i++) {
        if (simulate_ntp_server_response(&servers[i]) == 0) {
            double quality = calculate_sync_quality(&servers[i]);
            printf("服务器 %d: %s (质量: %.2f)\n", 
                   i + 1, servers[i].server_name, quality);
            
            if (quality > best_quality) {
                best_quality = quality;
                best_server = i;
            }
        }
    }
    
    // 选择最佳服务器进行同步
    if (best_server >= 0) {
        printf("\n选择最佳服务器进行同步:\n");
        printf("  服务器: %s\n", servers[best_server].server_name);
        printf("  质量: %.2f\n", best_quality);
        
        if (ntp_time_synchronization(&servers[best_server]) != 0) {
            printf("NTP时间同步失败\n");
        }
    } else {
        printf("\n没有找到合适的NTP服务器\n");
    }
    
    // 显示同步后的时间
    printf("\n同步后的时间:\n");
    show_current_time();
    
    return 0;
}

// 辅助函数声明
void show_current_time();

int main() {
    return demo_ntp_synchronization();
}

示例5:时间管理工具

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

/**
 * 时间管理工具配置
 */
typedef struct {
    int set_time;
    time_t target_time;
    int show_time;
    int sync_time;
    char time_string[64];
    int verbose;
} time_tool_config_t;

/**
 * 显示帮助信息
 */
void show_help(const char *program_name) {
    printf("用法: %s [选项]\n", program_name);
    printf("\n选项:\n");
    printf("  -s, --set TIME     设置系统时间 (格式: YYYY-MM-DD HH:MM:SS)\n");
    printf("  -g, --get          显示当前系统时间\n");
    printf("  -S, --sync         同步时间\n");
    printf("  -v, --verbose      详细输出\n");
    printf("  -h, --help         显示此帮助信息\n");
    printf("\n示例:\n");
    printf("  %s -g              # 显示当前时间\n", program_name);
    printf("  %s -s \"2023-12-25 10:30:00\"  # 设置时间\n", program_name);
    printf("  %s -v -g           # 详细显示当前时间\n", program_name);
}

/**
 * 解析时间字符串
 */
int parse_time_string(const char *time_str, time_t *result) {
    struct tm tm_time = {0};
    char *endptr;
    
    // 尝试解析 ISO 格式时间: YYYY-MM-DD HH:MM:SS
    if (strptime(time_str, "%Y-%m-%d %H:%M:%S", &tm_time) != NULL) {
        *result = mktime(&tm_time);
        if (*result == -1) {
            printf("时间转换失败\n");
            return -1;
        }
        return 0;
    }
    
    // 尝试解析 Unix 时间戳
    *result = strtol(time_str, &endptr, 10);
    if (*endptr == '\0' && *result > 0) {
        return 0;
    }
    
    printf("无法解析时间字符串: %s\n", time_str);
    printf("支持的格式:\n");
    printf("  YYYY-MM-DD HH:MM:SS\n");
    printf("  Unix时间戳\n");
    return -1;
}

/**
 * 详细显示时间信息
 */
void show_detailed_time() {
    struct timeval tv;
    struct tm *tm_info;
    char time_str[128];
    
    if (gettimeofday(&tv, NULL) == 0) {
        // 显示多种时间格式
        tm_info = localtime(&tv.tv_sec);
        
        // 标准格式
        strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
        printf("标准时间: %s.%06ld\n", time_str, tv.tv_usec);
        
        // Unix时间戳
        printf("Unix时间戳: %ld.%06ld\n", tv.tv_sec, tv.tv_usec);
        
        // UTC时间
        tm_info = gmtime(&tv.tv_sec);
        strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S UTC", tm_info);
        printf("UTC时间: %s\n", time_str);
        
        // 星期和年份信息
        strftime(time_str, sizeof(time_str), "%A, %B %d, %Y", localtime(&tv.tv_sec));
        printf("详细日期: %s\n", time_str);
        
    } else {
        printf("获取时间失败: %s\n", strerror(errno));
    }
}

/**
 * 设置系统时间
 */
int set_system_time(time_t target_time) {
    struct timeval tv;
    struct timezone tz = {0, 0};
    int result;
    
    printf("设置系统时间为: %ld\n", target_time);
    
    tv.tv_sec = target_time;
    tv.tv_usec = 0;
    
    result = settimeofday(&tv, &tz);
    if (result == 0) {
        printf("✓ 系统时间设置成功\n");
        show_current_time();
        return 0;
    } else {
        printf("✗ 系统时间设置失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("  需要root权限来设置系统时间\n");
        }
        return -1;
    }
}

/**
 * 演示时间管理工具
 */
int demo_time_management_tool() {
    time_tool_config_t config = {0};
    uid_t uid = getuid();
    
    printf("=== 时间管理工具演示 ===\n");
    
    // 显示工具信息
    printf("工具功能:\n");
    printf("  1. 显示系统时间\n");
    printf("  2. 设置系统时间\n");
    printf("  3. 时间同步\n");
    printf("  4. 详细时间信息\n");
    
    // 权限检查
    printf("\n权限检查:\n");
    printf("  当前用户ID: %d\n", uid);
    if (uid == 0) {
        printf("  ✓ 具有root权限,可以设置系统时间\n");
    } else {
        printf("  ✗ 没有root权限,时间设置功能将受限\n");
    }
    
    // 演示显示时间功能
    printf("\n1. 显示当前时间:\n");
    show_current_time();
    
    printf("\n2. 详细时间信息:\n");
    show_detailed_time();
    
    // 演示时间设置功能
    printf("\n3. 时间设置功能演示:\n");
    
    // 获取当前时间
    struct timeval current_time;
    if (gettimeofday(&current_time, NULL) == 0) {
        time_t future_time = current_time.tv_sec + 60;  // 1分钟后
        
        printf("  尝试设置时间为1分钟后: %ld\n", future_time);
        
        if (uid == 0) {
            // 有权限时尝试设置
            if (set_system_time(future_time) == 0) {
                printf("  ✓ 时间设置成功\n");
                
                // 恢复原始时间
                printf("  恢复原始时间: %ld\n", current_time.tv_sec);
                set_system_time(current_time.tv_sec);
            }
        } else {
            // 无权限时模拟设置
            struct timeval future_tv;
            future_tv.tv_sec = future_time;
            future_tv.tv_usec = current_time.tv_usec;
            
            int result = settimeofday(&future_tv, NULL);
            if (result != 0) {
                printf("  ✗ 时间设置失败 (预期): %s\n", strerror(errno));
                printf("  需要root权限才能设置系统时间\n");
            }
        }
    }
    
    // 演示时间格式解析
    printf("\n4. 时间格式解析演示:\n");
    const char *time_formats[] = {
        "2023-12-25 10:30:00",
        "1703498200",  // Unix时间戳
        NULL
    };
    
    for (int i = 0; time_formats[i]; i++) {
        time_t parsed_time;
        printf("  解析时间字符串: %s\n", time_formats[i]);
        if (parse_time_string(time_formats[i], &parsed_time) == 0) {
            printf("  ✓ 解析成功: %ld\n", parsed_time);
        } else {
            printf("  ✗ 解析失败\n");
        }
    }
    
    // 显示工具使用建议
    printf("\n=== 工具使用建议 ===\n");
    printf("1. 时间设置需要root权限\n");
    printf("2. 建议使用NTP服务进行时间同步\n");
    printf("3. 避免频繁手动调整系统时间\n");
    printf("4. 记录时间变更操作日志\n");
    printf("5. 使用逐步调整避免时间跳跃\n");
    
    return 0;
}

// 辅助函数声明
void show_current_time();

int main() {
    return demo_time_management_tool();
}

settimeofday 使用注意事项

系统要求:

  1. 内核版本: 支持settimeofday的Linux内核
  2. 权限要求: 需要CAP_SYS_TIME能力或root权限
  3. 架构支持: 支持所有主流架构

参数限制:

  1. 时间有效性: tv参数必须指向有效的timeval结构
  2. 时区参数: tz参数通常应为NULL(已被废弃)
  3. 时间范围: 时间值应在有效范围内

错误处理:

  1. EPERM: 权限不足(需要CAP_SYS_TIME或root权限)
  2. EINVAL: 时间值无效
  3. EFAULT: 指针参数指向无效内存

安全考虑:

  1. 权限提升: 不当使用可能导致安全风险
  2. 系统稳定性: 频繁的时间调整可能影响系统稳定性
  3. 应用程序影响: 时间跳跃可能影响依赖时间的应用程序

最佳实践:

  1. 权限检查: 执行前检查是否具有足够权限
  2. 时间验证: 验证时间值的有效性和合理性
  3. 错误处理: 妥善处理各种错误情况
  4. 日志记录: 记录时间变更操作
  5. 逐步调整: 避免大的时间跳跃,使用逐步调整

时间结构体说明

struct timeval:

struct timeval {
    time_t tv_sec;      // 秒数
    suseconds_t tv_usec; // 微秒数
};

struct timezone(已废弃):

struct timezone {
    int tz_minuteswest; // 西偏分钟数
    int tz_dsttime;     // 夏令时标志
};

相关函数对比

1. settimeofday vs clock_settime:

// settimeofday(传统接口)
settimeofday(&tv, NULL);

// clock_settime(现代接口)
clock_settime(CLOCK_REALTIME, &timespec);

2. settimeofday vs adjtime:

// settimeofday:直接设置时间
settimeofday(&tv, NULL);

// adjtime:逐步调整时间
adjtime(&delta, NULL);

常见使用场景

1. 系统管理:

// 系统启动时设置初始时间
settimeofday(&initial_time, NULL);

2. NTP客户端:

// 时间同步服务设置系统时间
settimeofday(&ntp_time, NULL);

3. 测试环境:

// 测试时模拟特定时间点
settimeofday(&test_time, NULL);

时间同步策略

1. 突然跳跃 vs 逐步调整:

  • 突然跳跃: 使用settimeofday直接设置
  • 逐步调整: 使用adjtime逐步调整

2. 同步频率:

  • 高精度: 每分钟同步
  • 普通: 每小时同步
  • 低精度: 每天同步

总结

settimeofday 是Linux系统中重要的时间管理函数,提供了:

  1. 精确时间控制: 可以精确到微秒级别设置系统时间
  2. 灵活接口: 支持多种时间格式和设置方式
  3. 权限管理: 通过权限控制保证系统安全
  4. 标准兼容: 符合POSIX标准

通过合理使用 settimeofday,可以实现精确的时间管理和同步。在实际应用中,需要注意权限要求、错误处理和系统稳定性等关键问题。建议在生产环境中使用专业的NTP服务进行时间同步,避免手动频繁调整系统时间。

发表在 linux文章 | 标签为 | 留下评论

setuid系统调用及示例

我们来深入学习 setuid 系统调用

1. 函数介绍

在 Linux 系统中,每个进程都运行在一个特定的用户 (User) 上下文中。这个用户上下文决定了进程拥有哪些权限,比如能否读写某个文件、能否绑定到特权端口(端口号小于 1024)等。

每个进程通常有三类相关的用户 ID:

  • 真实用户 ID (Real User ID – RUID): 登录系统时分配给用户的 ID。它标识了“你是谁”。
  • 有效用户 ID (Effective User ID – EUID): 内核用来进行权限检查时使用的 ID。它决定了“你能做什么”。这是最重要的一个。
  • 保存的设置用户 ID (Saved Set-User-ID – SUID): 用于在有效用户 ID 和真实用户 ID 之间来回切换的一个“备份”ID。

setuid (Set User ID) 系统调用的主要作用是设置调用进程的有效用户 ID (EUID)。根据调用者的权限和当前 ID 状态,它也可能同时修改保存的设置用户 ID (SUID)。

简单来说,setuid 让你的程序可以“以某个用户的身份”去执行操作,从而获得或限制与该用户相关的权限。

一个非常常见的场景是:一个需要监听 80 端口(特权端口)的 Web 服务器程序。它通常以 root 用户(UID 0)启动,以便能绑定到 80 端口。但一旦绑定成功,为了安全起见,它会使用 setuid 将自己的有效用户 ID 切换到一个权限较低的普通用户(如 www-data),这样即使程序出现漏洞被攻击,攻击者也无法获得 root 权限。

2. 函数原型

#include <unistd.h>    // 包含系统调用声明
#include <sys/types.h> // 包含 uid_t 类型定义

int setuid(uid_t uid);

3. 功能

设置调用进程的有效用户 ID (Effective UID)。根据调用者的权限(是否为 root)和目标 uid,行为会有所不同。

4. 参数

  • uid:
    • uid_t 类型。
    • 指定要设置的新的有效用户 ID。

5. 返回值

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

6. 行为规则

setuid 的具体行为取决于调用进程的权限:

  1. 如果调用者是特权用户 (超级用户, root, EUID == 0):
    • 可以将有效用户 ID (euid) 设置为任意有效的用户 ID。
    • 同时,真实用户 ID (ruid) 和保存的设置用户 ID (suid) 也会被设置为相同的 uid 值
    • 这是特权用户的强大能力。
  2. 如果调用者是普通用户 (EUID != 0):
    • uid 参数必须是调用进程的真实用户 ID (ruid) 或 保存的设置用户 ID (suid) 之一。
    • 只能将有效用户 ID (euid) 设置为 ruid 或 suid
    • 真实用户 ID (ruid) 和 保存的设置用户 ID (suid) 不会被修改
    • 这是为了防止普通用户随意获取其他用户的权限。

7. 错误码 (errno)

  • EINVALuid 参数无效(虽然在 Linux 中通常不会返回此错误)。
  • EPERM: 调用者没有权限执行此操作。对于普通用户,这意味着 uid 既不是 ruid 也不是 suid

8. 相似函数或关联函数

  • setgid: 设置组 ID,与 setuid 功能类似,但针对的是组而非用户。
  • seteuid: 专门用于设置有效用户 ID,行为比 setuid 更受限(普通用户只能设置为 ruid 或 suid)。
  • setreuid: 同时设置真实用户 ID 和 有效用户 ID。
  • setresuid: 同时设置 真实有效 和 保存的设置 用户 ID,提供了最精细的控制。
  • getuid: 获取调用进程的真实用户 ID。
  • geteuid: 获取调用进程的有效用户 ID。
  • chmod / chown: 与文件权限和所有权相关的系统调用,其行为也受 setuid 影响。

9. 示例代码

下面的示例演示了 setuid 在不同权限下的行为,以及如何检查用户 ID。

#define _GNU_SOURCE // 启用 GNU 扩展以使用 getresuid
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

void print_current_uids(const char* context) {
    uid_t ruid, euid, suid;
    printf("[%s] Current UIDs - Real: %d, Effective: %d, Saved: %d\n",
           context, getuid(), geteuid(), (getresuid(&ruid, &euid, &suid) == 0) ? suid : -1);
}

int main() {
    uid_t original_ruid, original_euid, original_suid;
    uid_t target_uid;
    int result;

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

    // 1. 获取并打印初始 UID
    if (getresuid(&original_ruid, &original_euid, &original_suid) != 0) {
        perror("getresuid");
        exit(EXIT_FAILURE);
    }
    print_current_uids("Start");

    // 2. 检查是否以 root 权限运行
    if (geteuid() == 0) {
        printf("\nRunning as ROOT (Privileged User)\n");
        // 作为 root,可以设置为任意有效的 UID
        // 这里我们尝试设置为 'nobody' 用户 (通常 UID 65534, 但请检查你的系统)
        target_uid = 65534;
        printf("Attempting to set UID to %d (usually 'nobody' user)...\n", target_uid);

        print_current_uids("Before setuid");
        result = setuid(target_uid);
        if (result == 0) {
            printf("setuid(%d) succeeded.\n", target_uid);
            print_current_uids("After setuid");
            printf("Note: All UIDs (Real, Effective, Saved) are now %d.\n", target_uid);
            printf("The process is now running with 'nobody' privileges.\n");
        } else {
            perror("setuid");
            printf("Failed to set UID to %d.\n", target_uid);
        }

    } else {
        printf("\nRunning as a REGULAR USER (UID: %d)\n", getuid());

        // 作为普通用户,只能设置为自己的 ruid 或 suid
        target_uid = original_ruid; // 选择设置为自己的真实 UID (这不会改变任何东西)
        printf("Attempting to set UID to my Real UID (%d)...\n", target_uid);
        print_current_uids("Before setuid");
        result = setuid(target_uid);
        if (result == 0) {
            printf("setuid(%d) succeeded (as expected).\n", target_uid);
            print_current_uids("After setuid");
        } else {
            perror("setuid");
        }

        // 尝试设置为一个无效的 UID (比如一个不存在的或不属于我的 UID)
        // 这通常会失败
        target_uid = 9999; // 假设这是一个无效的或不属于当前用户的 UID
        printf("\nAttempting to set UID to an invalid/different UID (%d)...\n", target_uid);
        result = setuid(target_uid);
        if (result == -1) {
            if (errno == EPERM) {
                printf("setuid(%d) failed with EPERM (Operation not permitted) - as expected for a regular user.\n", target_uid);
                printf("This is because %d is not my Real UID (%d) or Saved Set-UID (%d).\n",
                       target_uid, original_ruid, original_suid);
            } else {
                perror("setuid");
            }
            print_current_uids("After failed setuid");
        } else {
            printf("setuid(%d) unexpectedly succeeded.\n", target_uid);
            print_current_uids("After unexpected setuid");
        }
    }

    printf("\n--- Summary ---\n");
    printf("The setuid() function changes the Effective UID of the process.\n");
    printf("For root: It can change to any UID, and also changes Real and Saved UID.\n");
    printf("For regular users: It can only change Effective UID to Real or Saved UID.\n");
    printf("This is crucial for security, especially in programs like setuid binaries.\n");

    return 0;
}

10. 编译和运行

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

# 1. 作为普通用户运行
./setuid_example

# 2. 作为 root 用户运行 (需要 sudo 权限)
# 注意:切换到 root 权限执行程序有风险,请小心!
sudo ./setuid_example

11. 预期输出 (作为普通用户运行)

--- Demonstrating setuid ---
[Start] Current UIDs - Real: 1000, Effective: 1000, Saved: 1000

Running as a REGULAR USER (UID: 1000)
Attempting to set UID to my Real UID (1000)...
[Before setuid] Current UIDs - Real: 1000, Effective: 1000, Saved: 1000
setuid(1000) succeeded (as expected).
[After setuid] Current UIDs - Real: 1000, Effective: 1000, Saved: 1000

Attempting to set UID to an invalid/different UID (9999)...
setuid(9999) failed with EPERM (Operation not permitted) - as expected for a regular user.
This is because 9999 is not my Real UID (1000) or Saved Set-UID (1000).
[After failed setuid] Current UIDs - Real: 1000, Effective: 1000, Saved: 1000

--- Summary ---
The setuid() function changes the Effective UID of the process.
For root: It can change to any UID, and also changes Real and Saved UID.
For regular users: It can only change Effective UID to Real or Saved UID.
This is crucial for security, especially in programs like setuid binaries.

12. 预期输出 (使用 sudo 以 root 权限运行)

--- Demonstrating setuid ---
[Start] Current UIDs - Real: 0, Effective: 0, Saved: 0

Running as ROOT (Privileged User)
Attempting to set UID to 65534 (usually 'nobody' user)...
[Before setuid] Current UIDs - Real: 0, Effective: 0, Saved: 0
setuid(65534) succeeded.
[After setuid] Current UIDs - Real: 65534, Effective: 65534, Saved: 65534
Note: All UIDs (Real, Effective, Saved) are now 65534.
The process is now running with 'nobody' privileges.

13. 关于 Set-UID (SUID) 位的说明

虽然上面的示例是在运行时调用 setuid,但 setuid 系统调用的强大之处还体现在可执行文件的 SUID 位上。

当你将一个可执行文件的 SUID 位设置为 1 时(例如使用 chmod u+s myprogram),会发生以下情况:

  1. 无论哪个用户执行这个文件,该进程启动时的有效用户 ID (EUID) 都会被设置为该文件所有者的用户 ID
  2. 这使得普通用户可以运行一个具有文件所有者权限的程序。

例如

  1. Root 用户创建一个程序 read_etc_shadow,其功能是读取 /etc/shadow 文件(普通用户无权读取)。
  2. Root 将此程序的所有者设为 root,并设置 SUID 位:sudo chown root:root read_etc_shadow && sudo chmod u+s read_etc_shadow
  3. 普通用户 alice 执行 ./read_etc_shadow
  4. 程序启动时,其 euid 是 0 (root),因此它可以成功读取 /etc/shadow

安全警告
SUID 程序是系统安全的关键点,因为它们允许普通用户临时获得更高的权限。编写 SUID 程序时必须极其小心,避免任何可能导致权限提升的安全漏洞。

14. 总结

setuid 是一个基础且重要的系统调用,用于管理进程的用户权限。理解它的行为规则(尤其是特权用户和普通用户的区别)对于编写安全的 Linux 程序至关重要。它常用于守护进程降权、SUID 程序以及需要特定用户权限的系统管理任务中。

发表在 linux文章 | 留下评论

setxattr系统调用及示例

setxattr 函数详解

1. 函数介绍

setxattr 是 Linux 系统中用于设置文件扩展属性(Extended Attributes,简称 xattrs)的系统调用。可以把扩展属性想象成”文件的隐藏标签”或”元数据贴纸”——它们是附加在文件上的额外信息,不会影响文件内容,但可以存储各种有用的元数据。

就像你可以在文件上贴便利贴记录信息一样,扩展属性允许你为文件附加自定义的元数据,比如:

  • 安全标签(SELinux 等)
  • 访问控制列表
  • 用户自定义的注释或标记
  • 备份状态信息
  • 文件分类标签

setxattr 函数让你能够为文件设置这些”便利贴”,而不需要修改文件内容本身。

2. 函数原型

#include <sys/xattr.h>

int setxattr(const char *path, const char *name, 
             const void *value, size_t size, int flags);

3. 功能

setxattr 函数用于为指定路径的文件设置扩展属性。它允许你为文件附加自定义的元数据,这些元数据与文件内容分离存储,但与文件关联。

4. 参数

  • path: 指向文件路径的指针
  • name: 扩展属性的名称(字符串格式)
  • value: 指向属性值的指针
  • size: 属性值的大小(以字节为单位)
  • flags: 控制操作行为的标志位

5. 扩展属性命名规范

扩展属性名称通常采用以下格式:

  • namespace.attribute_name

常见命名空间:

  • user.*: 用户自定义属性(最常用,需要文件写权限)
  • trusted.*: 受信任的属性(只有特权用户可访问)
  • system.*: 系统属性(由内核或系统服务使用)
  • security.*: 安全相关属性(如 SELinux 标签)

6. 标志位(flags 参数)

标志说明
00如果属性存在则替换,不存在则创建
XATTR_CREATE0x1仅当属性不存在时创建(类似”新建”)
XATTR_REPLACE0x2仅当属性已存在时替换(类似”更新”)

7. 返回值

  • 成功: 返回 0
  • 失败: 返回 -1,并设置相应的 errno 错误码

8. 常见错误码

  • EACCES: 权限不足
  • ENOTSUP: 文件系统不支持扩展属性
  • ENOSPC: 磁盘空间不足
  • EEXIST: 使用 XATTR_CREATE 时属性已存在
  • ENOATTR: 使用 XATTR_REPLACE 时属性不存在
  • ENAMETOOLONG: 属性名称过长
  • EINVAL: 参数无效
  • EROFS: 文件系统只读
  • EFAULT: 参数指针无效

9. 相似函数或关联函数

  • lsetxattr: 通过文件路径设置符号链接本身的扩展属性(不跟随符号链接)
  • fsetxattr: 通过文件描述符设置扩展属性
  • getxattr: 获取文件扩展属性的值
  • listxattr: 列出文件的所有扩展属性名称
  • removexattr: 删除文件的扩展属性
  • attr/xattr 命令行工具: 命令行界面的扩展属性操作

10. 示例代码

示例1:基础用法 – 设置用户自定义属性

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

// 创建测试文件
int create_test_file(const char *filename) {
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    const char *content = "这是一个测试文件\n用于演示扩展属性功能\n";
    write(fd, content, strlen(content));
    close(fd);
    
    printf("✓ 创建测试文件: %s\n", filename);
    return 0;
}

// 获取并显示扩展属性
void show_xattr_value(const char *filename, const char *attr_name) {
    char buffer[1024];
    ssize_t size = getxattr(filename, attr_name, buffer, sizeof(buffer) - 1);
    
    if (size != -1) {
        buffer[size] = '\0';
        printf("  %s = '%s'\n", attr_name, buffer);
    } else {
        if (errno == ENODATA) {
            printf("  %s = (属性不存在)\n", attr_name);
        } else {
            printf("  %s = (获取失败: %s)\n", attr_name, strerror(errno));
        }
    }
}

int main() {
    const char *filename = "setxattr_test.txt";
    const char *attr_name = "user.description";
    const char *attr_value = "这是一个重要的配置文件,包含系统设置信息";
    
    printf("=== setxattr 基础示例 ===\n\n");
    
    // 创建测试文件
    if (create_test_file(filename) == -1) {
        return 1;
    }
    
    // 显示初始状态
    printf("1. 初始状态:\n");
    show_xattr_value(filename, attr_name);
    printf("\n");
    
    // 使用 setxattr 设置扩展属性
    printf("2. 使用 setxattr 设置扩展属性:\n");
    printf("   属性名称: %s\n", attr_name);
    printf("   属性值: %s\n", attr_value);
    printf("   属性大小: %zu 字节\n", strlen(attr_value));
    printf("   标志位: 0 (默认行为)\n");
    
    if (setxattr(filename, attr_name, attr_value, strlen(attr_value), 0) == 0) {
        printf("   ✓ 扩展属性设置成功\n");
    } else {
        printf("   ✗ 扩展属性设置失败: %s\n", strerror(errno));
        
        // 错误处理
        switch (errno) {
            case EACCES:
                printf("     原因: 权限不足\n");
                break;
            case ENOTSUP:
                printf("     原因: 文件系统不支持扩展属性\n");
                break;
            case ENOSPC:
                printf("     原因: 磁盘空间不足\n");
                break;
            case ENAMETOOLONG:
                printf("     原因: 属性名称过长\n");
                break;
            case EINVAL:
                printf("     原因: 参数无效\n");
                break;
            case EROFS:
                printf("     原因: 文件系统只读\n");
                break;
        }
        unlink(filename);
        return 1;
    }
    
    // 验证设置结果
    printf("\n3. 验证设置结果:\n");
    show_xattr_value(filename, attr_name);
    printf("\n");
    
    // 修改已存在的属性
    printf("4. 修改已存在的属性:\n");
    const char *new_value = "更新后的配置文件描述,包含了新的设置信息";
    printf("   新属性值: %s\n", new_value);
    
    if (setxattr(filename, attr_name, new_value, strlen(new_value), 0) == 0) {
        printf("   ✓ 扩展属性更新成功\n");
    } else {
        printf("   ✗ 扩展属性更新失败: %s\n", strerror(errno));
    }
    
    // 验证更新结果
    printf("\n5. 验证更新结果:\n");
    show_xattr_value(filename, attr_name);
    printf("\n");
    
    // 清理资源
    printf("6. 清理资源:\n");
    if (removexattr(filename, attr_name) == 0) {
        printf("   ✓ 删除扩展属性成功\n");
    } else {
        printf("   ✗ 删除扩展属性失败: %s\n", strerror(errno));
    }
    
    if (unlink(filename) == 0) {
        printf("   ✓ 删除测试文件成功\n");
    } else {
        printf("   ✗ 删除测试文件失败: %s\n", strerror(errno));
    }
    
    printf("\n=== setxattr 特点 ===\n");
    printf("1. 原子操作: 设置属性是原子的\n");
    printf("2. 路径跟随: 会跟随符号链接\n");
    printf("3. 权限控制: 需要文件写权限\n");
    printf("4. 命名空间: 支持多种命名空间\n");
    printf("5. 标志控制: 支持创建/替换标志\n");
    printf("6. 大小限制: 属性值大小有限制\n");
    printf("7. 文件系统: 依赖文件系统支持\n");
    
    return 0;
}

示例2:不同标志位的使用

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

// 显示文件扩展属性列表
void show_xattr_list(const char *filename) {
    char *buffer;
    ssize_t list_size = listxattr(filename, NULL, 0);
    
    if (list_size == -1) {
        if (errno == ENOTSUP) {
            printf("  文件系统不支持扩展属性\n");
        } else {
            printf("  获取属性列表失败: %s\n", strerror(errno));
        }
        return;
    }
    
    if (list_size == 0) {
        printf("  没有扩展属性\n");
        return;
    }
    
    buffer = malloc(list_size + 1);
    if (!buffer) {
        printf("  内存分配失败\n");
        return;
    }
    
    ssize_t result = listxattr(filename, buffer, list_size);
    if (result == -1) {
        printf("  获取属性列表失败: %s\n", strerror(errno));
        free(buffer);
        return;
    }
    
    printf("  扩展属性列表:\n");
    char *attr_name = buffer;
    int count = 0;
    while (attr_name < buffer + result) {
        printf("    [%d] %s\n", ++count, attr_name);
        attr_name += strlen(attr_name) + 1;
    }
    
    free(buffer);
}

// 安全的属性设置函数
int safe_setxattr(const char *path, const char *name, 
                  const void *value, size_t size, int flags) {
    // 参数验证
    if (!path || !name || !value) {
        errno = EINVAL;
        return -1;
    }
    
    if (size == 0) {
        errno = EINVAL;
        return -1;
    }
    
    // 执行设置
    int result = setxattr(path, name, value, size, flags);
    
    // 错误处理
    if (result == -1) {
        switch (errno) {
            case EACCES:
                fprintf(stderr, "权限不足设置扩展属性 '%s'\n", name);
                break;
            case ENOTSUP:
                fprintf(stderr, "文件系统不支持扩展属性 '%s'\n", name);
                break;
            case ENOSPC:
                fprintf(stderr, "存储空间不足设置扩展属性 '%s'\n", name);
                break;
            case EEXIST:
                fprintf(stderr, "属性 '%s' 已存在 (使用 XATTR_CREATE)\n", name);
                break;
            case ENOATTR:
                fprintf(stderr, "属性 '%s' 不存在 (使用 XATTR_REPLACE)\n", name);
                break;
            case ENAMETOOLONG:
                fprintf(stderr, "属性名称 '%s' 过长\n", name);
                break;
            case EINVAL:
                fprintf(stderr, "无效参数设置扩展属性 '%s'\n", name);
                break;
            case EROFS:
                fprintf(stderr, "文件系统只读,无法设置扩展属性 '%s'\n", name);
                break;
            default:
                fprintf(stderr, "设置扩展属性 '%s' 失败: %s\n", name, strerror(errno));
                break;
        }
    }
    
    return result;
}

int main() {
    const char *filename = "setxattr_flags_test.txt";
    const char *attr_name = "user.test_flag";
    const char *initial_value = "初始值";
    const char *new_value = "新值";
    
    printf("=== setxattr 标志位使用示例 ===\n\n");
    
    // 创建测试文件
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return 1;
    }
    
    const char *content = "测试文件内容\n";
    write(fd, content, strlen(content));
    close(fd);
    printf("✓ 创建测试文件: %s\n\n", filename);
    
    // 显示初始状态
    printf("1. 初始状态:\n");
    show_xattr_list(filename);
    printf("\n");
    
    // 2. 使用 XATTR_CREATE 标志创建属性
    printf("2. 使用 XATTR_CREATE 标志创建属性:\n");
    printf("   标志: XATTR_CREATE (仅当属性不存在时创建)\n");
    printf("   属性: %s = %s\n", attr_name, initial_value);
    
    if (setxattr(filename, attr_name, initial_value, strlen(initial_value), XATTR_CREATE) == 0) {
        printf("   ✓ 成功创建属性\n");
    } else {
        printf("   ✗ 创建属性失败: %s\n", strerror(errno));
    }
    
    printf("   创建后属性列表:\n");
    show_xattr_list(filename);
    printf("\n");
    
    // 3. 再次使用 XATTR_CREATE 应该失败
    printf("3. 再次使用 XATTR_CREATE 应该失败:\n");
    printf("   标志: XATTR_CREATE (属性已存在)\n");
    printf("   属性: %s = %s\n", attr_name, new_value);
    
    if (setxattr(filename, attr_name, new_value, strlen(new_value), XATTR_CREATE) == -1) {
        if (errno == EEXIST) {
            printf("   ✓ 正确失败: 属性已存在 (EEXIST)\n");
        } else {
            printf("   ✗ 意外错误: %s\n", strerror(errno));
        }
    } else {
        printf("   ✗ 应该失败但成功了\n");
    }
    
    printf("   验证属性值未改变:\n");
    show_xattr_list(filename);
    printf("\n");
    
    // 4. 使用 XATTR_REPLACE 标志更新属性
    printf("4. 使用 XATTR_REPLACE 标志更新属性:\n");
    printf("   标志: XATTR_REPLACE (仅当属性已存在时替换)\n");
    printf("   属性: %s = %s\n", attr_name, new_value);
    
    if (setxattr(filename, attr_name, new_value, strlen(new_value), XATTR_REPLACE) == 0) {
        printf("   ✓ 成功更新属性\n");
    } else {
        printf("   ✗ 更新属性失败: %s\n", strerror(errno));
    }
    
    printf("   更新后属性列表:\n");
    show_xattr_list(filename);
    printf("\n");
    
    // 5. 使用 XATTR_REPLACE 更新不存在的属性应该失败
    printf("5. 使用 XATTR_REPLACE 更新不存在的属性:\n");
    const char *nonexistent_attr = "user.nonexistent";
    printf("   标志: XATTR_REPLACE (属性不存在)\n");
    printf("   属性: %s = %s\n", nonexistent_attr, "测试值");
    
    if (setxattr(filename, nonexistent_attr, "测试值", strlen("测试值"), XATTR_REPLACE) == -1) {
        if (errno == ENOATTR) {
            printf("   ✓ 正确失败: 属性不存在 (ENOATTR)\n");
        } else {
            printf("   ✗ 意外错误: %s\n", strerror(errno));
        }
    } else {
        printf("   ✗ 应该失败但成功了\n");
    }
    
    printf("   验证属性列表:\n");
    show_xattr_list(filename);
    printf("\n");
    
    // 6. 使用默认标志 (0) - 应该成功
    printf("6. 使用默认标志 (0) - 应该成功:\n");
    printf("   标志: 0 (存在则替换,不存在则创建)\n");
    printf("   属性: %s = %s\n", attr_name, "默认标志测试值");
    
    if (setxattr(filename, attr_name, "默认标志测试值", strlen("默认标志测试值"), 0) == 0) {
        printf("   ✓ 成功设置属性 (默认行为)\n");
    } else {
        printf("   ✗ 设置属性失败: %s\n", strerror(errno));
    }
    
    printf("   验证属性值已更新:\n");
    show_xattr_list(filename);
    printf("\n");
    
    // 7. 清理资源
    printf("7. 清理资源:\n");
    
    // 删除测试属性
    if (removexattr(filename, attr_name) == 0) {
        printf("   ✓ 删除属性 '%s' 成功\n", attr_name);
    } else {
        printf("   ✗ 删除属性 '%s' 失败: %s\n", attr_name, strerror(errno));
    }
    
    // 删除测试文件
    if (unlink(filename) == 0) {
        printf("   ✓ 删除文件 '%s' 成功\n", filename);
    } else {
        printf("   ✗ 删除文件 '%s' 失败: %s\n", filename, strerror(errno));
    }
    
    printf("\n=== 标志位使用总结 ===\n");
    printf("XATTR_CREATE (0x1):\n");
    printf("  • 仅当属性不存在时创建\n");
    printf("  • 属性存在时返回 EEXIST\n");
    printf("  • 适合确保创建新属性\n");
    printf("  • 防止意外覆盖现有属性\n\n");
    
    printf("XATTR_REPLACE (0x2):\n");
    printf("  • 仅当属性已存在时替换\n");
    printf("  • 属性不存在时返回 ENOATTR\n");
    printf("  • 适合更新现有属性\n");
    printf("  • 防止创建意外属性\n\n");
    
    printf("默认标志 (0):\n");
    printf("  • 属性存在则替换\n");
    printf("  • 属性不存在则创建\n");
    printf("  • 最常用的行为\n");
    printf("  • 简单直接的使用方式\n\n");
    
    printf("使用建议:\n");
    printf("1. 创建新属性时使用 XATTR_CREATE\n");
    printf("2. 更新现有属性时使用 XATTR_REPLACE\n");
    printf("3. 一般情况下使用默认标志 (0)\n");
    printf("4. 始终检查返回值和 errno\n");
    printf("5. 根据应用逻辑选择合适的标志\n");
    
    return 0;
}

示例3:完整的扩展属性管理工具

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/xattr.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <pwd.h>
#include <grp.h>

// 配置结构体
struct xattr_config {
    const char *filename;
    const char *attr_name;
    const char *attr_value;
    int operation;          // 0=set, 1=get, 2=list, 3=remove
    int flags;             // XATTR_CREATE, XATTR_REPLACE, 0
    int verbose;           // 详细输出
    int recursive;         // 递归操作
    int follow_links;      // 跟随符号链接
    int show_raw;          // 显示原始数据
};

// 操作类型枚举
enum {
    OP_SET = 0,
    OP_GET = 1,
    OP_LIST = 2,
    OP_REMOVE = 3,
    OP_COPY = 4
};

// 显示扩展属性列表
int show_xattr_list(const char *filename, int follow_links) {
    char *buffer;
    ssize_t list_size;
    
    if (follow_links) {
        list_size = listxattr(filename, NULL, 0);
    } else {
        list_size = llistxattr(filename, NULL, 0);
    }
    
    if (list_size == -1) {
        switch (errno) {
            case ENOTSUP:
                fprintf(stderr, "错误: 文件系统不支持扩展属性 '%s'\n", filename);
                break;
            case EACCES:
                fprintf(stderr, "错误: 权限不足访问 '%s'\n", filename);
                break;
            case ENOENT:
                fprintf(stderr, "错误: 文件不存在 '%s'\n", filename);
                break;
            default:
                fprintf(stderr, "错误: 获取属性列表失败 '%s': %s\n", 
                        filename, strerror(errno));
                break;
        }
        return -1;
    }
    
    if (list_size == 0) {
        printf("文件 '%s' 没有扩展属性\n", filename);
        return 0;
    }
    
    buffer = malloc(list_size + 1);
    if (!buffer) {
        fprintf(stderr, "错误: 内存分配失败\n");
        return -1;
    }
    
    if (follow_links) {
        list_size = listxattr(filename, buffer, list_size);
    } else {
        list_size = llistxattr(filename, buffer, list_size);
    }
    
    if (list_size == -1) {
        fprintf(stderr, "错误: 获取属性列表失败 '%s': %s\n", 
                filename, strerror(errno));
        free(buffer);
        return -1;
    }
    
    printf("文件 '%s' 的扩展属性:\n", filename);
    char *attr_name = buffer;
    int count = 0;
    while (attr_name < buffer + list_size) {
        printf("  [%d] %s\n", ++count, attr_name);
        attr_name += strlen(attr_name) + 1;
    }
    
    free(buffer);
    return 0;
}

// 显示单个扩展属性
int show_single_xattr(const char *filename, const char *attr_name, 
                      int follow_links, int show_raw) {
    char *buffer;
    ssize_t attr_size;
    
    // 先获取属性大小
    if (follow_links) {
        attr_size = getxattr(filename, attr_name, NULL, 0);
    } else {
        attr_size = lgetxattr(filename, attr_name, NULL, 0);
    }
    
    if (attr_size == -1) {
        switch (errno) {
            case ENODATA:
                fprintf(stderr, "错误: 属性 '%s' 不存在于文件 '%s'\n", 
                        attr_name, filename);
                break;
            case ENOTSUP:
                fprintf(stderr, "错误: 文件系统不支持扩展属性 '%s'\n", filename);
                break;
            case EACCES:
                fprintf(stderr, "错误: 权限不足访问属性 '%s'\n", attr_name);
                break;
            case ENOENT:
                fprintf(stderr, "错误: 文件不存在 '%s'\n", filename);
                break;
            default:
                fprintf(stderr, "错误: 获取属性 '%s' 失败: %s\n", 
                        attr_name, strerror(errno));
                break;
        }
        return -1;
    }
    
    if (attr_size == 0) {
        printf("属性 '%s': (空值)\n", attr_name);
        return 0;
    }
    
    buffer = malloc(attr_size + 1);
    if (!buffer) {
        fprintf(stderr, "错误: 内存分配失败\n");
        return -1;
    }
    
    if (follow_links) {
        attr_size = getxattr(filename, attr_name, buffer, attr_size);
    } else {
        attr_size = lgetxattr(filename, attr_name, buffer, attr_size);
    }
    
    if (attr_size == -1) {
        fprintf(stderr, "错误: 获取属性值失败 '%s': %s\n", 
                attr_name, strerror(errno));
        free(buffer);
        return -1;
    }
    
    buffer[attr_size] = '\0';
    
    if (show_raw) {
        // 显示原始二进制数据
        printf("属性 '%s' (原始数据):\n", attr_name);
        printf("  大小: %zd 字节\n", attr_size);
        printf("  数据: ");
        for (ssize_t i = 0; i < attr_size && i < 64; i++) {
            printf("%02x ", (unsigned char)buffer[i]);
        }
        if (attr_size > 64) {
            printf("...(还有 %zd 字节)", attr_size - 64);
        }
        printf("\n");
    } else {
        // 显示可读数据
        int is_printable = 1;
        for (ssize_t i = 0; i < attr_size; i++) {
            if (buffer[i] < 32 || buffer[i] > 126) {
                if (buffer[i] != '\n' && buffer[i] != '\t' && buffer[i] != '\r') {
                    is_printable = 0;
                    break;
                }
            }
        }
        
        printf("属性 '%s': ", attr_name);
        if (is_printable) {
            printf("'%s'\n", buffer);
        } else {
            printf("(二进制数据,%zd 字节)\n", attr_size);
        }
    }
    
    free(buffer);
    return 0;
}

// 设置扩展属性
int set_extended_attribute(const char *filename, const char *attr_name, 
                          const char *attr_value, int flags, int follow_links) {
    ssize_t value_size = strlen(attr_value);
    
    printf("设置扩展属性:\n");
    printf("  文件: %s\n", filename);
    printf("  属性: %s\n", attr_name);
    printf("  值: '%s'\n", attr_value);
    printf("  大小: %zd 字节\n", value_size);
    printf("  标志: ");
    switch (flags) {
        case XATTR_CREATE: printf("XATTR_CREATE (仅创建)\n"); break;
        case XATTR_REPLACE: printf("XATTR_REPLACE (仅替换)\n"); break;
        case 0: printf("默认 (创建或替换)\n"); break;
        default: printf("0x%x\n", flags); break;
    }
    printf("  跟随链接: %s\n", follow_links ? "是" : "否");
    
    int result;
    if (follow_links) {
        result = setxattr(filename, attr_name, attr_value, value_size, flags);
    } else {
        result = lsetxattr(filename, attr_name, attr_value, value_size, flags);
    }
    
    if (result == 0) {
        printf("✓ 扩展属性设置成功\n");
        return 0;
    } else {
        switch (errno) {
            case EACCES:
                fprintf(stderr, "✗ 权限不足: 需要文件写权限\n");
                break;
            case ENOTSUP:
                fprintf(stderr, "✗ 文件系统不支持扩展属性\n");
                break;
            case ENOSPC:
                fprintf(stderr, "✗ 存储空间不足\n");
                break;
            case EEXIST:
                fprintf(stderr, "✗ 属性已存在 (使用 XATTR_CREATE)\n");
                break;
            case ENOATTR:
                fprintf(stderr, "✗ 属性不存在 (使用 XATTR_REPLACE)\n");
                break;
            case ENAMETOOLONG:
                fprintf(stderr, "✗ 属性名称过长\n");
                break;
            case EINVAL:
                fprintf(stderr, "✗ 无效参数\n");
                break;
            case EROFS:
                fprintf(stderr, "✗ 文件系统只读\n");
                break;
            default:
                fprintf(stderr, "✗ 设置失败: %s\n", strerror(errno));
                break;
        }
        return -1;
    }
}

// 显示帮助信息
void show_help(const char *program_name) {
    printf("用法: %s [选项] 文件 [属性名] [属性值]\n", program_name);
    printf("\n选项:\n");
    printf("  -s, --set              设置扩展属性\n");
    printf("  -g, --get              获取扩展属性\n");
    printf("  -l, --list             列出所有扩展属性\n");
    printf("  -r, --remove           删除扩展属性\n");
    printf("  -c, --create           仅当属性不存在时创建 (XATTR_CREATE)\n");
    printf("  -p, --replace          仅当属性存在时替换 (XATTR_REPLACE)\n");
    printf("  -f, --follow-links     跟随符号链接\n");
    printf("  -R, --raw              显示原始数据\n");
    printf("  -v, --verbose          详细输出\n");
    printf("  -h, --help             显示此帮助信息\n");
    printf("\n示例:\n");
    printf("  %s -s file.txt user.description \"测试文件\"  # 设置属性\n", program_name);
    printf("  %s -g file.txt user.description              # 获取属性\n", program_name);
    printf("  %s -l file.txt                                # 列出属性\n", program_name);
    printf("  %s -r file.txt user.description              # 删除属性\n", program_name);
    printf("  %s -c -s file.txt user.new \"新属性\"        # 创建新属性\n", program_name);
    printf("  %s -p -s file.txt user.desc \"更新值\"       # 更新属性\n", program_name);
}

int main(int argc, char *argv[]) {
    struct xattr_config config = {
        .filename = NULL,
        .attr_name = NULL,
        .attr_value = NULL,
        .operation = OP_LIST,  // 默认列出属性
        .flags = 0,
        .verbose = 0,
        .recursive = 0,
        .follow_links = 1,      // 默认跟随符号链接
        .show_raw = 0
    };
    
    printf("=== 扩展属性管理工具 ===\n\n");
    
    // 解析命令行参数
    static struct option long_options[] = {
        {"set",         no_argument,       0, 's'},
        {"get",         no_argument,       0, 'g'},
        {"list",        no_argument,       0, 'l'},
        {"remove",      no_argument,       0, 'r'},
        {"create",      no_argument,       0, 'c'},
        {"replace",     no_argument,       0, 'p'},
        {"follow-links", no_argument,      0, 'f'},
        {"raw",         no_argument,       0, 'R'},
        {"verbose",     no_argument,       0, 'v'},
        {"help",        no_argument,       0, 'h'},
        {0, 0, 0, 0}
    };
    
    int opt;
    while ((opt = getopt_long(argc, argv, "sglrcpfRvh", long_options, NULL)) != -1) {
        switch (opt) {
            case 's':
                config.operation = OP_SET;
                break;
            case 'g':
                config.operation = OP_GET;
                break;
            case 'l':
                config.operation = OP_LIST;
                break;
            case 'r':
                config.operation = OP_REMOVE;
                break;
            case 'c':
                config.flags = XATTR_CREATE;
                break;
            case 'p':
                config.flags = XATTR_REPLACE;
                break;
            case 'f':
                config.follow_links = 1;
                break;
            case 'R':
                config.show_raw = 1;
                break;
            case 'v':
                config.verbose = 1;
                break;
            case 'h':
                show_help(argv[0]);
                return 0;
            default:
                fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv[0]);
                return 1;
        }
    }
    
    // 获取文件参数
    if (optind < argc) {
        config.filename = argv[optind];
        
        if (optind + 1 < argc) {
            config.attr_name = argv[optind + 1];
            
            if (optind + 2 < argc) {
                config.attr_value = argv[optind + 2];
            }
        }
    } else {
        fprintf(stderr, "错误: 请指定文件名\n");
        fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv[0]);
        return 1;
    }
    
    // 验证参数
    if (config.operation == OP_SET && !config.attr_value) {
        fprintf(stderr, "错误: 设置属性需要指定属性值\n");
        return 1;
    }
    
    if ((config.operation == OP_GET || 
         config.operation == OP_REMOVE) && !config.attr_name) {
        fprintf(stderr, "错误: 操作需要指定属性名\n");
        return 1;
    }
    
    // 显示配置信息(详细模式)
    if (config.verbose) {
        printf("配置信息:\n");
        printf("  文件: %s\n", config.filename);
        printf("  操作: ");
        switch (config.operation) {
            case OP_SET: printf("设置属性\n"); break;
            case OP_GET: printf("获取属性\n"); break;
            case OP_LIST: printf("列出属性\n"); break;
            case OP_REMOVE: printf("删除属性\n"); break;
            default: printf("未知操作\n"); break;
        }
        if (config.attr_name) printf("  属性名: %s\n", config.attr_name);
        if (config.attr_value) printf("  属性值: %s\n", config.attr_value);
        printf("  标志: ");
        switch (config.flags) {
            case XATTR_CREATE: printf("XATTR_CREATE\n"); break;
            case XATTR_REPLACE: printf("XATTR_REPLACE\n"); break;
            case 0: printf("默认\n"); break;
            default: printf("0x%x\n", config.flags); break;
        }
        printf("  跟随链接: %s\n", config.follow_links ? "是" : "否");
        printf("  显示原始数据: %s\n", config.show_raw ? "是" : "否");
        printf("\n");
    }
    
    // 执行相应操作
    int result = 0;
    
    switch (config.operation) {
        case OP_LIST:
            result = show_xattr_list(config.filename, config.follow_links);
            break;
            
        case OP_GET:
            result = show_single_xattr(config.filename, config.attr_name, 
                                     config.follow_links, config.show_raw);
            break;
            
        case OP_SET:
            result = set_extended_attribute(config.filename, config.attr_name, 
                                           config.attr_value, config.flags, 
                                           config.follow_links);
            break;
            
        case OP_REMOVE:
            printf("删除扩展属性:\n");
            printf("  文件: %s\n", config.filename);
            printf("  属性: %s\n", config.attr_name);
            printf("  跟随链接: %s\n", config.follow_links ? "是" : "否");
            
            if (config.follow_links) {
                result = removexattr(config.filename, config.attr_name);
            } else {
                result = lremovexattr(config.filename, config.attr_name);
            }
            
            if (result == 0) {
                printf("✓ 扩展属性删除成功\n");
            } else {
                switch (errno) {
                    case ENODATA:
                        fprintf(stderr, "✗ 属性不存在\n");
                        break;
                    case EACCES:
                        fprintf(stderr, "✗ 权限不足\n");
                        break;
                    case ENOTSUP:
                        fprintf(stderr, "✗ 文件系统不支持扩展属性\n");
                        break;
                    case EROFS:
                        fprintf(stderr, "✗ 文件系统只读\n");
                        break;
                    default:
                        fprintf(stderr, "✗ 删除失败: %s\n", strerror(errno));
                        break;
                }
                result = -1;
            }
            break;
            
        default:
            fprintf(stderr, "未知操作类型: %d\n", config.operation);
            result = -1;
            break;
    }
    
    // 显示操作后状态(详细模式)
    if (config.verbose && config.operation != OP_LIST && config.operation != OP_GET) {
        printf("\n操作后状态:\n");
        show_xattr_list(config.filename, config.follow_links);
    }
    
    printf("\n=== 扩展属性最佳实践 ===\n");
    printf("使用建议:\n");
    printf("1. 选择合适的命名空间 (user.*, security.*, etc.)\n");
    printf("2. 合理使用 XATTR_CREATE 和 XATTR_REPLACE 标志\n");
    printf("3. 始终检查返回值和 errno\n");
    printf("4. 注意属性大小限制 (通常 64KB)\n");
    printf("5. 考虑文件系统兼容性\n");
    printf("6. 安全地处理敏感属性\n");
    printf("7. 及时清理不需要的属性\n");
    printf("8. 使用适当的权限控制\n");
    printf("\n");
    
    printf("常见应用场景:\n");
    printf("1. 安全标签 (SELinux, AppArmor)\n");
    printf("2. 文件元数据存储\n");
    printf("3. 备份和同步状态\n");
    printf("4. 访问控制列表\n");
    printf("5. 应用程序自定义属性\n");
    printf("6. 版本控制信息\n");
    printf("7. 缓存管理\n");
    printf("8. 审计和日志信息\n");
    
    return (result == 0) ? 0 : 1;
}

4. 编译和运行说明

# 编译示例程序
gcc -o setxattr_example1 example1.c
gcc -o setxattr_example2 example2.c
gcc -o setxattr_example3 example3.c

# 运行示例
./setxattr_example1
./setxattr_example2
./setxattr_example3 --help

# 实际使用示例
./setxattr_example3 -s test_file.txt user.description "测试文件"
./setxattr_example3 -g test_file.txt user.description
./setxattr_example3 -l test_file.txt
./setxattr_example3 -r test_file.txt user.description
./setxattr_example3 -c -s test_file.txt user.new "新属性"
./setxattr_example3 -p -s test_file.txt user.description "更新值"

5. 系统要求检查

# 检查文件系统支持
grep -w xattr /boot/config-$(uname -r)

# 检查文件系统类型
df -T .

# 检查扩展属性支持
ls /usr/include/sys/xattr.h

# 测试文件系统支持
touch test_file && setfattr -n user.test -v "test" test_file 2>/dev/null && echo "支持扩展属性" || echo "不支持扩展属性"
rm -f test_file

6. 重要注意事项

6.1 文件系统支持

不是所有文件系统都支持扩展属性:

  • 支持: ext2/3/4, XFS, Btrfs, ReiserFS
  • 不支持: FAT32, NTFS (某些版本)

6.2 权限要求

// 用户属性需要文件写权限
int check_xattr_permissions(const char *filename) {
    if (access(filename, W_OK) == 0) {
        return 0;  // 有写权限
    } else {
        errno = EACCES;
        return -1;  // 权限不足
    }
}

6.3 大小限制

// 检查属性大小限制
int check_xattr_size_limit(const char *value) {
    if (!value) return -1;
    
    size_t value_size = strlen(value);
    if (value_size > 65536) {  // 64KB 限制
        errno = ENOSPC;
        return -1;
    }
    
    return 0;
}

6.4 错误处理

// 安全的扩展属性设置函数
int safe_setxattr(const char *path, const char *name, 
                  const void *value, size_t size, int flags) {
    // 参数验证
    if (!path || !name || !value) {
        errno = EINVAL;
        return -1;
    }
    
    if (size == 0 || size > 65536) {
        errno = EINVAL;
        return -1;
    }
    
    // 权限检查
    if (access(path, W_OK) != 0) {
        errno = EACCES;
        return -1;
    }
    
    // 执行设置
    int result = setxattr(path, name, value, size, flags);
    
    // 错误处理
    if (result == -1) {
        switch (errno) {
            case EACCES:
                fprintf(stderr, "权限不足设置扩展属性 '%s'\n", name);
                break;
            case ENOTSUP:
                fprintf(stderr, "文件系统不支持扩展属性 '%s'\n", name);
                break;
            case ENOSPC:
                fprintf(stderr, "存储空间不足设置扩展属性 '%s'\n", name);
                break;
            case EEXIST:
                fprintf(stderr, "属性 '%s' 已存在 (使用 XATTR_CREATE)\n", name);
                break;
            case ENOATTR:
                fprintf(stderr, "属性 '%s' 不存在 (使用 XATTR_REPLACE)\n", name);
                break;
            case ENAMETOOLONG:
                fprintf(stderr, "属性名称 '%s' 过长\n", name);
                break;
            case EINVAL:
                fprintf(stderr, "无效参数设置扩展属性 '%s'\n", name);
                break;
            case EROFS:
                fprintf(stderr, "文件系统只读,无法设置扩展属性 '%s'\n", name);
                break;
        }
    }
    
    return result;
}

7. 实际应用场景

7.1 安全标签管理

// 设置 SELinux 安全标签
int set_selinux_label(const char *filename, const char *label) {
    return setxattr(filename, "security.selinux", label, strlen(label), 0);
}

7.2 文件元数据存储

// 存储文件版本信息
int set_file_version(const char *filename, const char *version) {
    return setxattr(filename, "user.version", version, strlen(version), 0);
}

// 存储文件摘要信息
int set_file_checksum(const char *filename, const char *checksum) {
    return setxattr(filename, "user.checksum", checksum, strlen(checksum), 0);
}

7.3 应用程序状态管理

// 存储备份状态
int set_backup_status(const char *filename, const char *status) {
    return setxattr(filename, "user.backup_status", status, strlen(status), 0);
}

// 存储同步时间戳
int set_sync_timestamp(const char *filename, time_t timestamp) {
    char timestamp_str[32];
    snprintf(timestamp_str, sizeof(timestamp_str), "%ld", timestamp);
    return setxattr(filename, "user.last_sync", timestamp_str, strlen(timestamp_str), 0);
}

8. 性能优化建议

// 批量设置扩展属性
int batch_set_xattrs(const char *filename, 
                     const char **names, const char **values, int count) {
    int success_count = 0;
    int failed_count = 0;
    
    for (int i = 0; i < count; i++) {
        if (setxattr(filename, names[i], values[i], strlen(values[i]), 0) == 0) {
            success_count++;
        } else {
            failed_count++;
            fprintf(stderr, "设置属性 '%s' 失败: %s\n", names[i], strerror(errno));
        }
    }
    
    printf("批量设置完成: 成功 %d, 失败 %d\n", success_count, failed_count);
    return failed_count;
}

// 预先检查属性存在性
int check_and_set_xattr(const char *filename, const char *name, 
                       const char *value, int flags) {
    // 如果是替换操作,先检查属性是否存在
    if (flags == XATTR_REPLACE) {
        char dummy_buffer[1];
        ssize_t result = getxattr(filename, name, dummy_buffer, sizeof(dummy_buffer));
        if (result == -1 && errno == ENODATA) {
            errno = ENOATTR;
            return -1;  // 属性不存在
        }
    }
    
    // 执行设置操作
    return setxattr(filename, name, value, strlen(value), flags);
}

这些示例全面展示了 setxattr 及相关函数的各种使用方法,从基础的属性设置到完整的管理工具,帮助你全面掌握 Linux 系统中的扩展属性机制。

发表在 linux文章 | 留下评论

shutdown系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 shutdown 函数,它用于部分完全地关闭一个面向连接的套接字(如 TCP 套接字)的数据传输


1. 函数介绍

shutdown 是一个 Linux 系统调用,专门用于更精细地控制已连接套接字的关闭过程。与 close 函数不同(close 会完全关闭套接字,释放其文件描述符),shutdown 允许你:

  1. 关闭数据流的一个方向:例如,告诉对方“我不会再发送数据了”(但仍可以接收数据)。
  2. 关闭数据流的两个方向:完全禁止在此套接字上进行任何发送和接收操作(但仍保持文件描述符打开,直到调用 close)。

你可以把 shutdown 想象成电话通话中的话筒控制

  • 全双工通话:你可以说话(发送),也可以听对方说话(接收)。
  • shutdown(SHUT_WR):相当于你按下了“禁麦”按钮。你不能再说话(发送数据),但你仍然可以听到对方说话(接收数据)。
  • shutdown(SHUT_RD):相当于你戴上了耳塞。你听不到对方说话(接收数据),但(理论上)你还可以说话(发送数据)——不过对方可能听不到或会收到错误。
  • shutdown(SHUT_RDWR):相当于你挂断了电话的通话功能。你既不能说也不能听,但电话线(套接字文件描述符)本身可能还没被物理拔掉(close)。

这对于实现优雅的连接关闭(如 TCP 的四次挥手)和单向通信非常有用。


2. 函数原型

#include <sys/socket.h> // 必需

int shutdown(int sockfd, int how);

3. 功能

  • 部分关闭: 根据 how 参数,关闭套接字 sockfd 的发送能力、接收能力或两者。
  • 发送信号: 对于 TCP 套接字,shutdown 会触发相应的 TCP 连接终止序列(如发送 FIN 包)来通知对端。
  • 状态改变: 改变套接字的内部状态,使其无法再执行被禁止的操作。

4. 参数

  • int sockfd: 这是一个已连接(对于 TCP)或已绑定/连接(对于 UDP,如果使用了 connect)的有效套接字文件描述符
  • int how: 这个参数指定了要执行的关闭操作类型。它必须是以下值之一:
    • SHUT_RD关闭接收方向。套接字不再接收数据。任何传入的数据都可能被丢弃,后续的 read 或 recv 调用将返回 0(表示 EOF)。
    • SHUT_WR关闭发送方向。套接字不再发送数据。对于 TCP,这会发送一个 FIN 包给对方,表示本端不再发送数据。后续的 write 或 send 调用将失败(通常返回错误 EPIPE 或导致 SIGPIPE 信号)。
    • SHUT_RDWR关闭接收和发送方向。这相当于同时执行 SHUT_RD 和 SHUT_WR。对于 TCP,这会关闭两个方向的数据流。

5. 返回值

  • 成功时: 返回 0。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EBADF sockfd 不是有效的文件描述符,EINVAL how 参数无效,ENOTCONN 套接字未连接等)。

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

  • close: 完全关闭套接字,释放其文件描述符。如果套接字的引用计数变为 0,其效果类似于 shutdown(SHUT_RDWR) 后再释放资源。通常在 shutdown 之后调用 close
  • read / write / send / recvshutdown 会影响这些函数的行为。例如,shutdown(SHUT_RD) 后 read 会立即返回 0。
  • TCP 协议shutdown 的行为与 TCP 连接的状态转换密切相关,特别是 FIN 包的发送和接收。

7. 示例代码

示例 1:TCP 客户端使用 shutdown 实现半关闭

这个例子演示了 TCP 客户端如何在发送完所有数据后,使用 shutdown(SHUT_WR) 告诉服务器它不会再发送更多数据,然后继续接收服务器的回复。

// shutdown_client.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PORT 8084
#define SERVER_IP "127.0.0.1"
#define BUFFER_SIZE 1024

int main() {
    int sock;
    struct sockaddr_in serv_addr;
    char *message = "Here is the complete message from client.";
    char buffer[BUFFER_SIZE];
    ssize_t bytes_sent, bytes_received;

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {
        fprintf(stderr, "Invalid address\n");
        close(sock);
        exit(EXIT_FAILURE);
    }

    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("connection failed");
        close(sock);
        exit(EXIT_FAILURE);
    }

    printf("Connected to server.\n");

    // 1. 发送数据到服务器
    bytes_sent = write(sock, message, strlen(message));
    if (bytes_sent < 0) {
        perror("write failed");
        close(sock);
        exit(EXIT_FAILURE);
    } else {
        printf("Sent %zd bytes to server.\n", bytes_sent);
    }

    // 2. 关闭发送方向 (SHUT_WR)
    // 这告诉服务器:'我的数据发完了,不会再发了'
    // 但客户端仍然可以接收服务器发送的数据
    printf("Shutting down write direction (SHUT_WR)...\n");
    if (shutdown(sock, SHUT_WR) < 0) {
        perror("shutdown SHUT_WR failed");
        // 即使 shutdown 失败,也应尝试关闭套接字
    } else {
        printf("Write direction shut down successfully.\n");
    }

    // 3. 继续接收服务器的回复
    printf("Now reading server's response...\n");
    while ((bytes_received = read(sock, buffer, BUFFER_SIZE - 1)) > 0) {
        buffer[bytes_received] = '\0';
        printf("Received from server: %s", buffer);
    }

    if (bytes_received < 0) {
        perror("read failed");
    } else {
        printf("Server closed connection (EOF received).\n");
    }

    // 4. 最后关闭套接字文件描述符
    close(sock);
    printf("Client socket closed.\n");

    return 0;
}

代码解释:

  1. 客户端创建 TCP 套接字并连接到服务器。
  2. 使用 write 向服务器发送一条消息。
  3. 关键步骤: 调用 shutdown(sock, SHUT_WR)
    • 这会向服务器发送一个 TCP FIN 包,表明客户端不会再发送数据。
    • 服务器的 read 调用在收到这个 FIN 后会返回 0(EOF)。
    • 但是,客户端的套接字仍然打开,并且仍然可以接收数据。
  4. 客户端进入一个 while 循环,使用 read 继续接收服务器可能发送的任何回复数据,直到服务器也关闭连接(read 返回 0)。
  5. 最后,调用 close(sock) 完全关闭套接字文件描述符。

示例 2:TCP 服务器使用 shutdown 响应客户端

这个例子演示了 TCP 服务器如何在收到客户端的 FIN(即 read 返回 0)后,使用 shutdown 和 close 来优雅地关闭连接。

// shutdown_server.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PORT 8084
#define BACKLOG 10
#define BUFFER_SIZE 1024

int main() {
    int server_fd, client_fd;
    struct sockaddr_in address, client_address;
    socklen_t client_addr_len = sizeof(client_address);
    char buffer[BUFFER_SIZE];
    char *reply = "Server received your message. Here is the server's final reply.";
    ssize_t bytes_received;
    int opt = 1;

    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    if (listen(server_fd, BACKLOG) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d\n", PORT);

    client_fd = accept(server_fd, (struct sockaddr *)&client_address, &client_addr_len);
    if (client_fd < 0) {
        perror("accept failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Client connected.\n");

    // 1. 从客户端接收数据
    printf("Receiving data from client...\n");
    while ((bytes_received = read(client_fd, buffer, BUFFER_SIZE - 1)) > 0) {
        buffer[bytes_received] = '\0';
        printf("Received from client: %s", buffer);
    }

    if (bytes_received < 0) {
        perror("read failed");
        close(client_fd);
        close(server_fd);
        exit(EXIT_FAILURE);
    } else {
        // bytes_received == 0, 表示客户端已关闭发送方向 (SHUT_WR)
        printf("Client has shut down its write direction (EOF received).\n");
    }

    // 2. 向客户端发送最终回复
    printf("Sending final reply to client...\n");
    if (write(client_fd, reply, strlen(reply)) != (ssize_t)strlen(reply)) {
        perror("write reply failed");
    } else {
        printf("Final reply sent.\n");
    }

    // 3. 关闭服务器的写方向
    // 这会向客户端发送 FIN,表明服务器也不会再发送数据
    printf("Shutting down server's write direction (SHUT_WR)...\n");
    if (shutdown(client_fd, SHUT_WR) < 0) {
        perror("shutdown SHUT_WR failed");
    } else {
        printf("Server's write direction shut down.\n");
    }

    // 4. (可选) 继续等待一段时间,看客户端是否也关闭
    // 在这个简单例子中,我们直接关闭
    printf("Closing client socket.\n");
    close(client_fd);

    close(server_fd);
    printf("Server sockets closed.\n");

    return 0;
}

代码解释:

  1. 服务器创建、绑定、监听套接字,并 accept 客户端连接。
  2. 服务器进入一个 while 循环,使用 read 从客户端接收数据。
  3. 当 read 返回 0 时,表示客户端已调用 shutdown(SHUT_WR) 或 close,其发送方向已关闭。
  4. 服务器向客户端发送一个最终的回复消息。
  5. 关键步骤: 服务器调用 shutdown(client_fd, SHUT_WR)
    • 这会向客户端发送一个 TCP FIN 包。
    • 客户端的 read 调用在收到这个 FIN 后会返回 0。
  6. 最后,服务器调用 close(client_fd) 完全关闭与该客户端的连接。

示例 3:对比 shutdown 和 close

这个例子通过伪代码和解释来说明 shutdown 和 close 的区别。

// 假设 sock 是一个已连接的 TCP 套接字

// --- 情况一:只使用 close ---
write(sock, "Hello", 5);
close(sock); // 1. 发送 FIN (如果这是最后一个引用)
             // 2. 释放文件描述符
             // 3. 内核可能立即终止连接或尝试优雅关闭

// --- 情况二:使用 shutdown 后再 close (优雅关闭) ---
write(sock, "Hello", 5);
shutdown(sock, SHUT_WR); // 1. 发送 FIN,告诉对方'我发完了'
                         // 2. 套接字仍然打开,仍可 read

char buffer[1024];
ssize_t n;
while ((n = read(sock, buffer, sizeof(buffer))) > 0) {
    // 处理客户端最后发送的数据
}
// read 返回 0,表示客户端也关闭了

close(sock); // 3. 此时 close 只是释放本地文件描述符
             //    TCP 连接已经通过 FIN/ACK 交互优雅地关闭了

解释:

  • 仅使用 close: 这种方式简单直接。当 close 被调用且该套接字的引用计数变为 0 时,内核会尝试关闭连接。这通常涉及发送 FIN,但整个过程是隐式的。如果在发送缓冲区还有数据时立即 close,行为可能取决于系统实现(数据可能被发送,也可能被丢弃)。
  • 使用 shutdown + close: 这是一种更优雅明确的关闭方式。
    1. 发送完数据后,调用 shutdown(SHUT_WR) 明确表示“数据发送完毕”。这会可靠地发送 FIN 给对方。
    2. 程序继续使用 read 来接收对方可能在收到 FIN 后发送的剩余数据。
    3. 当 read 也返回 0(收到对方的 FIN 并回复 ACK)时,双方都确认了连接的单向关闭。
    4. 最后调用 close 仅仅是清理本地资源(文件描述符)。

重要提示与注意事项:

  1. 仅适用于连接型套接字shutdown 主要用于面向连接的套接字,如 TCP (SOCK_STREAM)。对于无连接的套接字,如 UDP (SOCK_DGRAM),它的行为是未定义的或没有意义的。
  2. 不释放文件描述符shutdown 不会关闭套接字的文件描述符。你仍然需要调用 close() 来最终释放资源。
  3. TCP 语义shutdown 的行为与底层 TCP 协议紧密相关。SHUT_WR 导致发送 FINSHUT_RD 影响接收缓冲区的行为。
  4. 优雅关闭: 在需要确保所有数据都被发送和接收的场景中(如 HTTP/1.1 Connection: close),使用 shutdown 是实现优雅关闭的标准方法。
  5. 错误处理: 始终检查 shutdown 的返回值。在套接字已经关闭或无效时调用它会失败。
  6. SHUT_RD 的实用性SHUT_RD 的使用场景相对较少。关闭接收通常意味着你不再关心对方的数据,直接 close 或在 read 返回 0 后 close 通常就足够了。

总结:

shutdown 是一个用于精细控制 TCP 连接关闭过程的系统调用。它允许程序在完全终止连接之前,单方面地关闭数据流的一个或两个方向。这对于实现协议规定的优雅关闭序列(如 HTTP)和处理单向数据流非常重要。理解它与 close 的区别,并在需要时正确使用它,是编写健壮网络应用程序的关键技能之一。

发表在 linux文章 | 留下评论

sigaltstack系统调用及示例

sigaltstack 函数详解

1. 函数介绍

sigaltstack 是Linux系统调用,用于设置和获取信号处理程序的备用栈(alternate signal stack)。当进程收到信号时,内核通常在当前栈上执行信号处理程序。使用 sigaltstack 可以为信号处理程序指定一个独立的栈空间,这对于处理栈溢出等异常情况特别有用。

2. 函数原型

#include <signal.h>
int sigaltstack(const stack_t *ss, stack_t *oss);

3. 功能

sigaltstack 允许进程为信号处理程序设置一个备用的栈空间。当信号被递送到使用备用栈的信号处理程序时,内核会切换到备用栈执行信号处理程序,执行完毕后再切换回原来的栈。

4. 参数

  • *const stack_t ss: 指向新栈设置的指针(NULL表示不改变当前设置)
  • *stack_t oss: 指向存储旧栈设置的指针(NULL表示不获取旧设置)

5. 返回值

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

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

  • signal/sigaction: 设置信号处理程序
  • sigprocmask: 设置信号屏蔽字
  • setjmp/longjmp: 非局部跳转
  • getcontext/setcontext: 上下文操作

7. 示例代码

示例1:基础sigaltstack使用

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

/**
 * 信号栈结构体
 */
typedef struct {
    void *stack_ptr;
    size_t stack_size;
    int is_active;
} signal_stack_t;

/**
 * 显示当前信号栈信息
 */
void show_signal_stack_info() {
    stack_t current_stack;
    
    if (sigaltstack(NULL, &current_stack) == 0) {
        printf("=== 当前信号栈信息 ===\n");
        printf("栈指针: %p\n", current_stack.ss_sp);
        printf("栈大小: %zu 字节\n", current_stack.ss_size);
        printf("栈标志: ");
        if (current_stack.ss_flags & SS_ONSTACK) {
            printf("SS_ONSTACK (正在使用中)\n");
        } else if (current_stack.ss_flags & SS_DISABLE) {
            printf("SS_DISABLE (已禁用)\n");
        } else {
            printf("SS_ENABLED (已启用)\n");
        }
        printf("\n");
    } else {
        printf("获取信号栈信息失败: %s\n", strerror(errno));
    }
}

/**
 * 信号处理程序
 */
void signal_handler(int sig) {
    stack_t current_stack;
    
    printf("信号处理程序执行中 (信号: %d)\n", sig);
    
    // 检查当前是否在备用栈上
    if (sigaltstack(NULL, &current_stack) == 0) {
        if (current_stack.ss_flags & SS_ONSTACK) {
            printf("  ✓ 正在备用栈上执行\n");
        } else {
            printf("  ✗ 在主栈上执行\n");
        }
    }
    
    printf("  当前栈指针: %p\n", &sig);
    printf("  信号处理完成\n\n");
}

/**
 * 演示基础sigaltstack使用方法
 */
int demo_sigaltstack_basic() {
    stack_t new_stack, old_stack;
    char *stack_buffer;
    struct sigaction sa;
    
    printf("=== 基础sigaltstack使用示例 ===\n");
    
    // 显示原始信号栈信息
    printf("1. 原始信号栈信息:\n");
    show_signal_stack_info();
    
    // 分配备用栈空间
    size_t stack_size = SIGSTKSZ;  // 系统推荐的栈大小
    stack_buffer = malloc(stack_size);
    if (!stack_buffer) {
        perror("分配栈空间失败");
        return -1;
    }
    
    printf("2. 分配备用栈空间:\n");
    printf("   栈大小: %zu 字节\n", stack_size);
    printf("   栈地址: %p\n", (void*)stack_buffer);
    
    // 设置备用栈
    new_stack.ss_sp = stack_buffer;
    new_stack.ss_size = stack_size;
    new_stack.ss_flags = 0;  // 启用栈
    
    printf("3. 设置备用信号栈:\n");
    if (sigaltstack(&new_stack, &old_stack) == 0) {
        printf("   ✓ 备用栈设置成功\n");
        show_signal_stack_info();
    } else {
        printf("   ✗ 备用栈设置失败: %s\n", strerror(errno));
        free(stack_buffer);
        return -1;
    }
    
    // 设置使用备用栈的信号处理程序
    printf("4. 设置信号处理程序:\n");
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_ONSTACK;  // 使用备用栈
    
    if (sigaction(SIGUSR1, &sa, NULL) == 0) {
        printf("   ✓ 信号处理程序设置成功 (使用备用栈)\n");
    } else {
        printf("   ✗ 信号处理程序设置失败: %s\n", strerror(errno));
        free(stack_buffer);
        return -1;
    }
    
    // 发送信号测试
    printf("5. 发送测试信号:\n");
    if (kill(getpid(), SIGUSR1) == 0) {
        printf("   ✓ 信号发送成功\n");
        sleep(1);  // 等待信号处理完成
    } else {
        printf("   ✗ 信号发送失败: %s\n", strerror(errno));
    }
    
    // 禁用备用栈
    printf("6. 禁用备用栈:\n");
    stack_t disable_stack;
    disable_stack.ss_flags = SS_DISABLE;
    
    if (sigaltstack(&disable_stack, NULL) == 0) {
        printf("   ✓ 备用栈禁用成功\n");
        show_signal_stack_info();
    } else {
        printf("   ✗ 备用栈禁用失败: %s\n", strerror(errno));
    }
    
    // 清理资源
    free(stack_buffer);
    
    return 0;
}

int main() {
    return demo_sigaltstack_basic();
}

示例2:栈溢出保护演示

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <setjmp.h>

/**
 * 栈溢出保护结构
 */
typedef struct {
    stack_t alt_stack;
    char *stack_buffer;
    sigjmp_buf jump_buffer;
    int overflow_detected;
} stack_protection_t;

/**
 * 栈溢出信号处理程序
 */
void stack_overflow_handler(int sig) {
    printf("⚠ 检测到栈溢出异常 (信号: %d)\n", sig);
    
    // 检查是否在备用栈上
    stack_t current_stack;
    if (sigaltstack(NULL, &current_stack) == 0) {
        if (current_stack.ss_flags & SS_ONSTACK) {
            printf("  ✓ 在备用栈上安全处理异常\n");
        } else {
            printf("  ✗ 不在备用栈上 (异常情况)\n");
        }
    }
    
    // 恢复到安全状态
    printf("  跳转到安全恢复点...\n");
    siglongjmp(((stack_protection_t*)NULL)->jump_buffer, 1);
}

/**
 * 递归函数(可能导致栈溢出)
 */
void recursive_function(int depth) {
    char buffer[1024];  // 消耗栈空间
    
    // 填充缓冲区以确保栈使用
    memset(buffer, depth & 0xFF, sizeof(buffer));
    
    printf("递归深度: %d, 栈地址: %p\n", depth, (void*)&buffer);
    
    // 深度递归可能导致栈溢出
    if (depth < 1000) {  // 限制递归深度
        recursive_function(depth + 1);
    }
}

/**
 * 安全的递归执行
 */
int safe_recursive_execution(stack_protection_t *protection) {
    struct sigaction sa;
    int result;
    
    printf("=== 栈溢出保护演示 ===\n");
    
    // 设置信号处理程序
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = stack_overflow_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_ONSTACK;
    
    if (sigaction(SIGSEGV, &sa, NULL) != 0) {
        printf("设置SIGSEGV处理程序失败: %s\n", strerror(errno));
        return -1;
    }
    
    if (sigaction(SIGBUS, &sa, NULL) != 0) {
        printf("设置SIGBUS处理程序失败: %s\n", strerror(errno));
        return -1;
    }
    
    // 设置跳转点
    result = sigsetjmp(protection->jump_buffer, 1);
    if (result == 0) {
        printf("开始安全递归执行...\n");
        
        // 执行可能引起栈溢出的操作
        recursive_function(0);
        
        printf("递归执行正常完成\n");
        return 0;
    } else {
        printf("✓ 从栈溢出异常中成功恢复\n");
        return 1;
    }
}

/**
 * 演示栈溢出保护
 */
int demo_stack_overflow_protection() {
    stack_protection_t protection = {0};
    size_t stack_size = SIGSTKSZ * 2;  // 更大的备用栈
    
    printf("=== 栈溢出保护演示 ===\n");
    
    // 分配备用栈
    protection.stack_buffer = malloc(stack_size);
    if (!protection.stack_buffer) {
        perror("分配备用栈失败");
        return -1;
    }
    
    printf("分配备用栈: %zu 字节\n", stack_size);
    
    // 设置备用栈
    protection.alt_stack.ss_sp = protection.stack_buffer;
    protection.alt_stack.ss_size = stack_size;
    protection.alt_stack.ss_flags = 0;
    
    if (sigaltstack(&protection.alt_stack, NULL) != 0) {
        printf("设置备用栈失败: %s\n", strerror(errno));
        free(protection.stack_buffer);
        return -1;
    }
    
    printf("备用栈设置成功\n");
    show_signal_stack_info();
    
    // 执行安全递归
    int result = safe_recursive_execution(&protection);
    
    // 清理资源
    free(protection.stack_buffer);
    
    return result;
}

// 辅助函数声明
void show_signal_stack_info();

int main() {
    return demo_stack_overflow_protection();
}

示例3:多信号处理程序管理

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>

/**
 * 多栈管理器
 */
typedef struct {
    stack_t stack1;
    stack_t stack2;
    char *buffer1;
    char *buffer2;
    int current_stack;
} multi_stack_manager_t;

/**
 * 信号处理程序1(使用栈1)
 */
void signal_handler_1(int sig) {
    printf("信号处理程序1执行 (信号: %d)\n", sig);
    
    stack_t current_stack;
    if (sigaltstack(NULL, &current_stack) == 0) {
        if (current_stack.ss_flags & SS_ONSTACK) {
            printf("  使用备用栈1执行\n");
        }
    }
    
    printf("  处理程序1完成\n");
}

/**
 * 信号处理程序2(使用栈2)
 */
void signal_handler_2(int sig) {
    printf("信号处理程序2执行 (信号: %d)\n", sig);
    
    stack_t current_stack;
    if (sigaltstack(NULL, &current_stack) == 0) {
        if (current_stack.ss_flags & SS_ONSTACK) {
            printf("  使用备用栈2执行\n");
        }
    }
    
    printf("  处理程序2完成\n");
}

/**
 * 初始化多栈管理器
 */
int init_multi_stack_manager(multi_stack_manager_t *manager) {
    size_t stack_size = SIGSTKSZ;
    
    printf("=== 多栈管理器初始化 ===\n");
    
    // 分配两个栈空间
    manager->buffer1 = malloc(stack_size);
    manager->buffer2 = malloc(stack_size);
    
    if (!manager->buffer1 || !manager->buffer2) {
        printf("分配栈空间失败\n");
        if (manager->buffer1) free(manager->buffer1);
        if (manager->buffer2) free(manager->buffer2);
        return -1;
    }
    
    printf("栈1地址: %p, 大小: %zu\n", (void*)manager->buffer1, stack_size);
    printf("栈2地址: %p, 大小: %zu\n", (void*)manager->buffer2, stack_size);
    
    // 初始化栈结构
    manager->stack1.ss_sp = manager->buffer1;
    manager->stack1.ss_size = stack_size;
    manager->stack1.ss_flags = 0;
    
    manager->stack2.ss_sp = manager->buffer2;
    manager->stack2.ss_size = stack_size;
    manager->stack2.ss_flags = 0;
    
    manager->current_stack = 0;
    
    return 0;
}

/**
 * 切换备用栈
 */
int switch_alternate_stack(multi_stack_manager_t *manager, int stack_id) {
    stack_t *target_stack = (stack_id == 1) ? &manager->stack1 : &manager->stack2;
    
    printf("切换到备用栈 %d\n", stack_id);
    
    if (sigaltstack(target_stack, NULL) == 0) {
        printf("✓ 成功切换到备用栈 %d\n", stack_id);
        manager->current_stack = stack_id;
        return 0;
    } else {
        printf("✗ 切换备用栈 %d 失败: %s\n", stack_id, strerror(errno));
        return -1;
    }
}

/**
 * 演示多信号处理程序
 */
int demo_multi_signal_handlers() {
    multi_stack_manager_t manager = {0};
    struct sigaction sa1, sa2;
    
    printf("=== 多信号处理程序演示 ===\n");
    
    // 初始化多栈管理器
    if (init_multi_stack_manager(&manager) != 0) {
        printf("初始化多栈管理器失败\n");
        return -1;
    }
    
    // 设置信号处理程序1(使用栈1)
    printf("\n设置信号处理程序1:\n");
    switch_alternate_stack(&manager, 1);
    
    memset(&sa1, 0, sizeof(sa1));
    sa1.sa_handler = signal_handler_1;
    sigemptyset(&sa1.sa_mask);
    sa1.sa_flags = SA_ONSTACK;
    
    if (sigaction(SIGUSR1, &sa1, NULL) == 0) {
        printf("✓ 信号处理程序1设置成功\n");
    } else {
        printf("✗ 信号处理程序1设置失败: %s\n", strerror(errno));
    }
    
    // 设置信号处理程序2(使用栈2)
    printf("\n设置信号处理程序2:\n");
    switch_alternate_stack(&manager, 2);
    
    memset(&sa2, 0, sizeof(sa2));
    sa2.sa_handler = signal_handler_2;
    sigemptyset(&sa2.sa_mask);
    sa2.sa_flags = SA_ONSTACK;
    
    if (sigaction(SIGUSR2, &sa2, NULL) == 0) {
        printf("✓ 信号处理程序2设置成功\n");
    } else {
        printf("✗ 信号处理程序2设置失败: %s\n", strerror(errno));
    }
    
    // 测试信号处理
    printf("\n测试信号处理:\n");
    
    // 发送SIGUSR1
    printf("发送SIGUSR1信号:\n");
    switch_alternate_stack(&manager, 1);
    if (kill(getpid(), SIGUSR1) == 0) {
        printf("✓ SIGUSR1发送成功\n");
        sleep(1);
    }
    
    // 发送SIGUSR2
    printf("发送SIGUSR2信号:\n");
    switch_alternate_stack(&manager, 2);
    if (kill(getpid(), SIGUSR2) == 0) {
        printf("✓ SIGUSR2发送成功\n");
        sleep(1);
    }
    
    // 清理资源
    free(manager.buffer1);
    free(manager.buffer2);
    
    return 0;
}

int main() {
    return demo_multi_signal_handlers();
}

示例4:线程安全的信号栈管理

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>

/**
 * 线程信号栈管理器
 */
typedef struct {
    stack_t signal_stack;
    char *stack_buffer;
    pthread_t thread_id;
    int thread_num;
} thread_stack_manager_t;

/**
 * 线程信号处理程序
 */
void thread_signal_handler(int sig) {
    pthread_t current_thread = pthread_self();
    
    printf("线程 %lu 收到信号 %d\n", (unsigned long)current_thread, sig);
    
    stack_t current_stack;
    if (sigaltstack(NULL, &current_stack) == 0) {
        if (current_stack.ss_flags & SS_ONSTACK) {
            printf("  线程 %lu 在备用栈上处理信号\n", (unsigned long)current_thread);
        } else {
            printf("  线程 %lu 在主栈上处理信号\n", (unsigned long)current_thread);
        }
    }
    
    printf("  线程 %lu 信号处理完成\n", (unsigned long)current_thread);
}

/**
 * 初始化线程栈管理器
 */
int init_thread_stack_manager(thread_stack_manager_t *manager, int thread_num) {
    size_t stack_size = SIGSTKSZ;
    
    manager->thread_num = thread_num;
    manager->thread_id = pthread_self();
    
    // 分配栈空间
    manager->stack_buffer = malloc(stack_size);
    if (!manager->stack_buffer) {
        printf("线程 %d: 分配栈空间失败\n", thread_num);
        return -1;
    }
    
    // 初始化栈结构
    manager->signal_stack.ss_sp = manager->stack_buffer;
    manager->signal_stack.ss_size = stack_size;
    manager->signal_stack.ss_flags = 0;
    
    printf("线程 %d: 分配备用栈 %p, 大小 %zu\n", 
           thread_num, (void*)manager->stack_buffer, stack_size);
    
    return 0;
}

/**
 * 线程工作函数
 */
void* thread_worker(void *arg) {
    thread_stack_manager_t *manager = (thread_stack_manager_t*)arg;
    struct sigaction sa;
    sigset_t set;
    
    printf("工作线程 %d 启动 (ID: %lu)\n", 
           manager->thread_num, (unsigned long)manager->thread_id);
    
    // 设置备用栈
    if (sigaltstack(&manager->signal_stack, NULL) != 0) {
        printf("线程 %d: 设置备用栈失败: %s\n", 
               manager->thread_num, strerror(errno));
        return NULL;
    }
    
    printf("线程 %d: 备用栈设置成功\n", manager->thread_num);
    
    // 设置信号处理程序
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = thread_signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_ONSTACK;
    
    if (sigaction(SIGUSR1, &sa, NULL) != 0) {
        printf("线程 %d: 设置信号处理程序失败: %s\n", 
               manager->thread_num, strerror(errno));
        return NULL;
    }
    
    printf("线程 %d: 信号处理程序设置成功\n", manager->thread_num);
    
    // 阻塞SIGUSR1以便测试
    sigemptyset(&set);
    sigaddset(&set, SIGUSR1);
    pthread_sigmask(SIG_BLOCK, &set, NULL);
    
    // 执行工作
    for (int i = 0; i < 5; i++) {
        printf("线程 %d: 工作中... (%d/5)\n", manager->thread_num, i + 1);
        sleep(2);
    }
    
    // 解除阻塞并等待信号
    printf("线程 %d: 等待信号...\n", manager->thread_num);
    pthread_sigmask(SIG_UNBLOCK, &set, NULL);
    
    // 等待一段时间让信号处理完成
    sleep(2);
    
    printf("线程 %d: 工作完成\n", manager->thread_num);
    return NULL;
}

/**
 * 演示线程安全的信号栈管理
 */
int demo_thread_safe_signal_stacks() {
    const int num_threads = 3;
    pthread_t threads[num_threads];
    thread_stack_manager_t managers[num_threads];
    sigset_t set;
    
    printf("=== 线程安全的信号栈管理演示 ===\n");
    
    // 阻塞SIGUSR1信号
    sigemptyset(&set);
    sigaddset(&set, SIGUSR1);
    pthread_sigmask(SIG_BLOCK, &set, NULL);
    
    // 创建工作线程
    printf("创建 %d 个工作线程:\n", num_threads);
    
    for (int i = 0; i < num_threads; i++) {
        if (init_thread_stack_manager(&managers[i], i + 1) != 0) {
            printf("初始化线程 %d 失败\n", i + 1);
            // 清理已分配的资源
            for (int j = 0; j < i; j++) {
                free(managers[j].stack_buffer);
            }
            return -1;
        }
        
        if (pthread_create(&threads[i], NULL, thread_worker, &managers[i]) != 0) {
            printf("创建线程 %d 失败\n", i + 1);
            free(managers[i].stack_buffer);
            // 清理已分配的资源
            for (int j = 0; j < i; j++) {
                free(managers[j].stack_buffer);
            }
            return -1;
        }
        
        managers[i].thread_id = threads[i];
        printf("创建线程 %d: ID=%lu\n", i + 1, (unsigned long)threads[i]);
    }
    
    // 等待线程启动
    sleep(1);
    
    // 向所有线程发送信号
    printf("\n向所有线程发送SIGUSR1信号:\n");
    pthread_sigmask(SIG_UNBLOCK, &set, NULL);
    
    for (int i = 0; i < num_threads; i++) {
        // 注意:实际应用中需要更精确的线程信号发送方法
        if (kill(getpid(), SIGUSR1) == 0) {
            printf("向线程 %d 发送信号成功\n", i + 1);
        } else {
            printf("向线程 %d 发送信号失败: %s\n", i + 1, strerror(errno));
        }
        sleep(1);  // 间隔发送
    }
    
    // 等待所有线程完成
    printf("\n等待所有线程完成:\n");
    for (int i = 0; i < num_threads; i++) {
        void *result;
        pthread_join(threads[i], &result);
        printf("线程 %d 已完成\n", i + 1);
        
        // 清理资源
        free(managers[i].stack_buffer);
    }
    
    return 0;
}

int main() {
    return demo_thread_safe_signal_stacks();
}

示例5:信号栈监控和调试

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/resource.h>

/**
 * 信号栈监控器
 */
typedef struct {
    stack_t original_stack;
    stack_t current_stack;
    int stack_switch_count;
    size_t total_stack_size;
    time_t last_switch_time;
} stack_monitor_t;

/**
 * 详细的栈信息显示
 */
void show_detailed_stack_info(const char *context) {
    stack_t stack_info;
    
    printf("=== %s ===\n", context);
    
    if (sigaltstack(NULL, &stack_info) == 0) {
        printf("栈指针: %p\n", stack_info.ss_sp);
        printf("栈大小: %zu 字节\n", stack_info.ss_size);
        printf("栈标志: 0x%x\n", stack_info.ss_flags);
        
        if (stack_info.ss_flags & SS_ONSTACK) {
            printf("状态: 正在使用备用栈\n");
        } else if (stack_info.ss_flags & SS_DISABLE) {
            printf("状态: 备用栈已禁用\n");
        } else {
            printf("状态: 备用栈已启用但未使用\n");
        }
        
        // 计算栈使用情况(简化版)
        void *current_sp;
        asm volatile("mov %%rsp, %0" : "=r"(current_sp));
        printf("当前栈指针: %p\n", current_sp);
        
        if (stack_info.ss_sp) {
            ptrdiff_t distance = (char*)current_sp - (char*)stack_info.ss_sp;
            printf("距离栈底: %td 字节\n", distance);
            
            if (distance > 0 && (size_t)distance < stack_info.ss_size) {
                double usage = (double)distance / stack_info.ss_size * 100;
                printf("栈使用率: %.1f%%\n", usage);
            }
        }
    } else {
        printf("获取栈信息失败: %s\n", strerror(errno));
    }
    
    printf("\n");
}

/**
 * 信号处理程序(带监控)
 */
void monitored_signal_handler(int sig) {
    static int call_count = 0;
    call_count++;
    
    printf("监控信号处理程序执行 (第 %d 次, 信号: %d)\n", call_count, sig);
    
    show_detailed_stack_info("信号处理程序中的栈状态");
    
    // 模拟一些栈使用
    char local_buffer[512];
    memset(local_buffer, call_count & 0xFF, sizeof(local_buffer));
    
    printf("  处理程序使用了 %zu 字节本地缓冲区\n", sizeof(local_buffer));
    printf("  处理程序执行完成\n\n");
}

/**
 * 栈使用压力测试
 */
void stack_pressure_test(int depth) {
    char buffer[1024];  // 每层消耗1KB栈空间
    
    // 填充缓冲区
    memset(buffer, depth & 0xFF, sizeof(buffer));
    
    if (depth < 50) {  // 限制递归深度
        printf("递归深度: %d, 使用栈空间: %d KB\n", depth, depth);
        stack_pressure_test(depth + 1);
    } else {
        printf("达到最大递归深度: %d\n", depth);
    }
}

/**
 * 演示信号栈监控和调试
 */
int demo_stack_monitoring() {
    stack_t alt_stack, old_stack;
    char *stack_buffer;
    struct sigaction sa;
    struct rlimit rl;
    
    printf("=== 信号栈监控和调试演示 ===\n");
    
    // 显示系统栈限制
    printf("1. 系统栈限制信息:\n");
    if (getrlimit(RLIMIT_STACK, &rl) == 0) {
        printf("   主栈大小限制: %ld 字节", rl.rlim_cur);
        if (rl.rlim_cur == RLIM_INFINITY) {
            printf(" (无限制)");
        }
        printf("\n");
        printf("   最大栈大小: %ld 字节", rl.rlim_max);
        if (rl.rlim_max == RLIM_INFINITY) {
            printf(" (无限制)");
        }
        printf("\n");
    }
    
    // 显示初始栈状态
    show_detailed_stack_info("初始栈状态");
    
    // 分配备用栈
    size_t stack_size = SIGSTKSZ * 4;  // 4倍标准大小
    stack_buffer = malloc(stack_size);
    if (!stack_buffer) {
        perror("分配备用栈失败");
        return -1;
    }
    
    printf("2. 分配备用栈:\n");
    printf("   请求大小: %zu 字节 (%.1f KB)\n", stack_size, stack_size / 1024.0);
    printf("   分配地址: %p\n", (void*)stack_buffer);
    
    // 设置备用栈
    alt_stack.ss_sp = stack_buffer;
    alt_stack.ss_size = stack_size;
    alt_stack.ss_flags = 0;
    
    printf("3. 设置备用栈:\n");
    if (sigaltstack(&alt_stack, &old_stack) == 0) {
        printf("   ✓ 备用栈设置成功\n");
        show_detailed_stack_info("设置备用栈后");
    } else {
        printf("   ✗ 备用栈设置失败: %s\n", strerror(errno));
        free(stack_buffer);
        return -1;
    }
    
    // 设置监控信号处理程序
    printf("4. 设置监控信号处理程序:\n");
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = monitored_signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_ONSTACK;
    
    if (sigaction(SIGUSR1, &sa, NULL) == 0) {
        printf("   ✓ 监控信号处理程序设置成功\n");
    } else {
        printf("   ✗ 监控信号处理程序设置失败: %s\n", strerror(errno));
        free(stack_buffer);
        return -1;
    }
    
    // 发送多个信号进行测试
    printf("5. 发送测试信号:\n");
    for (int i = 1; i <= 3; i++) {
        printf("   发送第 %d 个信号:\n", i);
        if (kill(getpid(), SIGUSR1) == 0) {
            printf("     ✓ 信号发送成功\n");
            sleep(1);  // 等待处理完成
        } else {
            printf("     ✗ 信号发送失败: %s\n", strerror(errno));
        }
    }
    
    // 栈压力测试
    printf("6. 栈压力测试:\n");
    printf("   开始递归栈使用测试...\n");
    stack_pressure_test(0);
    printf("   栈压力测试完成\n");
    
    show_detailed_stack_info("压力测试后栈状态");
    
    // 禁用备用栈
    printf("7. 禁用备用栈:\n");
    stack_t disable_stack;
    disable_stack.ss_flags = SS_DISABLE;
    
    if (sigaltstack(&disable_stack, NULL) == 0) {
        printf("   ✓ 备用栈禁用成功\n");
        show_detailed_stack_info("禁用备用栈后");
    } else {
        printf("   ✗ 备用栈禁用失败: %s\n", strerror(errno));
    }
    
    // 清理资源
    free(stack_buffer);
    
    printf("=== 监控演示完成 ===\n");
    return 0;
}

int main() {
    return demo_stack_monitoring();
}

sigaltstack 使用注意事项

系统要求:

  1. 内核版本: 支持信号备用栈的Linux内核
  2. 权限要求: 通常不需要特殊权限
  3. 架构支持: 支持所有主流架构

栈大小考虑:

  1. 最小大小: 至少MINSIGSTKSZ字节
  2. 推荐大小: 使用SIGSTKSZ或更大
  3. 动态分配: 建议在堆上分配栈空间

错误处理:

  1. ENOMEM: 内存不足
  2. EINVAL: 参数无效
  3. EPERM: 权限不足

安全考虑:

  1. 栈溢出保护: 备用栈可以防止主栈溢出
  2. 信号安全: 确保信号处理程序的安全执行
  3. 资源管理: 及时释放分配的栈空间

最佳实践:

  1. 适当大小: 根据信号处理程序的需求分配栈大小
  2. 错误检查: 始终检查sigaltstack的返回值
  3. 资源清理: 程序结束时释放栈空间
  4. 线程安全: 多线程环境中每个线程需要独立的栈
  5. 监控调试: 监控栈使用情况以便调试

信号栈标志说明

SS_ONSTACK:

  • 含义: 当前正在使用备用栈执行信号处理程序
  • 用途: 检查信号处理程序是否在备用栈上执行

SS_DISABLE:

  • 含义: 备用栈被禁用
  • 用途: 禁用备用栈功能

SS_ENABLED:

  • 含义: 备用栈已启用但未使用
  • 用途: 正常状态,可以使用备用栈

相关常量

SIGSTKSZ:

  • 含义: 系统推荐的信号栈大小
  • 典型值: 8KB或更大

MINSIGSTKSZ:

  • 含义: 信号栈的最小大小
  • 典型值: 2KB

常见使用场景

1. 栈溢出保护:

// 为可能引起栈溢出的程序设置备用栈
stack_t alt_stack;
alt_stack.ss_sp = malloc(SIGSTKSZ);
alt_stack.ss_size = SIGSTKSZ;
alt_stack.ss_flags = 0;
sigaltstack(&alt_stack, NULL);

2. 信号处理程序:

// 为信号处理程序设置独立的执行环境
struct sigaction sa;
sa.sa_handler = signal_handler;
sa.sa_flags = SA_ONSTACK;
sigaction(SIGSEGV, &sa, NULL);

3. 多线程应用:

// 每个线程设置独立的信号栈
void* thread_function(void *arg) {
    stack_t thread_stack;
    thread_stack.ss_sp = malloc(SIGSTKSZ);
    thread_stack.ss_size = SIGSTKSZ;
    thread_stack.ss_flags = 0;
    sigaltstack(&thread_stack, NULL);
    // 线程工作...
}

总结

sigaltstack 是Linux系统中重要的信号处理机制,提供了:

  1. 栈隔离: 为信号处理程序提供独立的执行环境
  2. 异常处理: 防止栈溢出等异常情况
  3. 安全执行: 确保信号处理程序的安全执行
  4. 灵活配置: 支持动态栈管理和配置

通过合理使用 sigaltstack,可以构建更加健壮和安全的信号处理系统。在实际应用中,需要注意栈大小、错误处理和资源管理等关键问题。

发表在 未分类 | 留下评论

signalfd4系统调用及示例

signalfd64

1. 函数介绍

在传统的 Linux 信号处理中,我们使用 sigaction 来设置信号处理函数。当信号到达时,内核会中断程序的正常执行流程,转而去执行我们注册的处理函数。这是一种异步的处理方式。

但是,有时候我们希望用一种同步基于文件描述符 (File Descriptor) 的方式来处理信号。这样做的好处是:

  1. 统一 I/O 模型:可以将信号处理集成到 selectpollepoll 等 I/O 多路复用机制中。程序可以像等待文件描述符就绪一样等待信号。
  2. 避免信号处理函数的复杂性:信号处理函数有诸多限制(只能调用异步信号安全函数),并且容易引入竞态条件。使用 signalfd 可以在程序的主循环中处理信号,避免这些问题。
  3. 获取更详细的信号信息:可以从文件描述符中读取到完整的 signalfd_siginfo 结构体,包含信号的所有信息。

signalfd (Signal File Descriptor) 系统调用就是为此而设计的。它创建一个特殊的文件描述符,该描述符用于接收由你指定的信号集中的信号。当这些信号中的任何一个到来时,这个文件描述符就会变为“可读”状态。你可以用 read() 从这个文件描述符中读取信号信息。

signalfd64 是 signalfd 的一个更新的、64 位兼容的版本。在现代 glibc 和内核中,用户空间程序通常调用的是 signalfd,而它在底层可能会根据系统架构和内核版本自动选择使用 signalfd 或 signalfd64。对于我们编程来说,直接使用 signalfd 即可。

简单来说,signalfd 就是把信号“变成”了可以像文件一样读取的数据流,让你可以用处理文件 I/O 的方式来处理信号。

2. 函数原型

#include <sys/signalfd.h> // 包含系统调用声明和相关结构体

// glibc 提供的标准接口
int signalfd(int fd, const sigset_t *mask, int flags);

注意:底层系统调用可能有 signalfd 和 signalfd64 之分,但 glibc 会为我们处理好兼容性问题。

3. 功能

创建或修改一个信号文件描述符,该描述符可以用来接收由 mask 参数指定的信号集中的信号。

4. 参数

  • fd:
    • int 类型。
    • 如果是 -1,则表示创建一个新的 signalfd
    • 如果是一个已存在的 signalfd 的文件描述符,则表示修改该 signalfd 的信号掩码。
  • mask:
    • const sigset_t * 类型。
    • 一个指向信号集的指针。这个信号集定义了你希望通过这个 signalfd 接收的信号。在创建或修改 signalfd 之前,你必须先使用 sigprocmask() 将这些信号阻塞掉。这是 signalfd 能正常工作的前提。
  • flags:
    • int 类型。
    • 用于修改 signalfd 行为的标志位。常用的标志有:
      • SFD_CLOEXEC: 在执行 exec() 系列函数时自动关闭该文件描述符。
      • SFD_NONBLOCK: 使 read() 操作变为非阻塞模式。如果当前没有信号可读,read() 会立即返回 -1 并设置 errno 为 EAGAIN 或 EWOULDBLOCK

5. 返回值

  • 成功: 返回一个有效的信号文件描述符(一个非负整数)。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

  • EINVALflags 参数包含无效标志,或者 fd 是一个已存在的 signalfd 但 mask 为 NULL
  • EMFILE: 进程已打开的文件描述符数量达到上限。
  • ENFILE: 系统已打开的文件描述符数量达到上限。
  • ENOMEM: 内核内存不足。
  • EBADFfd 参数不是 -1,也不是一个有效的 signalfd 文件描述符。

7. 读取信号信息

一旦 signalfd 创建成功并有信号到达,你就可以使用 read() 系统调用从该文件描述符中读取信号信息。

struct signalfd_siginfo si;
ssize_t s = read(sfd, &si, sizeof(si));
if (s != sizeof(si)) {
    // Handle error
}
// Now you can access signal information in 'si'
printf("Got signal %d from PID %d\n", si.ssi_signo, si.ssi_pid);

struct signalfd_siginfo 结构体包含了丰富的信号信息,例如:

  • ssi_signo: 信号编号。
  • ssi_errno: 伴随信号的错误码。
  • ssi_code: 信号产生的原因。
  • ssi_pid: 发送信号的进程 ID (如果适用)。
  • ssi_uid: 发送信号的用户 ID (如果适用)。
  • ssi_fd: 与信号相关的文件描述符 (如果适用,如 SIGIO)。
  • ssi_band: 与 SIGIO/SIGURG 相关的带外数据。
  • ssi_overrun: 实时信号队列溢出的数量。
  • ssi_trapno: 导致信号产生的陷阱号。
  • ssi_status: 退出状态或信号 (对于 SIGCHLD)。
  • ssi_int / ssi_ptr: 通过 sigqueue() 发送的伴随数据。
  • ssi_utime / ssi_stime: 用户和系统 CPU 时间。
  • ssi_addr: 导致信号产生的内存地址。

8. 相似函数或关联函数

  • sigaction: 传统的信号处理方式,设置信号处理函数。
  • sigprocmask: 用于设置/查询信号屏蔽字。在使用 signalfd 之前必须先用它来阻塞要监听的信号。
  • read: 用于从 signalfd 中读取信号信息。
  • pollselectepoll_wait: I/O 多路复用函数,可以监听 signalfd 文件描述符的可读事件。
  • close: 关闭 signalfd 文件描述符。

9. 示例代码

下面的示例演示了如何使用 signalfd 来同步处理信号,并将其集成到 poll 系统调用中。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>     // 信号处理相关
#include <sys/signalfd.h> // signalfd 相关
#include <poll.h>       // poll 相关
#include <string.h>
#include <errno.h>
#include <sys/wait.h>   // wait 相关

int main() {
    sigset_t mask;
    int sfd, j;
    struct pollfd fds[2]; // 监听 signalfd 和 标准输入
    struct signalfd_siginfo si;
    ssize_t s;
    char buf[1024]; // 用于读取标准输入

    printf("--- Demonstrating signalfd ---\n");
    printf("PID: %d\n", getpid());
    printf("Try sending signals:\n");
    printf("  kill -USR1 %d\n", getpid());
    printf("  kill -USR2 %d\n", getpid());
    printf("  Or press Ctrl+C (SIGINT) or Ctrl+\\ (SIGQUIT)\n");
    printf("  Type 'exit' and press Enter to quit.\n");
    printf("  This program uses poll() to wait for signals or input.\n");

    // 1. 创建要监听的信号集
    sigemptyset(&mask);
    sigaddset(&mask, SIGUSR1);
    sigaddset(&mask, SIGUSR2);
    sigaddset(&mask, SIGINT);  // Ctrl+C
    sigaddset(&mask, SIGQUIT); // Ctrl+\

    // 2. 阻塞这些信号
    // 这是使用 signalfd 的关键前提!
    // 必须先阻塞信号,这样信号才不会被默认处理或由 sigaction 处理
    // 而是排队等待 signalfd 读取
    if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
        perror("sigprocmask");
        exit(EXIT_FAILURE);
    }
    printf("Blocked signals: SIGUSR1, SIGUSR2, SIGINT, SIGQUIT\n");

    // 3. 创建 signalfd
    // -1 表示创建新的 signalfd
    // &mask 是要监听的信号集
    // SFD_CLOEXEC 在 exec 时关闭 fd,SFD_NONBLOCK 设置为非阻塞
    sfd = signalfd(-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK);
    if (sfd == -1) {
        perror("signalfd");
        exit(EXIT_FAILURE);
    }
    printf("Created signalfd: %d\n", sfd);

    // 4. 设置 poll 的文件描述符数组
    // 监听 signalfd
    fds[0].fd = sfd;
    fds[0].events = POLLIN; // 等待可读事件
    // 监听标准输入 (stdin)
    fds[1].fd = STDIN_FILENO;
    fds[1].events = POLLIN; // 等待可读事件

    // 5. 主循环:使用 poll 等待事件
    while (1) {
        // poll 会阻塞,直到 fds 数组中的任何一个 fd 准备好
        // -1 表示无限期等待
        int poll_num = poll(fds, 2, -1);
        if (poll_num == -1) {
            if (errno == EINTR) {
                // poll 被信号中断 (不太可能发生,因为我们用的是 signalfd)
                continue;
            } else {
                perror("poll");
                break;
            }
        }

        if (poll_num > 0) {
            // 检查 signalfd 是否有数据可读
            if (fds[0].revents & POLLIN) {
                // 6. 从 signalfd 读取信号信息
                s = read(sfd, &si, sizeof(si));
                if (s != sizeof(si)) {
                    if (errno == EAGAIN || errno == EWOULDBLOCK) {
                        // 非阻塞模式下没有数据可读 (不太可能,因为 poll 说了有)
                        continue;
                    } else {
                        perror("read signalfd");
                        break;
                    }
                }

                // 7. 处理接收到的信号
                printf("\nReceived signal: %d", si.ssi_signo);
                switch(si.ssi_signo) {
                    case SIGUSR1:
                        printf(" (SIGUSR1)");
                        break;
                    case SIGUSR2:
                        printf(" (SIGUSR2)");
                        break;
                    case SIGINT:
                        printf(" (SIGINT - Ctrl+C)");
                        break;
                    case SIGQUIT:
                        printf(" (SIGQUIT - Ctrl+\\)");
                        break;
                }
                printf("\n  Sender PID: %d\n", si.ssi_pid);
                printf("  Sender UID: %d\n", si.ssi_uid);

                // 如果收到 SIGINT 或 SIGQUIT,则退出
                if (si.ssi_signo == SIGINT || si.ssi_signo == SIGQUIT) {
                    printf("Exiting due to signal.\n");
                    break;
                }
            }

            // 检查标准输入是否有数据可读
            if (fds[1].revents & POLLIN) {
                ssize_t nread = read(STDIN_FILENO, buf, sizeof(buf) - 1);
                if (nread > 0) {
                    buf[nread] = '\0';
                    printf("Read from stdin: %s", buf); // buf 可能已包含 \n
                    // 如果用户输入 'exit',则退出
                    if (strncmp(buf, "exit\n", 5) == 0) {
                        printf("Exiting due to 'exit' command.\n");
                        break;
                    }
                } else if (nread == 0) {
                    // EOF on stdin (e.g., Ctrl+D)
                    printf("EOF on stdin. Exiting.\n");
                    break;
                } else {
                    if (errno != EAGAIN && errno != EWOULDBLOCK) {
                        perror("read stdin");
                        break;
                    }
                }
            }

            // 检查是否有错误或挂起事件
            if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
                fprintf(stderr, "Error on signalfd.\n");
                break;
            }
            if (fds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) {
                 fprintf(stderr, "Error on stdin.\n");
                 break;
            }
        }
    }

    // 8. 清理资源
    printf("Closing signalfd...\n");
    close(sfd);
    printf("Program finished.\n");

    return 0;
}

10. 编译和运行

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

# 运行程序
./signalfd_example
# 程序会输出 PID,并提示你可以发送哪些信号
# 在另一个终端尝试发送信号,或在程序运行的终端按 Ctrl+C 等

11. 预期输出

--- Demonstrating signalfd ---
PID: 12345
Blocked signals: SIGUSR1, SIGUSR2, SIGINT, SIGQUIT
Created signalfd: 3
Try sending signals:
  kill -USR1 12345
  kill -USR2 12345
  Or press Ctrl+C (SIGINT) or Ctrl+\ (SIGQUIT)
  Type 'exit' and press Enter to quit.
  This program uses poll() to wait for signals or input.

Read from stdin: hello
Read from stdin: world

Received signal: 10 (SIGUSR1)
  Sender PID: 12346
  Sender UID: 1000

Received signal: 12 (SIGUSR2)
  Sender PID: 12346
  Sender UID: 1000

Received signal: 2 (SIGINT - Ctrl+C)
  Sender PID: 0
  Sender UID: 0
Exiting due to signal.
Closing signalfd...
Program finished.

12. 总结

signalfd 提供了一种现代化、同步化的方式来处理信号,特别适合于事件驱动的程序(如服务器)。通过将其与 pollselectepoll 等 I/O 多路复用技术结合,可以构建出高效且结构清晰的程序。记住两个关键点:

  1. 先阻塞,再创建:必须先用 sigprocmask 阻塞要监听的信号。
  2. 像文件一样读取:使用 read() 从 signalfd 中读取 struct signalfd_siginfo 结构体来获取信号信息。

这种方式避免了传统信号处理函数的复杂性和潜在风险,是编写健壮 Linux 应用程序的有力工具。

发表在 linux文章 | 标签为 | 留下评论

setpgid系统调用及示例

setpgid 函数详解

1. 函数介绍

setpgid 是Linux系统调用,用于设置进程的进程组ID(Process Group ID)。进程组是进程的集合,用于信号分发和作业控制。通过设置进程组,可以将相关进程组织在一起,便于统一管理和控制。

2. 函数原型

#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);

3. 功能

setpgid 将指定进程(由pid标识)加入到指定的进程组(由pgid标识)。如果进程组不存在,则创建新的进程组。这个函数主要用于作业控制和信号管理。

4. 参数

  • pid_t pid: 目标进程ID(0表示当前进程)
  • pid_t pgid: 进程组ID(0表示使用pid作为进程组ID)

5. 返回值

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

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

  • getpgid: 获取进程的进程组ID
  • setpgrp: 设置进程组(等同于setpgid(0,0))
  • getsid: 获取会话ID
  • setsid: 创建新会话

7. 示例代码

示例1:基础setpgid使用

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

/**
 * 显示进程组信息
 */
void show_process_group_info() {
    pid_t pid = getpid();
    pid_t pgid = getpgid(0);
    pid_t ppid = getppid();
    
    printf("进程ID: %d\n", pid);
    printf("父进程ID: %d\n", ppid);
    printf("进程组ID: %d\n", pgid);
    printf("会话ID: %d\n", getsid(0));
    printf("\n");
}

/**
 * 演示基础setpgid使用方法
 */
int demo_setpgid_basic() {
    pid_t original_pgid, new_pgid;
    int result;
    
    printf("=== 基础setpgid使用示例 ===\n");
    
    // 显示原始进程组信息
    printf("1. 原始进程组信息:\n");
    show_process_group_info();
    original_pgid = getpgid(0);
    
    // 将当前进程移动到新的进程组
    printf("2. 创建新的进程组:\n");
    pid_t new_group_id = getpid();  // 使用当前进程ID作为新进程组ID
    
    printf("   尝试将进程 %d 移动到进程组 %d\n", getpid(), new_group_id);
    result = setpgid(0, new_group_id);
    
    if (result == 0) {
        printf("   ✓ 成功创建并加入新进程组\n");
        
        // 验证设置结果
        new_pgid = getpgid(0);
        printf("   新的进程组ID: %d\n", new_pgid);
        
        if (new_pgid == new_group_id) {
            printf("   ✓ 进程组设置正确\n");
        } else {
            printf("   ✗ 进程组设置可能有问题\n");
        }
        
        show_process_group_info();
    } else {
        printf("   ✗ 创建新进程组失败: %s\n", strerror(errno));
        if (errno == EACCES) {
            printf("   原因:不允许从会话领导进程更改进程组\n");
        } else if (errno == EINVAL) {
            printf("   原因:进程ID或进程组ID无效\n");
        } else if (errno == EPERM) {
            printf("   原因:权限不足\n");
        } else if (errno == ESRCH) {
            printf("   原因:指定的进程不存在\n");
        }
    }
    
    return 0;
}

int main() {
    return demo_setpgid_basic();
}

示例2:父子进程组管理

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

/**
 * 子进程函数
 */
void child_process(int child_id) {
    printf("子进程 %d 启动\n", getpid());
    printf("  子进程 %d 的原始进程组: %d\n", getpid(), getpgid(0));
    
    // 子进程创建自己的进程组
    pid_t new_pgid = getpid();
    printf("  子进程 %d 尝试创建新进程组: %d\n", getpid(), new_pgid);
    
    int result = setpgid(0, new_pgid);
    if (result == 0) {
        printf("  ✓ 子进程 %d 成功创建新进程组\n", getpid());
        printf("  子进程 %d 当前进程组: %d\n", getpid(), getpgid(0));
    } else {
        printf("  ✗ 子进程 %d 创建新进程组失败: %s\n", getpid(), strerror(errno));
    }
    
    // 模拟一些工作
    sleep(3);
    printf("子进程 %d 结束\n", getpid());
    exit(0);
}

/**
 * 演示父子进程组管理
 */
int demo_parent_child_groups() {
    pid_t child1_pid, child2_pid;
    pid_t original_pgid;
    
    printf("=== 父子进程组管理演示 ===\n");
    
    // 显示父进程信息
    printf("父进程信息:\n");
    printf("  进程ID: %d\n", getpid());
    printf("  原始进程组: %d\n", getpgid(0));
    printf("  会话ID: %d\n", getsid(0));
    printf("\n");
    
    // 创建第一个子进程
    printf("1. 创建第一个子进程:\n");
    child1_pid = fork();
    if (child1_pid == 0) {
        child_process(1);
    } else if (child1_pid > 0) {
        printf("  父进程创建子进程1: PID=%d\n", child1_pid);
        printf("  子进程1的进程组: %d\n", getpgid(child1_pid));
    } else {
        perror("创建子进程1失败");
        return -1;
    }
    
    // 创建第二个子进程
    printf("\n2. 创建第二个子进程:\n");
    child2_pid = fork();
    if (child2_pid == 0) {
        child_process(2);
    } else if (child2_pid > 0) {
        printf("  父进程创建子进程2: PID=%d\n", child2_pid);
        printf("  子进程2的进程组: %d\n", getpgid(child2_pid));
    } else {
        perror("创建子进程2失败");
        // 清理已创建的子进程
        kill(child1_pid, SIGKILL);
        return -1;
    }
    
    // 父进程也创建新进程组
    printf("\n3. 父进程创建新进程组:\n");
    original_pgid = getpgid(0);
    pid_t parent_pgid = getpid();
    
    printf("  父进程尝试创建新进程组: %d\n", parent_pgid);
    int result = setpgid(0, parent_pgid);
    if (result == 0) {
        printf("  ✓ 父进程成功创建新进程组\n");
        printf("  父进程当前进程组: %d\n", getpgid(0));
    } else {
        printf("  ✗ 父进程创建新进程组失败: %s\n", strerror(errno));
    }
    
    // 等待子进程结束
    printf("\n4. 等待子进程结束:\n");
    int status;
    pid_t finished_pid;
    
    while ((finished_pid = wait(&status)) > 0) {
        printf("  子进程 %d 已结束,退出状态: %d\n", finished_pid, WEXITSTATUS(status));
    }
    
    // 恢复父进程原始进程组
    printf("\n5. 恢复父进程原始进程组:\n");
    result = setpgid(0, original_pgid);
    if (result == 0) {
        printf("  ✓ 父进程成功恢复原始进程组: %d\n", getpgid(0));
    } else {
        printf("  ✗ 父进程恢复原始进程组失败: %s\n", strerror(errno));
    }
    
    return 0;
}

int main() {
    return demo_parent_child_groups();
}

示例3:进程组信号处理

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <signal.h>

/**
 * 信号处理函数
 */
void signal_handler(int sig) {
    printf("进程 %d 接收到信号 %d (%s)\n", getpid(), sig, strsignal(sig));
}

/**
 * 工作进程函数
 */
void worker_process(int worker_id) {
    // 设置信号处理
    signal(SIGTERM, signal_handler);
    signal(SIGINT, signal_handler);
    signal(SIGUSR1, signal_handler);
    
    printf("工作进程 %d 启动,进程组: %d\n", getpid(), getpgid(0));
    
    // 创建自己的进程组
    pid_t new_pgid = getpid();
    if (setpgid(0, new_pgid) == 0) {
        printf("工作进程 %d 成功创建进程组: %d\n", getpid(), new_pgid);
    }
    
    // 模拟长时间工作
    for (int i = 0; i < 10; i++) {
        printf("工作进程 %d 正在工作... (%d/10)\n", getpid(), i + 1);
        sleep(2);
    }
    
    printf("工作进程 %d 结束\n", getpid());
    exit(0);
}

/**
 * 演示进程组信号处理
 */
int demo_process_group_signaling() {
    pid_t workers[3];
    pid_t original_pgid;
    
    printf("=== 进程组信号处理演示 ===\n");
    
    // 保存原始进程组
    original_pgid = getpgid(0);
    printf("主进程原始进程组: %d\n", original_pgid);
    
    // 创建工作进程组
    printf("\n1. 创建工作进程组:\n");
    for (int i = 0; i < 3; i++) {
        workers[i] = fork();
        if (workers[i] == 0) {
            worker_process(i + 1);
        } else if (workers[i] > 0) {
            printf("  创建工作进程 %d: PID=%d\n", i + 1, workers[i]);
            
            // 将子进程加入同一进程组
            pid_t group_id = workers[0];  // 使用第一个子进程的PID作为组ID
            if (setpgid(workers[i], group_id) == 0) {
                printf("  工作进程 %d 加入进程组: %d\n", workers[i], group_id);
            } else {
                printf("  工作进程 %d 加入进程组失败: %s\n", workers[i], strerror(errno));
            }
        } else {
            perror("创建工作进程失败");
            // 清理已创建的进程
            for (int j = 0; j < i; j++) {
                kill(workers[j], SIGKILL);
            }
            return -1;
        }
    }
    
    // 显示进程组信息
    printf("\n2. 进程组信息:\n");
    for (int i = 0; i < 3; i++) {
        pid_t pgid = getpgid(workers[i]);
        printf("  工作进程 %d (PID=%d) 进程组: %d\n", i + 1, workers[i], pgid);
    }
    
    // 等待一段时间后发送信号
    printf("\n3. 等待5秒后发送信号到进程组...\n");
    sleep(5);
    
    // 向进程组发送信号(发送给组内所有进程)
    pid_t target_group = getpgid(workers[0]);
    printf("向进程组 %d 发送 SIGUSR1 信号\n", target_group);
    
    if (kill(-target_group, SIGUSR1) == 0) {  // 负数表示发送给整个进程组
        printf("✓ 成功发送信号到进程组\n");
    } else {
        printf("✗ 发送信号失败: %s\n", strerror(errno));
    }
    
    // 等待一段时间观察信号处理
    sleep(3);
    
    // 发送终止信号
    printf("\n4. 发送终止信号:\n");
    printf("向进程组 %d 发送 SIGTERM 信号\n", target_group);
    
    if (kill(-target_group, SIGTERM) == 0) {
        printf("✓ 成功发送终止信号\n");
    } else {
        printf("✗ 发送终止信号失败: %s\n", strerror(errno));
    }
    
    // 等待所有工作进程结束
    printf("\n5. 等待工作进程结束:\n");
    int status;
    pid_t finished_pid;
    int finished_count = 0;
    
    while (finished_count < 3) {
        finished_pid = wait(&status);
        if (finished_pid > 0) {
            finished_count++;
            printf("  工作进程 %d 已结束,退出状态: %d\n", 
                   finished_pid, WEXITSTATUS(status));
        } else {
            break;
        }
    }
    
    printf("所有工作进程已完成\n");
    return 0;
}

int main() {
    return demo_process_group_signaling();
}

示例4:会话和进程组管理

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <signal.h>

/**
 * 显示会话和进程组信息
 */
void show_session_info() {
    printf("=== 会话和进程组信息 ===\n");
    printf("进程ID: %d\n", getpid());
    printf("父进程ID: %d\n", getppid());
    printf("进程组ID: %d\n", getpgid(0));
    printf("会话ID: %d\n", getsid(0));
    printf("前台进程组: %d\n", tcgetpgrp(STDIN_FILENO));
    printf("\n");
}

/**
 * 会话领导进程
 */
void session_leader() {
    printf("会话领导进程启动 (PID: %d)\n", getpid());
    show_session_info();
    
    // 创建新会话
    pid_t sid = setsid();
    if (sid != -1) {
        printf("✓ 成功创建新会话: %d\n", sid);
        show_session_info();
    } else {
        printf("✗ 创建新会话失败: %s\n", strerror(errno));
        exit(1);
    }
    
    // 创建子进程组
    for (int i = 0; i < 2; i++) {
        pid_t child_pid = fork();
        if (child_pid == 0) {
            // 子进程
            printf("子进程 %d 启动\n", getpid());
            
            // 创建自己的进程组
            pid_t new_pgid = getpid();
            if (setpgid(0, new_pgid) == 0) {
                printf("子进程 %d 成功创建进程组: %d\n", getpid(), new_pgid);
            }
            
            // 模拟工作
            for (int j = 0; j < 5; j++) {
                printf("子进程 %d 工作中... (%d/5)\n", getpid(), j + 1);
                sleep(2);
            }
            
            printf("子进程 %d 结束\n", getpid());
            exit(0);
        } else if (child_pid > 0) {
            printf("会话领导进程创建子进程: %d\n", child_pid);
            
            // 将子进程加入特定进程组
            if (setpgid(child_pid, child_pid) == 0) {
                printf("子进程 %d 加入进程组成功\n", child_pid);
            }
        }
    }
    
    // 等待子进程结束
    int status;
    pid_t finished_pid;
    while ((finished_pid = wait(&status)) > 0) {
        printf("子进程 %d 已结束\n", finished_pid);
    }
    
    printf("会话领导进程结束\n");
    exit(0);
}

/**
 * 演示会话和进程组管理
 */
int demo_session_management() {
    pid_t session_leader_pid;
    
    printf("=== 会话和进程组管理演示 ===\n");
    
    // 显示初始信息
    printf("1. 初始进程信息:\n");
    show_session_info();
    
    // 创建会话领导进程
    printf("2. 创建会话领导进程:\n");
    session_leader_pid = fork();
    if (session_leader_pid == 0) {
        session_leader();
    } else if (session_leader_pid > 0) {
        printf("主进程创建会话领导进程: %d\n", session_leader_pid);
        
        // 等待会话领导进程结束
        int status;
        waitpid(session_leader_pid, &status, 0);
        printf("会话领导进程已结束\n");
    } else {
        perror("创建会话领导进程失败");
        return -1;
    }
    
    // 显示最终信息
    printf("\n3. 最终进程信息:\n");
    show_session_info();
    
    return 0;
}

int main() {
    return demo_session_management();
}

示例5:作业控制演示

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <signal.h>

/**
 * 后台作业进程
 */
void background_job(int job_id) {
    printf("后台作业 %d 启动 (PID: %d)\n", job_id, getpid());
    
    // 创建独立的进程组
    if (setpgid(0, 0) == 0) {
        printf("后台作业 %d 成功创建进程组\n", job_id);
    } else {
        printf("后台作业 %d 创建进程组失败: %s\n", job_id, strerror(errno));
    }
    
    // 模拟长时间运行的作业
    for (int i = 0; i < 20; i++) {
        printf("后台作业 %d 运行中... (%d/20)\n", job_id, i + 1);
        sleep(1);
        
        // 检查是否收到终止信号
        if (i == 10) {
            // 模拟作业暂停和恢复
            printf("后台作业 %d 暂停工作\n", job_id);
            sleep(2);
            printf("后台作业 %d 恢复工作\n", job_id);
        }
    }
    
    printf("后台作业 %d 完成\n", job_id);
    exit(0);
}

/**
 * 作业控制管理器
 */
typedef struct {
    pid_t pid;
    int job_id;
    int status;  // 0:运行, 1:暂停, 2:完成
    char command[256];
} job_t;

/**
 * 显示作业列表
 */
void show_job_list(job_t *jobs, int count) {
    printf("\n=== 作业列表 ===\n");
    printf("%-5s %-8s %-10s %s\n", "作业ID", "进程ID", "状态", "命令");
    printf("----------------------------------------\n");
    
    for (int i = 0; i < count; i++) {
        const char *status_str;
        switch (jobs[i].status) {
            case 0: status_str = "运行"; break;
            case 1: status_str = "暂停"; break;
            case 2: status_str = "完成"; break;
            default: status_str = "未知"; break;
        }
        
        printf("%-5d %-8d %-10s %s\n", 
               jobs[i].job_id, jobs[i].pid, status_str, jobs[i].command);
    }
    printf("\n");
}

/**
 * 演示作业控制
 */
int demo_job_control() {
    job_t jobs[3];
    int job_count = 0;
    
    printf("=== 作业控制演示 ===\n");
    
    // 创建后台作业
    printf("1. 创建后台作业:\n");
    for (int i = 0; i < 3; i++) {
        pid_t job_pid = fork();
        if (job_pid == 0) {
            background_job(i + 1);
        } else if (job_pid > 0) {
            // 父进程记录作业信息
            jobs[job_count].pid = job_pid;
            jobs[job_count].job_id = i + 1;
            jobs[job_count].status = 0;  // 运行中
            snprintf(jobs[job_count].command, sizeof(jobs[job_count].command), 
                     "background_job_%d", i + 1);
            job_count++;
            
            printf("  创建后台作业 %d: PID=%d\n", i + 1, job_pid);
            
            // 将子进程加入独立进程组
            if (setpgid(job_pid, job_pid) == 0) {
                printf("  作业 %d 成功创建独立进程组\n", i + 1);
            }
        } else {
            perror("创建后台作业失败");
        }
    }
    
    // 显示初始作业列表
    show_job_list(jobs, job_count);
    
    // 演示向特定作业发送信号
    printf("2. 向作业发送信号:\n");
    if (job_count > 0) {
        printf("  向作业 1 (PID=%d) 发送 SIGUSR1 信号\n", jobs[0].pid);
        if (kill(jobs[0].pid, SIGUSR1) == 0) {
            printf("  ✓ 信号发送成功\n");
        } else {
            printf("  ✗ 信号发送失败: %s\n", strerror(errno));
        }
    }
    
    // 等待一段时间
    printf("\n3. 等待作业运行...\n");
    sleep(5);
    
    // 显示当前作业状态
    show_job_list(jobs, job_count);
    
    // 演示作业控制操作
    printf("4. 作业控制操作:\n");
    
    // 暂停一个作业
    if (job_count > 1) {
        printf("  暂停作业 2 (PID=%d)\n", jobs[1].pid);
        if (kill(-getpgid(jobs[1].pid), SIGSTOP) == 0) {  // 发送给整个进程组
            jobs[1].status = 1;  // 暂停
            printf("  ✓ 作业 2 已暂停\n");
        } else {
            printf("  ✗ 暂停作业 2 失败: %s\n", strerror(errno));
        }
    }
    
    // 显示更新后的作业列表
    show_job_list(jobs, job_count);
    
    // 恢复暂停的作业
    if (job_count > 1) {
        printf("  恢复作业 2 (PID=%d)\n", jobs[1].pid);
        if (kill(-getpgid(jobs[1].pid), SIGCONT) == 0) {
            jobs[1].status = 0;  // 运行中
            printf("  ✓ 作业 2 已恢复\n");
        } else {
            printf("  ✗ 恢复作业 2 失败: %s\n", strerror(errno));
        }
    }
    
    // 等待所有作业完成
    printf("\n5. 等待所有作业完成:\n");
    int completed_jobs = 0;
    while (completed_jobs < job_count) {
        int status;
        pid_t finished_pid = waitpid(-1, &status, WNOHANG);
        if (finished_pid > 0) {
            // 找到完成的作业并更新状态
            for (int i = 0; i < job_count; i++) {
                if (jobs[i].pid == finished_pid) {
                    jobs[i].status = 2;  // 完成
                    printf("  作业 %d (PID=%d) 已完成\n", jobs[i].job_id, finished_pid);
                    completed_jobs++;
                    break;
                }
            }
        } else if (finished_pid == 0) {
            // 没有作业完成,短暂等待
            usleep(100000);  // 100ms
        } else {
            // 错误
            if (errno != ECHILD) {
                perror("等待作业完成时出错");
            }
            break;
        }
    }
    
    // 显示最终作业列表
    show_job_list(jobs, job_count);
    
    printf("作业控制演示完成\n");
    return 0;
}

int main() {
    return demo_job_control();
}

setpgid 使用注意事项

系统要求:

  1. 内核版本: 支持进程组管理的Linux内核
  2. 权限要求: 通常不需要特殊权限
  3. 架构支持: 支持所有主流架构

参数规则:

  1. pid为0: 表示当前进程
  2. pgid为0: 表示使用pid作为进程组ID
  3. 进程限制: 只能操作当前会话中的进程
  4. 会话限制: 不能将进程移到不同会话的进程组

错误处理:

  1. EACCES: 不允许从会话领导进程更改进程组
  2. EINVAL: 进程ID或进程组ID无效
  3. EPERM: 权限不足
  4. ESRCH: 指定的进程不存在

性能考虑:

  1. 系统调用开销: 频繁调用有性能开销
  2. 内核数据结构: 需要更新内核中的进程组信息
  3. 同步操作: 涉及内核数据结构的同步更新

安全考虑:

  1. 进程控制: 可能影响其他进程的行为
  2. 信号传播: 进程组信号会影响组内所有进程
  3. 会话管理: 不当的会话操作可能影响终端控制

最佳实践:

  1. 时机选择: 在进程创建后尽早设置进程组
  2. 错误处理: 妥善处理各种错误情况
  3. 状态检查: 验证设置结果的正确性
  4. 资源清理: 及时清理不需要的进程组
  5. 文档记录: 记录进程组管理策略

进程组和会话概念

进程组(Process Group):

  • 定义: 一组相关进程的集合
  • 用途: 信号分发、作业控制
  • 特点: 同一进程组的进程可以被统一控制

会话(Session):

  • 定义: 一个或多个进程组的集合
  • 用途: 终端控制、登录会话管理
  • 特点: 会话领导进程控制整个会话

关系层次:

会话 (Session)
└── 进程组 (Process Group)
    └── 进程 (Process)

常见使用场景

1. Shell作业控制:

// 创建后台作业的独立进程组
setpgid(child_pid, child_pid);

2. 守护进程:

// 创建独立会话,脱离控制终端
setsid();

3. 多进程应用:

// 将相关进程组织到同一进程组
setpgid(worker_pid, group_leader_pid);

信号与进程组

组信号发送:

// 向整个进程组发送信号
kill(-pgid, SIGTERM);

前台进程组:

// 设置前台进程组(控制终端)
tcsetpgrp(STDIN_FILENO, pgid);

总结

setpgid 是Linux系统中重要的进程管理函数,提供了:

  1. 进程组控制: 灵活的进程组管理能力
  2. 作业控制: 支持shell风格的作业控制
  3. 信号管理: 便于向进程组发送信号
  4. 会话管理: 支持会话和终端控制

通过合理使用 setpgid,可以构建更加灵活和可控的多进程应用程序。在实际应用中,需要注意进程组的生命周期管理、错误处理和信号控制等关键问题。

发表在 linux文章 | 标签为 | 留下评论

fallocate系统调用及示例

fallocate – 预分配文件空间

1. 函数介绍

[fallocate](file:///root/代码目录/fio/helpers.h#L7-L7) 是 Linux 系统调用,用于为文件预分配磁盘空间。你可以把它想象成”预订”磁盘空间,就像你在餐厅预订座位一样——你告诉系统你需要多少空间,系统就为你预留出来,但此时还没有实际写入数据。

这个函数的主要优势是:

  • 提高文件系统性能:避免文件碎片
  • 确保文件有足够空间:防止写入时空间不足
  • 快速操作:比实际写入数据更快

2. 函数原型

#include <fcntl.h>

int fallocate(int fd, int mode, off_t offset, off_t len);

3. 功能

为文件预分配指定范围的磁盘空间,而不需要实际写入数据。这可以优化文件系统的存储布局,提高I/O性能。

4. 参数

  • int fd: 文件描述符,通过 open() 函数获得
  • int mode: 操作模式
    • 0: 默认模式,分配空间
    • FALLOC_FL_PUNCH_HOLE: 创建空洞(释放空间)
    • FALLOC_FL_COLLAPSE_RANGE: 折叠范围
    • FALLOC_FL_ZERO_RANGE: 清零范围
  • off_t offset: 文件中的起始偏移量(字节)
  • off_t len: 要分配的长度(字节)

5. 返回值

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

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

  • [posix_fallocate()](file:///root/代码目录/fio/helpers.h#L8-L8): POSIX标准版本,可移植性更好
  • truncate(): 改变文件大小
  • ftruncate(): 改变文件描述符对应的文件大小
  • lseek(): 移动文件指针位置

7. 示例代码

示例1:基本的文件空间预分配

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main() {
    int fd;
    int ret;
    
    // 创建一个新文件用于测试
    fd = open("test_file.dat", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("打开文件失败");
        exit(EXIT_FAILURE);
    }
    
    printf("文件创建成功,文件描述符: %d\n", fd);
    
    // 预分配10MB的空间
    ret = fallocate(fd, 0, 0, 10 * 1024 * 1024);
    if (ret == -1) {
        if (errno == EOPNOTSUPP) {
            printf("警告: 当前文件系统不支持 fallocate\n");
        } else {
            perror("fallocate 调用失败");
        }
        close(fd);
        exit(EXIT_FAILURE);
    }
    
    printf("成功预分配10MB空间\n");
    
    // 关闭文件
    close(fd);
    printf("文件已关闭\n");
    
    return 0;
}

示例2:创建空洞文件(释放指定范围的空间)

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main() {
    int fd;
    int ret;
    char data[] = "Hello, World!";
    
    // 创建一个新文件
    fd = open("sparse_file.dat", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("打开文件失败");
        exit(EXIT_FAILURE);
    }
    
    printf("创建文件,文件描述符: %d\n", fd);
    
    // 先写入一些数据在文件开头
    if (write(fd, data, strlen(data)) == -1) {
        perror("写入数据失败");
        close(fd);
        exit(EXIT_FAILURE);
    }
    
    printf("已在文件开头写入: %s\n", data);
    
    // 预分配10MB空间在1MB偏移处(创建空洞)
    ret = fallocate(fd, 0, 1024 * 1024, 10 * 1024 * 1024);
    if (ret == -1) {
        perror("fallocate 调用失败");
        close(fd);
        exit(EXIT_FAILURE);
    }
    
    printf("在1MB偏移处预分配了10MB空间\n");
    printf("此时文件大小约为11MB,但实际占用磁盘空间很小\n");
    
    // 关闭文件
    close(fd);
    printf("文件已关闭\n");
    
    return 0;
}

示例3:使用PUNCH_HOLE模式释放文件中间部分的空间

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main() {
    int fd;
    int ret;
    char data[1024];
    
    // 创建测试文件并填充数据
    fd = open("punch_hole_test.dat", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("打开文件失败");
        exit(EXIT_FAILURE);
    }
    
    printf("创建测试文件\n");
    
    // 填充1MB数据
    memset(data, 'A', sizeof(data));
    for (int i = 0; i < 1024; i++) {
        if (write(fd, data, sizeof(data)) == -1) {
            perror("写入数据失败");
            close(fd);
            exit(EXIT_FAILURE);
        }
    }
    
    printf("已写入1MB数据\n");
    
    // 使用PUNCH_HOLE模式释放中间512KB的空间
    // 注意:PUNCH_HOLE需要与FALLOC_FL_KEEP_SIZE一起使用
    ret = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 
                    256 * 1024, 512 * 1024);
    if (ret == -1) {
        if (errno == EOPNOTSUPP) {
            printf("当前文件系统不支持 PUNCH_HOLE 操作\n");
        } else {
            perror("fallocate PUNCH_HOLE 操作失败");
        }
    } else {
        printf("成功在文件中间创建了512KB的空洞\n");
        printf("这部分空间已被释放,但文件大小保持不变\n");
    }
    
    // 关闭文件
    close(fd);
    printf("文件已关闭\n");
    
    return 0;
}

总结

fallocate 是一个非常有用的系统调用,特别适用于以下场景:

  1. 数据库系统预分配文件空间
  2. 大文件写入前的空间预留
  3. 虚拟机磁盘映像创建
  4. 需要避免文件碎片的高性能应用

记住,不是所有文件系统都支持所有模式,使用时需要检查返回值并做好错误处理。

发表在 linux文章 | 标签为 | 留下评论

fadvise64系统调用及示例

fadvise64 – 文件访问建议

函数介绍

fadvise64是一个Linux系统调用,用于向内核提供关于文件访问模式的建议。它帮助内核优化文件I/O操作,提高性能。

函数原型

#include <fcntl.h>
#include <sys/syscall.h>
#include <unistd.h>

int fadvise64(int fd, off_t offset, off_t len, int advice);

功能

向内核提供文件访问模式建议,帮助内核优化缓存和预读策略。

参数

  • int fd: 文件描述符
  • off_t offset: 建议适用的文件起始偏移量
  • off_t len: 建议适用的文件长度(0表示到文件末尾)
  • int advice: 访问建议类型
    • POSIX_FADV_NORMAL: 普通访问模式(默认)
    • POSIX_FADV_SEQUENTIAL: 顺序访问
    • POSIX_FADV_RANDOM: 随机访问
    • POSIX_FADV_NOREUSE: 数据只访问一次
    • POSIX_FADV_WILLNEED: 数据即将被访问
    • POSIX_FADV_DONTNEED: 数据不再需要

返回值

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

特殊限制

  • 需要Linux 2.5.60以上内核支持
  • 某些文件系统可能不完全支持
  • 建议只是提示,内核可能忽略

相似函数

  • madvise(): 内存访问建议
  • readahead(): 文件预读
  • posix_fadvise(): POSIX标准版本

示例代码

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

// 系统调用包装
static int fadvise64_wrapper(int fd, off_t offset, off_t len, int advice) {
    return syscall(__NR_fadvise64, fd, offset, len, advice);
}

// 创建测试文件
int create_test_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) {
        memset(buffer, 'A', 4096);
        for (size_t i = 0; i < size; i += 4096) {
            size_t write_size = (size - i > 4096) ? 4096 : (size - i);
            write(fd, buffer, write_size);
        }
        free(buffer);
    }
    
    return fd;
}

int main() {
    int fd;
    int result;
    
    printf("=== Fadvise64 函数示例 ===\n");
    
    // 示例1: 基本使用
    printf("\n示例1: 基本使用\n");
    
    // 创建大文件用于测试
    fd = create_test_file("test_fadvise64.dat", 1024 * 1024); // 1MB
    if (fd == -1) {
        exit(EXIT_FAILURE);
    }
    printf("创建测试文件: test_fadvise64.dat (1MB)\n");
    
    close(fd);
    
    // 重新打开文件进行测试
    fd = open("test_fadvise64.dat", O_RDONLY);
    if (fd == -1) {
        perror("打开测试文件失败");
        unlink("test_fadvise64.dat");
        exit(EXIT_FAILURE);
    }
    printf("打开测试文件进行fadvise64测试\n");
    
    // 示例2: 不同的访问建议
    printf("\n示例2: 不同的访问建议\n");
    
    // POSIX_FADV_NORMAL - 普通访问模式
    result = fadvise64_wrapper(fd, 0, 0, POSIX_FADV_NORMAL);
    if (result == 0) {
        printf("设置POSIX_FADV_NORMAL成功\n");
    } else {
        printf("设置POSIX_FADV_NORMAL失败: %s\n", strerror(errno));
    }
    
    // POSIX_FADV_SEQUENTIAL - 顺序访问
    result = fadvise64_wrapper(fd, 0, 1024*1024, POSIX_FADV_SEQUENTIAL);
    if (result == 0) {
        printf("设置POSIX_FADV_SEQUENTIAL成功\n");
        printf("提示内核将进行顺序访问,优化预读策略\n");
    }
    
    // POSIX_FADV_RANDOM - 随机访问
    result = fadvise64_wrapper(fd, 0, 1024*1024, POSIX_FADV_RANDOM);
    if (result == 0) {
        printf("设置POSIX_FADV_RANDOM成功\n");
        printf("提示内核将进行随机访问,减少预读\n");
    }
    
    // POSIX_FADV_WILLNEED - 数据即将被访问
    result = fadvise64_wrapper(fd, 0, 64*1024, POSIX_FADV_WILLNEED);
    if (result == 0) {
        printf("设置POSIX_FADV_WILLNEED成功\n");
        printf("提示内核预读前64KB数据\n");
    }
    
    // POSIX_FADV_DONTNEED - 数据不再需要
    result = fadvise64_wrapper(fd, 0, 64*1024, POSIX_FADV_DONTNEED);
    if (result == 0) {
        printf("设置POSIX_FADV_DONTNEED成功\n");
        printf("提示内核可以丢弃前64KB数据的缓存\n");
    }
    
    // POSIX_FADV_NOREUSE - 数据只访问一次
    result = fadvise64_wrapper(fd, 64*1024, 64*1024, POSIX_FADV_NOREUSE);
    if (result == 0) {
        printf("设置POSIX_FADV_NOREUSE成功\n");
        printf("提示内核64KB-128KB范围的数据只访问一次\n");
    }
    
    // 示例3: 错误处理演示
    printf("\n示例3: 错误处理演示\n");
    
    // 使用无效的文件描述符
    result = fadvise64_wrapper(999, 0, 1024, POSIX_FADV_NORMAL);
    if (result == -1) {
        if (errno == EBADF) {
            printf("无效文件描述符错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 使用无效的建议类型
    result = fadvise64_wrapper(fd, 0, 1024, 999);
    if (result == -1) {
        if (errno == EINVAL) {
            printf("无效建议类型错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 使用负的偏移量
    result = fadvise64_wrapper(fd, -1024, 1024, POSIX_FADV_NORMAL);
    if (result == -1) {
        printf("负偏移量处理: %s\n", strerror(errno));
    }
    
    // 示例4: 实际使用场景演示
    printf("\n示例4: 实际使用场景演示\n");
    
    // 场景1: 大文件顺序读取
    printf("场景1: 大文件顺序读取优化\n");
    printf("处理大日志文件的代码示例:\n");
    printf("int process_large_log(const char* filename) {\n");
    printf("    int fd = open(filename, O_RDONLY);\n");
    printf("    if (fd == -1) return -1;\n");
    printf("    \n");
    printf("    // 提示内核将顺序访问整个文件\n");
    printf("    posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);\n");
    printf("    \n");
    printf("    // 读取处理文件...\n");
    printf("    char buffer[8192];\n");
    printf("    ssize_t bytes;\n");
    printf("    while ((bytes = read(fd, buffer, sizeof(buffer))) > 0) {\n");
    printf("        // 处理数据\n");
    printf("    }\n");
    printf("    \n");
    printf("    close(fd);\n");
    printf("    return 0;\n");
    printf("}\n\n");
    
    // 场景2: 随机访问数据库文件
    printf("场景2: 随机访问数据库文件\n");
    printf("数据库文件访问优化:\n");
    printf("int access_database_file(const char* filename) {\n");
    printf("    int fd = open(filename, O_RDWR);\n");
    printf("    if (fd == -1) return -1;\n");
    printf("    \n");
    printf("    // 提示内核将随机访问文件\n");
    printf("    posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM);\n");
    printf("    \n");
    printf("    // 根据需要预读特定区域\n");
    printf("    posix_fadvise(fd, index_offset, index_size, POSIX_FADV_WILLNEED);\n");
    printf("    \n");
    printf("    // 访问完成后释放不需要的缓存\n");
    printf("    posix_fadvise(fd, old_data_offset, old_data_size, POSIX_FADV_DONTNEED);\n");
    printf("    \n");
    printf("    close(fd);\n");
    printf("    return 0;\n");
    printf("}\n\n");
    
    // 示例5: 不同建议类型的效果说明
    printf("示例5: 不同建议类型的效果说明\n");
    
    printf("POSIX_FADV_NORMAL:\n");
    printf("  - 默认访问模式\n");
    printf("  - 使用系统默认的预读和缓存策略\n");
    printf("  - 适用于一般情况\n\n");
    
    printf("POSIX_FADV_SEQUENTIAL:\n");
    printf("  - 优化顺序访问\n");
    printf("  - 增加预读量\n");
    printf("  - 适用于大文件顺序读取\n");
    printf("  - 提高顺序读取性能\n\n");
    
    printf("POSIX_FADV_RANDOM:\n");
    printf("  - 优化随机访问\n");
    printf("  - 减少或禁用预读\n");
    printf("  - 适用于数据库、索引文件\n");
    printf("  - 减少不必要的内存占用\n\n");
    
    printf("POSIX_FADV_NOREUSE:\n");
    printf("  - 数据只访问一次\n");
    printf("  - 访问后尽快释放缓存\n");
    printf("  - 适用于一次性处理的大文件\n");
    printf("  - 节省内存资源\n\n");
    
    printf("POSIX_FADV_WILLNEED:\n");
    printf("  - 数据即将被访问\n");
    printf("  - 提前预读数据到缓存\n");
    printf("  - 适用于已知访问模式的场景\n");
    printf("  - 减少实际访问时的等待\n\n");
    
    printf("POSIX_FADV_DONTNEED:\n");
    printf("  - 数据不再需要\n");
    printf("  - 尽快释放缓存空间\n");
    printf("  - 适用于处理完成的数据\n");
    printf("  - 释放系统资源\n\n");
    
    // 示例6: 性能测试演示
    printf("示例6: 性能影响演示\n");
    
    printf("fadvise64对性能的影响:\n");
    printf("1. 正确使用可显著提高I/O性能\n");
    printf("2. 错误使用可能导致性能下降\n");
    printf("3. 效果因文件系统和硬件而异\n");
    printf("4. 大文件效果更明显\n");
    printf("5. 需要根据实际访问模式选择\n\n");
    
    // 示例7: 实际应用建议
    printf("示例7: 实际应用建议\n");
    
    printf("使用fadvise64的最佳实践:\n");
    printf("1. 在文件打开后尽早设置建议\n");
    printf("2. 根据实际访问模式选择合适的建议\n");
    printf("3. 对于大文件效果更明显\n");
    printf("4. 不要过度使用,避免增加系统负担\n");
    printf("5. 在长时间运行的应用中适时调整\n");
    printf("6. 测试不同建议对性能的影响\n\n");
    
    printf("常见应用场景:\n");
    printf("- 大文件处理和分析\n");
    printf("- 数据库系统\n");
    printf("- 日志处理系统\n");
    printf("- 备份和归档工具\n");
    printf("- 媒体播放器\n");
    printf("- 科学计算应用\n\n");
    
    // 示例8: 与相关函数的对比
    printf("示例8: 与相关函数的对比\n");
    
    printf("fadvise64 vs madvise:\n");
    printf("fadvise64:\n");
    printf("  - 针对文件I/O\n");
    printf("  - 影响文件缓存策略\n");
    printf("  - 在文件描述符上操作\n\n");
    
    printf("madvise:\n");
    printf("  - 针对内存映射\n");
    printf("  - 影响内存管理策略\n");
    printf("  - 在内存地址上操作\n\n");
    
    printf("fadvise64 vs readahead:\n");
    printf("fadvise64:\n");
    printf("  - 更通用的建议机制\n");
    printf("  - 支持多种访问模式\n");
    printf("  - 可以指定文件区域\n\n");
    
    printf("readahead:\n");
    printf("  - 专门用于预读\n");
    printf("  - 立即执行预读操作\n");
    printf("  - 较为直接但不够灵活\n\n");
    
    // 清理资源
    close(fd);
    unlink("test_fadvise64.dat");
    
    printf("总结:\n");
    printf("fadvise64是Linux提供的文件访问优化机制\n");
    printf("通过向内核提供访问建议来优化性能\n");
    printf("支持多种访问模式的优化\n");
    printf("是处理大文件和特定访问模式的重要工具\n");
    printf("需要根据实际应用场景合理使用\n");
    
    return 0;
}

发表在 linux文章 | 标签为 | 留下评论

signalfd系统调用及示例

好的,我们来深入学习 signalfd 和 signalfd4 系统调用

signalfd

1. 函数介绍

在传统的 Linux 信号处理中,我们使用 sigaction 来设置信号处理函数。当信号到达时,内核会中断程序的正常执行流程,转而去执行我们注册的处理函数。这是一种异步的处理方式。

但是,有时候我们希望用一种同步基于文件描述符 (File Descriptor) 的方式来处理信号。这样做的好处是:

  1. 统一 I/O 模型:可以将信号处理集成到 selectpollepoll 等 I/O 多路复用机制中。程序可以像等待文件描述符就绪一样等待信号。
  2. 避免信号处理函数的复杂性:信号处理函数有诸多限制(只能调用异步信号安全函数),并且容易引入竞态条件。使用 signalfd 可以在程序的主循环中处理信号,避免这些问题。
  3. 获取更详细的信号信息:可以从文件描述符中读取到完整的 signalfd_siginfo 结构体,包含信号的所有信息。

signalfd (Signal File Descriptor) 系统调用就是为此而设计的。它创建一个特殊的文件描述符,该描述符用于接收由你指定的信号集中的信号。当这些信号中的任何一个到来时,这个文件描述符就会变为“可读”状态。你可以用 read() 从这个文件描述符中读取信号信息。

signalfd64 是 signalfd 的一个更新的、64 位兼容的版本。在现代 glibc 和内核中,用户空间程序通常调用的是 signalfd,而它在底层可能会根据系统架构和内核版本自动选择使用 signalfd 或 signalfd64。对于我们编程来说,直接使用 signalfd 即可。

简单来说,signalfd 就是把信号“变成”了可以像文件一样读取的数据流,让你可以用处理文件 I/O 的方式来处理信号。

2. 函数原型

#include <sys/signalfd.h> // 包含系统调用声明和相关结构体

// glibc 提供的标准接口
int signalfd(int fd, const sigset_t *mask, int flags);

注意:底层系统调用可能有 signalfd 和 signalfd64 之分,但 glibc 会为我们处理好兼容性问题。

3. 功能

创建或修改一个信号文件描述符,该描述符可以用来接收由 mask 参数指定的信号集中的信号。

4. 参数

  • fd:
    • int 类型。
    • 如果是 -1,则表示创建一个新的 signalfd
    • 如果是一个已存在的 signalfd 的文件描述符,则表示修改该 signalfd 的信号掩码。
  • mask:
    • const sigset_t * 类型。
    • 一个指向信号集的指针。这个信号集定义了你希望通过这个 signalfd 接收的信号。在创建或修改 signalfd 之前,你必须先使用 sigprocmask() 将这些信号阻塞掉。这是 signalfd 能正常工作的前提。
  • flags:
    • int 类型。
    • 用于修改 signalfd 行为的标志位。常用的标志有:
      • SFD_CLOEXEC: 在执行 exec() 系列函数时自动关闭该文件描述符。
      • SFD_NONBLOCK: 使 read() 操作变为非阻塞模式。如果当前没有信号可读,read() 会立即返回 -1 并设置 errno 为 EAGAIN 或 EWOULDBLOCK

5. 返回值

  • 成功: 返回一个有效的信号文件描述符(一个非负整数)。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

  • EINVALflags 参数包含无效标志,或者 fd 是一个已存在的 signalfd 但 mask 为 NULL
  • EMFILE: 进程已打开的文件描述符数量达到上限。
  • ENFILE: 系统已打开的文件描述符数量达到上限。
  • ENOMEM: 内核内存不足。
  • EBADFfd 参数不是 -1,也不是一个有效的 signalfd 文件描述符。

7. 读取信号信息

一旦 signalfd 创建成功并有信号到达,你就可以使用 read() 系统调用从该文件描述符中读取信号信息。

struct signalfd_siginfo si;
ssize_t s = read(sfd, &si, sizeof(si));
if (s != sizeof(si)) {
    // Handle error
}
// Now you can access signal information in 'si'
printf("Got signal %d from PID %d\n", si.ssi_signo, si.ssi_pid);

struct signalfd_siginfo 结构体包含了丰富的信号信息,例如:

  • ssi_signo: 信号编号。
  • ssi_errno: 伴随信号的错误码。
  • ssi_code: 信号产生的原因。
  • ssi_pid: 发送信号的进程 ID (如果适用)。
  • ssi_uid: 发送信号的用户 ID (如果适用)。
  • ssi_fd: 与信号相关的文件描述符 (如果适用,如 SIGIO)。
  • ssi_band: 与 SIGIO/SIGURG 相关的带外数据。
  • ssi_overrun: 实时信号队列溢出的数量。
  • ssi_trapno: 导致信号产生的陷阱号。
  • ssi_status: 退出状态或信号 (对于 SIGCHLD)。
  • ssi_int / ssi_ptr: 通过 sigqueue() 发送的伴随数据。
  • ssi_utime / ssi_stime: 用户和系统 CPU 时间。
  • ssi_addr: 导致信号产生的内存地址。

8. 相似函数或关联函数

  • sigaction: 传统的信号处理方式,设置信号处理函数。
  • sigprocmask: 用于设置/查询信号屏蔽字。在使用 signalfd 之前必须先用它来阻塞要监听的信号。
  • read: 用于从 signalfd 中读取信号信息。
  • pollselectepoll_wait: I/O 多路复用函数,可以监听 signalfd 文件描述符的可读事件。
  • close: 关闭 signalfd 文件描述符。

9. 示例代码

下面的示例演示了如何使用 signalfd 来同步处理信号,并将其集成到 poll 系统调用中。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>     // 信号处理相关
#include <sys/signalfd.h> // signalfd 相关
#include <poll.h>       // poll 相关
#include <string.h>
#include <errno.h>
#include <sys/wait.h>   // wait 相关

int main() {
    sigset_t mask;
    int sfd, j;
    struct pollfd fds[2]; // 监听 signalfd 和 标准输入
    struct signalfd_siginfo si;
    ssize_t s;
    char buf[1024]; // 用于读取标准输入

    printf("--- Demonstrating signalfd ---\n");
    printf("PID: %d\n", getpid());
    printf("Try sending signals:\n");
    printf("  kill -USR1 %d\n", getpid());
    printf("  kill -USR2 %d\n", getpid());
    printf("  Or press Ctrl+C (SIGINT) or Ctrl+\\ (SIGQUIT)\n");
    printf("  Type 'exit' and press Enter to quit.\n");
    printf("  This program uses poll() to wait for signals or input.\n");

    // 1. 创建要监听的信号集
    sigemptyset(&mask);
    sigaddset(&mask, SIGUSR1);
    sigaddset(&mask, SIGUSR2);
    sigaddset(&mask, SIGINT);  // Ctrl+C
    sigaddset(&mask, SIGQUIT); // Ctrl+\

    // 2. 阻塞这些信号
    // 这是使用 signalfd 的关键前提!
    // 必须先阻塞信号,这样信号才不会被默认处理或由 sigaction 处理
    // 而是排队等待 signalfd 读取
    if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
        perror("sigprocmask");
        exit(EXIT_FAILURE);
    }
    printf("Blocked signals: SIGUSR1, SIGUSR2, SIGINT, SIGQUIT\n");

    // 3. 创建 signalfd
    // -1 表示创建新的 signalfd
    // &mask 是要监听的信号集
    // SFD_CLOEXEC 在 exec 时关闭 fd,SFD_NONBLOCK 设置为非阻塞
    sfd = signalfd(-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK);
    if (sfd == -1) {
        perror("signalfd");
        exit(EXIT_FAILURE);
    }
    printf("Created signalfd: %d\n", sfd);

    // 4. 设置 poll 的文件描述符数组
    // 监听 signalfd
    fds[0].fd = sfd;
    fds[0].events = POLLIN; // 等待可读事件
    // 监听标准输入 (stdin)
    fds[1].fd = STDIN_FILENO;
    fds[1].events = POLLIN; // 等待可读事件

    // 5. 主循环:使用 poll 等待事件
    while (1) {
        // poll 会阻塞,直到 fds 数组中的任何一个 fd 准备好
        // -1 表示无限期等待
        int poll_num = poll(fds, 2, -1);
        if (poll_num == -1) {
            if (errno == EINTR) {
                // poll 被信号中断 (不太可能发生,因为我们用的是 signalfd)
                continue;
            } else {
                perror("poll");
                break;
            }
        }

        if (poll_num > 0) {
            // 检查 signalfd 是否有数据可读
            if (fds[0].revents & POLLIN) {
                // 6. 从 signalfd 读取信号信息
                s = read(sfd, &si, sizeof(si));
                if (s != sizeof(si)) {
                    if (errno == EAGAIN || errno == EWOULDBLOCK) {
                        // 非阻塞模式下没有数据可读 (不太可能,因为 poll 说了有)
                        continue;
                    } else {
                        perror("read signalfd");
                        break;
                    }
                }

                // 7. 处理接收到的信号
                printf("\nReceived signal: %d", si.ssi_signo);
                switch(si.ssi_signo) {
                    case SIGUSR1:
                        printf(" (SIGUSR1)");
                        break;
                    case SIGUSR2:
                        printf(" (SIGUSR2)");
                        break;
                    case SIGINT:
                        printf(" (SIGINT - Ctrl+C)");
                        break;
                    case SIGQUIT:
                        printf(" (SIGQUIT - Ctrl+\\)");
                        break;
                }
                printf("\n  Sender PID: %d\n", si.ssi_pid);
                printf("  Sender UID: %d\n", si.ssi_uid);

                // 如果收到 SIGINT 或 SIGQUIT,则退出
                if (si.ssi_signo == SIGINT || si.ssi_signo == SIGQUIT) {
                    printf("Exiting due to signal.\n");
                    break;
                }
            }

            // 检查标准输入是否有数据可读
            if (fds[1].revents & POLLIN) {
                ssize_t nread = read(STDIN_FILENO, buf, sizeof(buf) - 1);
                if (nread > 0) {
                    buf[nread] = '\0';
                    printf("Read from stdin: %s", buf); // buf 可能已包含 \n
                    // 如果用户输入 'exit',则退出
                    if (strncmp(buf, "exit\n", 5) == 0) {
                        printf("Exiting due to 'exit' command.\n");
                        break;
                    }
                } else if (nread == 0) {
                    // EOF on stdin (e.g., Ctrl+D)
                    printf("EOF on stdin. Exiting.\n");
                    break;
                } else {
                    if (errno != EAGAIN && errno != EWOULDBLOCK) {
                        perror("read stdin");
                        break;
                    }
                }
            }

            // 检查是否有错误或挂起事件
            if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
                fprintf(stderr, "Error on signalfd.\n");
                break;
            }
            if (fds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) {
                 fprintf(stderr, "Error on stdin.\n");
                 break;
            }
        }
    }

    // 8. 清理资源
    printf("Closing signalfd...\n");
    close(sfd);
    printf("Program finished.\n");

    return 0;
}

10. 编译和运行

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

# 运行程序
./signalfd_example
# 程序会输出 PID,并提示你可以发送哪些信号
# 在另一个终端尝试发送信号,或在程序运行的终端按 Ctrl+C 等

11. 预期输出

--- Demonstrating signalfd ---
PID: 12345
Blocked signals: SIGUSR1, SIGUSR2, SIGINT, SIGQUIT
Created signalfd: 3
Try sending signals:
  kill -USR1 12345
  kill -USR2 12345
  Or press Ctrl+C (SIGINT) or Ctrl+\ (SIGQUIT)
  Type 'exit' and press Enter to quit.
  This program uses poll() to wait for signals or input.

Read from stdin: hello
Read from stdin: world

Received signal: 10 (SIGUSR1)
  Sender PID: 12346
  Sender UID: 1000

Received signal: 12 (SIGUSR2)
  Sender PID: 12346
  Sender UID: 1000

Received signal: 2 (SIGINT - Ctrl+C)
  Sender PID: 0
  Sender UID: 0
Exiting due to signal.
Closing signalfd...
Program finished.

12. 总结

signalfd 提供了一种现代化、同步化的方式来处理信号,特别适合于事件驱动的程序(如服务器)。通过将其与 pollselectepoll 等 I/O 多路复用技术结合,可以构建出高效且结构清晰的程序。记住两个关键点:

  1. 先阻塞,再创建:必须先用 sigprocmask 阻塞要监听的信号。
  2. 像文件一样读取:使用 read() 从 signalfd 中读取 struct signalfd_siginfo 结构体来获取信号信息。

这种方式避免了传统信号处理函数的复杂性和潜在风险,是编写健壮 Linux 应用程序的有力工具。好的,我们来深入学习 signalfd 和 signalfd4 系统调用,从 Linux 编程小白的角度出发。

发表在 linux文章 | 标签为 | 留下评论

socketpair系统调用及示例

好的,我们继续按照您的列表顺序,介绍下一个函数socketpair 是一个 Linux 系统调用,用于创建一对相互连接的匿名套接字。这对套接字就像一个双向的管道(bidirectional pipe),允许两个进程(通常是具有亲缘关系的进程,如父子进程或线程)通过套接字语义进行双向(全双工)通信。

1. 函数介绍

socketpair 是一个 Linux 系统调用,用于创建一对相互连接的匿名套接字。这对套接字就像一个双向的管道(bidirectional pipe),允许两个进程(通常是具有亲缘关系的进程,如父子进程或线程)通过套接字语义进行双向(全双工)通信。

你可以把它想象成一对连在一起的对讲机

  • 你和朋友各拿一个对讲机(sv[0] 和 sv[1])。
  • 你对你的对讲机说话,朋友能从他的对讲机里听到。
  • 朋友对他的对讲机说话,你能从你的对讲机里听到。
  • 这两个对讲机是预先配对好的,它们之间有直接的通信链路。

与 pipe 创建的单向管道不同,socketpair 创建的是双向的。虽然 socketpair 创建的是套接字,但它们是匿名的,没有关联网络地址(如 IP 和端口),只能用于本地进程间通信。


2. 函数原型

#include <sys/socket.h> // 必需

int socketpair(int domain, int type, int protocol, int sv[2]);

3. 功能

  • 创建套接字对: 请求内核创建两个未命名(匿名)的、相互连接的套接字。
  • 返回文件描述符: 将这两个套接字的文件描述符通过 sv 数组返回给调用者。
    • sv[0] 是第一个套接字的文件描述符。
    • sv[1] 是第二个套接字的文件描述符。
  • 建立连接: 这两个套接字在创建后就处于已连接状态,数据可以从 sv[0] 写入并从 sv[1] 读出,反之亦然。

4. 参数

  • int domain: 指定套接字的通信域。
    • AF_UNIX (或 AF_LOCAL): Unix 域套接字。这是 socketpair 最常用且在 POSIX 标准中唯一要求支持的域。它用于同一台主机上的进程间通信。
    • AF_INET: 在某些系统(如 Linux)上也支持,但这不是标准行为,且不常用。
  • int type: 指定套接字的类型。
    • SOCK_STREAM流式套接字。提供有序可靠双向的字节流服务。数据传输没有边界(像管道一样)。
    • SOCK_DGRAM数据报套接字。提供无连接不可靠有边界的数据报服务。每个 send/write 调用对应一个独立的消息单元。
    • (在 Linux 上,SOCK_SEQPACKET 也可能被支持,提供有序、可靠、有边界的连接)
  • int protocol: 指定具体的协议。
    • 通常设置为 0,表示使用给定 domain 和 type 的默认协议
  • int sv[2]: 这是一个包含两个整数的数组,用于接收 socketpair 调用返回的两个相互连接的套接字文件描述符
    • sv[0]: 第一个套接字的文件描述符。
    • sv[1]: 第二个套接字的文件描述符。

5. 返回值

  • 成功时: 返回 0。同时,sv[0] 和 sv[1] 被填充为有效的文件描述符。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EAFNOSUPPORT 不支持的地址族,EMFILE 进程打开的文件描述符已达上限,ENFILE 系统打开的文件总数已达上限,EOPNOTSUPP 指定的类型或协议在此域中不受支持,EPROTONOSUPPORT 不支持的协议等)。

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

  • pipe: 创建一个单向的匿名管道。socketpair 可以看作是 pipe 的双向版本。
  • socket / bind / connect: 用于创建和连接命名套接字(如网络套接字)。socketpair 创建的是匿名的、预先连接的套接字。
  • fork: 经常与 socketpair 结合使用,父进程和子进程各自保留套接字对的一端,用于彼此通信。
  • read / write / send / recv: 用于对 socketpair 返回的文件描述符进行数据传输。

7. 示例代码

示例 1:父子进程通过 socketpair 通信

这个经典的例子演示了如何使用 socketpair 在父进程和子进程之间建立双向通信通道。

// socketpair_fork.c
// 注意:为了编译此示例,需要链接 pthread 库: gcc -o socketpair_fork socketpair_fork.c
#include <sys/socket.h> // socketpair
#include <sys/wait.h>   // wait
#include <unistd.h>     // fork, read, write, close
#include <stdio.h>      // perror, printf
#include <stdlib.h>     // exit
#include <string.h>     // strlen

#define BUFFER_SIZE 256

int main() {
    int sv[2];          // 用于存储 socketpair 返回的两个文件描述符
    pid_t pid;
    char buf[BUFFER_SIZE];
    ssize_t bytes_read;

    // 1. 创建一对相互连接的 Unix 流式套接字
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
        perror("socketpair failed");
        exit(EXIT_FAILURE);
    }

    printf("Socket pair created: sv[0] = %d, sv[1] = %d\n", sv[0], sv[1]);

    // 2. 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork failed");
        // 创建子进程失败,需要关闭已创建的套接字
        close(sv[0]);
        close(sv[1]);
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        // --- 子进程 ---
        // 关闭不需要的 sv[0]
        close(sv[0]);

        // 子进程通过 sv[1] 发送消息给父进程
        const char *msg_to_parent = "Hello from child process!";
        printf("Child: Sending message to parent...\n");
        if (write(sv[1], msg_to_parent, strlen(msg_to_parent)) != (ssize_t)strlen(msg_to_parent)) {
            perror("Child: write to parent failed");
            close(sv[1]);
            _exit(EXIT_FAILURE);
        }
        printf("Child: Message sent.\n");

        // 子进程通过 sv[1] 接收来自父进程的消息
        printf("Child: Waiting for message from parent...\n");
        bytes_read = read(sv[1], buf, BUFFER_SIZE - 1);
        if (bytes_read > 0) {
            buf[bytes_read] = '\0'; // 确保字符串结束
            printf("Child: Received from parent: %s\n", buf);
        } else if (bytes_read == 0) {
            printf("Child: Parent closed the connection.\n");
        } else {
            perror("Child: read from parent failed");
        }

        // 子进程完成通信,关闭其套接字
        close(sv[1]);
        printf("Child: Exiting.\n");
        _exit(EXIT_SUCCESS);

    } else {
        // --- 父进程 ---
        // 关闭不需要的 sv[1]
        close(sv[1]);

        // 父进程通过 sv[0] 接收来自子进程的消息
        printf("Parent: Waiting for message from child...\n");
        bytes_read = read(sv[0], buf, BUFFER_SIZE - 1);
        if (bytes_read > 0) {
            buf[bytes_read] = '\0';
            printf("Parent: Received from child: %s\n", buf);
        } else if (bytes_read == 0) {
            printf("Parent: Child closed the connection.\n");
        } else {
            perror("Parent: read from child failed");
        }

        // 父进程通过 sv[0] 发送消息给子进程
        const char *msg_to_child = "Hello from parent process!";
        printf("Parent: Sending message to child...\n");
        if (write(sv[0], msg_to_child, strlen(msg_to_child)) != (ssize_t)strlen(msg_to_child)) {
            perror("Parent: write to child failed");
        } else {
            printf("Parent: Message sent.\n");
        }

        // 父进程完成通信,关闭其套接字
        close(sv[0]);

        // 等待子进程结束
        int status;
        if (wait(&status) == -1) {
            perror("Parent: wait for child failed");
            exit(EXIT_FAILURE);
        }

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

        printf("Parent: Exiting.\n");
    }

    return 0;
}

代码解释:

  1. 调用 socketpair(AF_UNIX, SOCK_STREAM, 0, sv) 创建一对 Unix 域流式套接字。
  2. 调用 fork() 创建子进程。
  3. 子进程 (pid == 0):
    • 关闭不需要的 sv[0]
    • 通过 sv[1] 向父进程发送一条消息。
    • 通过 sv[1] 从父进程读取一条消息。
    • 通信完成后,关闭 sv[1] 并退出。
  4. 父进程 (pid > 0):
    • 关闭不需要的 sv[1]
    • 通过 sv[0] 从子进程读取一条消息。
    • 通过 sv[0] 向子进程发送一条消息。
    • 通信完成后,关闭 sv[0]
    • 调用 wait() 等待子进程结束并检查其退出状态。

示例 2:使用 socketpair 和 SOCK_DGRAM 进行有边界通信

这个例子演示了使用 SOCK_DGRAM 类型的 socketpair,它保留了消息边界。

// socketpair_dgram.c
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFFER_SIZE 256

int main() {
    int sv[2];
    char buf[BUFFER_SIZE];
    ssize_t bytes_read;

    // 创建一对 Unix 数据报套接字
    if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) == -1) {
        perror("socketpair SOCK_DGRAM failed");
        exit(EXIT_FAILURE);
    }

    printf("SOCK_DGRAM Socket pair created: sv[0] = %d, sv[1] = %d\n", sv[0], sv[1]);

    // 通过 sv[0] 发送两条独立的消息
    const char *msg1 = "First message";
    const char *msg2 = "Second message is longer than the first one.";

    printf("Sending two messages through sv[0]...\n");
    if (write(sv[0], msg1, strlen(msg1)) != (ssize_t)strlen(msg1)) {
        perror("write msg1 failed");
        close(sv[0]);
        close(sv[1]);
        exit(EXIT_FAILURE);
    }
    if (write(sv[0], msg2, strlen(msg2)) != (ssize_t)strlen(msg2)) {
        perror("write msg2 failed");
        close(sv[0]);
        close(sv[1]);
        exit(EXIT_FAILURE);
    }
    printf("Messages sent.\n");

    // 通过 sv[1] 逐个接收消息 (保留边界)
    printf("Receiving messages through sv[1]...\n");

    bytes_read = read(sv[1], buf, BUFFER_SIZE - 1);
    if (bytes_read > 0) {
        buf[bytes_read] = '\0';
        printf("Received message 1 (%zd bytes): %s\n", bytes_read, buf);
    }

    bytes_read = read(sv[1], buf, BUFFER_SIZE - 1);
    if (bytes_read > 0) {
        buf[bytes_read] = '\0';
        printf("Received message 2 (%zd bytes): %s\n", bytes_read, buf);
    }

    // 如果再读一次,会因为没有数据而阻塞 (或返回 0 如果另一端关闭)
    // bytes_read = read(sv[1], buf, BUFFER_SIZE - 1);
    // printf("Third read returned: %zd\n", bytes_read); // Would block

    close(sv[0]);
    close(sv[1]);

    printf("Communication finished.\n");
    return 0;
}

代码解释:

  1. 调用 socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) 创建一对 Unix 域数据报套接字。
  2. 通过 sv[0] 连续发送两条长度不同的消息。
  3. 通过 sv[1] 连续调用 read 两次,每次读取一条完整的消息,展示了 SOCK_DGRAM 的消息边界特性。
  4. 如果再调用一次 read,它会因为没有更多数据而阻塞(对于阻塞套接字)。

示例 3:线程间使用 socketpair 通信

这个例子演示了如何在线程之间使用 socketpair 进行通信。

// socketpair_threads.c
// 编译时需要链接 pthread 库: gcc -o socketpair_threads socketpair_threads.c -lpthread
#define _GNU_SOURCE // 启用 GNU 扩展,如 pthread_yield
#include <sys/socket.h>
#include <pthread.h> // pthread_create, pthread_join
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFFER_SIZE 128

// 全局变量,用于在线程间共享 socketpair 文件描述符
int sv[2];

void* thread_function(void *arg) {
    char buffer[BUFFER_SIZE];
    ssize_t bytes_read;

    printf("Thread: Running...\n");

    // 线程通过 sv[1] 发送消息
    const char *msg = "Message from thread";
    printf("Thread: Sending message...\n");
    if (write(sv[1], msg, strlen(msg)) != (ssize_t)strlen(msg)) {
        perror("Thread: write failed");
        return NULL;
    }

    // 线程通过 sv[1] 接收消息
    printf("Thread: Waiting for reply...\n");
    bytes_read = read(sv[1], buffer, BUFFER_SIZE - 1);
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("Thread: Received reply: %s\n", buffer);
    }

    printf("Thread: Exiting.\n");
    return NULL;
}

int main() {
    pthread_t thread;
    char buffer[BUFFER_SIZE];
    ssize_t bytes_read;

    // 创建 socketpair
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
        perror("socketpair failed");
        exit(EXIT_FAILURE);
    }

    printf("Main: Socket pair created.\n");

    // 创建线程
    if (pthread_create(&thread, NULL, thread_function, NULL) != 0) {
        perror("pthread_create failed");
        close(sv[0]);
        close(sv[1]);
        exit(EXIT_FAILURE);
    }

    // 主线程通过 sv[0] 接收消息
    printf("Main: Waiting for message from thread...\n");
    bytes_read = read(sv[0], buffer, BUFFER_SIZE - 1);
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("Main: Received from thread: %s\n", buffer);
    }

    // 主线程通过 sv[0] 发送回复
    const char *reply = "Reply from main";
    printf("Main: Sending reply...\n");
    if (write(sv[0], reply, strlen(reply)) != (ssize_t)strlen(reply)) {
        perror("Main: write reply failed");
    }

    // 等待线程结束
    if (pthread_join(thread, NULL) != 0) {
        perror("pthread_join failed");
    }

    close(sv[0]);
    close(sv[1]);
    printf("Main: Exiting.\n");

    return 0;
}

代码解释:

  1. 在 main 函数中创建 socketpair,并将返回的文件描述符存储在全局变量 sv 中。
  2. 创建一个新线程 thread
  3. 主线程:
    • 通过 sv[0] 从线程读取消息。
    • 通过 sv[0] 向线程发送回复。
    • 等待线程结束。
  4. 线程函数:
    • 通过 sv[1] 向主线程发送消息。
    • 通过 sv[1] 从主线程读取回复。
    • 退出。

重要提示与注意事项:

  1. 域的选择: 在可移植代码中,应始终使用 AF_UNIX 作为 domain 参数。
  2. 类型的选择:
    • SOCK_STREAM: 提供无边界的字节流,行为类似 pipe,但双向。
    • SOCK_DGRAM: 提供有边界的、独立的消息传递,行为类似 UDP,但本地。
  3. 与 pipe 的比较:
    • pipe 是单向的,socketpair 是双向的。
    • socketpair 提供了套接字的所有功能(如 sendrecv, 套接字选项等),而 pipe 只能用 read/write
  4. 关闭: 与管道和普通套接字一样,使用完毕后需要调用 close() 关闭两端的文件描述符。
  5. 错误处理: 始终检查返回值。失败可能由资源限制或不支持的参数引起。
  6. 用途: 常用于需要双向 IPC 的场景,特别是在 fork 之后或在多线程环境中。

总结:

socketpair 是一个强大的工具,用于在同一主机上创建一对相互连接的匿名套接字。它提供了比 pipe 更灵活的双向通信能力,并且具有套接字的所有特性。理解其参数和使用方法对于实现高效的本地进程间或线程间通信非常有帮助。

发表在 linux文章 | 留下评论

socket系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 socket 函数,它是网络编程的基础,用于创建一个套接字 (socket),这是进程间通过网络进行通信的端点


1. 函数介绍

socket 是一个 Linux 系统调用,用于创建一个新的**套接字 **(socket)。套接字是一种抽象的概念,它是网络通信的基础端点。你可以把套接字想象成电话的听筒:

  • 你需要先有一个听筒(调用 socket 创建)。
  • 然后你可以用它来拨打电话(connect,作为客户端)或接听电话(bind + listen + accept,作为服务器)。
  • 通过这个听筒,你可以说话(send/write)和听话(recv/read)。

socket 函数本身并不执行网络连接,它只是创建一个通信的“插头”或“接口”,后续需要使用 bindlistenacceptconnectsendrecv 等函数来完成具体的网络操作。


2. 函数原型

#include <sys/socket.h> // 必需
#include <sys/types.h>  // 有时需要

int socket(int domain, int type, int protocol);

3. 功能

  • 创建套接字: 请求内核创建一个新的套接字对象。
  • 指定通信特性: 通过参数定义套接字的**通信域 **(domain)、**类型 (type) 和协议 **(protocol),从而确定套接字的行为和能力。
  • 返回文件描述符: 如果成功,返回一个与新创建的套接字关联的**文件描述符 **(file descriptor)。后续所有对该套接字的操作(如 bindconnectreadwrite)都将使用这个文件描述符。

4. 参数

  • int domain: 指定套接字的通信域,即套接字可以通信的范围。
    • AF_INET: IPv4 Internet 协议域。这是最常用的域,用于通过 IPv4 网络进行通信。
    • AF_INET6: IPv6 Internet 协议域。用于通过 IPv6 网络进行通信。
    • AF_UNIX 或 AF_LOCAL: Unix 域套接字。用于同一台机器上进程间的本地通信,不涉及网络协议栈,非常高效。
    • AF_PACKET: 用于直接访问网络接口(数据链路层)。
    • AF_NETLINK: 用于与内核进行通信。
  • int type: 指定套接字的通信语义类型
    • SOCK_STREAM流式套接字。提供面向连接可靠有序的双向数据传输。TCP 协议就是基于流式套接字的。数据像水流一样,没有边界。
    • SOCK_DGRAM数据报套接字。提供无连接不可靠(可能丢包、重复、乱序)、有边界的数据传输。UDP 协议就是基于数据报套接字的。数据以一个独立的“包裹”(数据报)形式发送。
    • SOCK_RAW原始套接字。允许直接访问底层协议(如 IP 或 ICMP)。通常需要 root 权限。
    • SOCK_SEQPACKET有序数据包套接字。提供面向连接、可靠、有边界有序的数据传输(像 TCP 一样可靠有序,但像 UDP 一样有消息边界)。
    • 修饰符 (可以按位或 | 到 type 上):
      • SOCK_NONBLOCK: 将套接字设置为非阻塞模式。等同于创建套接字后调用 fcntl(sock, F_SETFL, O_NONBLOCK)
      • SOCK_CLOEXEC: 在调用 exec() 时自动关闭该套接字。等同于创建后调用 fcntl(sock, F_SETFD, FD_CLOEXEC)。这可以防止将套接字意外传递给新执行的程序。
  • int protocol: 指定在给定域和类型下使用的具体协议
    • 在大多数情况下,对于 AF_INET 和 AF_INET6
      • 如果 type 是 SOCK_STREAM,则 protocol 通常为 IPPROTO_TCP (或 0,内核会选择默认协议 TCP)。
      • 如果 type 是 SOCK_DGRAM,则 protocol 通常为 IPPROTO_UDP (或 0,内核会选择默认协议 UDP)。
    • 通常设置为 0,表示使用给定 domain 和 type 的默认协议
    • 对于原始套接字 (SOCK_RAW),需要显式指定协议,如 IPPROTO_ICMPIPPROTO_RAW 等。

5. 返回值

  • 成功时: 返回一个非负整数,即新创建套接字的**文件描述符 **(file descriptor)。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EACCES 权限不足,EAFNOSUPPORT 不支持的地址族,EINVAL 无效参数,EMFILE 进程打开的文件描述符已达上限,ENFILE 系统打开的文件总数已达上限,ENOMEM 内存不足等)。

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

  • bind: 将套接字与一个本地地址(IP 地址和端口号)关联起来。
  • listen: 使套接字进入监听状态,准备接收来自客户端的连接请求(用于服务器)。
  • accept: 从监听套接字的连接队列中提取第一个未决连接,创建一个新的套接字用于与该客户端通信(用于服务器)。
  • connect: 主动向服务器发起连接请求(用于客户端)。
  • close: 关闭套接字文件描述符,释放相关资源。
  • read / write / send / recv: 通过套接字发送和接收数据。
  • getaddrinfo: 现代的、线程安全的地址解析函数,用于将主机名和服务名转换为套接字地址结构。

7. 示例代码

示例 1:创建不同类型的套接字

这个例子演示了如何创建几种常见的套接字。

#include <sys/socket.h> // socket
#include <stdio.h>      // perror, printf
#include <stdlib.h>     // exit

int main() {
    int sockfd;

    // 1. 创建一个 IPv4 的 TCP 流式套接字 (最常用)
    printf("Creating AF_INET, SOCK_STREAM socket...\n");
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket AF_INET SOCK_STREAM");
        // 不 exit,继续演示其他类型
    } else {
        printf("Success! Socket file descriptor: %d\n", sockfd);
        close(sockfd); // 创建后立即关闭,仅作演示
    }

    // 2. 创建一个 IPv4 的 UDP 数据报套接字
    printf("\nCreating AF_INET, SOCK_DGRAM socket...\n");
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket AF_INET SOCK_DGRAM");
    } else {
        printf("Success! Socket file descriptor: %d\n", sockfd);
        close(sockfd);
    }

    // 3. 创建一个 Unix 域流式套接字
    printf("\nCreating AF_UNIX, SOCK_STREAM socket...\n");
    sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket AF_UNIX SOCK_STREAM");
    } else {
        printf("Success! Socket file descriptor: %d\n", sockfd);
        close(sockfd);
    }

    // 4. 创建一个非阻塞的 TCP 套接字 (使用 SOCK_NONBLOCK 修饰符)
    printf("\nCreating non-blocking AF_INET, SOCK_STREAM socket...\n");
    sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
    if (sockfd == -1) {
        perror("socket AF_INET SOCK_STREAM | SOCK_NONBLOCK");
    } else {
        printf("Success! Non-blocking socket file descriptor: %d\n", sockfd);
        // 检查是否真的非阻塞 (可选)
        // int flags = fcntl(sockfd, F_GETFL, 0);
        // if (flags & O_NONBLOCK) printf("Confirmed: socket is non-blocking.\n");
        close(sockfd);
    }

    // 5. 尝试创建一个无效的套接字组合 (例如,原始套接字需要权限)
    printf("\nTrying to create a raw socket (may fail without root)...\n");
    sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (sockfd == -1) {
        perror("socket AF_INET SOCK_RAW IPPROTO_ICMP (expected to fail without root)");
    } else {
        printf("Success! Raw socket file descriptor: %d\n", sockfd);
        close(sockfd);
    }

    printf("\nSocket creation examples completed.\n");
    return 0;
}

代码解释:

  1. 演示了创建四种不同类型的套接字:
    • IPv4 TCP 流式套接字 (AF_INETSOCK_STREAM):这是网络编程中最常见的类型,用于可靠的、面向连接的通信(如 HTTP)。
    • IPv4 UDP 数据报套接字 (AF_INETSOCK_DGRAM):用于无连接的、不可靠但快速的通信(如 DNS 查询)。
    • Unix 域流式套接字 (AF_UNIXSOCK_STREAM):用于同一主机上进程间的高效通信。
    • 非阻塞 TCP 套接字 (AF_INETSOCK_STREAM | SOCK_NONBLOCK):使用 SOCK_NONBLOCK 修饰符创建,避免后续 I/O 操作阻塞。
  2. 每次调用 socket 后都检查返回值。
  3. 如果成功,打印返回的文件描述符,并立即调用 close 关闭它(因为这只是演示创建)。
  4. 最后尝试创建一个需要 root 权限的原始套接字 (SOCK_RAW),在普通用户权限下会失败。

示例 2:简单的 TCP 服务器套接字设置

这个例子演示了服务器端如何创建、绑定、监听一个 TCP 套接字。

#include <sys/socket.h> // socket, bind, listen
#include <netinet/in.h> // sockaddr_in
#include <arpa/inet.h>  // inet_addr (虽然此例未用,但常与网络编程相关)
#include <unistd.h>     // close
#include <stdio.h>      // perror, printf
#include <stdlib.h>     // exit
#include <string.h>     // memset

#define PORT 8080
#define BACKLOG 10 // 等待连接队列的最大长度

int main() {
    int server_fd;
    struct sockaddr_in address;
    int opt = 1; // 用于 setsockopt

    // 1. 创建套接字
    printf("Creating server socket...\n");
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }
    printf("Socket created successfully. File descriptor: %d\n", server_fd);

    // 2. (可选但推荐) 设置套接字选项
    // SO_REUSEADDR: 允许套接字绑定到处于 TIME_WAIT 状态的地址
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt SO_REUSEADDR failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    printf("Socket option SO_REUSEADDR set.\n");

    // 3. 配置服务器地址结构
    memset(&address, 0, sizeof(address)); // 清零结构体
    address.sin_family = AF_INET;         // IPv4
    address.sin_addr.s_addr = INADDR_ANY; // 绑定到所有本地接口 (0.0.0.0)
    address.sin_port = htons(PORT);       // 端口号,从主机字节序转换为网络字节序

    // 4. 将套接字绑定到地址和端口
    printf("Binding socket to port %d...\n", PORT);
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    printf("Socket bound successfully.\n");

    // 5. 让套接字进入监听状态
    printf("Putting socket into listening mode (backlog: %d)...\n", BACKLOG);
    if (listen(server_fd, BACKLOG) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    printf("Server is now listening for incoming connections.\n");

    printf("\nServer setup complete. Waiting for connections...\n");
    printf("(Run a client to connect, e.g., 'telnet localhost %d' or 'nc localhost %d')\n", PORT, PORT);

    // --- 服务器已准备好,可以调用 accept() 来接受连接 ---
    // 这里为了演示 socket, bind, listen,暂时不实现 accept 循环

    // (在实际服务器中,这里会有一个循环调用 accept, fork/handle, close client_sock)

    // 按 Ctrl+C 退出程序
    pause(); // 永久挂起,直到收到信号

    // 6. 关闭套接字 (在实际程序中,这会在适当的地方调用)
    close(server_fd);
    printf("Server socket closed.\n");

    return 0;
}

代码解释:

  1. 调用 socket(AF_INET, SOCK_STREAM, 0) 创建一个 IPv4 TCP 套接字。
  2. (重要) 调用 setsockopt 设置 SO_REUSEADDR 选项。这允许服务器在重启时立即绑定到同一个地址,即使之前的连接可能处于 TIME_WAIT 状态。这是一个很好的实践。
  3. 初始化 sockaddr_in 结构体 address 来指定服务器的地址和端口:
    • sin_family = AF_INET:指定 IPv4。
    • sin_addr.s_addr = INADDR_ANY:绑定到所有可用的网络接口(服务器可能有多个网卡)。如果只想绑定到特定 IP,可以使用 inet_addr("192.168.1.100") 之类的函数。
    • sin_port = htons(PORT):设置端口号。重要:使用 htons() (host to network short) 将主机字节序的端口号转换为网络字节序。网络协议要求使用大端字节序。
  4. 调用 bind(server_fd, ...) 将套接字与指定的地址和端口绑定。
  5. 调用 listen(server_fd, BACKLOG) 使套接字进入监听模式。BACKLOG 参数指定了内核为此套接字维护的未完成连接队列的最大长度。
  6. 此时,服务器已准备好接收客户端连接。后续需要在一个循环中调用 accept() 来处理连接。
  7. 为了演示,程序调用 pause() 挂起,等待用户按 Ctrl+C 退出。
  8. 程序退出前关闭服务器套接字。

编译和测试:

gcc -o tcp_server tcp_server.c
./tcp_server
# 在另一个终端:
# telnet localhost 8080
# 或者
# nc localhost 8080

示例 3:简单的 TCP 客户端套接字设置

这个例子演示了客户端如何创建套接字并连接到服务器。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> // inet_addr, inet_ntoa
#include <unistd.h>    // close, read, write
#include <stdio.h>     // perror, printf
#include <stdlib.h>    // exit
#include <string.h>    // strlen, memset

#define PORT 8080
#define SERVER_IP "127.0.0.1" // 本地回环地址

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char *hello = "Hello from client";
    char buffer[1024] = {0};

    // 1. 创建套接字
    printf("Creating client socket...\n");
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket creation error");
        exit(EXIT_FAILURE);
    }
    printf("Client socket created successfully. File descriptor: %d\n", sock);

    // 2. 配置服务器地址结构
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // 将服务器 IP 地址从文本转换为二进制
    // inet_addr 已过时,推荐使用 inet_pton
    // if (inet_addr(SERVER_IP) == INADDR_NONE) { ... handle error ... }
    if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {
        perror("inet_pton error or invalid address");
        close(sock);
        exit(EXIT_FAILURE);
    }

    // 3. 连接到服务器
    printf("Connecting to server %s:%d...\n", SERVER_IP, PORT);
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("connection failed");
        close(sock);
        exit(EXIT_FAILURE);
    }
    printf("Connected to server successfully.\n");

    // 4. 发送数据到服务器
    printf("Sending message to server: %s\n", hello);
    if (send(sock, hello, strlen(hello), 0) != (int)strlen(hello)) {
         perror("send failed");
         close(sock);
         exit(EXIT_FAILURE);
    }
    printf("Message sent.\n");

    // 5. 读取服务器的响应
    printf("Reading response from server...\n");
    int valread = read(sock, buffer, 1024);
    if (valread > 0) {
        printf("Received from server: %s\n", buffer);
    } else if (valread == 0) {
        printf("Server closed the connection.\n");
    } else {
        perror("read failed");
    }

    // 6. 关闭套接字
    close(sock);
    printf("Client socket closed.\n");

    return 0;
}

代码解释:

  1. 调用 socket(AF_INET, SOCK_STREAM, 0) 创建一个 IPv4 TCP 套接字。
  2. 初始化 sockaddr_in 结构体 serv_addr 来指定服务器的地址和端口。
    • sin_family = AF_INET
    • sin_port = htons(PORT):服务器端口。
    • sin_addr:服务器 IP 地址。使用 inet_pton() 将点分十进制字符串 "127.0.0.1" 转换为网络二进制格式。inet_addr() 是旧函数,不推荐。
  3. 调用 connect(sock, ...) 主动向服务器发起连接请求。这个调用会阻塞,直到连接建立或失败。
  4. 连接成功后,使用 send() (或 write()) 向服务器发送数据。
  5. 使用 read() (或 recv()) 从服务器读取响应数据。
  6. 通信结束后,调用 close() 关闭套接字。

重要提示与注意事项:

  1. 字节序: 网络协议规定使用大端字节序(Big-Endian)。主机字节序可能是大端或小端。使用 htons (host to network short), htonl (host to network long), ntohsntohl 进行转换。
  2. 错误处理: 始终检查 socket 及后续网络函数的返回值。
  3. 资源管理: 使用完套接字后,务必调用 close() 关闭它,以释放文件描述符和内核资源。
  4. 阻塞与非阻塞: 默认情况下,套接字是阻塞的。connectreadwrite 等操作可能会无限期挂起。可以使用 SOCK_NONBLOCK 创建非阻塞套接字,或用 fcntl 修改现有套接字的标志。
  5. bind 对于客户端?: 客户端通常不需要显式调用 bind。操作系统会自动为客户端套接字分配一个临时的端口号(ephemeral port)。
  6. getaddrinfo: 对于需要处理 IPv4/IPv6 透明性或域名解析的现代程序,推荐使用 getaddrinfo() 来获取地址信息,而不是手动填充 sockaddr_in

总结:

socket 函数是网络编程的起点,它创建了通信的端点。理解其参数(域、类型、协议)对于选择正确的通信方式至关重要。它是构建客户端和服务器应用程序的基础。

发表在 linux文章 | 标签为 | 留下评论

splice系统调用及示例

splice 函数详解

1. 函数介绍

splice 是Linux系统调用,用于在两个文件描述符之间高效地移动数据,而无需将数据复制到用户空间。它是Linux特有的零拷贝I/O操作,特别适用于管道、套接字和文件之间的数据传输,能够显著提高I/O性能。

2. 函数原型

#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>

ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out,
               size_t len, unsigned int flags);

3. 功能

splice 在两个文件描述符之间直接传输数据,避免了用户空间和内核空间之间的数据拷贝。它利用内核中的管道缓冲区机制,实现了高效的零拷贝数据传输。

4. 参数

  • int fd_in: 源文件描述符
  • *loff_t off_in: 源文件偏移量指针(NULL表示使用当前文件位置)
  • int fd_out: 目标文件描述符
  • *loff_t off_out: 目标文件偏移量指针(NULL表示使用当前文件位置)
  • size_t len: 要传输的数据长度
  • unsigned int flags: 控制标志

5. 返回值

  • 成功: 返回实际传输的字节数
  • 失败: 返回-1,并设置errno

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

  • tee: 在两个管道之间复制数据
  • vmsplice: 将用户空间数据写入管道
  • sendfile: 在文件描述符之间传输数据
  • copy_file_range: 复制文件数据

7. 示例代码

示例1:基础splice使用

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

/**
 * 演示基础splice使用方法
 */
int demo_splice_basic() {
    int pipefd[2];
    int input_fd, output_fd;
    const char *input_file = "input.txt";
    const char *output_file = "output.txt";
    char test_data[] = "This is test data for splice operation.\nHello, splice!\n";
    ssize_t bytes_transferred;
    
    printf("=== 基础splice使用示例 ===\n");
    
    // 创建测试输入文件
    input_fd = open(input_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (input_fd == -1) {
        perror("创建输入文件失败");
        return -1;
    }
    
    write(input_fd, test_data, strlen(test_data));
    close(input_fd);
    printf("创建测试输入文件: %s\n", input_file);
    
    // 创建管道
    if (pipe(pipefd) == -1) {
        perror("创建管道失败");
        unlink(input_file);
        return -1;
    }
    
    printf("创建管道: 读端=%d, 写端=%d\n", pipefd[0], pipefd[1]);
    
    // 打开输入文件用于读取
    input_fd = open(input_file, O_RDONLY);
    if (input_fd == -1) {
        perror("打开输入文件失败");
        close(pipefd[0]);
        close(pipefd[1]);
        unlink(input_file);
        return -1;
    }
    
    // 打开输出文件用于写入
    output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (output_fd == -1) {
        perror("创建输出文件失败");
        close(input_fd);
        close(pipefd[0]);
        close(pipefd[1]);
        unlink(input_file);
        return -1;
    }
    
    printf("开始splice操作:\n");
    
    // 第一步:从文件读取到管道(使用SPLICE_F_MOVE标志)
    bytes_transferred = splice(input_fd, NULL, pipefd[1], NULL, 
                              strlen(test_data), SPLICE_F_MOVE);
    if (bytes_transferred == -1) {
        perror("splice读取到管道失败");
        goto cleanup;
    }
    printf("  从文件到管道传输了 %zd 字节\n", bytes_transferred);
    
    // 第二步:从管道写入到文件
    bytes_transferred = splice(pipefd[0], NULL, output_fd, NULL, 
                              bytes_transferred, SPLICE_F_MOVE);
    if (bytes_transferred == -1) {
        perror("splice从管道写入文件失败");
        goto cleanup;
    }
    printf("  从管道到文件传输了 %zd 字节\n", bytes_transferred);
    
    printf("splice操作完成\n");
    
    // 验证输出文件内容
    printf("\n验证输出文件内容:\n");
    char buffer[256];
    lseek(output_fd, 0, SEEK_SET);
    int output_read_fd = open(output_file, O_RDONLY);
    if (output_read_fd != -1) {
        ssize_t read_bytes = read(output_read_fd, buffer, sizeof(buffer) - 1);
        if (read_bytes > 0) {
            buffer[read_bytes] = '\0';
            printf("输出文件内容: %s", buffer);
        }
        close(output_read_fd);
    }
    
cleanup:
    close(input_fd);
    close(output_fd);
    close(pipefd[0]);
    close(pipefd[1]);
    unlink(input_file);
    unlink(output_file);
    
    return 0;
}

int main() {
    return demo_splice_basic();
}

示例2:管道到套接字的数据传输

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

/**
 * 服务器线程函数
 */
void* server_thread(void *arg) {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    int pipefd[2];
    char buffer[1024];
    ssize_t bytes_read, bytes_written;
    
    printf("服务器线程启动\n");
    
    // 创建TCP服务器套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("创建服务器套接字失败");
        return NULL;
    }
    
    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(8080);
    
    // 绑定套接字
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("绑定套接字失败");
        close(server_fd);
        return NULL;
    }
    
    // 监听连接
    if (listen(server_fd, 1) == -1) {
        perror("监听失败");
        close(server_fd);
        return NULL;
    }
    
    printf("服务器监听在端口 8080\n");
    
    // 接受客户端连接
    client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
    if (client_fd == -1) {
        perror("接受连接失败");
        close(server_fd);
        return NULL;
    }
    
    printf("客户端连接: %s:%d\n", 
           inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
    
    // 创建管道用于splice操作
    if (pipe(pipefd) == -1) {
        perror("创建管道失败");
        close(client_fd);
        close(server_fd);
        return NULL;
    }
    
    // 准备测试数据
    const char *test_data = "Hello from splice server!\nThis is data transferred using splice.\n";
    
    // 将数据写入管道
    bytes_written = write(pipefd[1], test_data, strlen(test_data));
    if (bytes_written == -1) {
        perror("写入管道失败");
        close(pipefd[0]);
        close(pipefd[1]);
        close(client_fd);
        close(server_fd);
        return NULL;
    }
    
    printf("写入管道 %zd 字节数据\n", bytes_written);
    
    // 使用splice将管道数据传输到套接字
    ssize_t bytes_transferred = splice(pipefd[0], NULL, client_fd, NULL, 
                                      bytes_written, SPLICE_F_MOVE | SPLICE_F_MORE);
    if (bytes_transferred == -1) {
        perror("splice传输失败");
    } else {
        printf("splice传输了 %zd 字节到客户端\n", bytes_transferred);
    }
    
    // 清理资源
    close(pipefd[0]);
    close(pipefd[1]);
    close(client_fd);
    close(server_fd);
    
    printf("服务器线程结束\n");
    return NULL;
}

/**
 * 客户端函数
 */
int client_function() {
    int client_fd;
    struct sockaddr_in server_addr;
    char buffer[1024];
    ssize_t bytes_received;
    
    printf("客户端启动\n");
    
    // 创建TCP客户端套接字
    client_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (client_fd == -1) {
        perror("创建客户端套接字失败");
        return -1;
    }
    
    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    // 连接到服务器
    printf("连接到服务器...\n");
    if (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("连接服务器失败");
        close(client_fd);
        return -1;
    }
    
    printf("连接成功\n");
    
    // 接收数据
    bytes_received = read(client_fd, buffer, sizeof(buffer) - 1);
    if (bytes_received > 0) {
        buffer[bytes_received] = '\0';
        printf("接收到服务器数据 (%zd 字节):\n%s", bytes_received, buffer);
    } else if (bytes_received == -1) {
        perror("接收数据失败");
    } else {
        printf("连接关闭\n");
    }
    
    close(client_fd);
    printf("客户端结束\n");
    return 0;
}

/**
 * 演示管道到套接字的splice传输
 */
int demo_pipe_to_socket_splice() {
    pthread_t server_tid;
    
    printf("=== 管道到套接字splice传输演示 ===\n");
    
    // 创建服务器线程
    if (pthread_create(&server_tid, NULL, server_thread, NULL) != 0) {
        perror("创建服务器线程失败");
        return -1;
    }
    
    // 等待服务器启动
    sleep(1);
    
    // 运行客户端
    client_function();
    
    // 等待服务器线程结束
    pthread_join(server_tid, NULL);
    
    return 0;
}

int main() {
    return demo_pipe_to_socket_splice();
}

示例3:文件到文件的零拷贝传输

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

/**
 * 创建大文件用于测试
 */
int create_large_test_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(1024 * 1024);  // 1MB缓冲区
    if (!buffer) {
        perror("分配缓冲区失败");
        close(fd);
        return -1;
    }
    
    // 填充测试数据
    for (int i = 0; i < 1024 * 1024; i++) {
        buffer[i] = 'A' + (i % 26);
    }
    
    // 写入数据
    size_t written = 0;
    while (written < size) {
        size_t to_write = (size - written < 1024 * 1024) ? size - written : 1024 * 1024;
        ssize_t result = write(fd, buffer, to_write);
        if (result == -1) {
            perror("写入文件失败");
            free(buffer);
            close(fd);
            return -1;
        }
        written += result;
    }
    
    free(buffer);
    close(fd);
    
    printf("创建了 %zu MB 的测试文件: %s\n", size / (1024 * 1024), filename);
    return 0;
}

/**
 * 使用splice进行文件传输
 */
ssize_t splice_file_transfer(int src_fd, int dst_fd, size_t total_size) {
    int pipefd[2];
    ssize_t total_transferred = 0;
    ssize_t bytes_transferred;
    const size_t chunk_size = 64 * 1024;  // 64KB chunks
    
    // 创建管道
    if (pipe(pipefd) == -1) {
        perror("创建管道失败");
        return -1;
    }
    
    printf("开始splice文件传输 (%zu 字节)...\n", total_size);
    
    while (total_transferred < (ssize_t)total_size) {
        size_t remaining = total_size - total_transferred;
        size_t to_transfer = (remaining < chunk_size) ? remaining : chunk_size;
        
        // 从源文件读取到管道
        bytes_transferred = splice(src_fd, NULL, pipefd[1], NULL, 
                                  to_transfer, SPLICE_F_MOVE | SPLICE_F_MORE);
        if (bytes_transferred == -1) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                continue;  // 重试
            }
            perror("splice读取失败");
            close(pipefd[0]);
            close(pipefd[1]);
            return -1;
        }
        
        if (bytes_transferred == 0) {
            break;  // 文件结束
        }
        
        // 从管道写入到目标文件
        ssize_t bytes_written = splice(pipefd[0], NULL, dst_fd, NULL, 
                                      bytes_transferred, SPLICE_F_MOVE);
        if (bytes_written == -1) {
            perror("splice写入失败");
            close(pipefd[0]);
            close(pipefd[1]);
            return -1;
        }
        
        total_transferred += bytes_written;
        
        if (total_transferred % (10 * 1024 * 1024) == 0) {  // 每10MB显示一次进度
            printf("已传输: %.1f MB\n", total_transferred / (1024.0 * 1024.0));
        }
    }
    
    close(pipefd[0]);
    close(pipefd[1]);
    
    printf("文件传输完成: %zd 字节\n", total_transferred);
    return total_transferred;
}

/**
 * 比较splice和传统read/write性能
 */
int compare_transfer_performance() {
    const char *src_file = "large_source.dat";
    const char *dst_file_splice = "large_dest_splice.dat";
    const char *dst_file_traditional = "large_dest_traditional.dat";
    const size_t file_size = 100 * 1024 * 1024;  // 100MB
    struct timespec start, end;
    double splice_time, traditional_time;
    
    printf("=== 文件传输性能对比 ===\n");
    
    // 创建测试文件
    if (create_large_test_file(src_file, file_size) != 0) {
        return -1;
    }
    
    // 测试splice性能
    printf("\n1. 测试splice传输性能:\n");
    clock_gettime(CLOCK_MONOTONIC, &start);
    
    int src_fd = open(src_file, O_RDONLY);
    int dst_fd = open(dst_file_splice, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    
    if (src_fd != -1 && dst_fd != -1) {
        ssize_t transferred = splice_file_transfer(src_fd, dst_fd, file_size);
        if (transferred != -1) {
            clock_gettime(CLOCK_MONOTONIC, &end);
            splice_time = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
            printf("splice传输时间: %.3f 秒\n", splice_time);
            printf("splice传输速度: %.2f MB/s\n", file_size / (1024.0 * 1024.0) / splice_time);
        }
    }
    
    if (src_fd != -1) close(src_fd);
    if (dst_fd != -1) close(dst_fd);
    
    // 测试传统read/write性能
    printf("\n2. 测试传统read/write性能:\n");
    clock_gettime(CLOCK_MONOTONIC, &start);
    
    src_fd = open(src_file, O_RDONLY);
    dst_fd = open(dst_file_traditional, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    
    if (src_fd != -1 && dst_fd != -1) {
        char *buffer = malloc(64 * 1024);
        if (buffer) {
            ssize_t bytes_read, bytes_written;
            ssize_t total_transferred = 0;
            
            while ((bytes_read = read(src_fd, buffer, 64 * 1024)) > 0) {
                bytes_written = write(dst_fd, buffer, bytes_read);
                if (bytes_written == -1) {
                    perror("写入失败");
                    break;
                }
                total_transferred += bytes_written;
            }
            
            free(buffer);
            clock_gettime(CLOCK_MONOTONIC, &end);
            traditional_time = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
            printf("传统传输时间: %.3f 秒\n", traditional_time);
            printf("传统传输速度: %.2f MB/s\n", file_size / (1024.0 * 1024.0) / traditional_time);
        }
    }
    
    if (src_fd != -1) close(src_fd);
    if (dst_fd != -1) close(dst_fd);
    
    // 显示性能对比
    printf("\n=== 性能对比结果 ===\n");
    if (splice_time > 0 && traditional_time > 0) {
        double improvement = (traditional_time - splice_time) / traditional_time * 100;
        printf("splice性能提升: %.1f%%\n", improvement);
    }
    
    // 验证文件一致性
    printf("\n验证文件一致性:\n");
    struct stat src_stat, splice_stat, traditional_stat;
    
    if (stat(src_file, &src_stat) == 0 && 
        stat(dst_file_splice, &splice_stat) == 0 &&
        stat(dst_file_traditional, &traditional_stat) == 0) {
        
        if (src_stat.st_size == splice_stat.st_size && 
            splice_stat.st_size == traditional_stat.st_size) {
            printf("✓ 文件大小一致\n");
        } else {
            printf("✗ 文件大小不一致\n");
        }
    }
    
    // 清理测试文件
    unlink(src_file);
    unlink(dst_file_splice);
    unlink(dst_file_traditional);
    
    return 0;
}

int main() {
    return compare_transfer_performance();
}

示例4:网络代理服务器

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

/**
 * 代理连接结构
 */
typedef struct {
    int client_fd;
    int target_fd;
    int pipe_to_target[2];
    int pipe_to_client[2];
    volatile int active;
} proxy_connection_t;

/**
 * 创建到目标服务器的连接
 */
int connect_to_target(const char *target_ip, int target_port) {
    int target_fd;
    struct sockaddr_in target_addr;
    
    target_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (target_fd == -1) {
        perror("创建目标连接失败");
        return -1;
    }
    
    memset(&target_addr, 0, sizeof(target_addr));
    target_addr.sin_family = AF_INET;
    target_addr.sin_port = htons(target_port);
    target_addr.sin_addr.s_addr = inet_addr(target_ip);
    
    if (connect(target_fd, (struct sockaddr*)&target_addr, sizeof(target_addr)) == -1) {
        perror("连接目标服务器失败");
        close(target_fd);
        return -1;
    }
    
    printf("成功连接到目标服务器 %s:%d\n", target_ip, target_port);
    return target_fd;
}

/**
 * 代理连接处理线程
 */
void* proxy_thread(void *arg) {
    proxy_connection_t *conn = (proxy_connection_t*)arg;
    ssize_t bytes_transferred;
    struct pollfd fds[2];
    int nfds = 2;
    
    printf("代理线程启动,处理客户端连接 %d\n", conn->client_fd);
    
    // 设置非阻塞模式
    int flags = fcntl(conn->client_fd, F_GETFL, 0);
    fcntl(conn->client_fd, F_SETFL, flags | O_NONBLOCK);
    flags = fcntl(conn->target_fd, F_GETFL, 0);
    fcntl(conn->target_fd, F_SETFL, flags | O_NONBLOCK);
    
    // 初始化poll结构
    fds[0].fd = conn->client_fd;
    fds[0].events = POLLIN;
    fds[1].fd = conn->target_fd;
    fds[1].events = POLLIN;
    
    // 主代理循环
    while (conn->active) {
        int activity = poll(fds, nfds, 1000);  // 1秒超时
        if (activity == -1) {
            if (errno == EINTR) continue;
            perror("poll失败");
            break;
        }
        
        if (activity == 0) {
            continue;  // 超时,继续循环
        }
        
        // 处理客户端到目标服务器的数据
        if (fds[0].revents & POLLIN) {
            bytes_transferred = splice(conn->client_fd, NULL, conn->pipe_to_target[1], NULL,
                                      64 * 1024, SPLICE_F_MOVE | SPLICE_F_MORE);
            if (bytes_transferred > 0) {
                printf("从客户端接收 %zd 字节\n", bytes_transferred);
                
                // 将数据从管道传输到目标服务器
                ssize_t sent = splice(conn->pipe_to_target[0], NULL, conn->target_fd, NULL,
                                     bytes_transferred, SPLICE_F_MOVE);
                if (sent > 0) {
                    printf("转发到目标服务器 %zd 字节\n", sent);
                } else if (sent == -1) {
                    perror("转发到目标服务器失败");
                    break;
                }
            } else if (bytes_transferred == -1) {
                if (errno != EAGAIN && errno != EWOULDBLOCK) {
                    perror("从客户端接收数据失败");
                    break;
                }
            } else if (bytes_transferred == 0) {
                printf("客户端连接关闭\n");
                break;
            }
        }
        
        // 处理目标服务器到客户端的数据
        if (fds[1].revents & POLLIN) {
            bytes_transferred = splice(conn->target_fd, NULL, conn->pipe_to_client[1], NULL,
                                      64 * 1024, SPLICE_F_MOVE | SPLICE_F_MORE);
            if (bytes_transferred > 0) {
                printf("从目标服务器接收 %zd 字节\n", bytes_transferred);
                
                // 将数据从管道传输到客户端
                ssize_t sent = splice(conn->pipe_to_client[0], NULL, conn->client_fd, NULL,
                                     bytes_transferred, SPLICE_F_MOVE);
                if (sent > 0) {
                    printf("转发到客户端 %zd 字节\n", sent);
                } else if (sent == -1) {
                    perror("转发到客户端失败");
                    break;
                }
            } else if (bytes_transferred == -1) {
                if (errno != EAGAIN && errno != EWOULDBLOCK) {
                    perror("从目标服务器接收数据失败");
                    break;
                }
            } else if (bytes_transferred == 0) {
                printf("目标服务器连接关闭\n");
                break;
            }
        }
        
        // 检查错误条件
        if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
            printf("客户端连接异常\n");
            break;
        }
        
        if (fds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) {
            printf("目标服务器连接异常\n");
            break;
        }
    }
    
    conn->active = 0;
    printf("代理线程结束\n");
    return NULL;
}

/**
 * 代理服务器主函数
 */
int demo_proxy_server() {
    int server_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    
    printf("=== 网络代理服务器演示 ===\n");
    
    // 创建代理服务器套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("创建代理服务器失败");
        return -1;
    }
    
    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(8081);
    
    // 绑定套接字
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("绑定代理服务器失败");
        close(server_fd);
        return -1;
    }
    
    // 监听连接
    if (listen(server_fd, 10) == -1) {
        perror("代理服务器监听失败");
        close(server_fd);
        return -1;
    }
    
    printf("代理服务器监听在端口 8081\n");
    printf("注意:这是一个演示程序,实际使用需要完善错误处理\n");
    
    // 由于这是一个演示程序,我们只处理一个连接
    printf("等待客户端连接...\n");
    
    int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
    if (client_fd == -1) {
        perror("接受客户端连接失败");
        close(server_fd);
        return -1;
    }
    
    printf("客户端连接: %s:%d\n", 
           inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
    
    // 连接到目标服务器(这里使用回环地址作为示例)
    int target_fd = connect_to_target("127.0.0.1", 80);  // 假设80端口有服务
    if (target_fd == -1) {
        printf("注意:无法连接到目标服务器,演示继续但不会转发数据\n");
        target_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (target_fd == -1) {
            close(client_fd);
            close(server_fd);
            return -1;
        }
    }
    
    // 创建代理连接结构
    proxy_connection_t conn;
    conn.client_fd = client_fd;
    conn.target_fd = target_fd;
    conn.active = 1;
    
    // 创建管道
    if (pipe(conn.pipe_to_target) == -1 || pipe(conn.pipe_to_client) == -1) {
        perror("创建管道失败");
        close(client_fd);
        close(target_fd);
        close(server_fd);
        return -1;
    }
    
    printf("代理连接建立完成\n");
    printf("代理服务器功能演示:\n");
    printf("  - 使用splice实现零拷贝数据传输\n");
    printf("  - 双向数据转发\n");
    printf("  - 非阻塞I/O操作\n");
    printf("  - 高效的网络代理\n");
    
    // 由于这是一个演示,我们不实际运行代理循环
    // 在实际应用中,这里会启动代理线程处理数据转发
    
    sleep(2);  // 模拟处理时间
    
    // 清理资源
    close(conn.pipe_to_target[0]);
    close(conn.pipe_to_target[1]);
    close(conn.pipe_to_client[0]);
    close(conn.pipe_to_client[1]);
    close(client_fd);
    close(target_fd);
    close(server_fd);
    
    printf("代理服务器演示完成\n");
    return 0;
}

int main() {
    return demo_proxy_server();
}

示例5:splice性能优化和最佳实践

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

/**
 * splice性能测试配置
 */
typedef struct {
    size_t buffer_size;
    size_t chunk_size;
    int use_splice;
    int use_flags;
    const char *description;
} splice_test_config_t;

/**
 * 创建测试数据文件
 */
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(1024 * 1024);
    if (!buffer) {
        perror("分配缓冲区失败");
        close(fd);
        return -1;
    }
    
    srand(time(NULL));
    for (size_t i = 0; i < size; i += 1024) {
        for (int j = 0; j < 1024 && i + j < size; j++) {
            buffer[j] = rand() % 256;
        }
        write(fd, buffer, (size - i < 1024) ? size - i : 1024);
    }
    
    free(buffer);
    close(fd);
    
    printf("创建测试文件: %s (%.1f MB)\n", filename, size / (1024.0 * 1024.0));
    return 0;
}

/**
 * 使用splice进行数据传输
 */
double test_splice_performance(const char *src_file, const char *dst_file, 
                              size_t chunk_size) {
    int src_fd, dst_fd, pipefd[2];
    struct timespec start, end;
    ssize_t bytes_transferred, total_transferred = 0;
    struct stat file_stat;
    
    // 获取文件大小
    if (stat(src_file, &file_stat) == -1) {
        perror("获取文件状态失败");
        return -1;
    }
    
    // 打开文件
    src_fd = open(src_file, O_RDONLY);
    dst_fd = open(dst_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (src_fd == -1 || dst_fd == -1) {
        perror("打开文件失败");
        if (src_fd != -1) close(src_fd);
        if (dst_fd != -1) close(dst_fd);
        return -1;
    }
    
    // 创建管道
    if (pipe(pipefd) == -1) {
        perror("创建管道失败");
        close(src_fd);
        close(dst_fd);
        return -1;
    }
    
    // 开始计时
    clock_gettime(CLOCK_MONOTONIC, &start);
    
    // 执行splice传输
    while (total_transferred < file_stat.st_size) {
        size_t remaining = file_stat.st_size - total_transferred;
        size_t to_transfer = (remaining < chunk_size) ? remaining : chunk_size;
        
        // 读取到管道
        bytes_transferred = splice(src_fd, NULL, pipefd[1], NULL, 
                                  to_transfer, SPLICE_F_MOVE | SPLICE_F_MORE);
        if (bytes_transferred <= 0) {
            if (bytes_transferred == -1) {
                perror("splice读取失败");
            }
            break;
        }
        
        // 写入到目标文件
        ssize_t bytes_written = splice(pipefd[0], NULL, dst_fd, NULL, 
                                      bytes_transferred, SPLICE_F_MOVE);
        if (bytes_written <= 0) {
            if (bytes_written == -1) {
                perror("splice写入失败");
            }
            break;
        }
        
        total_transferred += bytes_written;
    }
    
    // 结束计时
    clock_gettime(CLOCK_MONOTONIC, &end);
    
    // 清理资源
    close(pipefd[0]);
    close(pipefd[1]);
    close(src_fd);
    close(dst_fd);
    
    double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
    return elapsed;
}

/**
 * 使用传统read/write进行数据传输
 */
double test_traditional_performance(const char *src_file, const char *dst_file, 
                                   size_t buffer_size) {
    int src_fd, dst_fd;
    struct timespec start, end;
    char *buffer;
    ssize_t bytes_read, bytes_written;
    ssize_t total_transferred = 0;
    struct stat file_stat;
    
    // 获取文件大小
    if (stat(src_file, &file_stat) == -1) {
        perror("获取文件状态失败");
        return -1;
    }
    
    // 分配缓冲区
    buffer = malloc(buffer_size);
    if (!buffer) {
        perror("分配缓冲区失败");
        return -1;
    }
    
    // 打开文件
    src_fd = open(src_file, O_RDONLY);
    dst_fd = open(dst_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (src_fd == -1 || dst_fd == -1) {
        perror("打开文件失败");
        free(buffer);
        if (src_fd != -1) close(src_fd);
        if (dst_fd != -1) close(dst_fd);
        return -1;
    }
    
    // 开始计时
    clock_gettime(CLOCK_MONOTONIC, &start);
    
    // 执行传统传输
    while ((bytes_read = read(src_fd, buffer, buffer_size)) > 0) {
        bytes_written = write(dst_fd, buffer, bytes_read);
        if (bytes_written == -1) {
            perror("写入失败");
            break;
        }
        total_transferred += bytes_written;
    }
    
    // 结束计时
    clock_gettime(CLOCK_MONOTONIC, &end);
    
    // 清理资源
    free(buffer);
    close(src_fd);
    close(dst_fd);
    
    double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
    return elapsed;
}

/**
 * 演示splice性能优化和最佳实践
 */
int demo_splice_optimization() {
    const char *src_file = "performance_test_src.dat";
    const char *dst_file_splice = "performance_test_dst_splice.dat";
    const char *dst_file_traditional = "performance_test_dst_traditional.dat";
    const size_t file_size = 200 * 1024 * 1024;  // 200MB
    const size_t chunk_sizes[] = {4096, 16384, 65536, 262144, 1048576};  // 4KB到1MB
    const int num_chunk_sizes = sizeof(chunk_sizes) / sizeof(chunk_sizes[0]);
    
    printf("=== splice性能优化和最佳实践演示 ===\n");
    
    // 检查系统是否支持splice
    printf("检查splice支持:\n");
    int test_pipe[2];
    if (pipe(test_pipe) == 0) {
        printf("  ✓ 系统支持管道操作\n");
        close(test_pipe[0]);
        close(test_pipe[1]);
    } else {
        printf("  ✗ 系统不支持管道操作\n");
        return -1;
    }
    
    // 创建测试文件
    printf("\n创建测试文件...\n");
    if (create_test_data_file(src_file, file_size) != 0) {
        return -1;
    }
    
    printf("\n=== 性能测试结果 ===\n");
    printf("%-12s %-12s %-15s %-15s %-10s\n", 
           "传输方式", "块大小", "传输时间(秒)", "传输速度(MB/s)", "性能提升");
    printf("%-12s %-12s %-15s %-15s %-10s\n", 
           "--------", "--------", "------------", "------------", "--------");
    
    // 测试不同块大小的splice性能
    for (int i = 0; i < num_chunk_sizes; i++) {
        char dst_file[256];
        snprintf(dst_file, sizeof(dst_file), "splice_chunk_%zu.dat", chunk_sizes[i]);
        
        double elapsed = test_splice_performance(src_file, dst_file, chunk_sizes[i]);
        if (elapsed > 0) {
            double speed = file_size / (1024.0 * 1024.0) / elapsed;
            printf("%-12s %-12zu %-15.3f %-15.2f %-10s\n", 
                   "splice", chunk_sizes[i], elapsed, speed, "N/A");
        }
        
        unlink(dst_file);
    }
    
    // 测试传统方法性能(使用不同缓冲区大小)
    const size_t buffer_sizes[] = {4096, 16384, 65536, 262144};
    const int num_buffer_sizes = sizeof(buffer_sizes) / sizeof(buffer_sizes[0]);
    
    for (int i = 0; i < num_buffer_sizes; i++) {
        char dst_file[256];
        snprintf(dst_file, sizeof(dst_file), "traditional_buf_%zu.dat", buffer_sizes[i]);
        
        double elapsed = test_traditional_performance(src_file, dst_file, buffer_sizes[i]);
        if (elapsed > 0) {
            double speed = file_size / (1024.0 * 1024.0) / elapsed;
            printf("%-12s %-12zu %-15.3f %-15.2f %-10s\n", 
                   "传统方法", buffer_sizes[i], elapsed, speed, "N/A");
        }
        
        unlink(dst_file);
    }
    
    // 最佳实践建议
    printf("\n=== splice使用最佳实践 ===\n");
    printf("1. 块大小选择:\n");
    printf("   - 小文件: 4KB-16KB\n");
    printf("   - 大文件: 64KB-1MB\n");
    printf("   - 根据系统和硬件调整\n");
    
    printf("\n2. 标志使用:\n");
    printf("   - SPLICE_F_MOVE: 移动页面而不是复制\n");
    printf("   - SPLICE_F_MORE: 提示还有更多数据\n");
    printf("   - SPLICE_F_GIFT: 释放用户页面\n");
    
    printf("\n3. 性能优化:\n");
    printf("   - 使用合适的管道大小\n");
    printf("   - 避免频繁的小块传输\n");
    printf("   - 结合非阻塞I/O使用\n");
    printf("   - 监控系统资源使用\n");
    
    printf("\n4. 错误处理:\n");
    printf("   - 检查返回值和errno\n");
    printf("   - 处理部分传输情况\n");
    printf("   - 优雅降级到传统方法\n");
    
    // 清理测试文件
    unlink(src_file);
    unlink(dst_file_splice);
    unlink(dst_file_traditional);
    
    return 0;
}

int main() {
    return demo_splice_optimization();
}

splice 使用注意事项

系统要求:

  1. 内核版本: 需要Linux 2.6.17或更高版本
  2. 架构支持: 支持所有主流架构
  3. 编译选项: 需要定义_GNU_SOURCE

文件描述符要求:

  1. 管道支持: 至少一个文件描述符必须是管道
  2. 类型限制: 某些文件类型可能不支持splice
  3. 权限要求: 需要适当的文件访问权限

标志参数:

  1. SPLICE_F_MOVE: 尝试移动页面而不是复制
  2. SPLICE_F_NONBLOCK: 非阻塞操作
  3. SPLICE_F_MORE: 提示还有更多数据
  4. SPLICE_F_GIFT: 释放用户页面给内核

错误处理:

  1. EINVAL: 参数无效
  2. ENOMEM: 内存不足
  3. ESPIPE: 文件描述符不支持lseek
  4. EAGAIN: 非阻塞操作时资源不可用

性能考虑:

  1. 块大小: 选择合适的传输块大小
  2. 管道大小: 调整管道缓冲区大小
  3. 系统调用: 减少不必要的系统调用
  4. 内存对齐: 考虑内存对齐优化

安全考虑:

  1. 权限检查: 确保有适当的文件访问权限
  2. 资源限制: 避免消耗过多系统资源
  3. 错误恢复: 妥善处理错误情况

最佳实践:

  1. 适当使用: 在合适的场景下使用splice
  2. 性能测试: 在实际环境中测试性能
  3. 错误处理: 完善的错误处理机制
  4. 资源管理: 及时释放分配的资源

splice vs 相似函数对比

splice vs sendfile:

// splice: 需要管道作为中间缓冲
splice(fd_in, NULL, pipe_write, NULL, len, flags);
splice(pipe_read, NULL, fd_out, NULL, len, flags);

// sendfile: 直接在文件描述符间传输
sendfile(fd_out, fd_in, &offset, count);

splice vs tee:

// splice: 移动数据
splice(pipe1_read, NULL, pipe2_write, NULL, len, flags);

// tee: 复制数据(不消耗源数据)
tee(pipe1_read, pipe2_write, len, flags);

常见使用场景

1. 网络代理:

// 在代理服务器中高效转发数据
splice(client_fd, NULL, pipe_write, NULL, len, SPLICE_F_MOVE);
splice(pipe_read, NULL, target_fd, NULL, len, SPLICE_F_MOVE);

2. 文件复制:

// 高效的文件复制操作
splice(src_fd, NULL, pipe_write, NULL, len, SPLICE_F_MOVE);
splice(pipe_read, NULL, dst_fd, NULL, len, SPLICE_F_MOVE);

3. 日志处理:

// 实时日志转发和处理
splice(log_fd, NULL, pipe_write, NULL, len, SPLICE_F_MORE);
splice(pipe_read, NULL, network_fd, NULL, len, SPLICE_F_MOVE);

总结

splice 是Linux系统中强大的零拷贝I/O操作函数,提供了:

  1. 高效传输: 避免用户空间和内核空间的数据拷贝
  2. 灵活使用: 支持多种文件描述符类型
  3. 性能优化: 显著提高大文件和网络传输性能
  4. 系统集成: 与Linux内核深度集成

通过合理使用 splice,可以构建高性能的数据传输应用。在实际应用中,需要注意系统要求、错误处理和性能优化等关键问题。

发表在 linux文章 | 留下评论

statfs系统调用及示例

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

1. 函数介绍

在 Linux 系统中,文件和目录都存储在各种各样的文件系统之上,比如你系统盘常用的 ext4,或者 U 盘上的 vfat (FAT32)。每个文件系统都有自己的特性,比如总容量多大、现在用了多少、还剩多少空间、文件名最长支持多少个字符等等。

statfs (Stat File System) 系统调用的作用就是查询指定路径所在文件系统的各种统计信息和属性

你可以把它想象成一个“文件系统信息查询器”。你随便给它一个路径(比如 /home/tmp/mnt/my_usb),它就能告诉你这个路径所在的那个磁盘分区(文件系统)的详细情况。

简单来说,statfs 就是让你用程序来查看某个磁盘分区或挂载点的“健康报告”和“容量信息”。

典型应用场景

  • 磁盘空间监控:检查磁盘剩余空间,防止程序因磁盘写满而崩溃。
  • 系统信息工具:像 df 命令就是使用 statfs (或类似的 statvfs) 来显示磁盘使用情况的。
  • 文件系统类型检查:确认某个挂载点使用的是什么类型的文件系统(例如,检查 /tmp 是否是 tmpfs)。
  • 资源管理:根据可用空间决定是否执行某些操作。

2. 函数原型

#include <sys/vfs.h>    // 包含系统调用声明 (在某些系统上可能是 <sys/statfs.h>)
// #include <sys/statfs.h> // 备选包含

int statfs(const char *path, struct statfs *buf);
int fstatfs(int fd, struct statfs *buf); // fstatfs 通过已打开的文件描述符查询

3. 功能

获取指定路径 (path) 或文件描述符 (fd) 所在文件系统的统计信息,并将结果存储在 buf 指向的 struct statfs 结构体中。

4. 参数

  • path:
    • const char * 类型。
    • 指向一个以 null 结尾的字符串,表示文件系统中的任意一个路径名。函数会查询这个路径所在的文件系统的统计信息。
  • fd:
    • int 类型。
    • 一个已打开文件的有效文件描述符。fstatfs 会查询该文件描述符对应的文件所在的文件系统的统计信息。
  • buf:
    • struct statfs * 类型。
    • 一个指向 struct statfs 结构体的指针。函数调用成功后,会将查询到的文件系统信息填充到这个结构体中。

5. 返回值

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

6. 错误码 (errno)

  • EACCES: (对于 statfs) 搜索 path 中的一个或多个组件时权限不足。
  • EFAULTpath 或 buf 指向了调用进程无法访问的内存地址。
  • EIO: I/O 错误(例如,读取文件系统超级块失败)。
  • ELOOP: 解析 path 时遇到符号链接循环。
  • ENAMETOOLONGpath 太长。
  • ENOENTpath 指定的文件或目录不存在。
  • ENOMEM: 内核内存不足。
  • ENOSYS: 系统不支持 statfs
  • ENOTDIRpath 的某个前缀不是目录。
  • EOVERFLOW: 结构体中的某些值溢出。
  • EBADF: (对于 fstatfsfd 不是有效的文件描述符。

7. struct statfs 结构体

这个结构体包含了文件系统的各种信息。主要成员包括(定义可能因架构和内核版本略有不同):

struct statfs {
    __fsword_t f_type;    /* 文件系统类型 (Magic Number) */
    __fsword_t f_bsize;   /* 文件系统最优传输块大小 (Optimal transfer block size) */
    fsblkcnt_t f_blocks;  /* 文件系统中的总块数 */
    fsblkcnt_t f_bfree;   /* 文件系统中空闲的块数 */
    fsblkcnt_t f_bavail;  /* 对非特权用户可用的空闲块数 (可能小于 f_bfree) */
    fsfilcnt_t f_files;   /* 文件系统中的总 Inode 数 */
    fsfilcnt_t f_ffree;   /* 文件系统中空闲的 Inode 数 */
    fsid_t     f_fsid;    /* 文件系统 ID */
    __fsword_t f_namelen; /* 文件名最大长度 */
    __fsword_t f_frsize;  /* 片段大小 (Fragment size) */
    __fsword_t f_flags;   /* 文件系统挂载标志 */
    ...                   /* 可能还有其他字段 */
};

关键字段解释:

  • f_bsize: 这是文件系统推荐用于 I/O 操作的块大小。进行读写操作时使用这个大小通常效率最高。
  • f_blocks: 文件系统总共有多少个块。
  • f_bfree: 文件系统总共有多少个空闲块。
  • f_bavail: 对普通用户(非 root)来说,实际还可以使用的空闲块数量。有些文件系统会保留一部分空间给 root 用户,以防系统关键进程因磁盘满而无法运行。
  • f_files: 文件系统总共包含多少个 Inode(索引节点)。每个文件或目录都对应一个 Inode。
  • f_ffree: 文件系统中空闲的 Inode 数量。
  • f_type: 文件系统的类型,用一个魔数 (Magic Number) 表示。例如,EXT4_SUPER_MAGIC (0xEF53) 代表 ext4,TMPFS_MAGIC (0x01021994) 代表 tmpfs。可以通过比较这个值来判断文件系统类型。
  • f_namelen: 文件系统支持的文件名或目录名的最大长度。

8. 相似函数或关联函数

  • statvfs / fstatvfs: POSIX 标准定义的函数,功能与 statfs / fstatfs 几乎相同,但使用 struct statvfs 结构体。通常推荐使用 statvfs 以获得更好的可移植性。
  • df: 命令行工具,显示文件系统磁盘空间使用情况。它在底层调用的就是 statfs 或 statvfs
  • getmntent: 用于读取 /proc/mounts 或 /etc/mtab 文件,获取系统上所有已挂载文件系统的信息。
  • du: 命令行工具,估算文件和目录的空间使用情况。它通过遍历目录和文件来计算,而不是查询文件系统元数据。

9. 示例代码

下面的示例演示了如何使用 statfs 来查询不同路径的文件系统信息。

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

// 打印文件系统信息的辅助函数
void print_fs_info(const char* path, const struct statfs *sfs) {
    printf("Filesystem information for '%s':\n", path);
    printf("  Optimal I/O Block Size: %ld bytes\n", (long)sfs->f_bsize);
    printf("  Total Data 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]);
    printf("  Maximum Filename Length: %ld\n", (long)sfs->f_namelen);
    printf("  Fragment Size:          %ld\n", (long)sfs->f_frsize);
    printf("  Mount Flags:            0x%lx\n", (unsigned long)sfs->f_flags);

    // 计算并打印人类可读的空间大小
    // 注意:块大小和数量可能非常大,使用 double 避免溢出
    double total_bytes = (double)sfs->f_blocks * sfs->f_bsize;
    double free_bytes = (double)sfs->f_bfree * sfs->f_bsize;
    double avail_bytes = (double)sfs->f_bavail * sfs->f_bsize;

    const char *units[] = {"B", "KiB", "MiB", "GiB", "TiB"};
    int unit_index = 0;
    double size_to_print = total_bytes;
    while (size_to_print >= 1024 && unit_index < 4) {
        size_to_print /= 1024;
        unit_index++;
    }
    printf("  Total Size:             %.2f %s\n", size_to_print, units[unit_index]);

    unit_index = 0;
    size_to_print = avail_bytes;
    while (size_to_print >= 1024 && unit_index < 4) {
        size_to_print /= 1024;
        unit_index++;
    }
    printf("  Available Size:         %.2f %s\n", size_to_print, units[unit_index]);

    // 识别文件系统类型 (仅列举几个常见类型)
    switch (sfs->f_type) {
        case 0xEF53: // EXT2/3/4
            printf("  Filesystem Type:        ext2/ext3/ext4\n");
            break;
        case 0x6969: // NFS
            printf("  Filesystem Type:        NFS\n");
            break;
        case 0x517B: // SMB/CIFS
            printf("  Filesystem Type:        SMB/CIFS\n");
            break;
        case 0x52654973: // ReiserFS
            printf("  Filesystem Type:        ReiserFS\n");
            break;
        case 0x5346544E: // NTFS
            printf("  Filesystem Type:        NTFS\n");
            break;
        case 0x4d44: // FAT
            printf("  Filesystem Type:        FAT\n");
            break;
        case 0x01021994: // TMPFS
            printf("  Filesystem Type:        tmpfs\n");
            break;
        case 0x9123683E: // Btrfs
            printf("  Filesystem Type:        Btrfs\n");
            break;
        default:
            printf("  Filesystem Type:        Unknown (Magic: 0x%lx)\n", (unsigned long)sfs->f_type);
            break;
    }
    printf("\n");
}

int main(int argc, char *argv[]) {
    struct statfs sfs;
    int fd;

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

    // 1. 如果没有提供命令行参数,则查询几个常见的路径
    if (argc == 1) {
        const char *default_paths[] = {"/", "/tmp", "/home", "/proc", "/sys", "/dev"};
        int num_paths = sizeof(default_paths) / sizeof(default_paths[0]);

        for (int i = 0; i < num_paths; i++) {
            if (statfs(default_paths[i], &sfs) == 0) {
                print_fs_info(default_paths[i], &sfs);
            } else {
                // 忽略某些可能不存在或无法访问的路径的错误
                if (errno != ENOENT && errno != EACCES) {
                     printf("Failed to statfs '%s': %s\n", default_paths[i], strerror(errno));
                     printf("\n");
                }
            }
        }
    } else {
        // 2. 如果提供了命令行参数,则查询指定的路径
        for (int i = 1; i < argc; i++) {
            const char *path = argv[i];
            printf("Querying path: '%s'\n", path);

            // 使用 statfs 查询
            if (statfs(path, &sfs) == 0) {
                print_fs_info(path, &sfs);
            } else {
                printf("statfs failed for '%s': %s\n", path, strerror(errno));
            }

            // 使用 fstatfs 查询 (如果路径是文件)
            // 先打开文件
            fd = open(path, O_RDONLY);
            if (fd != -1) {
                if (fstatfs(fd, &sfs) == 0) {
                    printf("Querying via file descriptor for '%s':\n", path);
                    print_fs_info("(via fd)", &sfs);
                } else {
                    printf("fstatfs failed for fd of '%s': %s\n", path, strerror(errno));
                }
                close(fd);
            } else {
                // 如果打开失败,可能是因为 path 是目录,跳过 fstatfs
                printf("Could not open '%s' as file, skipping fstatfs.\n", path);
            }
            printf("------------------------\n");
        }
    }

    // 3. 演示一个实际应用:检查磁盘空间是否充足
    printf("--- Checking Disk Space ---\n");
    const char *check_path = "/tmp"; // 检查 /tmp 分区
    if (statfs(check_path, &sfs) == 0) {
        double avail_bytes = (double)sfs.f_bavail * sfs.f_bsize;
        double required_bytes = 100 * 1024 * 1024; // 假设需要 100MB

        printf("Checking if '%s' has at least %.2f MiB available...\n",
               check_path, required_bytes / (1024 * 1024));

        if (avail_bytes >= required_bytes) {
            printf("OK: %.2f MiB available, %.2f MiB required.\n",
                   avail_bytes / (1024 * 1024), required_bytes / (1024 * 1024));
        } else {
            printf("WARNING: Only %.2f MiB available, %.2f MiB required. Insufficient space!\n",
                   avail_bytes / (1024 * 1024), required_bytes / (1024 * 1024));
        }
    } else {
        perror("statfs for space check");
    }

    return 0;
}

10. 编译和运行

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

# 1. 不带参数运行,查询默认路径
./statfs_example

# 2. 带参数运行,查询指定路径
./statfs_example / /home /tmp /proc/self

# 3. 查询一个文件
touch /tmp/test_statfs_file
./statfs_example /tmp/test_statfs_file

11. 预期输出 (片段)

--- Demonstrating statfs ---
Filesystem information for '/':
  Optimal I/O Block Size: 4096 bytes
  Total Data Blocks:      123456789
  Free Blocks:            56789012
  Available Blocks:       50000000 (for non-root users)
  Total Inodes:           30000000
  Free Inodes:            25000000
  Filesystem ID:          12345:67890
  Maximum Filename Length: 255
  Fragment Size:          4096
  Mount Flags:            0x400
  Total Size:             471.86 GiB
  Available Size:         190.73 GiB
  Filesystem Type:        ext2/ext3/ext4

Filesystem information for '/tmp':
  Optimal I/O Block Size: 4096 bytes
  Total Data Blocks:      2048000
  Free Blocks:            2048000
  Available Blocks:       2048000 (for non-root users)
  Total Inodes:           512000
  Free Inodes:            512000
  Filesystem ID:          0:1234567
  Maximum Filename Length: 255
  Fragment Size:          4096
  Mount Flags:            0x41c
  Total Size:             7.81 GiB
  Available Size:         7.81 GiB
  Filesystem Type:        tmpfs

Filesystem information for '/proc':
  ...
  Filesystem Type:        proc
  ...

Filesystem information for '/sys':
  ...
  Filesystem Type:        sysfs
  ...

--- Checking Disk Space ---
Checking if '/tmp' has at least 100.00 MiB available...
OK: 8000.00 MiB available, 100.00 MiB required.

12. 总结

statfs 是一个非常实用的系统调用,用于获取文件系统级别的信息。它对于系统管理、磁盘监控和资源检查类的应用程序非常有价值。通过检查空闲块数 (f_bavail) 和块大小 (f_bsize),可以轻松计算出可用磁盘空间,这对于防止程序因磁盘写满而出错至关重要。理解 struct statfs 中各个字段的含义是使用此函数的关键。

发表在 linux文章 | 标签为 | 留下评论

statvfs-statfs系统调用及示例

我们来深入学习 statfs 和 statvfs 这两个系统调用。这两个函数功能非常相似,主要区别在于标准化和可移植性。

1. 函数介绍

在 Linux 系统中,文件和目录都存储在各种各样的文件系统之上(如 ext4, XFS, Btrfs, tmpfs 等)。每个文件系统都有自己的特性,比如总容量多大、现在用了多少、还剩多少空间、支持的文件名最长多少字符等等。

statfs 和 statvfs 系统调用的作用就是查询指定路径所在文件系统的各种统计信息和属性

你可以把它们想象成“文件系统信息查询器”。你随便给它一个路径(比如 /home/tmp/mnt/my_usb),它就能告诉你这个路径所在的那个磁盘分区(文件系统)的详细情况。

简单来说,statfs 和 statvfs 就是让你用程序来查看某个磁盘分区或挂载点的“健康报告”和“容量信息”。

2. 函数原型

// statfs: Linux 特定的系统调用
#include <sys/vfs.h>    // 包含系统调用声明 (在某些系统上可能是 <sys/statfs.h>)
// #include <sys/statfs.h> // 备选包含

int statfs(const char *path, struct statfs *buf);
int fstatfs(int fd, struct statfs *buf); // 通过已打开的文件描述符查询


// statvfs: POSIX 标准定义的系统调用,可移植性更好
#include <sys/statvfs.h> // 包含系统调用声明

int statvfs(const char *path, struct statvfs *buf);
int fstatvfs(int fd, struct statvfs *buf); // 通过已打开的文件描述符查询

3. 功能

获取指定路径 (path) 或文件描述符 (fd) 所在文件系统的统计信息,并将结果存储在相应的结构体 (buf) 中。

4. 参数

两者参数完全相同:

  • path:
    • const char * 类型。
    • 指向一个以 null 结尾的字符串,表示文件系统中的任意一个路径名。函数会查询这个路径所在的文件系统的统计信息。
  • fd:
    • int 类型。
    • 一个已打开文件的有效文件描述符。fstatfs/fstatvfs 会查询该文件描述符对应的文件所在的文件系统的统计信息。
  • buf:
    • struct statfs * 或 struct statvfs * 类型。
    • 一个指向相应结构体的指针。函数调用成功后,会将查询到的文件系统信息填充到这个结构体中。

5. 返回值

两者返回值也相同:

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

6. 错误码 (errno)

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

  • EACCES: (对于 statfs/statvfs) 搜索 path 中的一个或多个组件时权限不足。
  • EFAULTpath 或 buf 指向了调用进程无法访问的内存地址。
  • EIO: I/O 错误(例如,读取文件系统超级块失败)。
  • ELOOP: 解析 path 时遇到符号链接循环。
  • ENAMETOOLONGpath 太长。
  • ENOENTpath 指定的文件或目录不存在。
  • ENOMEM: 内核内存不足。
  • ENOSYS: 系统不支持该调用。
  • ENOTDIRpath 的某个前缀不是目录。
  • EOVERFLOW: 结构体中的某些值溢出。
  • EBADF: (对于 fstatfs/fstatvfsfd 不是有效的文件描述符。

7. struct statfs 和 struct statvfs 结构体

这两个结构体包含的信息非常相似,但 struct statvfs 是 POSIX 标准化的,字段名更规范。

struct statfs (Linux 特定)

struct statfs {
    __fsword_t f_type;    /* 文件系统类型 (Magic Number) */
    __fsword_t f_bsize;   /* 文件系统最优传输块大小 (Optimal transfer block size) */
    fsblkcnt_t f_blocks;  /* 文件系统中的总块数 */
    fsblkcnt_t f_bfree;   /* 文件系统中空闲的块数 */
    fsblkcnt_t f_bavail;  /* 对非特权用户可用的空闲块数 (可能小于 f_bfree) */
    fsfilcnt_t f_files;   /* 文件系统中的总 Inode 数 */
    fsfilcnt_t f_ffree;   /* 文件系统中空闲的 Inode 数 */
    fsid_t     f_fsid;    /* 文件系统 ID */
    __fsword_t f_namelen; /* 文件名最大长度 */
    __fsword_t f_frsize;  /* 片段大小 (Fragment size) */
    __fsword_t f_flags;   /* 文件系统挂载标志 */
    ...                   /* 可能还有其他字段 */
};

struct statvfs (POSIX 标准)

struct statvfs {
    unsigned long  f_bsize;    /* 文件系统块大小 (用于统计) */
    unsigned long  f_frsize;   /* 片段大小 (Fragment size) */
    fsblkcnt_t     f_blocks;   /* 文件系统中的总片段数 (f_frsize) */
    fsblkcnt_t     f_bfree;    /* 文件系统中空闲的片段数 */
    fsblkcnt_t     f_bavail;   /* 对非特权用户可用的片段数 */
    fsfilcnt_t     f_files;    /* 文件系统中的总 Inode 数 */
    fsfilcnt_t     f_ffree;    /* 文件系统中空闲的 Inode 数 */
    fsfilcnt_t     f_favail;   /* 对非特权用户可用的空闲 Inode 数 */
    unsigned long  f_fsid;     /* 文件系统 ID */
    unsigned long  f_flag;     /* 挂载标志 */
    unsigned long  f_namemax;  /* 最大文件名长度 */
};

关键字段解释 (两个结构体通用概念):

  • f_bsize / f_frsizef_frsize 是文件系统的基本片段大小。f_bsize 是推荐用于 I/O 操作的块大小(可能与 f_frsize 相同或不同)。计算总大小时通常用 f_blocks * f_frsize
  • f_blocks: 文件系统总共有多少个片段 (f_frsize)。
  • f_bfree: 文件系统总共有多少个空闲片段。
  • f_bavail: 对普通用户(非 root)来说,实际还可以使用的空闲片段数量。有些文件系统会保留一部分空间给 root 用户。
  • f_files: 文件系统总共包含多少个 Inode(索引节点)。
  • f_ffree: 文件系统中空闲的 Inode 数量。
  • f_type / f_fsid: 文件系统的类型标识符或 ID。
  • f_namelen / f_namemax: 文件系统支持的文件名或目录名的最大长度。

8. statfs vs statvfs:该如何选择?

  • statfs:
    • 优点:是 Linux 原生接口,可能包含一些 statvfs 没有的 Linux 特定信息(如 f_type 魔数)。
    • 缺点仅限 Linux。如果你的代码需要在其他 Unix 或类 Unix 系统(如 BSD, macOS)上编译运行,statfs 可能不存在或行为不同。
  • statvfs:
    • 优点POSIX 标准。这意味着它在所有符合 POSIX 标准的系统上都可用,具有最佳的可移植性
    • 缺点:可能缺少一些 Linux 特定的详细信息。

对于 Linux 编程小白的建议

  1. 优先使用 statvfs。它是标准的、可移植的。除非你有特殊需求必须使用 statfs 的特定功能,否则 statvfs 是更好的选择。
  2. 如果你确定只在 Linux 上运行,并且需要访问 f_type 这样的 Linux 特定信息,可以使用 statfs

9. 示例代码

下面的示例演示了如何使用 statfs 和 statvfs 来查询文件系统信息,并比较它们的输出。

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

// 辅助函数:打印人类可读的大小
void print_human_readable(double bytes, const char* prefix) {
    const char *units[] = {"B", "KiB", "MiB", "GiB", "TiB"};
    int unit_index = 0;
    double size_to_print = bytes;
    while (size_to_print >= 1024 && unit_index < 4) {
        size_to_print /= 1024;
        unit_index++;
    }
    printf("%s%.2f %s", prefix, size_to_print, units[unit_index]);
}

// 使用 statfs 打印信息
void print_fs_info_statfs(const char* path, const struct statfs *sfs) {
    printf("=== Filesystem information for '%s' (via statfs) ===\n", path);
    printf("  Optimal I/O Block Size: %ld bytes\n", (long)sfs->f_bsize);
    printf("  Fragment Size:          %ld bytes\n", (long)sfs->f_frsize);
    printf("  Total Fragments:        %ld\n", (long)sfs->f_blocks);
    printf("  Free Fragments:         %ld\n", (long)sfs->f_bfree);
    printf("  Available Fragments:    %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]);
    printf("  Maximum Filename Length: %ld\n", (long)sfs->f_namelen);
    printf("  Mount Flags:            0x%lx\n", (unsigned long)sfs->f_flags);

    // 计算并打印人类可读的空间大小
    double total_bytes = (double)sfs->f_blocks * sfs->f_frsize;
    double free_bytes = (double)sfs->f_bfree * sfs->f_frsize;
    double avail_bytes = (double)sfs->f_bavail * sfs->f_frsize;

    printf("  Total Size:             ");
    print_human_readable(total_bytes, "");
    printf("\n");
    printf("  Available Size:         ");
    print_human_readable(avail_bytes, "");
    printf("\n");

    // 识别文件系统类型 (仅列举几个常见类型)
    switch (sfs->f_type) {
        case 0xEF53: // EXT2/3/4
            printf("  Filesystem Type:        ext2/ext3/ext4\n");
            break;
        case 0x6969: // NFS
            printf("  Filesystem Type:        NFS\n");
            break;
        case 0x517B: // SMB/CIFS
            printf("  Filesystem Type:        SMB/CIFS\n");
            break;
        case 0x52654973: // ReiserFS
            printf("  Filesystem Type:        ReiserFS\n");
            break;
        case 0x5346544E: // NTFS
            printf("  Filesystem Type:        NTFS\n");
            break;
        case 0x4d44: // FAT
            printf("  Filesystem Type:        FAT\n");
            break;
        case 0x01021994: // TMPFS
            printf("  Filesystem Type:        tmpfs\n");
            break;
        case 0x9123683E: // Btrfs
            printf("  Filesystem Type:        Btrfs\n");
            break;
        default:
            printf("  Filesystem Type:        Unknown (Magic: 0x%lx)\n", (unsigned long)sfs->f_type);
            break;
    }
    printf("\n");
}

// 使用 statvfs 打印信息
void print_fs_info_statvfs(const char* path, const struct statvfs *svfs) {
    printf("=== Filesystem information for '%s' (via statvfs) ===\n", path);
    printf("  Filesystem Block Size:  %ld bytes\n", (long)svfs->f_bsize);
    printf("  Fragment Size:          %ld bytes\n", (long)svfs->f_frsize);
    printf("  Total Fragments:        %ld\n", (long)svfs->f_blocks);
    printf("  Free Fragments:         %ld\n", (long)svfs->f_bfree);
    printf("  Available Fragments:    %ld (for non-root users)\n", (long)svfs->f_bavail);
    printf("  Available Fragments (non-root): %ld\n", (long)svfs->f_bavail); // Same as f_bavail in most cases
    printf("  Total Inodes:           %ld\n", (long)svfs->f_files);
    printf("  Free Inodes:            %ld\n", (long)svfs->f_ffree);
    printf("  Available Inodes (non-root): %ld\n", (long)svfs->f_favail);
    printf("  Filesystem ID:          %ld\n", (unsigned long)svfs->f_fsid);
    printf("  Maximum Filename Length: %ld\n", (long)svfs->f_namemax);
    printf("  Mount Flags:            0x%lx\n", svfs->f_flag);

    // 计算并打印人类可读的空间大小
    double total_bytes = (double)svfs->f_blocks * svfs->f_frsize;
    double free_bytes = (double)svfs->f_bfree * svfs->f_frsize;
    double avail_bytes = (double)svfs->f_bavail * svfs->f_frsize;

    printf("  Total Size:             ");
    print_human_readable(total_bytes, "");
    printf("\n");
    printf("  Available Size:         ");
    print_human_readable(avail_bytes, "");
    printf("\n");
    printf("\n");
}

int main(int argc, char *argv[]) {
    struct statfs sfs;
    struct statvfs svfs;
    int fd;

    printf("--- Demonstrating statfs vs statvfs ---\n");

    // 1. 如果没有提供命令行参数,则查询根目录 "/"
    const char *path = "/";
    if (argc > 1) {
        path = argv[1];
    }
    printf("Querying path: '%s'\n\n", path);

    // 2. 使用 statfs 查询
    if (statfs(path, &sfs) == 0) {
        print_fs_info_statfs(path, &sfs);
    } else {
        printf("statfs failed for '%s': %s\n\n", path, strerror(errno));
    }

    // 3. 使用 statvfs 查询
    if (statvfs(path, &svfs) == 0) {
        print_fs_info_statvfs(path, &svfs);
    } else {
        printf("statvfs failed for '%s': %s\n\n", path, strerror(errno));
    }

    // 4. 演示 fstatfs 和 fstatvfs (通过文件描述符)
    printf("=== Comparing fstatfs and fstatvfs ===\n");
    fd = open(path, O_RDONLY); // 尝试打开路径 (对于目录是合法的)
    if (fd != -1) {
        printf("Opened '%s' as file descriptor %d\n", path, fd);

        if (fstatfs(fd, &sfs) == 0) {
            printf("\n--- fstatfs via fd %d ---\n", fd);
            printf("  Total Fragments (fstatfs): %ld\n", (long)sfs.f_blocks);
            printf("  Free Fragments (fstatfs):  %ld\n", (long)sfs.f_bfree);
        } else {
            printf("fstatfs failed: %s\n", strerror(errno));
        }

        if (fstatvfs(fd, &svfs) == 0) {
            printf("\n--- fstatvfs via fd %d ---\n", fd);
            printf("  Total Fragments (fstatvfs): %ld\n", (long)svfs.f_blocks);
            printf("  Free Fragments (fstatvfs):  %ld\n", (long)svfs.f_bfree);
        } else {
            printf("fstatvfs failed: %s\n", strerror(errno));
        }

        close(fd);
    } else {
        // 如果打开失败(例如 path 是文件),尝试打开文件本身
        fd = open(path, O_RDONLY);
        if (fd != -1) {
             printf("Opened file '%s' as file descriptor %d\n", path, fd);
             // ... (对文件进行 fstatfs/fstatvfs 调用)
             // 为简洁起见,此处省略对文件的详细查询,逻辑同上
             close(fd);
        } else {
            printf("Could not open '%s' as file/dir for fstatfs/fstatvfs demo.\n", path);
        }
    }

    // 5. 演示一个实际应用:检查磁盘空间是否充足
    printf("\n=== Checking Disk Space ===\n");
    const char *check_path = "/tmp"; // 检查 /tmp 分区
    if (statvfs(check_path, &svfs) == 0) {
        double avail_bytes = (double)svfs.f_bavail * svfs.f_frsize;
        double required_bytes = 100 * 1024 * 1024; // 假设需要 100MB

        printf("Checking if '%s' has at least %.2f MiB available...\n",
               check_path, required_bytes / (1024.0*1024.0));

        if (avail_bytes >= required_bytes) {
            printf("OK: ");
            print_human_readable(avail_bytes, "");
            printf(" available, %.2f MiB required.\n", required_bytes / (1024.0*1024.0));
        } else {
            printf("WARNING: Only ");
            print_human_readable(avail_bytes, "");
            printf(" available, %.2f MiB required. Insufficient space!\n", required_bytes / (1024.0*1024.0));
        }
    } else {
        perror("statvfs for space check");
    }

    return 0;
}

10. 编译和运行

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

# 1. 不带参数运行,查询根目录 "/"
./statfs_statvfs_example

# 2. 带参数运行,查询指定路径
./statfs_statvfs_example /home
./statfs_statvfs_example /tmp

11. 预期输出 (片段)

--- Demonstrating statfs vs statvfs ---
Querying path: '/'

=== Filesystem information for '/' (via statfs) ===
  Optimal I/O Block Size: 4096 bytes
  Fragment Size:          4096 bytes
  Total Fragments:        123456789
  Free Fragments:         56789012
  Available Fragments:    50000000 (for non-root users)
  Total Inodes:           30000000
  Free Inodes:            25000000
  Filesystem ID:          12345:67890
  Maximum Filename Length: 255
  Mount Flags:            0x400
  Total Size:             471.86 GiB
  Available Size:         190.73 GiB
  Filesystem Type:        ext2/ext3/ext4

=== Filesystem information for '/' (via statvfs) ===
  Filesystem Block Size:  4096 bytes
  Fragment Size:          4096 bytes
  Total Fragments:        123456789
  Free Fragments:         56789012
  Available Fragments:    50000000 (for non-root users)
  Available Fragments (non-root): 50000000
  Total Inodes:           30000000
  Free Inodes:            25000000
  Available Inodes (non-root): 25000000
  Filesystem ID:          12345
  Maximum Filename Length: 255
  Mount Flags:            0x400
  Total Size:             471.86 GiB
  Available Size:         190.73 GiB

=== Comparing fstatfs and fstatvfs ===
Opened '/' as file descriptor 3

--- fstatfs via fd 3 ---
  Total Fragments (fstatfs): 123456789
  Free Fragments (fstatfs):  56789012

--- fstatvfs via fd 3 ---
  Total Fragments (fstatvfs): 123456789
  Free Fragments (fstatvfs):  56789012

=== Checking Disk Space ===
Checking if '/tmp' has at least 100.00 MiB available...
OK: 7.81 GiB available, 100.00 MiB required.

12. 总结

statfs 和 statvfs 都是用于获取文件系统信息的强大工具。

  • statfs: Linux 特定的系统调用,可能提供更详细的 Linux 专有信息(如文件系统类型魔数)。
  • statvfs: POSIX 标准系统调用,具有更好的可移植性,是跨平台开发的首选。
  • 共同点:都能获取文件系统的总容量、可用空间、Inode 信息等关键统计数据。
  • 选择建议
    • 默认选择 statvfs,以确保代码的可移植性。
    • 仅当需要 Linux 特定信息且确定只在 Linux 上运行时,才考虑使用 statfs
  • 实际应用:常用于磁盘空间监控、系统信息工具(如 df 命令就是基于它们实现的)、资源管理等场景。理解返回结构体中各个字段的含义是正确使用它们的关键。
发表在 linux文章 | 留下评论

statx系统调用及示例

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


1. 函数介绍

statx 是一个相对较新的 Linux 系统调用(内核版本 >= 4.11),它是对传统 statlstatfstat 系列函数的现代化扩展和增强

它的主要功能是获取文件的状态信息(如大小、权限、所有者、时间戳等),与 stat 系列函数相同。但 statx 提供了以下显著优势:

  1. 更高效: 通过 flags 参数,调用者可以精确指定需要查询哪些文件属性,内核只返回请求的信息,避免了获取不必要的数据,从而提高了效率。
  2. 更丰富的信息statx 可以返回一些传统 stat 结构 (struct stat) 无法提供的新属性,例如:
    • 创建时间 (stx_btime): 文件的创建时间(birth time),这是许多文件系统支持但传统 stat 无法获取的。
    • 扩展的文件类型和权限: 提供了更详细的文件类型和权限信息。
    • 文件系统 ID (stx_dev_majorstx_dev_minor): 更明确地标识文件所在的设备。
  3. 更好的可扩展性statx 使用了新的 struct statx 结构,这个结构设计时就考虑了未来的扩展性,更容易添加新字段而不会破坏现有程序。
  4. 统一接口: 一个函数就能实现 statlstatfstat 的功能,通过 flags 参数控制是否跟随符号链接。

简单来说,statx 是一个更快、更强大、更灵活的 stat


2. 函数原型

#include <fcntl.h>     // 定义 AT_* 常量和 AT_STATX_* 常量
#include <sys/stat.h>  // 定义 struct statx

int statx(int dirfd, const char *pathname, int flags,
          unsigned int mask, struct statx *statxbuf);

3. 功能

  • 获取文件状态: 根据提供的 pathname(结合 dirfd 和 flags)获取指定文件的详细状态信息。
  • 按需查询: 通过 mask 参数,调用者可以指定只查询感兴趣的文件属性子集,提高效率。
  • 灵活路径解析: 通过 dirfdpathnameflags 的组合,可以实现相对路径查找、绝对路径查找、控制符号链接行为等多种路径解析方式。

4. 参数

  • int dirfd: 用作相对路径查找的起始目录文件描述符
    • 如果 pathname 是相对路径(例如 "subdir/file.txt"),则相对于 dirfd 指向的目录进行查找。
    • 如果 pathname 是绝对路径(例如 "/home/user/file.txt"),则 dirfd 被忽略。
    • 可以传入特殊的值 AT_FDCWD,表示使用当前工作目录作为起始点进行相对路径查找。
  • const char *pathname: 指向要查询状态的文件的路径名。可以是相对路径或绝对路径。
  • int flags: 控制路径解析行为的标志位。可以是以下值的按位或组合:
    • 0: 默认行为。
    • AT_SYMLINK_NOFOLLOW: 如果 pathname 是一个符号链接,则不跟随该链接,而是返回符号链接本身的属性(类似 lstat 的行为)。
    • AT_NO_AUTOMOUNT: 阻止在路径解析过程中触发自动挂载文件系统。
    • AT_EMPTY_PATH: 如果 pathname 是一个空字符串 (""),则查询 dirfd 本身所引用的文件的状态。dirfd 必须是一个有效的文件描述符。
    • AT_STATX_SYNC_AS_STAT: 使 statx 的同步语义与 stat 相同(默认行为)。
    • AT_STATX_FORCE_SYNC: 强制与服务器同步,获取最新的属性(例如,对于网络文件系统)。
    • AT_STATX_DONT_SYNC: 不要与服务器同步,使用缓存中的属性(如果可用)。
  • unsigned int mask: 这是一个位掩码,用于指定调用者感兴趣的文件属性。内核只会填充 struct statx 中与 mask 对应的字段。常用的掩码标志包括:
    • STATX_TYPE: 文件类型 (e.g., 普通文件, 目录, 符号链接)。
    • STATX_MODE: 文件权限和类型。
    • STATX_NLINK: 硬链接数。
    • STATX_UID: 所有者用户 ID。
    • STATX_GID: 所有者组 ID。
    • STATX_ATIME: 上次访问时间。
    • STATX_MTIME: 上次修改时间。
    • STATX_CTIME: 上次状态更改时间。
    • STATX_INO: inode 编号。
    • STATX_SIZE: 文件大小 (字节)。
    • STATX_BLOCKS: 分配的 512B 块数。
    • STATX_BASIC_STATS: 以上所有基本属性的组合。
    • STATX_BTIME: 文件创建时间(birth time)。
    • STATX_MNT_ID: (Linux 5.8+) 挂载 ID。
    • STATX_DIOALIGN: (Linux 6.1+) 直接 I/O 对齐要求。
    • STATX_ALL: 所有已知属性的组合。
  • struct statx *statxbuf: 指向一个 struct statx 类型的结构体的指针。函数调用成功后,该结构体将被填入文件的状态信息。

5. struct statx 结构体

statx 使用新的 struct statx 结构体来返回信息,比 struct stat 更丰富和可扩展。

struct statx {
    __u32 stx_mask;        // 哪些字段被填充了 (对应 mask)
    __u32 stx_blksize;     // 文件系统 I/O 块大小
    __u64 stx_attributes;   // 文件的额外属性 (如不可变、追加)
    __u32 stx_nlink;       // 硬链接数
    __u32 stx_uid;         // 所有者用户 ID
    __u32 stx_gid;         // 所有者组 ID
    __u16 stx_mode;        // 文件类型和权限
    __u16 stx_pad1;        // 填充
    __u64 stx_ino;         // inode 编号
    __u64 stx_size;        // 文件大小 (字节)
    __u64 stx_blocks;      // 分配的 512B 块数
    __u64 stx_attributes_mask; // 有效 attributes 位掩码
    struct statx_timestamp stx_atime; // 上次访问时间
    struct statx_timestamp stx_btime; // 创建时间 (birth time)
    struct statx_timestamp stx_ctime; // 上次状态更改时间
    struct statx_timestamp stx_mtime; // 上次修改时间
    __u32 stx_rdev_major;  // (如果是设备文件) 设备 ID 主号
    __u32 stx_rdev_minor;  // (如果是设备文件) 设备 ID 次号
    __u32 stx_dev_major;   // 文件所在设备 ID 主号
    __u32 stx_dev_minor;   // 文件所在设备 ID 次号
    __u64 stx_mnt_id;      // 挂载 ID (Linux 5.8+)
    __u32 stx_dio_mem_align; // 直接 I/O 内存对齐 (Linux 6.1+)
    __u32 stx_dio_offset_align; // 直接 I/O 偏移对齐 (Linux 6.1+)
    __u64 stx_subvol;      // 子卷 ID (Btrfs) (Linux 6.9+)
    __u64 stx_dax;         // DAX 支持 (Linux 6.10+)
    __u64 stx_pad2[9];     // 保留供将来扩展
};

struct statx_timestamp {
    __s64 tv_sec;          // 秒
    __u32 tv_nsec;         // 纳秒
    __s32 __reserved;
};

6. 返回值

  • 成功时: 返回 0。同时,statxbuf 指向的 struct statx 结构体被成功填充。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 ENOENT 文件不存在,EACCES 权限不足,EINVAL 参数无效等)。

7. 示例代码

示例 1:基本使用 statx 获取文件信息

这个例子演示了如何使用 statx 获取文件的基本信息,并与传统 stat 进行比较。

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

void print_statx_info(const char *pathname, struct statx *sbx) {
    printf("--- statx info for '%s' ---\n", pathname);
    printf("  Mask (fields filled): 0x%x\n", sbx->stx_mask);
    printf("  Inode: %llu\n", (unsigned long long)sbx->stx_ino);
    printf("  Size: %llu bytes\n", (unsigned long long)sbx->stx_size);
    printf("  Blocks: %llu (512B blocks)\n", (unsigned long long)sbx->stx_blocks);
    printf("  Device ID: %xh/%d (major), %xh/%d (minor)\n",
           sbx->stx_dev_major, sbx->stx_dev_major,
           sbx->stx_dev_minor, sbx->stx_dev_minor);
    printf("  Links: %u\n", sbx->stx_nlink);
    printf("  Mode: %o (octal)\n", sbx->stx_mode);
    printf("  UID: %u\n", sbx->stx_uid);
    printf("  GID: %u\n", sbx->stx_gid);

    // 检查并打印时间戳
    if (sbx->stx_mask & STATX_ATIME) {
        char time_buf[100];
        struct tm *tm_info = localtime((time_t*)&sbx->stx_atime.tv_sec);
        strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
        printf("  Last Access: %s.%09ld\n", time_buf, sbx->stx_atime.tv_nsec);
    }
    if (sbx->stx_mask & STATX_MTIME) {
        char time_buf[100];
        struct tm *tm_info = localtime((time_t*)&sbx->stx_mtime.tv_sec);
        strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
        printf("  Last Modify: %s.%09ld\n", time_buf, sbx->stx_mtime.tv_nsec);
    }
    if (sbx->stx_mask & STATX_CTIME) {
        char time_buf[100];
        struct tm *tm_info = localtime((time_t*)&sbx->stx_ctime.tv_sec);
        strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
        printf("  Last Status Change: %s.%09ld\n", time_buf, sbx->stx_ctime.tv_nsec);
    }
    if (sbx->stx_mask & STATX_BTIME) {
        char time_buf[100];
        struct tm *tm_info = localtime((time_t*)&sbx->stx_btime.tv_sec);
        strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
        printf("  Birth Time: %s.%09ld\n", time_buf, sbx->stx_btime.tv_nsec);
    } else {
        printf("  Birth Time: Not available\n");
    }
    printf("---------------------------\n");
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    const char *pathname = argv[1];
    struct statx statx_buf;

    // --- 使用 statx 获取基本信息 ---
    // dirfd = AT_FDCWD: 使用当前目录作为相对路径起点
    // pathname = argv[1]: 文件路径
    // flags = 0: 默认行为,跟随符号链接
    // mask = STATX_BASIC_STATS: 请求所有基本文件属性
    // statx_buf: 用于接收结果的缓冲区
    printf("Calling statx with STATX_BASIC_STATS...\n");
    if (statx(AT_FDCWD, pathname, 0, STATX_BASIC_STATS, &statx_buf) == -1) {
        perror("statx");
        exit(EXIT_FAILURE);
    }

    print_statx_info(pathname, &statx_buf);

    // --- 比较: 使用传统 stat ---
    printf("\n--- Comparing with traditional stat ---\n");
    struct stat stat_buf;
    if (stat(pathname, &stat_buf) == -1) {
        perror("stat");
        // 即使 stat 失败,也继续演示 statx 的其他功能
    } else {
        printf("  stat() - Size: %ld bytes\n", (long)stat_buf.st_size);
        printf("  stat() - Inode: %ld\n", (long)stat_buf.st_ino);
        // ... 可以打印更多 stat 字段 ...
    }

    // --- 使用 statx 只获取特定信息 (例如,只获取大小和修改时间) ---
    printf("\nCalling statx with specific mask (SIZE | MTIME)...\n");
    struct statx statx_buf_minimal;
    if (statx(AT_FDCWD, pathname, 0, STATX_SIZE | STATX_MTIME, &statx_buf_minimal) == -1) {
        perror("statx minimal");
    } else {
        printf("  Minimal query result:\n");
        if (statx_buf_minimal.stx_mask & STATX_SIZE) {
            printf("    Size: %llu bytes\n", (unsigned long long)statx_buf_minimal.stx_size);
        }
        if (statx_buf_minimal.stx_mask & STATX_MTIME) {
            char time_buf[100];
            struct tm *tm_info = localtime((time_t*)&statx_buf_minimal.stx_mtime.tv_sec);
            strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
            printf("    Last Modify: %s.%09ld\n", time_buf, statx_buf_minimal.stx_mtime.tv_nsec);
        }
        printf("    Note: Only requested fields are filled. Mask = 0x%x\n", statx_buf_minimal.stx_mask);
    }

    return 0;
}

如何测试:

# 创建一个测试文件
echo "Hello, statx!" > test_statx_file.txt
touch -d "2023-01-01 10:00:00" test_statx_file.txt # 设置修改时间

# 编译并运行
gcc -o statx_basic_example statx_basic_example.c
./statx_basic_example test_statx_file.txt

代码解释:

  1. 定义了一个 print_statx_info 函数来格式化并打印 struct statx 的内容。
  2. 在 main 函数中,首先调用 statx(AT_FDCWD, pathname, 0, STATX_BASIC_STATS, &statx_buf)
    • AT_FDCWD: 使用当前工作目录解析相对路径。
    • 0: 默认 flags,表示如果 pathname 是符号链接,则跟随它。
    • STATX_BASIC_STATS: 请求所有基本的文件状态信息。
    • &statx_buf: 指向用于接收结果的 struct statx 变量。
  3. 调用成功后,打印所有获取到的信息。
  4. 为了对比,调用传统的 stat() 函数获取相同文件的信息。
  5. 再次调用 statx,但这次只请求 STATX_SIZE 和 STATX_MTIME
    • 这展示了 statx 的效率优势:内核只会填充请求的字段。
    • 打印结果时,可以看到 stx_mask 只包含 STATX_SIZE | STATX_MTIME 对应的位。

示例 2:使用 statx 处理符号链接和获取创建时间

这个例子演示了如何使用 statx 的 flags 参数来控制符号链接行为,并尝试获取文件的创建时间。

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

int main() {
    const char *target_file = "target_file.txt";
    const char *symlink_name = "my_symlink_to_target";

    // 1. 创建目标文件
    FILE *f = fopen(target_file, "w");
    if (f) {
        fprintf(f, "This is the target file.\n");
        fclose(f);
        printf("Created target file: %s\n", target_file);
    } else {
        perror("Failed to create target file");
    }

    // 2. 创建符号链接
    if (symlink(target_file, symlink_name) == -1) {
        if (errno != EEXIST) { // EEXIST 表示链接已存在,可以接受
            perror("Failed to create symbolic link");
            unlink(target_file);
            exit(EXIT_FAILURE);
        } else {
            printf("Symbolic link '%s' already exists.\n", symlink_name);
        }
    } else {
        printf("Created symbolic link '%s' -> '%s'\n", symlink_name, target_file);
    }

    struct statx statx_buf;

    // --- 3a. 使用 statx 跟随符号链接 (默认行为) ---
    printf("\n--- statx('%s', 0) - Following symlink ---\n", symlink_name);
    if (statx(AT_FDCWD, symlink_name, 0, STATX_BASIC_STATS | STATX_BTIME, &statx_buf) == -1) {
        perror("statx following symlink");
    } else {
        printf("  Inode: %llu (This is the INODE of the TARGET file)\n", (unsigned long long)statx_buf.stx_ino);
        printf("  File Type: ");
        if ((statx_buf.stx_mode & S_IFMT) == S_IFREG) printf("Regular File\n");
        else if ((statx_buf.stx_mode & S_IFMT) == S_IFLNK) printf("Symbolic Link\n");
        else printf("Other\n");
        if (statx_buf.stx_mask & STATX_BTIME) {
             printf("  Birth Time available.\n");
        } else {
            printf("  Birth Time NOT available.\n");
        }
    }

    // --- 3b. 使用 statx 不跟随符号链接 ---
    printf("\n--- statx('%s', AT_SYMLINK_NOFOLLOW) - NOT Following symlink ---\n", symlink_name);
    if (statx(AT_FDCWD, symlink_name, AT_SYMLINK_NOFOLLOW, STATX_BASIC_STATS | STATX_BTIME, &statx_buf) == -1) {
        perror("statx NOT following symlink");
    } else {
        printf("  Inode: %llu (This is the INODE of the SYMBOLIC LINK itself)\n", (unsigned long long)statx_buf.stx_ino);
        printf("  File Type: ");
        if ((statx_buf.stx_mode & S_IFMT) == S_IFREG) printf("Regular File\n");
        else if ((statx_buf.stx_mode & S_IFMT) == S_IFLNK) printf("Symbolic Link\n");
        else printf("Other\n");
        if (statx_buf.stx_mask & STATX_BTIME) {
             printf("  Birth Time available.\n");
        } else {
            printf("  Birth Time NOT available.\n");
        }
    }

    // --- 4. 尝试获取创建时间 (Birth Time) ---
    printf("\n--- Attempting to get Birth Time (stx_btime) ---\n");
    // 需要确保内核和文件系统支持
    if (statx(AT_FDCWD, target_file, 0, STATX_BTIME, &statx_buf) == -1) {
        perror("statx for btime");
    } else {
        if (statx_buf.stx_mask & STATX_BTIME) {
            char time_buf[100];
            struct tm *tm_info = localtime((time_t*)&statx_buf.stx_btime.tv_sec);
            strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
            printf("  Birth Time of '%s': %s.%09ld\n", target_file, time_buf, statx_buf.stx_btime.tv_nsec);
        } else {
            printf("  Birth Time is NOT supported by the filesystem for '%s'.\n", target_file);
        }
    }

    // --- 5. 使用 AT_EMPTY_PATH 查询已打开文件的状态 ---
    printf("\n--- Using AT_EMPTY_PATH with an open file descriptor ---\n");
    int fd = open(target_file, O_RDONLY);
    if (fd == -1) {
        perror("open target file");
    } else {
        // pathname 为空字符串 "", dirfd 是有效的文件描述符
        if (statx(fd, "", AT_EMPTY_PATH, STATX_BASIC_STATS, &statx_buf) == -1) {
            perror("statx with AT_EMPTY_PATH");
        } else {
            printf("  Status of file descriptor %d (refers to '%s'):\n", fd, target_file);
            printf("    Size: %llu bytes\n", (unsigned long long)statx_buf.stx_size);
            printf("    Inode: %llu\n", (unsigned long long)statx_buf.stx_ino);
        }
        close(fd);
    }

    // 清理 (可选)
    // printf("\nCleaning up...\n");
    // unlink(symlink_name);
    // unlink(target_file);

    return 0;
}

如何测试:

gcc -o statx_symlink_btime statx_symlink_btime.c
./statx_symlink_btime

代码解释:

  1. 创建一个目标文件 target_file.txt 和一个指向它的符号链接 my_symlink_to_target
  2. 比较 flags:
    • 调用 statx(symlink_name, 0, ...):默认行为,跟随符号链接。返回的是目标文件的信息(inode 是目标文件的)。
    • 调用 statx(symlink_name, AT_SYMLINK_NOFOLLOW, ...):不跟随符号链接。返回的是符号链接本身的信息(inode 是符号链接的)。
  3. 获取创建时间:
    • 调用 statx(target_file, 0, STATX_BTIME, ...) 尝试获取目标文件的创建时间。
    • 检查返回的 statx_buf.stx_mask 是否包含 STATX_BTIME。如果包含,说明文件系统支持并返回了创建时间;否则,说明不支持。
  4. 使用 AT_EMPTY_PATH:
    • 首先打开目标文件得到文件描述符 fd
    • 调用 statx(fd, "", AT_EMPTY_PATH, ...)。这里 pathname 是空字符串,dirfd 是 fdAT_EMPTY_PATH 标志告诉 statx 查询 fd 本身引用的文件。
    • 这提供了一种通过文件描述符获取文件状态的方法,类似于 fstat,但具有 statx 的所有优势。

重要提示与注意事项:

  1. 内核版本statx 需要 Linux 内核 4.11 或更高版本。
  2. glibc 版本: 需要 glibc 2.28 或更高版本才能在 <sys/stat.h> 中提供 statx 函数声明。如果 glibc 版本较低,可能需要手动定义或使用 syscall
  3. 效率: 通过使用 mask 参数,只请求需要的字段,可以显著提高性能,尤其是在网络文件系统或需要频繁查询的场景下。
  4. stx_btime (创建时间): 这个字段的可用性高度依赖于底层文件系统。例如,ext4 在较新内核上可能支持,而 tmpfs 或某些网络文件系统可能不支持。务必检查 stx_mask 来确认字段是否有效。
  5. dirfd 和 AT_* 标志: 这些参数提供了强大的路径解析能力,特别是 AT_SYMLINK_NOFOLLOW 和 AT_EMPTY_PATH
  6. 错误处理: 始终检查返回值和 errnoENOENT (文件不存在)、EACCES (权限不足) 是常见的错误。
  7. 替代方案: 对于不支持 statx 的旧系统,必须回退到使用 statlstatfstat

总结:

statx 是 Linux 文件状态查询功能的一次重要升级。它通过引入更精细的查询控制 (mask)、更丰富的属性(如 btime)、更灵活的路径解析 (dirfdflags) 以及更好的可扩展性 (struct statx),为开发者提供了更强大、更高效的文件元数据获取能力。对于追求性能和需要访问现代文件系统特性的应用程序来说,statx 是首选的文件状态查询接口。

发表在 linux文章 | 标签为 | 留下评论

stat系统调用及示例

好的,我们继续学习 Linux 系统编程中的重要函数。这次我们介绍一组用于获取文件状态信息的函数:statfstat, 和 lstat。它们让你能够查询文件的各种属性,如大小、权限、所有者、时间戳等,而无需打开文件(statlstat)或只需已打开的文件描述符(fstat)。


1. 函数介绍

statfstat, 和 lstat 是三个密切相关的 Linux 系统调用,它们都用于获取文件的状态信息。这些信息被填充到一个 struct stat 类型的结构体中。

  • stat: 通过文件路径名 (pathname) 获取文件状态。如果路径名指向一个符号链接 (symbolic link),它会跟随链接并返回链接目标文件的状态。
  • fstat: 通过已打开的文件描述符 (file descriptor) 获取文件状态。因为文件已经打开,所以它直接操作文件描述符,不涉及路径解析。
  • lstat: 也通过文件路径名 (pathname) 获取文件状态。但它不会跟随符号链接,而是返回符号链接本身的信息。

理解这三个函数的关键在于区分符号链接和其目标文件,以及何时需要获取哪个的信息。


2. 函数原型

#include <sys/types.h>  // 通常需要
#include <sys/stat.h>   // 必需,包含 struct stat 和函数声明
#include <unistd.h>     // 通常需要

// 通过路径名获取文件状态
int stat(const char *pathname, struct stat *statbuf);

// 通过文件描述符获取文件状态
int fstat(int fd, struct stat *statbuf);

// 通过路径名获取文件状态,但不跟随符号链接
int lstat(const char *pathname, struct stat *statbuf);

3. 功能

这三个函数的功能都是将指定文件的详细状态信息填充到调用者提供的 struct stat 结构体指针 statbuf 所指向的内存中。

  • stat(pathname, buf): 查找 pathname,如果它是一个符号链接,则查找链接指向的目标文件,并将该目标文件的状态信息存入 buf
  • fstat(fd, buf): 获取与文件描述符 fd 关联的文件的状态信息并存入 buf
  • lstat(pathname, buf): 查找 pathname,即使它是一个符号链接,也将该符号链接本身的状态信息存入 buf

4. 参数

  • const char *pathname (statlstat): 指向一个以空字符结尾的字符串,该字符串包含要查询状态的文件的路径名。
  • int fd (fstat): 一个有效的、已打开的文件描述符。
  • struct stat *statbuf: 指向一个 struct stat 类型的结构体的指针。函数调用成功后,该结构体将被填入文件的状态信息。

5. struct stat 结构体

struct stat 结构体包含了大量的文件属性信息。虽然具体实现可能因系统而异,但以下字段是标准且常用的:

struct stat {
    dev_t     st_dev;         // 文件所在设备的 ID
    ino_t     st_ino;         // inode 节点号 (唯一标识文件)
    mode_t    st_mode;        // 文件类型和权限
    nlink_t   st_nlink;       // 硬链接数
    uid_t     st_uid;         // 所有者用户 ID
    gid_t     st_gid;         // 所有者组 ID
    dev_t     st_rdev;        // (如果是设备文件) 设备 ID
    off_t     st_size;        // 文件大小 (以字节为单位)
    blksize_t st_blksize;     // 文件系统 I/O 块大小
    blkcnt_t  st_blocks;      // 分配的 512B 块数
    time_t    st_atime;       // 上次访问时间 (自 Unix 纪元以来的秒数)
    time_t    st_mtime;       // 上次修改时间
    time_t    st_ctime;       // 上次状态改变时间 (如权限、所有者)
    // ... 可能还有其他字段,取决于具体实现 ...
};

常用字段解释:

  • st_mode: 这个字段包含了文件类型和权限信息。可以通过一系列宏来测试:
    • 文件类型:
      • S_ISREG(m): 是否为普通文件。
      • S_ISDIR(m): 是否为目录。
      • S_ISCHR(m): 是否为字符设备文件。
      • S_ISBLK(m): 是否为块设备文件。
      • S_ISFIFO(m): 是否为命名管道 (FIFO)。
      • S_ISLNK(m): 是否为符号链接 (对 lstat 结果有效)。
      • S_ISSOCK(m): 是否为套接字。
    • 权限位 (按位与 & 操作检查):
      • S_IRUSRS_IWUSRS_IXUSR: 文件所有者的读、写、执行权限。
      • S_IRGRPS_IWGRPS_IXGRP: 文件所属组的读、写、执行权限。
      • S_IROTHS_IWOTHS_IXOTH: 其他用户的读、写、执行权限。
      • S_IRWXUS_IRWXGS_IRWXO: 分别代表所有者、组、其他用户的读/写/执行权限组合。
  • st_size: 文件的大小,以字节为单位。对于目录或设备文件,此值可能没有意义。
  • st_mtime: 文件内容上次被修改的时间。常用于判断文件是否已更新。

6. 返回值

这三个函数的返回值规则相同:

  • 成功时: 返回 0。同时,statbuf 指向的结构体被成功填充。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 ENOENT 文件不存在、EACCES 权限不足、EBADF fd 无效 (仅 fstat) 等)。

7. 示例代码

示例 1:使用 stat 获取普通文件信息

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pwd.h>  // getpwuid
#include <grp.h>  // getgrgid
#include <time.h> // ctime

void print_file_info(const char *pathname) {
    struct stat file_stat;
    struct passwd *pwd;
    struct group *grp;
    char date_str[100];

    // 调用 stat 获取文件状态
    if (stat(pathname, &file_stat) == -1) {
        perror("stat");
        return; // 或 exit(EXIT_FAILURE);
    }

    // 打印基本信息
    printf("File: %s\n", pathname);
    printf("  Size: %ld bytes\n", (long)file_stat.st_size);
    printf("  Inode: %ld\n", (long)file_stat.st_ino);
    printf("  Device ID: %ld\n", (long)file_stat.st_dev);

    // 打印文件类型
    printf("  Type: ");
    if (S_ISREG(file_stat.st_mode)) {
        printf("Regular File\n");
    } else if (S_ISDIR(file_stat.st_mode)) {
        printf("Directory\n");
    } else if (S_ISLNK(file_stat.st_mode)) {
        printf("Symbolic Link\n");
    } else if (S_ISCHR(file_stat.st_mode)) {
        printf("Character Device\n");
    } else if (S_ISBLK(file_stat.st_mode)) {
        printf("Block Device\n");
    } else if (S_ISFIFO(file_stat.st_mode)) {
        printf("FIFO (Named Pipe)\n");
    } else if (S_ISSOCK(file_stat.st_mode)) {
        printf("Socket\n");
    } else {
        printf("Unknown Type\n");
    }

    // 打印权限 (简化版)
    printf("  Permissions: ");
    printf( (S_ISDIR(file_stat.st_mode)) ? "d" : "-");
    printf( (file_stat.st_mode & S_IRUSR) ? "r" : "-");
    printf( (file_stat.st_mode & S_IWUSR) ? "w" : "-");
    printf( (file_stat.st_mode & S_IXUSR) ? "x" : "-");
    printf( (file_stat.st_mode & S_IRGRP) ? "r" : "-");
    printf( (file_stat.st_mode & S_IWGRP) ? "w" : "-");
    printf( (file_stat.st_mode & S_IXGRP) ? "x" : "-");
    printf( (file_stat.st_mode & S_IROTH) ? "r" : "-");
    printf( (file_stat.st_mode & S_IWOTH) ? "w" : "-");
    printf( (file_stat.st_mode & S_IXOTH) ? "x" : "-");
    printf("\n");

    // 打印所有者和组
    pwd = getpwuid(file_stat.st_uid);
    grp = getgrgid(file_stat.st_gid);
    printf("  Owner: %s (UID: %d)\n", pwd ? pwd->pw_name : "Unknown", (int)file_stat.st_uid);
    printf("  Group: %s (GID: %d)\n", grp ? grp->gr_name : "Unknown", (int)file_stat.st_gid);

    // 打印时间戳 (使用 ctime 格式化)
    // 注意: ctime 返回的字符串末尾自带 \n
    printf("  Last Access: %s", ctime(&file_stat.st_atime)); // ctime 返回的字符串已包含 \n
    printf("  Last Modify: %s", ctime(&file_stat.st_mtime));
    printf("  Last Status Change: %s", ctime(&file_stat.st_ctime));

}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    print_file_info(argv[1]);

    return 0;
}

代码解释:

  1. 定义了一个 print_file_info 函数来封装 stat 调用和信息打印。
  2. 调用 stat(argv[1], &file_stat) 获取文件状态。
  3. 检查返回值,失败则打印错误。
  4. 使用 S_ISREGS_ISDIR 等宏判断文件类型。
  5. 通过按位与操作检查 st_mode 来构建权限字符串。
  6. 使用 getpwuid 和 getgrgid 将 UID/GID 转换为用户名/组名。
  7. 使用 ctime 函数将 time_t 时间戳格式化为人类可读的字符串。

编译并运行:gcc -o stat_example stat_example.c && ./stat_example /etc/passwd

示例 2:比较 statlstatfstat 对符号链接的行为

这个例子创建一个符号链接,并比较三个函数获取的信息。

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    struct stat stat_info, lstat_info, fstat_info;
    int fd;
    const char *target_file = "target_file.txt";
    const char *symlink_name = "my_symlink";

    // 创建一个目标文件 (如果不存在)
    FILE *f = fopen(target_file, "w");
    if (f) {
        fprintf(f, "This is the target file.\n");
        fclose(f);
        printf("Created target file: %s\n", target_file);
    } else {
        perror("Failed to create target file");
        // 即使创建失败,也可能文件已存在,继续尝试后续步骤
    }

    // 创建符号链接 (如果不存在)
    if (symlink(target_file, symlink_name) == -1) {
        if (errno != EEXIST) { // EEXIST 表示链接已存在,可以接受
            perror("Failed to create symbolic link");
            // 清理可能已创建的目标文件
            unlink(target_file);
            exit(EXIT_FAILURE);
        } else {
            printf("Symbolic link '%s' already exists.\n", symlink_name);
        }
    } else {
        printf("Created symbolic link '%s' -> '%s'\n", symlink_name, target_file);
    }

    // --- 使用 stat ---
    printf("\n--- Using stat('%s', ...) ---\n", symlink_name);
    if (stat(symlink_name, &stat_info) == -1) {
        perror("stat failed");
    } else {
        printf("  Inode: %ld (This is the INODE of the TARGET file)\n", (long)stat_info.st_ino);
        printf("  Size: %ld bytes\n", (long)stat_info.st_size);
        if (S_ISLNK(stat_info.st_mode)) {
            printf("  Type: Symbolic Link (This should NOT happen with stat)\n");
        } else {
            printf("  Type: Not a Symbolic Link (stat followed the link)\n");
        }
    }

    // --- 使用 lstat ---
    printf("\n--- Using lstat('%s', ...) ---\n", symlink_name);
    if (lstat(symlink_name, &lstat_info) == -1) {
        perror("lstat failed");
    } else {
        printf("  Inode: %ld (This is the INODE of the SYMBOLIC LINK itself)\n", (long)lstat_info.st_ino);
        printf("  Size: %ld bytes (Size of the link's PATHNAME string)\n", (long)lstat_info.st_size);
        if (S_ISLNK(lstat_info.st_mode)) {
            printf("  Type: Symbolic Link (lstat did NOT follow the link)\n");
        } else {
            printf("  Type: Not a Symbolic Link (Unexpected)\n");
        }
    }

    // --- 使用 fstat ---
    printf("\n--- Using fstat(fd_of_symlink, ...) ---\n");
    // 打开符号链接本身 (需要 O_PATH 或 O_RDONLY, 且不跟随链接的行为取决于系统和标志,但 fstat 总是作用于已打开的 fd)
    // 更准确的做法是打开目标文件来演示 fstat
    fd = open(target_file, O_RDONLY); // 打开目标文件
    if (fd == -1) {
        perror("Failed to open target file for fstat");
        // 清理
        unlink(symlink_name);
        unlink(target_file);
        exit(EXIT_FAILURE);
    }

    if (fstat(fd, &fstat_info) == -1) {
        perror("fstat failed");
    } else {
        printf("  Inode: %ld (Inode of the file associated with fd %d)\n", (long)fstat_info.st_ino, fd);
        printf("  Size: %ld bytes\n", (long)fstat_info.st_size);
        if (S_ISLNK(fstat_info.st_mode)) {
            printf("  Type: Symbolic Link (Associated file is a symlink)\n");
        } else {
            printf("  Type: Not a Symbolic Link (Associated file is regular/dir/etc)\n");
        }
    }
    close(fd); // 关闭 fstat 使用的文件描述符


    // 清理 (可选)
    // printf("\nCleaning up...\n");
    // unlink(symlink_name);
    // unlink(target_file);

    return 0;
}

代码解释:

  1. 首先创建一个目标文件 target_file.txt 和一个指向它的符号链接 my_symlink
  2. 调用 stat(symlink_name, ...): 它返回的是目标文件 (target_file.txt) 的信息,包括目标文件的 inode 和大小。
  3. 调用 lstat(symlink_name, ...): 它返回的是符号链接本身 (my_symlink) 的信息,包括符号链接文件的 inode 和大小(大小通常是其指向路径名字符串的长度)。
  4. 调用 fstat(fd, ...): 它需要一个文件描述符。这里打开的是目标文件 target_file.txt,所以返回的是目标文件的信息。如果打开的是符号链接(且系统支持),fstat 仍然会作用于链接最终指向的文件(除非用特殊标志打开链接本身,但这比较复杂且不常用)。

这个例子清晰地展示了 stat 跟随链接,lstat 不跟随链接,而 fstat 作用于已打开的文件描述符所关联的文件。

理解 statfstatlstat 对于编写需要根据文件属性进行不同处理的程序至关重要。

发表在 linux文章 | 标签为 | 留下评论

swapon-off系统调用及示例

swapon/swapoff 函数详解

1. 函数介绍

swapon 和 swapoff 是Linux系统调用,用于管理系统的交换空间(swap space)。交换空间是磁盘上的一块区域,当物理内存不足时,系统会将不常用的内存页移动到交换空间,从而释放物理内存供其他进程使用。

2. 函数原型

#include <sys/swap.h>

int swapon(const char *path, int swapflags);
int swapoff(const char *path);

3. 功能

  • swapon: 启用指定的交换空间(文件或设备)
  • swapoff: 禁用指定的交换空间

4. 参数

  • *const char path: 交换文件或设备的路径
  • int swapflags: swapon的标志位(通常为0)

5. 返回值

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

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

  • swapctl: 更通用的交换控制接口
  • mkswap: 创建交换空间
  • /proc/swaps: 交换空间信息文件
  • free: 显示内存使用情况

7. 示例代码

示例1:基础swapon/swapoff使用

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

/**
 * 显示当前交换空间信息
 */
void show_swap_info() {
    FILE *fp;
    char line[256];
    
    printf("=== 当前交换空间信息 ===\n");
    
    fp = fopen("/proc/swaps", "r");
    if (fp) {
        printf("交换空间列表:\n");
        while (fgets(line, sizeof(line), fp)) {
            printf("  %s", line);
        }
        fclose(fp);
    } else {
        printf("无法读取交换空间信息: %s\n", strerror(errno));
    }
    
    printf("\n内存使用情况:\n");
    system("free -h");
    printf("\n");
}

/**
 * 创建交换文件
 */
int create_swap_file(const char *filename, size_t size_mb) {
    int fd;
    char *buffer;
    size_t chunk_size = 1024 * 1024;  // 1MB chunks
    size_t written = 0;
    
    printf("创建交换文件: %s (%zu MB)\n", filename, size_mb);
    
    // 创建文件
    fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0600);
    if (fd == -1) {
        perror("创建交换文件失败");
        return -1;
    }
    
    // 设置文件大小
    if (ftruncate(fd, size_mb * 1024 * 1024) == -1) {
        perror("设置文件大小失败");
        close(fd);
        unlink(filename);
        return -1;
    }
    
    // 可选:填充文件内容(对于某些文件系统可能需要)
    buffer = malloc(chunk_size);
    if (buffer) {
        memset(buffer, 0, chunk_size);
        
        // 写入一些数据以确保文件分配
        ssize_t result = write(fd, buffer, chunk_size);
        if (result == -1) {
            perror("写入文件失败");
        }
        
        free(buffer);
    }
    
    close(fd);
    
    // 创建交换空间
    printf("初始化交换空间...\n");
    char cmd[256];
    snprintf(cmd, sizeof(cmd), "mkswap %s", filename);
    
    int result = system(cmd);
    if (result != 0) {
        printf("创建交换空间失败\n");
        unlink(filename);
        return -1;
    }
    
    printf("交换文件创建成功\n");
    return 0;
}

/**
 * 演示基础swapon/swapoff使用方法
 */
int demo_swapon_swapoff_basic() {
    const char *swap_file = "/tmp/test_swapfile";
    const size_t swap_size = 64;  // 64MB
    int result;
    
    printf("=== 基础swapon/swapoff使用示例 ===\n");
    
    // 检查权限
    uid_t uid = getuid();
    printf("用户权限检查:\n");
    printf("  当前用户ID: %d\n", uid);
    if (uid == 0) {
        printf("  ✓ 具有root权限,可以管理交换空间\n");
    } else {
        printf("  ✗ 没有root权限,交换空间管理将失败\n");
        printf("  注意:swapon/swapoff需要root权限\n");
        return 0;
    }
    
    // 显示原始交换空间信息
    printf("\n1. 原始交换空间信息:\n");
    show_swap_info();
    
    // 创建交换文件
    printf("2. 创建测试交换文件:\n");
    if (create_swap_file(swap_file, swap_size) != 0) {
        printf("创建交换文件失败\n");
        return -1;
    }
    
    // 启用交换文件
    printf("3. 启用交换文件:\n");
    printf("   交换文件路径: %s\n", swap_file);
    
    result = swapon(swap_file, 0);
    if (result == 0) {
        printf("   ✓ 交换文件启用成功\n");
        show_swap_info();
    } else {
        printf("   ✗ 交换文件启用失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("   原因:权限不足\n");
        } else if (errno == EBUSY) {
            printf("   原因:交换空间已在使用中\n");
        } else if (errno == EINVAL) {
            printf("   原因:无效的交换文件\n");
        }
        unlink(swap_file);
        return -1;
    }
    
    // 禁用交换文件
    printf("4. 禁用交换文件:\n");
    result = swapoff(swap_file);
    if (result == 0) {
        printf("   ✓ 交换文件禁用成功\n");
        show_swap_info();
    } else {
        printf("   ✗ 交换文件禁用失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("   原因:权限不足\n");
        } else if (errno == EINVAL) {
            printf("   原因:无效的交换文件路径\n");
        } else if (errno == EBUSY) {
            printf("   原因:交换空间忙或正在使用\n");
        }
    }
    
    // 清理测试文件
    printf("5. 清理测试文件:\n");
    if (unlink(swap_file) == 0) {
        printf("   ✓ 测试文件清理成功\n");
    } else {
        printf("   ✗ 测试文件清理失败: %s\n", strerror(errno));
    }
    
    return 0;
}

int main() {
    return demo_swapon_swapoff_basic();
}

示例2:交换空间管理工具

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

/**
 * 交换空间条目结构
 */
typedef struct {
    char path[256];
    long size_kb;
    long used_kb;
    int priority;
    char type[32];
} swap_entry_t;

/**
 * 解析交换空间信息
 */
int parse_swap_info(swap_entry_t *entries, int max_entries) {
    FILE *fp;
    char line[512];
    int count = 0;
    
    fp = fopen("/proc/swaps", "r");
    if (!fp) {
        return -1;
    }
    
    // 跳过标题行
    fgets(line, sizeof(line), fp);
    
    while (fgets(line, sizeof(line), fp) && count < max_entries) {
        char *token;
        char *saveptr;
        int field = 0;
        
        token = strtok_r(line, " \t\n", &saveptr);
        while (token && field < 5) {
            switch (field) {
                case 0:  // Filename
                    strncpy(entries[count].path, token, sizeof(entries[count].path) - 1);
                    entries[count].path[sizeof(entries[count].path) - 1] = '\0';
                    break;
                case 1:  // Type
                    strncpy(entries[count].type, token, sizeof(entries[count].type) - 1);
                    entries[count].type[sizeof(entries[count].type) - 1] = '\0';
                    break;
                case 2:  // Size
                    entries[count].size_kb = atol(token);
                    break;
                case 3:  // Used
                    entries[count].used_kb = atol(token);
                    break;
                case 4:  // Priority
                    entries[count].priority = atoi(token);
                    break;
            }
            token = strtok_r(NULL, " \t\n", &saveptr);
            field++;
        }
        
        if (field >= 4) {
            count++;
        }
    }
    
    fclose(fp);
    return count;
}

/**
 * 显示详细的交换空间信息
 */
void show_detailed_swap_info() {
    swap_entry_t entries[32];
    int count = parse_swap_info(entries, 32);
    
    printf("=== 详细交换空间信息 ===\n");
    
    if (count <= 0) {
        printf("没有活动的交换空间\n");
        return;
    }
    
    printf("%-20s %-10s %-12s %-12s %-10s %-8s\n",
           "路径", "类型", "大小(MB)", "已用(MB)", "可用(MB)", "优先级");
    printf("%-20s %-10s %-12s %-12s %-10s %-8s\n",
           "----", "----", "--------", "--------", "--------", "------");
    
    long total_size = 0, total_used = 0;
    
    for (int i = 0; i < count; i++) {
        double size_mb = entries[i].size_kb / 1024.0;
        double used_mb = entries[i].used_kb / 1024.0;
        double free_mb = (entries[i].size_kb - entries[i].used_kb) / 1024.0;
        
        printf("%-20s %-10s %-12.1f %-12.1f %-10.1f %-8d\n",
               entries[i].path,
               entries[i].type,
               size_mb,
               used_mb,
               free_mb,
               entries[i].priority);
        
        total_size += entries[i].size_kb;
        total_used += entries[i].used_kb;
    }
    
    printf("\n总计:\n");
    printf("  总大小: %.1f MB\n", total_size / 1024.0);
    printf("  已使用: %.1f MB\n", total_used / 1024.0);
    printf("  可用: %.1f MB\n", (total_size - total_used) / 1024.0);
    printf("  使用率: %.1f%%\n", 
           total_size > 0 ? (total_used * 100.0 / total_size) : 0.0);
}

/**
 * 交换空间管理工具
 */
int demo_swap_management_tool() {
    const char *test_swap_file = "/tmp/swap_management_test";
    uid_t uid = getuid();
    
    printf("=== 交换空间管理工具演示 ===\n");
    
    // 权限检查
    printf("权限检查:\n");
    printf("  当前用户ID: %d\n", uid);
    if (uid == 0) {
        printf("  ✓ 具有root权限\n");
    } else {
        printf("  ✗ 没有root权限,部分功能将受限\n");
    }
    
    // 显示当前交换空间状态
    printf("\n1. 当前交换空间状态:\n");
    show_detailed_swap_info();
    
    // 如果有root权限,演示创建和管理交换文件
    if (uid == 0) {
        printf("\n2. 创建测试交换文件:\n");
        
        // 创建小的交换文件用于测试
        int fd = open(test_swap_file, O_CREAT | O_WRONLY | O_TRUNC, 0600);
        if (fd != -1) {
            // 设置文件大小为32MB
            if (ftruncate(fd, 32 * 1024 * 1024) == 0) {
                close(fd);
                
                // 创建交换空间
                char cmd[256];
                snprintf(cmd, sizeof(cmd), "mkswap %s >/dev/null 2>&1", test_swap_file);
                if (system(cmd) == 0) {
                    printf("  ✓ 创建交换文件成功: %s\n", test_swap_file);
                    
                    // 启用交换文件
                    printf("3. 启用交换文件:\n");
                    int result = swapon(test_swap_file, 0);
                    if (result == 0) {
                        printf("  ✓ 交换文件启用成功\n");
                        show_detailed_swap_info();
                        
                        // 禁用交换文件
                        printf("\n4. 禁用交换文件:\n");
                        result = swapoff(test_swap_file);
                        if (result == 0) {
                            printf("  ✓ 交换文件禁用成功\n");
                        } else {
                            printf("  ✗ 交换文件禁用失败: %s\n", strerror(errno));
                        }
                    } else {
                        printf("  ✗ 交换文件启用失败: %s\n", strerror(errno));
                    }
                } else {
                    printf("  ✗ 初始化交换空间失败\n");
                }
            } else {
                perror("  设置文件大小失败");
                close(fd);
            }
            
            // 清理测试文件
            unlink(test_swap_file);
        } else {
            perror("  创建交换文件失败");
        }
    }
    
    // 显示交换空间管理建议
    printf("\n=== 交换空间管理建议 ===\n");
    printf("1. 交换空间大小:\n");
    printf("   - 物理内存 < 2GB: 交换空间 = 物理内存 × 2\n");
    printf("   - 物理内存 2-8GB: 交换空间 = 物理内存\n");
    printf("   - 物理内存 > 8GB: 交换空间 = 4-8GB\n");
    
    printf("\n2. 交换空间类型:\n");
    printf("   - 交换分区: 性能更好,推荐用于生产环境\n");
    printf("   - 交换文件: 灵活性更好,便于管理\n");
    
    printf("\n3. 优先级设置:\n");
    printf("   - 高速设备设置高优先级\n");
    printf("   - SSD交换空间优先级高于HDD\n");
    
    printf("\n4. 监控和维护:\n");
    printf("   - 定期检查交换空间使用情况\n");
    printf("   - 避免交换空间使用率过高\n");
    printf("   - 及时清理不需要的交换文件\n");
    
    return 0;
}

int main() {
    return demo_swap_management_tool();
}

示例3:动态交换空间管理

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

/**
 * 交换空间监控器
 */
typedef struct {
    long total_swap_kb;
    long used_swap_kb;
    double usage_percentage;
    time_t last_update;
} swap_monitor_t;

/**
 * 获取交换空间使用情况
 */
int get_swap_usage(swap_monitor_t *monitor) {
    FILE *fp;
    char line[256];
    
    fp = fopen("/proc/meminfo", "r");
    if (!fp) {
        return -1;
    }
    
    long swap_total = 0, swap_free = 0;
    
    while (fgets(line, sizeof(line), fp)) {
        if (strncmp(line, "SwapTotal:", 10) == 0) {
            sscanf(line + 10, "%ld", &swap_total);
        } else if (strncmp(line, "SwapFree:", 9) == 0) {
            sscanf(line + 9, "%ld", &swap_free);
        }
    }
    
    fclose(fp);
    
    monitor->total_swap_kb = swap_total;
    monitor->used_swap_kb = swap_total - swap_free;
    monitor->usage_percentage = swap_total > 0 ? 
        (monitor->used_swap_kb * 100.0 / swap_total) : 0.0;
    monitor->last_update = time(NULL);
    
    return 0;
}

/**
 * 显示交换空间使用情况
 */
void show_swap_usage(const swap_monitor_t *monitor) {
    printf("交换空间使用情况:\n");
    printf("  总大小: %.1f MB\n", monitor->total_swap_kb / 1024.0);
    printf("  已使用: %.1f MB\n", monitor->used_swap_kb / 1024.0);
    printf("  可用: %.1f MB\n", (monitor->total_swap_kb - monitor->used_swap_kb) / 1024.0);
    printf("  使用率: %.1f%%\n", monitor->usage_percentage);
    
    char time_str[64];
    strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", 
             localtime(&monitor->last_update));
    printf("  更新时间: %s\n", time_str);
}

/**
 * 动态交换空间管理器
 */
typedef struct {
    char swap_file[256];
    size_t initial_size_mb;
    size_t max_size_mb;
    int is_active;
    swap_monitor_t monitor;
} dynamic_swap_manager_t;

/**
 * 创建动态交换文件
 */
int create_dynamic_swap_file(dynamic_swap_manager_t *manager, size_t size_mb) {
    int fd;
    
    printf("创建动态交换文件: %s (%zu MB)\n", manager->swap_file, size_mb);
    
    // 创建文件
    fd = open(manager->swap_file, O_CREAT | O_WRONLY | O_TRUNC, 0600);
    if (fd == -1) {
        perror("创建交换文件失败");
        return -1;
    }
    
    // 设置文件大小
    if (ftruncate(fd, size_mb * 1024 * 1024) == -1) {
        perror("设置文件大小失败");
        close(fd);
        unlink(manager->swap_file);
        return -1;
    }
    
    close(fd);
    
    // 创建交换空间
    char cmd[512];
    snprintf(cmd, sizeof(cmd), "mkswap %s >/dev/null 2>&1", manager->swap_file);
    
    if (system(cmd) != 0) {
        printf("初始化交换空间失败\n");
        unlink(manager->swap_file);
        return -1;
    }
    
    printf("动态交换文件创建成功\n");
    return 0;
}

/**
 * 启用动态交换空间
 */
int activate_dynamic_swap(dynamic_swap_manager_t *manager) {
    if (manager->is_active) {
        printf("交换空间已在使用中\n");
        return 0;
    }
    
    printf("启用动态交换空间...\n");
    
    int result = swapon(manager->swap_file, 0);
    if (result == 0) {
        manager->is_active = 1;
        printf("✓ 动态交换空间启用成功\n");
        return 0;
    } else {
        printf("✗ 动态交换空间启用失败: %s\n", strerror(errno));
        return -1;
    }
}

/**
 * 禁用动态交换空间
 */
int deactivate_dynamic_swap(dynamic_swap_manager_t *manager) {
    if (!manager->is_active) {
        printf("交换空间未启用\n");
        return 0;
    }
    
    printf("禁用动态交换空间...\n");
    
    int result = swapoff(manager->swap_file);
    if (result == 0) {
        manager->is_active = 0;
        printf("✓ 动态交换空间禁用成功\n");
        return 0;
    } else {
        printf("✗ 动态交换空间禁用失败: %s\n", strerror(errno));
        return -1;
    }
}

/**
 * 演示动态交换空间管理
 */
int demo_dynamic_swap_management() {
    dynamic_swap_manager_t manager = {0};
    uid_t uid = getuid();
    
    printf("=== 动态交换空间管理演示 ===\n");
    
    // 权限检查
    if (uid != 0) {
        printf("需要root权限来管理交换空间\n");
        return 0;
    }
    
    // 初始化管理器
    strncpy(manager.swap_file, "/tmp/dynamic_swap", sizeof(manager.swap_file) - 1);
    manager.initial_size_mb = 64;
    manager.max_size_mb = 256;
    manager.is_active = 0;
    
    printf("动态交换管理器初始化:\n");
    printf("  交换文件: %s\n", manager.swap_file);
    printf("  初始大小: %zu MB\n", manager.initial_size_mb);
    printf("  最大大小: %zu MB\n", manager.max_size_mb);
    
    // 获取初始交换空间状态
    printf("\n1. 初始交换空间状态:\n");
    if (get_swap_usage(&manager.monitor) == 0) {
        show_swap_usage(&manager.monitor);
    }
    
    // 创建并启用动态交换空间
    printf("\n2. 创建动态交换空间:\n");
    if (create_dynamic_swap_file(&manager, manager.initial_size_mb) != 0) {
        printf("创建动态交换空间失败\n");
        return -1;
    }
    
    printf("\n3. 启用动态交换空间:\n");
    if (activate_dynamic_swap(&manager) != 0) {
        printf("启用动态交换空间失败\n");
        unlink(manager.swap_file);
        return -1;
    }
    
    // 显示启用后的状态
    printf("\n4. 启用后的交换空间状态:\n");
    sleep(1);  // 等待系统更新状态
    if (get_swap_usage(&manager.monitor) == 0) {
        show_swap_usage(&manager.monitor);
    }
    
    // 模拟监控和调整
    printf("\n5. 监控交换空间使用情况:\n");
    for (int i = 0; i < 3; i++) {
        sleep(2);
        if (get_swap_usage(&manager.monitor) == 0) {
            printf("监控轮次 %d:\n", i + 1);
            show_swap_usage(&manager.monitor);
            
            // 简单的使用率监控逻辑
            if (manager.monitor.usage_percentage > 80.0) {
                printf("  警告:交换空间使用率过高 (%.1f%%)\n", 
                       manager.monitor.usage_percentage);
            } else if (manager.monitor.usage_percentage > 50.0) {
                printf("  提示:交换空间使用率中等 (%.1f%%)\n", 
                       manager.monitor.usage_percentage);
            } else {
                printf("  状态:交换空间使用率正常 (%.1f%%)\n", 
                       manager.monitor.usage_percentage);
            }
        }
    }
    
    // 禁用并清理
    printf("\n6. 禁用动态交换空间:\n");
    if (deactivate_dynamic_swap(&manager) != 0) {
        printf("禁用动态交换空间失败\n");
    }
    
    // 显示最终状态
    printf("\n7. 最终交换空间状态:\n");
    sleep(1);  // 等待系统更新状态
    if (get_swap_usage(&manager.monitor) == 0) {
        show_swap_usage(&manager.monitor);
    }
    
    // 清理文件
    printf("\n8. 清理交换文件:\n");
    if (unlink(manager.swap_file) == 0) {
        printf("✓ 交换文件清理成功\n");
    } else {
        printf("✗ 交换文件清理失败: %s\n", strerror(errno));
    }
    
    // 显示管理策略建议
    printf("\n=== 动态交换空间管理策略 ===\n");
    printf("1. 自动扩展策略:\n");
    printf("   - 监控交换空间使用率\n");
    printf("   - 使用率 > 80% 时扩展\n");
    printf("   - 使用率 < 30% 时收缩\n");
    
    printf("\n2. 性能优化:\n");
    printf("   - 使用SSD作为交换空间\n");
    printf("   - 设置适当的优先级\n");
    printf("   - 避免频繁的启用/禁用操作\n");
    
    printf("\n3. 安全考虑:\n");
    printf("   - 交换文件权限设置为600\n");
    printf("   - 定期清理临时交换文件\n");
    printf("   - 监控异常的交换空间使用\n");
    
    return 0;
}

int main() {
    return demo_dynamic_swap_management();
}

示例4:交换空间优先级管理

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

/**
 * 交换优先级管理器
 */
typedef struct {
    char path[256];
    int priority;
    int is_active;
} priority_swap_t;

/**
 * 创建带优先级的交换文件
 */
int create_priority_swap_file(const char *filename, size_t size_mb, int priority) {
    int fd;
    
    printf("创建优先级交换文件: %s (大小: %zu MB, 优先级: %d)\n", 
           filename, size_mb, priority);
    
    // 创建文件
    fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0600);
    if (fd == -1) {
        perror("创建交换文件失败");
        return -1;
    }
    
    // 设置文件大小
    if (ftruncate(fd, size_mb * 1024 * 1024) == -1) {
        perror("设置文件大小失败");
        close(fd);
        unlink(filename);
        return -1;
    }
    
    close(fd);
    
    // 创建交换空间
    char cmd[512];
    snprintf(cmd, sizeof(cmd), "mkswap %s >/dev/null 2>&1", filename);
    
    if (system(cmd) != 0) {
        printf("初始化交换空间失败\n");
        unlink(filename);
        return -1;
    }
    
    printf("交换文件创建成功\n");
    return 0;
}

/**
 * 启用带优先级的交换空间
 */
int activate_priority_swap(const char *filename, int priority) {
    printf("启用优先级交换空间: %s (优先级: %d)\n", filename, priority);
    
    // 使用swapflags设置优先级
    int swapflags = (priority << 16);  // 优先级存储在高16位
    
    int result = swapon(filename, swapflags);
    if (result == 0) {
        printf("✓ 交换空间启用成功\n");
        return 0;
    } else {
        printf("✗ 交换空间启用失败: %s\n", strerror(errno));
        return -1;
    }
}

/**
 * 演示交换空间优先级管理
 */
int demo_swap_priority_management() {
    priority_swap_t swaps[3];
    uid_t uid = getuid();
    
    printf("=== 交换空间优先级管理演示 ===\n");
    
    // 权限检查
    if (uid != 0) {
        printf("需要root权限来管理交换空间\n");
        return 0;
    }
    
    // 初始化交换空间配置
    strncpy(swaps[0].path, "/tmp/priority_swap_high", sizeof(swaps[0].path) - 1);
    swaps[0].priority = 10;  // 高优先级
    swaps[0].is_active = 0;
    
    strncpy(swaps[1].path, "/tmp/priority_swap_medium", sizeof(swaps[1].path) - 1);
    swaps[1].priority = 5;   // 中优先级
    swaps[1].is_active = 0;
    
    strncpy(swaps[2].path, "/tmp/priority_swap_low", sizeof(swaps[2].path) - 1);
    swaps[2].priority = 1;   // 低优先级
    swaps[2].is_active = 0;
    
    printf("交换空间优先级配置:\n");
    for (int i = 0; i < 3; i++) {
        printf("  %s: 优先级 %d\n", swaps[i].path, swaps[i].priority);
    }
    
    // 显示初始交换空间状态
    printf("\n1. 初始交换空间状态:\n");
    system("cat /proc/swaps");
    
    // 创建交换文件
    printf("\n2. 创建交换文件:\n");
    for (int i = 0; i < 3; i++) {
        size_t size_mb = 32 + i * 16;  // 32MB, 48MB, 64MB
        if (create_priority_swap_file(swaps[i].path, size_mb, swaps[i].priority) != 0) {
            printf("创建交换文件 %s 失败\n", swaps[i].path);
            // 清理已创建的文件
            for (int j = 0; j < i; j++) {
                unlink(swaps[j].path);
            }
            return -1;
        }
    }
    
    // 按优先级顺序启用交换空间
    printf("\n3. 按优先级启用交换空间:\n");
    // 注意:实际的优先级设置可能需要通过命令行参数传递
    for (int i = 0; i < 3; i++) {
        printf("启用交换空间 %d:\n", i + 1);
        // 这里简化处理,实际应用中可能需要更复杂的优先级设置
        int result = swapon(swaps[i].path, 0);
        if (result == 0) {
            swaps[i].is_active = 1;
            printf("  ✓ %s 启用成功\n", swaps[i].path);
        } else {
            printf("  ✗ %s 启用失败: %s\n", swaps[i].path, strerror(errno));
        }
    }
    
    // 显示启用后的状态
    printf("\n4. 启用后的交换空间状态:\n");
    system("cat /proc/swaps");
    
    printf("\n交换空间优先级说明:\n");
    printf("  - 数值越高优先级越高\n");
    printf("  - 高优先级交换空间先被使用\n");
    printf("  - 相同优先级的交换空间轮询使用\n");
    
    // 禁用交换空间
    printf("\n5. 禁用交换空间:\n");
    for (int i = 0; i < 3; i++) {
        if (swaps[i].is_active) {
            int result = swapoff(swaps[i].path);
            if (result == 0) {
                swaps[i].is_active = 0;
                printf("  ✓ %s 禁用成功\n", swaps[i].path);
            } else {
                printf("  ✗ %s 禁用失败: %s\n", swaps[i].path, strerror(errno));
            }
        }
    }
    
    // 清理文件
    printf("\n6. 清理交换文件:\n");
    for (int i = 0; i < 3; i++) {
        if (unlink(swaps[i].path) == 0) {
            printf("  ✓ %s 清理成功\n", swaps[i].path);
        } else {
            printf("  ✗ %s 清理失败: %s\n", swaps[i].path, strerror(errno));
        }
    }
    
    // 显示优先级管理最佳实践
    printf("\n=== 交换空间优先级管理最佳实践 ===\n");
    printf("1. 优先级设置原则:\n");
    printf("   - SSD交换空间设置高优先级\n");
    printf("   - 高速磁盘设置中等优先级\n");
    printf("   - 低速磁盘设置低优先级\n");
    
    printf("\n2. 负载均衡:\n");
    printf("   - 相同类型设备设置相同优先级\n");
    printf("   - 实现轮询使用,延长设备寿命\n");
    
    printf("\n3. 性能优化:\n");
    printf("   - 避免频繁调整优先级\n");
    printf("   - 根据实际性能测试设置\n");
    printf("   - 考虑I/O模式和访问模式\n");
    
    return 0;
}

int main() {
    return demo_swap_priority_management();
}

示例5:交换空间性能测试

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

/**
 * 内存压力测试器
 */
typedef struct {
    size_t allocated_memory_mb;
    size_t swap_used_mb;
    double test_duration_seconds;
    int swap_enabled;
} memory_test_result_t;

/**
 * 分配内存并测试交换性能
 */
int memory_pressure_test(size_t memory_mb, memory_test_result_t *result) {
    char **buffers;
    size_t num_buffers = memory_mb / 10;  // 每个缓冲区10MB
    size_t buffer_size = 10 * 1024 * 1024;  // 10MB
    struct timespec start, end;
    
    printf("开始内存压力测试: %zu MB\n", memory_mb);
    
    // 记录开始时间
    clock_gettime(CLOCK_MONOTONIC, &start);
    
    // 分配内存缓冲区数组
    buffers = malloc(num_buffers * sizeof(char*));
    if (!buffers) {
        printf("分配缓冲区数组失败\n");
        return -1;
    }
    
    // 逐步分配内存
    size_t allocated_buffers = 0;
    for (size_t i = 0; i < num_buffers; i++) {
        buffers[i] = malloc(buffer_size);
        if (buffers[i]) {
            // 填充数据以确保内存实际使用
            memset(buffers[i], i & 0xFF, buffer_size);
            allocated_buffers++;
            
            if (allocated_buffers % 10 == 0) {
                printf("已分配 %zu MB 内存\n", allocated_buffers * 10);
            }
        } else {
            printf("内存分配失败在第 %zu 次\n", i + 1);
            break;
        }
    }
    
    // 记录结束时间
    clock_gettime(CLOCK_MONOTONIC, &end);
    
    result->allocated_memory_mb = allocated_buffers * 10;
    result->test_duration_seconds = (end.tv_sec - start.tv_sec) + 
                                   (end.tv_nsec - start.tv_nsec) / 1e9;
    
    printf("内存分配完成: %zu MB, 耗时 %.2f 秒\n", 
           result->allocated_memory_mb, result->test_duration_seconds);
    
    // 检查交换空间使用情况
    FILE *fp = fopen("/proc/meminfo", "r");
    if (fp) {
        char line[256];
        long swap_total = 0, swap_free = 0;
        
        while (fgets(line, sizeof(line), fp)) {
            if (strncmp(line, "SwapTotal:", 10) == 0) {
                sscanf(line + 10, "%ld", &swap_total);
            } else if (strncmp(line, "SwapFree:", 9) == 0) {
                sscanf(line + 9, "%ld", &swap_free);
            }
        }
        
        fclose(fp);
        result->swap_used_mb = (swap_total - swap_free) / 1024;  // 转换为MB
        result->swap_enabled = (swap_total > 0);
    }
    
    // 释放内存
    for (size_t i = 0; i < allocated_buffers; i++) {
        if (buffers[i]) {
            free(buffers[i]);
        }
    }
    free(buffers);
    
    return 0;
}

/**
 * 演示交换空间性能测试
 */
int demo_swap_performance_test() {
    const char *test_swap_file = "/tmp/performance_test_swap";
    uid_t uid = getuid();
    
    printf("=== 交换空间性能测试演示 ===\n");
    
    // 权限检查
    if (uid != 0) {
        printf("需要root权限来管理交换空间\n");
        printf("将以普通用户权限进行内存测试\n");
    }
    
    // 显示系统内存信息
    printf("1. 系统内存信息:\n");
    system("free -h");
    
    printf("\n2. 当前交换空间信息:\n");
    system("cat /proc/swaps");
    
    // 如果有root权限,创建测试交换空间
    if (uid == 0) {
        printf("\n3. 创建测试交换空间:\n");
        
        // 创建128MB交换文件
        int fd = open(test_swap_file, O_CREAT | O_WRONLY | O_TRUNC, 0600);
        if (fd != -1) {
            if (ftruncate(fd, 128 * 1024 * 1024) == 0) {
                close(fd);
                
                // 创建交换空间
                char cmd[256];
                snprintf(cmd, sizeof(cmd), "mkswap %s >/dev/null 2>&1", test_swap_file);
                if (system(cmd) == 0) {
                    printf("  ✓ 创建测试交换文件成功\n");
                    
                    // 启用交换文件
                    if (swapon(test_swap_file, 0) == 0) {
                        printf("  ✓ 启用测试交换文件成功\n");
                        system("cat /proc/swaps");
                    } else {
                        printf("  ✗ 启用测试交换文件失败\n");
                    }
                } else {
                    printf("  ✗ 初始化交换空间失败\n");
                    close(fd);
                    unlink(test_swap_file);
                }
            } else {
                perror("  设置文件大小失败");
                close(fd);
                unlink(test_swap_file);
            }
        } else {
            perror("  创建交换文件失败");
        }
    }
    
    // 进行内存压力测试
    printf("\n4. 内存压力测试:\n");
    
    // 测试不同内存大小
    size_t test_sizes[] = {100, 200, 300, 400};
    int num_tests = sizeof(test_sizes) / sizeof(test_sizes[0]);
    
    for (int i = 0; i < num_tests; i++) {
        printf("\n测试 %d: 分配 %zu MB 内存\n", i + 1, test_sizes[i]);
        
        memory_test_result_t result = {0};
        if (memory_pressure_test(test_sizes[i], &result) == 0) {
            printf("  测试结果:\n");
            printf("    分配内存: %zu MB\n", result.allocated_memory_mb);
            printf("    测试耗时: %.2f 秒\n", result.test_duration_seconds);
            printf("    交换使用: %zu MB\n", result.swap_used_mb);
            printf("    交换启用: %s\n", result.swap_enabled ? "是" : "否");
            
            if (result.swap_enabled && result.swap_used_mb > 0) {
                printf("    ✓ 交换空间正常工作\n");
            } else if (result.swap_enabled) {
                printf("    提示:交换空间已启用但未使用\n");
            } else {
                printf("    提示:交换空间未启用\n");
            }
        } else {
            printf("  测试失败\n");
        }
        
        // 短暂休息
        sleep(2);
    }
    
    // 如果创建了测试交换空间,清理它
    if (uid == 0) {
        printf("\n5. 清理测试交换空间:\n");
        
        // 禁用交换文件
        if (swapoff(test_swap_file) == 0) {
            printf("  ✓ 禁用测试交换文件成功\n");
        } else {
            printf("  ✗ 禁用测试交换文件失败: %s\n", strerror(errno));
        }
        
        // 删除交换文件
        if (unlink(test_swap_file) == 0) {
            printf("  ✓ 删除测试交换文件成功\n");
        } else {
            printf("  ✗ 删除测试交换文件失败: %s\n", strerror(errno));
        }
    }
    
    // 显示性能测试总结
    printf("\n=== 性能测试总结 ===\n");
    printf("1. 交换空间性能指标:\n");
    printf("   - 交换I/O速度\n");
    printf("   - 内存分配延迟\n");
    printf("   - 系统响应时间\n");
    
    printf("\n2. 性能优化建议:\n");
    printf("   - 使用SSD作为交换空间\n");
    printf("   - 合理设置交换空间大小\n");
    printf("   - 调整交换倾向参数 (swappiness)\n");
    
    printf("\n3. 监控要点:\n");
    printf("   - 交换空间使用率\n");
    printf("   - 系统负载情况\n");
    printf("   - 内存使用趋势\n");
    
    return 0;
}

int main() {
    return demo_swap_performance_test();
}

swapon/swapoff 使用注意事项

系统要求:

  1. 内核版本: 支持交换空间管理的Linux内核
  2. 权限要求: 需要root权限
  3. 架构支持: 支持所有主流架构

文件要求:

  1. 权限: 交换文件权限必须为600
  2. 格式: 必须使用mkswap初始化
  3. 大小: 建议为页面大小的倍数

错误处理:

  1. EPERM: 权限不足
  2. EINVAL: 无效参数
  3. EBUSY: 交换空间正在使用中
  4. ENOMEM: 内存不足

安全考虑:

  1. 文件权限: 确保交换文件权限正确
  2. 数据安全: 交换空间可能包含敏感数据
  3. 系统稳定性: 不当的交换管理可能影响系统稳定性

最佳实践:

  1. 权限检查: 执行前检查是否具有足够权限
  2. 错误处理: 妥善处理各种错误情况
  3. 资源清理: 及时清理临时交换文件
  4. 监控管理: 监控交换空间使用情况
  5. 性能调优: 根据系统特性调整交换策略

交换空间相关常量和参数

交换标志:

// swapon标志(通常为0)
#define SWAP_FLAG_PREFER     0x8000  // 优先使用
#define SWAP_FLAG_DISCARD    0x10000 // 启用discard

系统参数:

// /proc/sys/vm/swappiness (0-100)
// 控制内核使用交换空间的倾向
echo 10 > /proc/sys/vm/swappiness  // 降低交换倾向

相关文件和命令

系统文件:

  • /proc/swaps: 当前交换空间信息
  • /proc/meminfo: 内存使用信息
  • /proc/sys/vm/swappiness: 交换倾向参数

相关命令:

# 显示交换空间信息
cat /proc/swaps
swapon --show

# 显示内存使用情况
free -h
cat /proc/meminfo

# 管理交换空间
swapon /swapfile
swapoff /swapfile
mkswap /swapfile

常见使用场景

1. 系统管理:

// 动态添加交换空间以应对内存不足
swapon("/tmp/emergency_swap", 0);

2. 性能调优:

// 为不同存储设备设置不同的交换优先级
swapon("/dev/ssd_swap", priority << 16);

3. 资源监控:

// 监控交换空间使用情况并动态调整
if (swap_usage > 90%) {
    // 添加更多交换空间
    swapon("/tmp/additional_swap", 0);
}

总结

swapon 和 swapoff 是Linux系统中重要的内存管理函数,提供了:

  1. 动态管理: 可以动态启用和禁用交换空间
  2. 灵活配置: 支持不同的交换文件和设备
  3. 优先级控制: 可以设置交换空间的使用优先级
  4. 系统集成: 与Linux内核深度集成

通过合理使用这些函数,可以构建灵活的内存管理系统,提高系统的稳定性和性能。在实际应用中,需要注意权限要求、错误处理和系统稳定性等关键问题。

发表在 linux文章 | 标签为 | 留下评论

symlink-symlinkat系统调用及示例

好的,我们来深入学习 symlink 和 symlinkat 系统调用

1. 函数介绍

在 Linux 文件系统中,一个文件可以有多个名字,这通过链接 (Link) 来实现。链接主要分为两种:

  1. 硬链接 (Hard Link): 由 link() 和 linkat() 创建。多个硬链接直接指向同一个 inode(文件的实际数据)。删除一个硬链接不会影响文件数据,只有当所有硬链接都被删除时,文件数据才会被真正删除。硬链接不能跨文件系统,也不能指向目录(除了 . 和 ..)。
  2. 符号链接 (Symbolic Link, Symlink): 由 symlink() 和 symlinkat() 创建。它更像是一个“快捷方式”或“指针”,它本身是一个特殊的文件,包含了指向另一个文件或目录的路径名(可以是绝对路径或相对路径)。删除符号链接本身不会影响目标文件,但如果删除了目标文件,符号链接就会变成“悬空链接”(dangling link),无法访问。

symlink 和 symlinkat 的作用就是创建符号链接

  • symlink: 在指定路径创建一个符号链接,指向另一个指定的路径。
  • symlinkat: 是 symlink 的扩展版本,允许更灵活地指定符号链接的创建位置(使用文件描述符作为参考目录)。

简单来说,symlink 和 symlinkat 就是让你用程序来创建文件或目录的“快捷方式”。

典型应用场景

  • 创建快捷方式:为一个深层目录或复杂路径的文件创建一个容易访问的别名。
  • 库版本管理:例如,libfoo.so 可能是一个指向 libfoo.so.1.2.3 的符号链接,方便程序链接。
  • 配置文件管理:将配置文件放在一个标准位置,但通过符号链接指向实际存储位置。
  • 避免文件复制:通过符号链接共享文件,而不占用额外磁盘空间。

2. 函数原型

#include <unistd.h> // 包含系统调用声明

// 创建符号链接
int symlink(const char *target, const char *linkpath);

// 创建符号链接 (扩展版,支持相对路径)
int symlinkat(const char *target, int newdirfd, const char *linkpath);

3. 功能

  • symlink: 创建一个名为 linkpath 的符号链接,其内容是字符串 target。这个 target 可以是任意路径字符串,不需要在调用时就存在。
  • symlinkat: 类似于 symlink,但 linkpath 的解释方式不同。如果 linkpath 是相对路径,它是相对于 newdirfd 文件描述符所指向的目录来解析的。如果 linkpath 是绝对路径,则 newdirfd 被忽略。特殊的 newdirfd 值 AT_FDCWD 可以用来表示当前工作目录,此时 symlinkat() 的行为与 symlink() 相同。

4. 参数

  • target:
    • const char * 类型。
    • 指向一个以 null 结尾的字符串,该字符串定义了符号链接的目标。这个目标路径在创建符号链接时不需要存在,可以是绝对路径(如 /usr/bin/python3)或相对路径(如 ../bin/my_script)。
  • linkpath:
    • const char * 类型。
    • 指向一个以 null 结尾的字符串,该字符串指定了要创建的符号链接文件的名称和路径
    • 对于 symlink: 这个路径是相对于当前工作目录解析的。
    • 对于 symlinkat: 如果是相对路径,则相对于 newdirfd 解析;如果是绝对路径,则忽略 newdirfd
  • newdirfd (仅 symlinkat):
    • int 类型。
    • 一个已打开目录的文件描述符。linkpath 如果是相对路径,将相对于这个目录进行解析。
    • 特殊值 AT_FDCWD 表示当前工作目录。

5. 返回值

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

6. 错误码 (errno)

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

  • EACCES: 写入包含 linkpath 的目录权限不足。
  • EEXISTlinkpath 已经存在。
  • EFAULTtarget 或 linkpath 指向了调用进程无法访问的内存地址。
  • EIO: I/O 错误。
  • ELOOP: 解析 linkpath 时遇到符号链接循环。
  • ENAMETOOLONGtarget 或 linkpath 太长。
  • ENOENTlinkpath 的某个前缀目录不存在。
  • ENOMEM: 内核内存不足。
  • ENOSPC: 设备空间不足,无法创建新的目录项。
  • ENOTDIRlinkpath 的某个前缀不是目录。
  • EPERM: 文件系统不支持符号链接,或者由于其他原因被禁止(例如挂载了 nosymfollow 选项)。
  • EROFSlinkpath 所在的文件系统是只读的。
  • EBADF: (仅 symlinkatnewdirfd 不是有效的文件描述符,且不等于 AT_FDCWD

7. 相似函数或关联函数

  • link / linkat: 创建硬链接。
  • readlink / readlinkat: 读取符号链接本身的内容(即它指向的目标路径)。
  • unlink: 删除文件或目录的链接。对于符号链接,它删除的是符号链接本身,而不是目标文件。
  • lstat: 获取文件状态,但如果文件是符号链接,它返回的是符号链接本身的信息,而不是目标文件的信息。
  • stat: 获取文件状态,如果文件是符号链接,它会跟随链接到目标文件并返回目标文件的信息。

8. 示例代码

下面的示例演示了如何使用 symlink 和 symlinkat 来创建符号链接,并展示它们与硬链接和普通文件的区别。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h> // 包含 stat, lstat
#include <fcntl.h>    // 包含 open, O_* flags
#include <string.h>
#include <errno.h>
#include <libgen.h>   // 包含 dirname, basename

// 辅助函数:打印文件信息
void print_file_info(const char *path, const char* description) {
    struct stat sb;
    printf("--- %s: %s ---\n", description, path);

    // 使用 lstat 获取符号链接本身的信息
    if (lstat(path, &sb) == -1) {
        perror("lstat");
        return;
    }

    printf("  Inode: %ld\n", (long) sb.st_ino);
    printf("  Links: %ld\n", (long) sb.st_nlink);
    printf("  Size:  %ld bytes\n", (long) sb.st_size);

    if (S_ISLNK(sb.st_mode)) {
        printf("  Type:  Symbolic Link\n");
        // 读取符号链接指向的目标
        char target_path[1024];
        ssize_t len = readlink(path, target_path, sizeof(target_path) - 1);
        if (len != -1) {
            target_path[len] = '\0';
            printf("  -> Points to: %s\n", target_path);
        } else {
            perror("readlink");
        }
    } else if (S_ISREG(sb.st_mode)) {
        printf("  Type:  Regular File\n");
    } else if (S_ISDIR(sb.st_mode)) {
        printf("  Type:  Directory\n");
    } else {
        printf("  Type:  Other (Mode: %o)\n", sb.st_mode & S_IFMT);
    }
    printf("\n");
}

int main() {
    const char *original_file = "original_file.txt";
    const char *hard_link = "hard_link_to_original.txt";
    const char *sym_link_absolute = "symlink_absolute_to_original.txt";
    const char *sym_link_relative = "symlink_relative_to_original.txt";
    const char *sym_link_dangling = "symlink_to_nonexistent.txt";
    const char *test_dir = "test_symlink_dir";
    const char *sym_link_in_dir = "symlink_in_dir.txt";
    int dirfd;

    printf("--- Demonstrating symlink and symlinkat ---\n");

    // 1. 创建一个原始文件
    FILE *f = fopen(original_file, "w");
    if (!f) {
        perror("fopen original_file.txt");
        exit(EXIT_FAILURE);
    }
    fprintf(f, "This is the content of the original file.\n");
    fclose(f);
    printf("Created original file: %s\n", original_file);

    // 2. 创建硬链接
    if (link(original_file, hard_link) == -1) {
        perror("link");
        // 清理并退出
        unlink(original_file);
        exit(EXIT_FAILURE);
    }
    printf("Created hard link: %s -> %s\n", hard_link, original_file);

    // 3. 创建符号链接 (绝对路径)
    // 注意:target 是字符串,不一定需要存在
    if (symlink("/full/path/to/somewhere", sym_link_absolute) == -1) {
        perror("symlink absolute");
        // 清理并退出
        unlink(original_file);
        unlink(hard_link);
        exit(EXIT_FAILURE);
    }
    printf("Created symbolic link (absolute target): %s -> /full/path/to/somewhere\n", sym_link_absolute);

    // 4. 创建符号链接 (相对路径)
    if (symlink(original_file, sym_link_relative) == -1) {
        perror("symlink relative");
        // 清理并退出
        unlink(original_file);
        unlink(hard_link);
        unlink(sym_link_absolute);
        exit(EXIT_FAILURE);
    }
    printf("Created symbolic link (relative target): %s -> %s\n", sym_link_relative, original_file);

    // 5. 创建一个指向不存在文件的符号链接 (悬空链接)
    if (symlink("this_file_does_not_exist.txt", sym_link_dangling) == -1) {
        perror("symlink dangling");
        // 清理并退出
        unlink(original_file);
        unlink(hard_link);
        unlink(sym_link_absolute);
        unlink(sym_link_relative);
        exit(EXIT_FAILURE);
    }
    printf("Created dangling symbolic link: %s -> this_file_does_not_exist.txt\n", sym_link_dangling);

    // 6. 演示 symlinkat
    // 首先创建一个目录
    if (mkdir(test_dir, 0755) == -1 && errno != EEXIST) {
        perror("mkdir test_symlink_dir");
        // 清理并退出
        unlink(original_file);
        unlink(hard_link);
        unlink(sym_link_absolute);
        unlink(sym_link_relative);
        unlink(sym_link_dangling);
        exit(EXIT_FAILURE);
    }
    printf("\nCreated directory: %s\n", test_dir);

    // 打开目录以获取文件描述符
    dirfd = open(test_dir, O_RDONLY | O_DIRECTORY);
    if (dirfd == -1) {
        perror("open test_symlink_dir");
        // 清理并退出
        unlink(original_file);
        unlink(hard_link);
        unlink(sym_link_absolute);
        unlink(sym_link_relative);
        unlink(sym_link_dangling);
        rmdir(test_dir);
        exit(EXIT_FAILURE);
    }

    // 使用 symlinkat 在目录中创建符号链接
    // linkpath 是相对路径 "symlink_in_dir.txt",它相对于 dirfd (test_symlink_dir) 解析
    // target 是 "../original_file.txt",这是符号链接存储的内容
    if (symlinkat("../original_file.txt", dirfd, sym_link_in_dir) == -1) {
        perror("symlinkat");
        close(dirfd);
        // 清理并退出
        unlink(original_file);
        unlink(hard_link);
        unlink(sym_link_absolute);
        unlink(sym_link_relative);
        unlink(sym_link_dangling);
        rmdir(test_dir);
        exit(EXIT_FAILURE);
    }
    printf("Created symbolic link using symlinkat: %s/%s -> ../original_file.txt\n", test_dir, sym_link_in_dir);
    close(dirfd);

    // 7. 检查所有创建的文件/链接的信息
    printf("\n--- File Information ---\n");
    print_file_info(original_file, "Original File");
    print_file_info(hard_link, "Hard Link");
    print_file_info(sym_link_absolute, "Symbolic Link (Absolute)");
    print_file_info(sym_link_relative, "Symbolic Link (Relative)");
    print_file_info(sym_link_dangling, "Dangling Symbolic Link");
    char full_sym_link_in_dir[512];
    snprintf(full_sym_link_in_dir, sizeof(full_sym_link_in_dir), "%s/%s", test_dir, sym_link_in_dir);
    print_file_info(full_sym_link_in_dir, "Symbolic Link (Created with symlinkat)");

    // 8. 演示访问符号链接
    printf("--- Accessing Files ---\n");
    // 访问原始文件
    if (access(original_file, F_OK) == 0) {
        printf("Can access original file '%s'.\n", original_file);
    }
    // 访问硬链接 (效果同原始文件)
    if (access(hard_link, F_OK) == 0) {
        printf("Can access hard link '%s'.\n", hard_link);
    }
    // 访问相对符号链接 (应该成功,指向存在的文件)
    if (access(sym_link_relative, F_OK) == 0) {
        printf("Can access symbolic link '%s' (follows to existing target).\n", sym_link_relative);
    } else {
        printf("Cannot access symbolic link '%s': %s\n", sym_link_relative, strerror(errno));
    }
    // 访问悬空符号链接 (会失败)
    if (access(sym_link_dangling, F_OK) == 0) {
        printf("Can access dangling symbolic link '%s' (unexpected).\n", sym_link_dangling);
    } else {
        printf("Cannot access dangling symbolic link '%s': %s (expected)\n", sym_link_dangling, strerror(errno));
    }

    // 9. 清理创建的文件和目录
    printf("\n--- Cleaning Up ---\n");
    unlink(original_file);
    unlink(hard_link);
    unlink(sym_link_absolute);
    unlink(sym_link_relative);
    unlink(sym_link_dangling);
    unlink(full_sym_link_in_dir);
    rmdir(test_dir);
    printf("All files and directory cleaned up.\n");

    printf("\n--- Summary ---\n");
    printf("1. Hard links (link) point to the same inode as the original file.\n");
    printf("2. Symbolic links (symlink) are separate files containing a path string.\n");
    printf("3. Symbolic links can point to non-existent targets (dangling).\n");
    printf("4. symlinkat allows creating symlinks relative to a directory file descriptor.\n");

    return 0;
}

9. 编译和运行

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

# 运行程序
./symlink_example

10. 预期输出

--- Demonstrating symlink and symlinkat ---
Created original file: original_file.txt
Created hard link: hard_link_to_original.txt -> original_file.txt
Created symbolic link (absolute target): symlink_absolute_to_original.txt -> /full/path/to/somewhere
Created symbolic link (relative target): symlink_relative_to_original.txt -> original_file.txt
Created dangling symbolic link: symlink_to_nonexistent.txt -> this_file_does_not_exist.txt

Created directory: test_symlink_dir
Created symbolic link using symlinkat: test_symlink_dir/symlink_in_dir.txt -> ../original_file.txt

--- File Information ---
--- Original File: original_file.txt ---
  Inode: 123456
  Links: 2
  Size:  42 bytes
  Type:  Regular File

--- Hard Link: hard_link_to_original.txt ---
  Inode: 123456
  Links: 2
  Size:  42 bytes
  Type:  Regular File

--- Symbolic Link (Absolute): symlink_absolute_to_original.txt ---
  Inode: 123457
  Links: 1
  Size:  23 bytes
  Type:  Symbolic Link
  -> Points to: /full/path/to/somewhere

--- Symbolic Link (Relative): symlink_relative_to_original.txt ---
  Inode: 123458
  Links: 1
  Size:  18 bytes
  Type:  Symbolic Link
  -> Points to: original_file.txt

--- Dangling Symbolic Link: symlink_to_nonexistent.txt ---
  Inode: 123459
  Links: 1
  Size:  27 bytes
  Type:  Symbolic Link
  -> Points to: this_file_does_not_exist.txt

--- Symbolic Link (Created with symlinkat): test_symlink_dir/symlink_in_dir.txt ---
  Inode: 123460
  Links: 1
  Size:  21 bytes
  Type:  Symbolic Link
  -> Points to: ../original_file.txt

--- Accessing Files ---
Can access original file 'original_file.txt'.
Can access hard link 'hard_link_to_original.txt'.
Can access symbolic link 'symlink_relative_to_original.txt' (follows to existing target).
Cannot access dangling symbolic link 'symlink_to_nonexistent.txt': No such file or directory (expected)

--- Cleaning Up ---
All files and directory cleaned up.

--- Summary ---
1. Hard links (link) point to the same inode as the original file.
2. Symbolic links (symlink) are separate files containing a path string.
3. Symbolic links can point to non-existent targets (dangling).
4. symlinkat allows creating symlinks relative to a directory file descriptor.

11. 总结

symlink 和 symlinkat 是创建符号链接的标准系统调用。

  • symlink(target, linkpath): 最常用的创建符号链接的方式。linkpath 相对于当前工作目录解析。
  • symlinkat(target, newdirfd, linkpath): 提供了更灵活的路径解析方式,特别是当需要在特定目录下创建链接时非常有用。当 newdirfd 为 AT_FDCWD 时,行为与 symlink 相同。

理解符号链接与硬链接的区别至关重要:

  • 硬链接增加文件的链接计数,共享 inode。
  • 符号链接是独立的文件,内容是目标路径字符串。

符号链接是 Linux 文件系统中强大而灵活的工具,广泛用于系统管理和程序设计中。

发表在 linux文章 | 标签为 | 留下评论

sync_file_range系统调用及示例

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

1. 函数介绍

在 Linux 系统中,为了提高性能,当你调用 write() 将数据写入文件时,这些数据通常不会立即写入到物理磁盘上。相反,它们会被暂时存储在内核的页缓存 (Page Cache) 中。操作系统会在稍后的某个时间点(或者当缓存满时)自动将这些数据刷新到磁盘。这种机制大大提高了写入速度,但也带来了一个问题:如果在数据被实际写入磁盘之前系统崩溃或断电,那么这部分数据就会丢失。

fsync() 和 fdatasync() 函数可以强制将文件的所有(或部分)修改数据从内核缓存刷新到磁盘,确保数据的持久化。但是,它们通常会刷新整个文件相关的数据和元数据,这可能比你需要的更多,导致不必要的性能开销。

sync_file_range (Sync File Range) 系统调用提供了更精细的控制。它允许你指定文件的一个特定字节范围,并决定对这个范围内的数据执行什么样的同步操作。你可以选择:

  • 将数据从缓存写入磁盘(但不等待完成)。
  • 等待数据写入磁盘完成。
  • 将缓存中的脏数据(已修改但未写回)回写到磁盘。

简单来说,sync_file_range 就是让你用程序精确地告诉操作系统:“请帮我确保文件的第 X 字节到第 Y 字节的数据已经安全地保存到硬盘上了”,并且你可以控制这个过程是异步还是同步。

典型应用场景

  • 数据库系统:数据库需要精确控制其事务日志和数据文件的刷新,以确保事务的原子性和持久性,同时最大化性能。它们可以只刷新刚刚写入的关键日志部分,而不是整个文件。
  • 大文件处理:在写入一个非常大的文件时,可以分段调用 sync_file_range,确保关键部分的数据已经落盘,而不需要等待整个大文件都写完。
  • 高性能应用:需要在数据安全性和写入性能之间取得平衡的应用。

2. 函数原型

#define _GNU_SOURCE // 需要定义这个宏才能使用 sync_file_range
#include <fcntl.h>  // 包含系统调用声明和标志

int sync_file_range(int fd, off64_t offset, off64_t nbytes, unsigned int flags);

注意:函数名和参数类型可能因系统架构(32/64位)和内核版本而略有不同(如 sync_file_range2),但 glibc 会处理好兼容性,使用 sync_file_range 即可。

3. 功能

对文件描述符 fd 对应的文件,在从 offset 开始的 nbytes 字节范围内,根据 flags 指定的操作,执行同步操作。

4. 参数

  • fd:
    • int 类型。
    • 一个已打开文件的有效文件描述符。
  • offset:
    • off64_t 类型。
    • 指定要同步的文件范围的起始字节偏移量。必须是非负数。
  • nbytes:
    • off64_t 类型。
    • 指定要同步的字节数。如果 nbytes 为 0,则表示同步从 offset 开始到文件末尾的所有数据。
  • flags:
    • unsigned int 类型。
    • 指定要执行的同步操作。可以是以下一个或多个标志的按位或 (|) 组合:
      • SYNC_FILE_RANGE_WAIT_BEFORE: 等待在 offset 和 nbytes 范围内之前发起的任何写入操作完成。
      • SYNC_FILE_RANGE_WRITE启动将指定范围内的脏页面(修改过的缓存数据)回写到磁盘的操作。这个操作通常是异步的,即函数可能在数据实际写入磁盘之前就返回。
      • SYNC_FILE_RANGE_WAIT_AFTER: 等待在 offset 和 nbytes 范围内由 SYNC_FILE_RANGE_WRITE 启动的回写操作完成

常见的标志组合

  • SYNC_FILE_RANGE_WRITE: 启动回写,但不等待。适用于“尽快开始保存数据,但我不等它完成”。
  • SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER: 启动回写并等待其完成。这是最常用的方式,确保指定范围的数据确实写入了磁盘。
  • SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER: 等待之前的写入完成 -> 启动回写 -> 等待回写完成。非常彻底,但可能性能较低。

5. 返回值

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

6. 错误码 (errno)

  • EBADFfd 不是有效的文件描述符,或者该文件不支持同步操作。
  • EINVALflags 参数无效,或者 offset 为负数。
  • EIO: I/O 错误。
  • ENOMEM: 内核内存不足。
  • ENOSPC: 设备上没有足够的空间。
  • ESPIPEfd 指向的是管道、套接字或 FIFO,这些不支持 sync_file_range

7. 相似函数或关联函数

  • fsync: 同步文件的所有数据和元数据(如修改时间、文件大小等)。它会等待所有操作完成。
  • fdatasync: 同步文件的数据和必要的元数据(不包括访问时间等非必要元数据),通常比 fsync 快一些。它也会等待完成。
  • msync: 用于同步 mmap 内存映射区域到文件。
  • sync: 同步所有已挂载文件系统上的缓冲数据到磁盘。
  • open with O_SYNC / O_DSYNC: 在打开文件时指定同步标志,使得后续的 write 操作具有同步语义。

8. 示例代码

下面的示例演示了如何使用 sync_file_range 来同步文件的特定部分。

#define _GNU_SOURCE // 启用 GNU 扩展以使用 sync_file_range
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>    // 包含 open, O_* flags, sync_file_range
#include <sys/stat.h> // 包含 open modes
#include <string.h>
#include <errno.h>
#include <time.h>     // 包含 clock_gettime

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

int main() {
    const char *filename = "sync_test_file.dat";
    const size_t file_size = 10 * 1024 * 1024; // 10 MiB
    const size_t chunk_size = 1024 * 1024;     // 1 MiB chunks
    int fd;
    char *data;
    struct timespec start, end;
    double elapsed_time;

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

    // 1. 分配内存并填充数据
    data = malloc(chunk_size);
    if (!data) {
        perror("malloc");
        exit(EXIT_FAILURE);
    }
    memset(data, 'A', chunk_size);

    // 2. 创建并打开文件 (O_DIRECT 通常不与普通 buffer 一起用,这里仅作演示文件操作)
    fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open");
        free(data);
        exit(EXIT_FAILURE);
    }
    printf("Created and opened file: %s\n", filename);

    // 3. 写入数据到文件
    printf("Writing %zu bytes (%.2f MiB) in %zu byte chunks...\n", file_size, file_size / (1024.0*1024.0), chunk_size);
    for (size_t i = 0; i < file_size; i += chunk_size) {
        if (write(fd, data, chunk_size) != (ssize_t)chunk_size) {
            perror("write");
            close(fd);
            free(data);
            unlink(filename); // 清理
            exit(EXIT_FAILURE);
        }
        // 每写入 2MB,演示一次部分同步
        if (i > 0 && (i % (2 * chunk_size)) == 0) {
             off64_t sync_offset = i - (2 * chunk_size);
             off64_t sync_nbytes = 2 * chunk_size;
             printf("  Written up to %zu bytes. Syncing range [%ld, %ld)...\n",
                    i, (long)sync_offset, (long)(sync_offset + sync_nbytes));

             // --- 关键演示:使用 sync_file_range 同步特定范围 ---
             // SYNC_FILE_RANGE_WRITE: 启动回写
             // SYNC_FILE_RANGE_WAIT_AFTER: 等待回写完成
             clock_gettime(CLOCK_MONOTONIC, &start);
             if (sync_file_range(fd, sync_offset, sync_nbytes,
                                 SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER) == -1) {
                 perror("sync_file_range");
                 // 不退出,继续演示
             }
             clock_gettime(CLOCK_MONOTONIC, &end);
             elapsed_time = time_diff_ms(start, end);
             printf("  -> sync_file_range for %ld bytes took %.2f ms\n", (long)sync_nbytes, elapsed_time);
        }
    }
    printf("Finished writing file.\n");

    // 4. 演示同步整个文件的最后部分
    printf("\n--- Syncing final part of the file ---\n");
    off64_t last_sync_offset = (file_size / chunk_size - 2) * chunk_size; // 倒数第二块开始
    off64_t last_sync_nbytes = file_size - last_sync_offset; // 到文件末尾
    printf("Syncing final range [%ld, EOF) using sync_file_range...\n", (long)last_sync_offset);

    clock_gettime(CLOCK_MONOTONIC, &start);
    if (sync_file_range(fd, last_sync_offset, last_sync_nbytes,
                        SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER) == -1) {
        perror("sync_file_range (final part)");
    }
    clock_gettime(CLOCK_MONOTONIC, &end);
    elapsed_time = time_diff_ms(start, end);
    printf("-> sync_file_range for final %ld bytes took %.2f ms\n", (long)last_sync_nbytes, elapsed_time);

    // 5. 对比:使用 fsync 同步整个文件
    printf("\n--- Comparing with fsync (syncs entire file) ---\n");
    printf("Calling fsync() to sync the entire file...\n");
    clock_gettime(CLOCK_MONOTONIC, &start);
    if (fsync(fd) == -1) {
        perror("fsync");
    }
    clock_gettime(CLOCK_MONOTONIC, &end);
    elapsed_time = time_diff_ms(start, end);
    printf("-> fsync() took %.2f ms\n", elapsed_time);

    // 6. 关闭文件
    close(fd);
    free(data);

    // 7. 验证文件大小
    struct stat sb;
    if (stat(filename, &sb) == 0) {
        printf("\n--- Verification ---\n");
        printf("File '%s' created successfully.\n", filename);
        printf("Final file size: %ld bytes (%.2f MiB)\n", (long)sb.st_size, sb.st_size / (1024.0*1024.0));
    }

    // 8. 清理 (注释掉这行可以保留文件用于检查)
    // unlink(filename);
    // printf("Deleted test file '%s'.\n", filename);

    printf("\n--- Summary ---\n");
    printf("1. sync_file_range allows synchronizing a specific byte range of a file.\n");
    printf("2. It can be more efficient than fsync for large files where only parts need syncing.\n");
    printf("3. Flags control whether to start writeback (WRITE) and/or wait for completion (WAIT_*).\n");
    printf("4. It provides fine-grained control for performance-critical applications like databases.\n");

    return 0;
}

9. 编译和运行

# 假设代码保存在 sync_file_range_example.c 中
# 注意:_GNU_SOURCE 可能已由 #define 定义,显式添加也无妨
gcc -D_GNU_SOURCE -o sync_file_range_example sync_file_range_example.c

# 运行程序
./sync_file_range_example

10. 预期输出 (时间值会有所不同)

--- Demonstrating sync_file_range ---
Created and opened file: sync_test_file.dat
Writing 10485760 bytes (10.00 MiB) in 1048576 byte chunks...
  Written up to 2097152 bytes. Syncing range [0, 2097152)...
  -> sync_file_range for 2097152 bytes took 5.23 ms
  Written up to 4194304 bytes. Syncing range [2097152, 4194304)...
  -> sync_file_range for 2097152 bytes took 3.12 ms
  Written up to 6291456 bytes. Syncing range [4194304, 6291456)...
  -> sync_file_range for 2097152 bytes took 2.87 ms
  Written up to 8388608 bytes. Syncing range [6291456, 8388608)...
  -> sync_file_range for 2097152 bytes took 3.01 ms
Finished writing file.

--- Syncing final part of the file ---
Syncing final range [8388608, EOF) using sync_file_range...
-> sync_file_range for final 2097152 bytes took 2.95 ms

--- Comparing with fsync (syncs entire file) ---
Calling fsync() to sync the entire file...
-> fsync() took 12.34 ms

--- Verification ---
File 'sync_test_file.dat' created successfully.
Final file size: 10485760 bytes (10.00 MiB)

--- Summary ---
1. sync_file_range allows synchronizing a specific byte range of a file.
2. It can be more efficient than fsync for large files where only parts need syncing.
3. Flags control whether to start writeback (WRITE) and/or wait for completion (WAIT_*).
4. It provides fine-grained control for performance-critical applications like databases.

11. 总结

sync_file_range 是一个强大的、用于精确控制文件数据同步的系统调用。它允许程序只同步文件的特定部分,而不是整个文件,从而在需要数据持久化保证的场景下提供了比 fsync 更好的性能。理解其标志位的含义(WRITEWAIT_BEFOREWAIT_AFTER)对于正确使用它至关重要。它是构建高性能、数据安全应用(如数据库、文件系统工具)的重要工具。

发表在 linux文章 | 留下评论

sync/syncfs系统调用及示例

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

1. 函数介绍

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

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

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

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

简单来说

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

2. 函数原型

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

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

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

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

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

3. 功能

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

4. 参数

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

5. 返回值

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

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

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

7. 相似函数或关联函数

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

8. 示例代码

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return 0;
}

9. 编译和运行

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

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

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

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

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

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

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

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

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

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

11. 总结

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

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

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

发表在 linux文章 | 留下评论

syscall系统调用及示例

syscall函数详解

syscall函数是Linux系统中访问系统调用的通用接口,它是用户程序与Linux内核之间的重要桥梁。可以将syscall想象成一个”万能钥匙”,它允许程序直接调用内核提供的各种底层功能,如文件操作、进程控制、网络通信、内存管理等。

1. 函数介绍

syscall函数是Linux系统中访问系统调用的通用接口,它是用户程序与Linux内核之间的重要桥梁。可以将syscall想象成一个”万能钥匙”,它允许程序直接调用内核提供的各种底层功能,如文件操作、进程控制、网络通信、内存管理等。

在Linux系统中,所有的系统功能都是通过系统调用来实现的。当你调用像open()read()write()这样的函数时,它们实际上都是通过syscall来与内核交互的。syscall函数提供了一种直接访问这些底层功能的方式,让你可以调用那些可能还没有标准C库封装的新系统调用。

使用场景:

  • 访问新的或非标准的系统调用
  • 学习和理解系统调用机制
  • 实现系统级编程和调试工具
  • 调用特定架构的系统调用
  • 性能敏感的应用程序直接调用内核功能

2. 函数原型

#include <unistd.h>
#include <sys/syscall.h>

long syscall(long number, ...);

3. 功能

syscall函数的主要功能是直接调用Linux内核提供的系统调用。它绕过了标准C库的封装层,直接与内核交互,这使得它能够访问所有可用的系统调用,包括那些还没有被标准库函数封装的调用。

4. 参数

  • number: 系统调用号
    • 类型:long
    • 含义:要调用的系统调用的唯一标识号,每个系统调用都有一个固定的编号
  • : 可变参数
    • 类型:可变参数列表
    • 含义:传递给系统调用的参数,参数的数量和类型取决于具体的系统调用

5. 返回值

  • 成功: 返回系统调用的返回值(通常是long类型)
  • 失败: 返回-1,并设置errno错误码
    • 各种错误码取决于具体的系统调用和错误情况

6. 相似函数或关联函数

  • 系统调用封装函数: 如open(), read(), write(), fork(), exec()等
  • syscall()的特定封装: 如tgkill(), gettid()等
  • 内联汇编: 直接使用汇编指令调用系统调用
  • ioctl(): 设备控制的系统调用
  • 系统调用表: /usr/include/asm/unistd.h中定义的系统调用号

7. 示例代码

示例1:基础syscall使用 – 常用系统调用

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

int main() {
    // 示例1: 使用syscall调用getpid()系统调用
    printf("=== 使用syscall调用getpid ===\n");
    pid_t pid = syscall(SYS_getpid);
    printf("进程ID: %d\n", pid);
    
    // 示例2: 使用syscall调用getppid()系统调用
    printf("\n=== 使用syscall调用getppid ===\n");
    pid_t ppid = syscall(SYS_getppid);
    printf("父进程ID: %d\n", ppid);
    
    // 示例3: 使用syscall调用getuid()系统调用
    printf("\n=== 使用syscall调用getuid ===\n");
    uid_t uid = syscall(SYS_getuid);
    printf("用户ID: %d\n", uid);
    
    // 示例4: 使用syscall调用geteuid()系统调用
    printf("\n=== 使用syscall调用geteuid ===\n");
    uid_t euid = syscall(SYS_geteuid);
    printf("有效用户ID: %d\n", euid);
    
    // 示例5: 使用syscall调用gettid()系统调用
    printf("\n=== 使用syscall调用gettid ===\n");
    pid_t tid = syscall(SYS_gettid);
    printf("线程ID: %d\n", tid);
    
    // 示例6: 使用syscall进行文件操作
    printf("\n=== 使用syscall进行文件操作 ===\n");
    
    // 创建或打开文件
    const char* filename = "syscall_test.txt";
    int fd = syscall(SYS_open, filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if(fd == -1) {
        perror("打开文件失败");
        return 1;
    }
    printf("文件描述符: %d\n", fd);
    
    // 写入数据
    const char* message = "Hello from syscall!\n";
    ssize_t bytes_written = syscall(SYS_write, fd, message, strlen(message));
    if(bytes_written == -1) {
        perror("写入文件失败");
    } else {
        printf("写入字节数: %zd\n", bytes_written);
    }
    
    // 关闭文件
    int close_result = syscall(SYS_close, fd);
    if(close_result == -1) {
        perror("关闭文件失败");
    } else {
        printf("文件已关闭\n");
    }
    
    // 示例7: 使用syscall读取文件
    printf("\n=== 使用syscall读取文件 ===\n");
    fd = syscall(SYS_open, filename, O_RDONLY);
    if(fd != -1) {
        char buffer[256];
        ssize_t bytes_read = syscall(SYS_read, fd, buffer, sizeof(buffer) - 1);
        if(bytes_read > 0) {
            buffer[bytes_read] = '\0';
            printf("读取内容: %s", buffer);
        }
        syscall(SYS_close, fd);
    }
    
    // 清理测试文件
    syscall(SYS_unlink, filename);
    
    return 0;
}

示例2:高级syscall使用 – 进程和内存管理

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>

int main() {
    printf("=== 高级syscall使用示例 ===\n");
    
    // 示例1: 使用mmap系统调用
    printf("\n1. 使用mmap进行内存映射:\n");
    void* mapped_memory = (void*)syscall(SYS_mmap, 
                                        NULL,           // 地址
                                        4096,           // 长度
                                        PROT_READ | PROT_WRITE,  // 保护
                                        MAP_PRIVATE | MAP_ANONYMOUS, // 标志
                                        -1,             // 文件描述符
                                        0);             // 偏移
    
    if(mapped_memory == MAP_FAILED) {
        perror("mmap失败");
    } else {
        printf("成功映射内存地址: %p\n", mapped_memory);
        
        // 在映射的内存中写入数据
        strcpy((char*)mapped_memory, "Hello from mapped memory!");
        printf("内存内容: %s\n", (char*)mapped_memory);
        
        // 取消内存映射
        syscall(SYS_munmap, mapped_memory, 4096);
        printf("内存映射已取消\n");
    }
    
    // 示例2: 使用brk系统调用调整程序断点
    printf("\n2. 使用brk调整程序断点:\n");
    void* current_brk = (void*)syscall(SYS_brk, 0);
    printf("当前程序断点: %p\n", current_brk);
    
    // 扩展堆空间
    void* new_brk = (void*)syscall(SYS_brk, (long)current_brk + 8192);
    if(new_brk != (void*)-1) {
        printf("新程序断点: %p\n", new_brk);
        printf("堆空间扩展了 %ld 字节\n", (long)new_brk - (long)current_brk);
        
        // 恢复原来的断点
        syscall(SYS_brk, (long)current_brk);
    }
    
    // 示例3: 使用fork系统调用创建子进程
    printf("\n3. 使用fork创建子进程:\n");
    pid_t pid = syscall(SYS_fork);
    
    if(pid == -1) {
        perror("fork失败");
    } else if(pid == 0) {
        // 子进程
        printf("子进程: PID=%d, PPID=%d\n", 
               syscall(SYS_getpid), syscall(SYS_getppid));
        exit(0);
    } else {
        // 父进程
        printf("父进程: PID=%d, 创建了子进程 PID=%d\n", 
               syscall(SYS_getpid), pid);
        // 等待子进程结束
        int status;
        syscall(SYS_wait4, pid, &status, 0, NULL);
        printf("子进程已结束\n");
    }
    
    // 示例4: 使用gettimeofday系统调用获取时间
    printf("\n4. 使用gettimeofday获取时间:\n");
    struct {
        long tv_sec;
        long tv_usec;
    } tv;
    
    int result = syscall(SYS_gettimeofday, &tv, NULL);
    if(result == 0) {
        printf("当前时间: %ld 秒 %ld 微秒\n", tv.tv_sec, tv.tv_usec);
    } else {
        perror("gettimeofday失败");
    }
    
    // 示例5: 使用uname系统调用获取系统信息
    printf("\n5. 使用uname获取系统信息:\n");
    struct {
        char sysname[65];
        char nodename[65];
        char release[65];
        char version[65];
        char machine[65];
    } uts;
    
    result = syscall(SYS_uname, &uts);
    if(result == 0) {
        printf("系统名称: %s\n", uts.sysname);
        printf("节点名称: %s\n", uts.nodename);
        printf("内核版本: %s\n", uts.release);
        printf("系统版本: %s\n", uts.version);
        printf("硬件架构: %s\n", uts.machine);
    } else {
        perror("uname失败");
    }
    
    return 0;
}

示例3:自定义系统调用和错误处理

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

// 通用的syscall错误处理函数
void handle_syscall_error(const char* syscall_name) {
    fprintf(stderr, "系统调用 %s 失败: ", syscall_name);
    switch(errno) {
        case EPERM:
            fprintf(stderr, "操作不被允许\n");
            break;
        case ENOENT:
            fprintf(stderr, "文件或目录不存在\n");
            break;
        case EACCES:
            fprintf(stderr, "权限不足\n");
            break;
        case EFAULT:
            fprintf(stderr, "地址错误\n");
            break;
        case EINVAL:
            fprintf(stderr, "无效参数\n");
            break;
        case ENOMEM:
            fprintf(stderr, "内存不足\n");
            break;
        case EBUSY:
            fprintf(stderr, "资源忙\n");
            break;
        default:
            fprintf(stderr, "%s\n", strerror(errno));
            break;
    }
}

// 获取系统调用名称的辅助函数
const char* get_syscall_name(long syscall_num) {
    switch(syscall_num) {
        case SYS_getpid: return "getpid";
        case SYS_getppid: return "getppid";
        case SYS_getuid: return "getuid";
        case SYS_geteuid: return "geteuid";
        case SYS_getgid: return "getgid";
        case SYS_getegid: return "getegid";
        default: return "unknown";
    }
}

// 安全的syscall包装函数
long safe_syscall(long number, const char* name) {
    errno = 0;  // 清除之前的错误
    long result = syscall(number);
    
    if(result == -1) {
        handle_syscall_error(name);
    }
    
    return result;
}

int main() {
    printf("=== 自定义系统调用封装和错误处理 ===\n");
    
    // 测试各种系统调用
    struct {
        long number;
        const char* name;
        const char* description;
    } syscalls[] = {
        {SYS_getpid, "getpid", "获取进程ID"},
        {SYS_getppid, "getppid", "获取父进程ID"},
        {SYS_getuid, "getuid", "获取用户ID"},
        {SYS_geteuid, "geteuid", "获取有效用户ID"},
        {SYS_getgid, "getgid", "获取组ID"},
        {SYS_getegid, "getegid", "获取有效组ID"}
    };
    
    int num_syscalls = sizeof(syscalls) / sizeof(syscalls[0]);
    
    printf("\n系统信息获取测试:\n");
    printf("----------------------------\n");
    
    for(int i = 0; i < num_syscalls; i++) {
        printf("%-10s: ", syscalls[i].description);
        long result = safe_syscall(syscalls[i].number, syscalls[i].name);
        if(result != -1) {
            printf("%ld\n", result);
        }
    }
    
    // 测试错误处理 - 调用不存在的系统调用
    printf("\n错误处理测试:\n");
    printf("----------------------------\n");
    printf("调用不存在的系统调用 (编号999999):\n");
    errno = 0;
    long result = syscall(999999);
    if(result == -1) {
        handle_syscall_error("invalid_syscall");
    }
    
    // 测试权限相关的系统调用
    printf("\n权限相关测试:\n");
    printf("----------------------------\n");
    
    // 获取当前工作目录 (使用getcwd系统调用)
    printf("当前工作目录测试:\n");
    char cwd[1024];
    errno = 0;
    long cwd_result = syscall(SYS_getcwd, cwd, sizeof(cwd));
    if(cwd_result != -1) {
        printf("当前目录: %s\n", cwd);
    } else {
        handle_syscall_error("getcwd");
    }
    
    // 测试系统资源使用情况
    printf("\n系统资源使用测试:\n");
    printf("----------------------------\n");
    
    struct {
        long ru_utime_sec;   // 用户CPU时间(秒)
        long ru_utime_usec;  // 用户CPU时间(微秒)
        long ru_stime_sec;   // 系统CPU时间(秒)
        long ru_stime_usec;  // 系统CPU时间(微秒)
        long ru_maxrss;      // 最大常驻集大小
        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;      // 消息发送数
        long ru_msgrcv;      // 消息接收数
        long ru_nsignals;    // 信号接收数
        long ru_nvcsw;       // 自愿上下文切换
        long ru_nivcsw;      // 非自愿上下文切换
    } usage;
    
    errno = 0;
    result = syscall(SYS_getrusage, RUSAGE_SELF, &usage);
    if(result == 0) {
        printf("用户CPU时间: %ld.%06ld 秒\n", usage.ru_utime_sec, usage.ru_utime_usec);
        printf("系统CPU时间: %ld.%06ld 秒\n", usage.ru_stime_sec, usage.ru_stime_usec);
        printf("最大内存使用: %ld KB\n", usage.ru_maxrss);
    } else {
        handle_syscall_error("getrusage");
    }
    
    // 演示syscall的优势 - 访问新系统调用
    printf("\n新系统调用演示:\n");
    printf("----------------------------\n");
    printf("注意: 某些新系统调用可能在当前内核版本中不可用\n");
    
    // 尝试调用eventfd系统调用(如果可用)
    errno = 0;
    result = syscall(SYS_eventfd, 0);
    if(result != -1) {
        printf("eventfd系统调用成功,文件描述符: %ld\n", result);
        close(result);  // 关闭文件描述符
    } else {
        printf("eventfd系统调用不可用或失败\n");
    }
    
    printf("\n=== 测试完成 ===\n");
    
    return 0;
}

示例4:性能对比 – syscall vs 标准库函数

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <string.h>

// 性能测试宏
#define ITERATIONS 1000000

// 获取当前时间(微秒)
long get_time_usec() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec * 1000000 + tv.tv_usec;
}

int main() {
    printf("=== syscall vs 标准库函数性能对比 ===\n");
    printf("测试迭代次数: %d 次\n\n", ITERATIONS);
    
    long start_time, end_time;
    long syscall_time, library_time;
    
    // 测试1: getpid系统调用性能
    printf("1. getpid() 性能测试:\n");
    
    // 使用syscall
    start_time = get_time_usec();
    for(int i = 0; i < ITERATIONS; i++) {
        syscall(SYS_getpid);
    }
    end_time = get_time_usec();
    syscall_time = end_time - start_time;
    
    // 使用标准库函数
    start_time = get_time_usec();
    for(int i = 0; i < ITERATIONS; i++) {
        getpid();
    }
    end_time = get_time_usec();
    library_time = end_time - start_time;
    
    printf("   syscall方式: %ld 微秒\n", syscall_time);
    printf("   标准库方式:  %ld 微秒\n", library_time);
    printf("   性能差异:    %ld 微秒 (%.2f%%)\n", 
           syscall_time - library_time,
           ((double)(syscall_time - library_time) / library_time) * 100);
    
    // 测试2: getuid系统调用性能
    printf("\n2. getuid() 性能测试:\n");
    
    // 使用syscall
    start_time = get_time_usec();
    for(int i = 0; i < ITERATIONS; i++) {
        syscall(SYS_getuid);
    }
    end_time = get_time_usec();
    syscall_time = end_time - start_time;
    
    // 使用标准库函数
    start_time = get_time_usec();
    for(int i = 0; i < ITERATIONS; i++) {
        getuid();
    }
    end_time = get_time_usec();
    library_time = end_time - start_time;
    
    printf("   syscall方式: %ld 微秒\n", syscall_time);
    printf("   标准库方式:  %ld 微秒\n", library_time);
    printf("   性能差异:    %ld 微秒 (%.2f%%)\n", 
           syscall_time - library_time,
           ((double)(syscall_time - library_time) / library_time) * 100);
    
    // 测试3: 文件操作性能对比
    printf("\n3. 文件操作性能测试:\n");
    
    const char* test_file = "perf_test.txt";
    const char* test_data = "Performance test data\n";
    
    // 使用syscall进行文件操作
    start_time = get_time_usec();
    for(int i = 0; i < 1000; i++) {  // 减少迭代次数以避免文件系统压力
        int fd = syscall(SYS_open, test_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
        if(fd != -1) {
            syscall(SYS_write, fd, test_data, strlen(test_data));
            syscall(SYS_close, fd);
        }
    }
    end_time = get_time_usec();
    long syscall_file_time = end_time - start_time;
    
    // 使用标准库函数进行文件操作
    start_time = get_time_usec();
    for(int i = 0; i < 1000; i++) {
        FILE* fp = fopen(test_file, "w");
        if(fp != NULL) {
            fwrite(test_data, 1, strlen(test_data), fp);
            fclose(fp);
        }
    }
    end_time = get_time_usec();
    long library_file_time = end_time - start_time;
    
    printf("   syscall方式: %ld 微秒\n", syscall_file_time);
    printf("   标准库方式:  %ld 微秒\n", library_file_time);
    printf("   性能差异:    %ld 微秒 (%.2f%%)\n", 
           syscall_file_time - library_file_time,
           ((double)(syscall_file_time - library_file_time) / library_file_time) * 100);
    
    // 清理测试文件
    unlink(test_file);
    
    // 总结
    printf("\n=== 性能测试总结 ===\n");
    printf("1. 系统调用通常比标准库函数更快,因为避免了额外的封装开销\n");
    printf("2. 但在某些情况下,标准库函数可能有优化(如缓存)\n");
    printf("3. syscall更适合需要精确控制或访问新功能的场景\n");
    printf("4. 标准库函数更适合日常开发,提供更好的可移植性和错误处理\n");
    
    return 0;
}

编译和运行

# 编译示例1
gcc -o syscall_example1 syscall_example1.c
./syscall_example1

# 编译示例2
gcc -o syscall_example2 syscall_example2.c
./syscall_example2

# 编译示例3
gcc -o syscall_example3 syscall_example3.c
./syscall_example3

# 编译示例4
gcc -o syscall_example4 syscall_example4.c
./syscall_example4

重要注意事项

  1. 系统调用号: 不同架构的系统调用号可能不同,通常在/usr/include/asm/unistd.h中定义
  2. 错误处理: syscall失败时返回-1并设置errno,必须进行检查
  3. 参数类型: 参数类型必须与系统调用期望的类型匹配
  4. 可移植性: syscall是Linux特有的,不适用于其他操作系统
  5. 性能: syscall通常比标准库函数更快,但标准库函数更安全易用
  6. 权限: 某些系统调用需要特定权限才能执行

通过这些示例,你可以理解syscall作为系统调用接口的强大功能,它为底层系统编程提供了直接访问内核功能的能力。

发表在 linux文章 | 标签为 | 留下评论

Uchardet库中的utf-8的置信度计算方法

Uchardet库中的utf-8的置信度计算方法
好的,感谢您提供 nsUTF8Prober.cpp 的代码。这让我们能够清晰地看到 uchardet 中 UTF-8 编码探测器的具体实现,特别是其置信度计算逻辑。

代码分析:nsUTF8Prober 置信度计算

1. 核心逻辑

该探测器的核心思想是:通过验证 UTF-8 编码规则来判断文本是否为 UTF-8。它使用一个状态机 (mCodingSM) 来跟踪字节序列是否符合 UTF-8 规范。

  • Reset(): 初始化探测器状态,重置状态机、多字节字符计数器 (mNumOfMBChar) 和探测状态 (mState)。
  • HandleData(): 这是处理输入字节流的主要函数。
    • 它逐字节地将数据传递给内部的状态机 (mCodingSM->NextState(aBuf[i]))。
    • 状态机返回 eItsMe 表示检测到明确违反 UTF-8 规则的序列,探测器状态变为 eFoundIt(虽然名字是 FoundIt,但在这里实际上是“确认不是 UTF-8”)。
    • 状态机返回 eStart 表示一个完整的 UTF-8 字符(可能是单字节 ASCII 或多字节序列)已被成功识别。
      • 如果这个字符是多字节的 (mCodingSM->GetCurrentCharLen() >= 2),则 mNumOfMBChar 计数器增加。
      • 代码还包含了构建 Unicode 码点 (currentCodePoint) 的逻辑,并将其存入 codePointBuffer(这可能用于后续更复杂的分析,或者是为了提供解码后的内容)。
    • 其他状态 (eErroreItsMe 外的其他中间状态) 表示正在处理一个多字节序列。
    • 关键优化: 在 HandleData 的末尾,有一个检查:if (mState == eDetecting) if (mNumOfMBChar > ENOUGH_CHAR_THRESHOLD && GetConfidence(0) > SHORTCUT_THRESHOLD) mState = eFoundIt; 这意味着,如果已经识别出足够多的多字节 UTF-8 字符 (mNumOfMBChar > 256),并且根据当前信息计算出的置信度也足够高 (GetConfidence(0) > SHORTCUT_THRESHOLD,虽然 SHORTCUT_THRESHOLD 在此文件中未定义,但通常在 .h 文件或主控逻辑中定义),探测器可以提前结束并确认文本很可能是 UTF-8。

2. 置信度计算 (GetConfidence)

这是最核心的部分,代码非常简洁:

#define ONE_CHAR_PROB   (float)0.50

float nsUTF8Prober::GetConfidence(int candidate)
{
  // 如果识别出的多字节 UTF-8 字符少于 6 个
  if (mNumOfMBChar < 6)
  {
    // 使用一种“悲观”模型计算置信度
    float unlike = 0.5f; // 初始假设文本不像 UTF-8 的概率是 50%

    // 对于每个已识别的多字节字符,都假设它“碰巧”符合 UTF-8 规则的概率是 50%
    // 因此,所有 N 个字符都“碰巧”符合的概率是 (0.5)^N
    for (PRUint32 i = 0; i < mNumOfMBChar; i++)
      unlike *= ONE_CHAR_PROB; // ONE_CHAR_PROB = 0.5

    // 置信度 = 1 - (文本不像 UTF-8 的概率)
    // 即,文本像 UTF-8 的概率
    return (float)1.0 - unlike;
  }
  else
  {
    // 如果已经识别出 6 个或更多多字节 UTF-8 字符
    // 认为非常可能是 UTF-8,返回一个很高的置信度 (0.99)
    return (float)0.99;
  }
}

3. 置信度计算方法解析

nsUTF8Prober 的置信度计算采用了一种基于统计显著性的启发式方法

  1. 少于 6 个多字节字符时 (Low Confidence Mode):
    • 它假设,如果一段文本不是 UTF-8,但恰好包含了 N 个看起来像有效 UTF-8 多字节序列的字节串,那么这种“巧合”发生的概率是 (0.5)^N。这里的 0.5 (ONE_CHAR_PROB) 是一个经验性的估计值,代表一个随机字节序列“碰巧”符合 UTF-8 多字节起始规则的概率。
    • 因此,它认为这段文本 UTF-8 的置信度为 1 - (0.5)^N
    • 例子:
      • mNumOfMBChar = 0confidence = 1.0 - 0.5 = 0.5 (完全不确定)
      • mNumOfMBChar = 1confidence = 1.0 - (0.5 * 0.5) = 1.0 - 0.25 = 0.75
      • mNumOfMBChar = 2confidence = 1.0 - (0.25 * 0.5) = 1.0 - 0.125 = 0.875
      • mNumOfMBChar = 3confidence = 1.0 - (0.125 * 0.5) = 1.0 - 0.0625 = 0.9375
      • mNumOfMBChar = 5confidence = 1.0 - (0.0625 * 0.5 * 0.5) = 1.0 - 0.015625 = 0.984375
    • 这种方法直观地反映了:发现的有效 UTF-8 多字节字符越多,它就越不可能是巧合,因此置信度越高。
  2. 6 个或更多多字节字符时 (High Confidence Mode):
    • 一旦识别出 6 个或更多的多字节 UTF-8 字符,算法就认为有足够的证据表明文本是 UTF-8 编码的,因此直接返回一个非常高的、接近确定的置信度 0.99。这是一种优化和经验判断,认为达到这个数量级后,误判的可能性极低。

4. 总结

nsUTF8Prober 的置信度计算方法相对简单但有效:

  • 核心依据:识别出的有效 UTF-8 多字节字符的数量 (mNumOfMBChar)。
  • 计算逻辑
    • 少量 (<6):使用 (1 - (0.5)^N) 的公式,基于“巧合概率”的倒数来估算置信度。
    • 大量 (>=6):直接返回高置信度 0.99
  • 优点
    • 计算简单快速。
    • 对于明显符合或不符合 UTF-8 规则的文本,能快速给出合理判断。
    • 通过状态机严格检查 UTF-8 规则,一旦发现违规立即否定。
  • 特点
    • 它更侧重于排除法:通过验证规则和统计显著性来增加“是 UTF-8”的信心,而不是直接计算“是 UTF-8”的概率。
    • 0.5 这个概率值是经验设定,可能在不同数据集下表现略有差异。

这种设计体现了 uchardet(及 chardet)注重实用性和效率的特点,通过简单的启发式规则在大多数情况下能给出较为可靠的置信度评估。

发表在 linux文章 | 留下评论

IP协议栈在进行IP分片重组时注意事项

IP协议栈在处理IP分片(IP Fragmentation)时,对分片长度的设定遵循一系列规范,这些规范主要由 IPv4IPv6 协议标准定义,特别是 RFC 791(IPv4)和 RFC 2460(IPv6)。以下是关于IP分片长度设定的主要规范:


一、IPv4 分片长度规范(RFC 791)

1. 最大传输单元(MTU)限制

  • 每个链路层网络有其最大传输单元(MTU),如以太网的MTU通常为 1500字节
  • 当IP数据报的大小超过出接口的MTU时,IP层必须进行分片。

2. IP头部中的关键字段

IPv4头部包含以下与分片相关的字段:

字段长度作用
Identification16位标识同一原始数据报的所有分片
Flags3位包含:DF(Don’t Fragment)、MF(More Fragments)
Fragment Offset13位指示该分片在原始数据报中的偏移量(以8字节为单位)

3. 分片长度的基本要求

  • 分片的数据部分必须是8字节的整数倍
  • 因为 Fragment Offset 字段以 8字节 为单位,所以每个分片(除最后一个)的数据部分必须能被8整除。
  • 例如:若MTU=1500,IP头部20字节,则数据部分最多1480字节。但为了满足8字节对齐,实际使用的数据部分为 1480 – (1480 % 8) = 1472 字节
  • 所以第一个分片可携带1472字节数据,偏移为0;下一个偏移为1472/8=184,依此类推。
  • 最小分片大小
  • 每个分片必须至少携带 8字节数据(否则无法形成有效分片),加上20字节IP头部,最小分片总长度为28字节。
  • 实际中,大多数链路要求最小帧长更高(如以太网为64字节),因此实际分片不会太小。

4. DF位(Don’t Fragment)

  • 如果IP头部中 DF=1,路由器不能对该数据报进行分片。
  • 若数据报过大且DF=1,则路由器丢弃该报文,并返回 ICMP “Fragmentation Needed” 错误(类型3,代码4)。

5. MF位(More Fragments)

  • 除最后一个分片外,所有分片的 MF=1
  • 最后一个分片的 MF=0

二、IPv6 分片规范(RFC 2460)

1. 禁止在中间路由器上分片

  • IPv6 禁止中间路由器对数据报进行分片
  • 分片只能由源主机进行(使用分片扩展头部)。

2. 路径MTU发现(PMTUD)是必须的

  • IPv6依赖路径MTU发现机制(PMTUD)来确定整条路径上的最小MTU。
  • 源主机根据PMTUD结果决定是否需要分片。

3. 分片扩展头部

  • 使用“分片扩展头部”(Fragment Extension Header)来支持分片。
  • 包含:
  • Identificaiton(32位)
  • Offset(13位,同IPv4,以8字节为单位)
  • M flag(1位,表示是否有更多分片)

4. 分片对齐要求

  • 与IPv4相同,分片偏移以8字节为单位,因此每个分片的数据部分(除最后一个)必须是8字节的整数倍。

5. 最小链路MTU

  • IPv6规定所有链路必须支持至少 1280字节 的MTU。
  • 实际中推荐使用 1500字节 以太网MTU。

三、通用规范总结

规范IPv4IPv6
是否允许中间路由器分片否(仅源主机可分片)
分片最小数据单元8字节对齐8字节对齐
分片偏移单位8字节8字节
分片标识字段16位32位
是否强制使用PMTUD否(可选)是(推荐或必需)
DF位作用控制是否允许分片无DF位,但PMTUD隐含此功能

四、实际应用中的建议

  1. 避免分片:分片会增加丢包重传开销、重组失败风险,建议使用路径MTU发现(PMTUD)来避免分片。
  2. TCP MSS(Maximum Segment Size):通常设置为 MTU - IP头 - TCP头 = 1500 - 20 - 20 = 1460 字节,以避免IP层分片。
  3. 防火墙和NAT设备可能丢弃分片包,导致通信失败。

五、参考标准

  • RFC 791 – Internet Protocol (IPv4)
  • RFC 2460 – Internet Protocol, Version 6 (IPv6)
  • RFC 1191 – Path MTU Discovery (IPv4)
  • RFC 1981 – Path MTU Discovery for IPv6
  • RFC 8200 – Updated IPv6 Specification

总结

IP协议栈对分片长度的核心规范包括:

  • 分片数据长度必须是8字节的整数倍(由Fragment Offset单位决定);
  • 每个分片包含IP头部+数据部分
  • IPv4允许路由器分片,IPv6仅允许源主机分片
  • 使用MTU和PMTUD控制分片行为,尽量避免分片以提高性能和可靠性。

合理设计应用数据大小、启用PMTUD、设置合适的MSS,是避免IP分片问题的关键。

发表在 linux文章 | 留下评论

fadvise64系统调用及示例

fadvise64 – 文件访问建议

函数介绍

fadvise64是一个Linux系统调用,用于向内核提供关于文件访问模式的建议。它帮助内核优化文件I/O操作,提高性能。

函数原型

#include <fcntl.h>
#include <sys/syscall.h>
#include <unistd.h>

int fadvise64(int fd, off_t offset, off_t len, int advice);

功能

向内核提供文件访问模式建议,帮助内核优化缓存和预读策略。

参数

  • int fd: 文件描述符
  • off_t offset: 建议适用的文件起始偏移量
  • off_t len: 建议适用的文件长度(0表示到文件末尾)
  • int advice: 访问建议类型
    • POSIX_FADV_NORMAL: 普通访问模式(默认)
    • POSIX_FADV_SEQUENTIAL: 顺序访问
    • POSIX_FADV_RANDOM: 随机访问
    • POSIX_FADV_NOREUSE: 数据只访问一次
    • POSIX_FADV_WILLNEED: 数据即将被访问
    • POSIX_FADV_DONTNEED: 数据不再需要

返回值

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

特殊限制

  • 需要Linux 2.5.60以上内核支持
  • 某些文件系统可能不完全支持
  • 建议只是提示,内核可能忽略

相似函数

  • madvise(): 内存访问建议
  • readahead(): 文件预读
  • posix_fadvise(): POSIX标准版本

示例代码

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

// 系统调用包装
static int fadvise64_wrapper(int fd, off_t offset, off_t len, int advice) {
    return syscall(__NR_fadvise64, fd, offset, len, advice);
}

// 创建测试文件
int create_test_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) {
        memset(buffer, 'A', 4096);
        for (size_t i = 0; i < size; i += 4096) {
            size_t write_size = (size - i > 4096) ? 4096 : (size - i);
            write(fd, buffer, write_size);
        }
        free(buffer);
    }
    
    return fd;
}

int main() {
    int fd;
    int result;
    
    printf("=== Fadvise64 函数示例 ===\n");
    
    // 示例1: 基本使用
    printf("\n示例1: 基本使用\n");
    
    // 创建大文件用于测试
    fd = create_test_file("test_fadvise64.dat", 1024 * 1024); // 1MB
    if (fd == -1) {
        exit(EXIT_FAILURE);
    }
    printf("创建测试文件: test_fadvise64.dat (1MB)\n");
    
    close(fd);
    
    // 重新打开文件进行测试
    fd = open("test_fadvise64.dat", O_RDONLY);
    if (fd == -1) {
        perror("打开测试文件失败");
        unlink("test_fadvise64.dat");
        exit(EXIT_FAILURE);
    }
    printf("打开测试文件进行fadvise64测试\n");
    
    // 示例2: 不同的访问建议
    printf("\n示例2: 不同的访问建议\n");
    
    // POSIX_FADV_NORMAL - 普通访问模式
    result = fadvise64_wrapper(fd, 0, 0, POSIX_FADV_NORMAL);
    if (result == 0) {
        printf("设置POSIX_FADV_NORMAL成功\n");
    } else {
        printf("设置POSIX_FADV_NORMAL失败: %s\n", strerror(errno));
    }
    
    // POSIX_FADV_SEQUENTIAL - 顺序访问
    result = fadvise64_wrapper(fd, 0, 1024*1024, POSIX_FADV_SEQUENTIAL);
    if (result == 0) {
        printf("设置POSIX_FADV_SEQUENTIAL成功\n");
        printf("提示内核将进行顺序访问,优化预读策略\n");
    }
    
    // POSIX_FADV_RANDOM - 随机访问
    result = fadvise64_wrapper(fd, 0, 1024*1024, POSIX_FADV_RANDOM);
    if (result == 0) {
        printf("设置POSIX_FADV_RANDOM成功\n");
        printf("提示内核将进行随机访问,减少预读\n");
    }
    
    // POSIX_FADV_WILLNEED - 数据即将被访问
    result = fadvise64_wrapper(fd, 0, 64*1024, POSIX_FADV_WILLNEED);
    if (result == 0) {
        printf("设置POSIX_FADV_WILLNEED成功\n");
        printf("提示内核预读前64KB数据\n");
    }
    
    // POSIX_FADV_DONTNEED - 数据不再需要
    result = fadvise64_wrapper(fd, 0, 64*1024, POSIX_FADV_DONTNEED);
    if (result == 0) {
        printf("设置POSIX_FADV_DONTNEED成功\n");
        printf("提示内核可以丢弃前64KB数据的缓存\n");
    }
    
    // POSIX_FADV_NOREUSE - 数据只访问一次
    result = fadvise64_wrapper(fd, 64*1024, 64*1024, POSIX_FADV_NOREUSE);
    if (result == 0) {
        printf("设置POSIX_FADV_NOREUSE成功\n");
        printf("提示内核64KB-128KB范围的数据只访问一次\n");
    }
    
    // 示例3: 错误处理演示
    printf("\n示例3: 错误处理演示\n");
    
    // 使用无效的文件描述符
    result = fadvise64_wrapper(999, 0, 1024, POSIX_FADV_NORMAL);
    if (result == -1) {
        if (errno == EBADF) {
            printf("无效文件描述符错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 使用无效的建议类型
    result = fadvise64_wrapper(fd, 0, 1024, 999);
    if (result == -1) {
        if (errno == EINVAL) {
            printf("无效建议类型错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 使用负的偏移量
    result = fadvise64_wrapper(fd, -1024, 1024, POSIX_FADV_NORMAL);
    if (result == -1) {
        printf("负偏移量处理: %s\n", strerror(errno));
    }
    
    // 示例4: 实际使用场景演示
    printf("\n示例4: 实际使用场景演示\n");
    
    // 场景1: 大文件顺序读取
    printf("场景1: 大文件顺序读取优化\n");
    printf("处理大日志文件的代码示例:\n");
    printf("int process_large_log(const char* filename) {\n");
    printf("    int fd = open(filename, O_RDONLY);\n");
    printf("    if (fd == -1) return -1;\n");
    printf("    \n");
    printf("    // 提示内核将顺序访问整个文件\n");
    printf("    posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);\n");
    printf("    \n");
    printf("    // 读取处理文件...\n");
    printf("    char buffer[8192];\n");
    printf("    ssize_t bytes;\n");
    printf("    while ((bytes = read(fd, buffer, sizeof(buffer))) > 0) {\n");
    printf("        // 处理数据\n");
    printf("    }\n");
    printf("    \n");
    printf("    close(fd);\n");
    printf("    return 0;\n");
    printf("}\n\n");
    
    // 场景2: 随机访问数据库文件
    printf("场景2: 随机访问数据库文件\n");
    printf("数据库文件访问优化:\n");
    printf("int access_database_file(const char* filename) {\n");
    printf("    int fd = open(filename, O_RDWR);\n");
    printf("    if (fd == -1) return -1;\n");
    printf("    \n");
    printf("    // 提示内核将随机访问文件\n");
    printf("    posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM);\n");
    printf("    \n");
    printf("    // 根据需要预读特定区域\n");
    printf("    posix_fadvise(fd, index_offset, index_size, POSIX_FADV_WILLNEED);\n");
    printf("    \n");
    printf("    // 访问完成后释放不需要的缓存\n");
    printf("    posix_fadvise(fd, old_data_offset, old_data_size, POSIX_FADV_DONTNEED);\n");
    printf("    \n");
    printf("    close(fd);\n");
    printf("    return 0;\n");
    printf("}\n\n");
    
    // 示例5: 不同建议类型的效果说明
    printf("示例5: 不同建议类型的效果说明\n");
    
    printf("POSIX_FADV_NORMAL:\n");
    printf("  - 默认访问模式\n");
    printf("  - 使用系统默认的预读和缓存策略\n");
    printf("  - 适用于一般情况\n\n");
    
    printf("POSIX_FADV_SEQUENTIAL:\n");
    printf("  - 优化顺序访问\n");
    printf("  - 增加预读量\n");
    printf("  - 适用于大文件顺序读取\n");
    printf("  - 提高顺序读取性能\n\n");
    
    printf("POSIX_FADV_RANDOM:\n");
    printf("  - 优化随机访问\n");
    printf("  - 减少或禁用预读\n");
    printf("  - 适用于数据库、索引文件\n");
    printf("  - 减少不必要的内存占用\n\n");
    
    printf("POSIX_FADV_NOREUSE:\n");
    printf("  - 数据只访问一次\n");
    printf("  - 访问后尽快释放缓存\n");
    printf("  - 适用于一次性处理的大文件\n");
    printf("  - 节省内存资源\n\n");
    
    printf("POSIX_FADV_WILLNEED:\n");
    printf("  - 数据即将被访问\n");
    printf("  - 提前预读数据到缓存\n");
    printf("  - 适用于已知访问模式的场景\n");
    printf("  - 减少实际访问时的等待\n\n");
    
    printf("POSIX_FADV_DONTNEED:\n");
    printf("  - 数据不再需要\n");
    printf("  - 尽快释放缓存空间\n");
    printf("  - 适用于处理完成的数据\n");
    printf("  - 释放系统资源\n\n");
    
    // 示例6: 性能测试演示
    printf("示例6: 性能影响演示\n");
    
    printf("fadvise64对性能的影响:\n");
    printf("1. 正确使用可显著提高I/O性能\n");
    printf("2. 错误使用可能导致性能下降\n");
    printf("3. 效果因文件系统和硬件而异\n");
    printf("4. 大文件效果更明显\n");
    printf("5. 需要根据实际访问模式选择\n\n");
    
    // 示例7: 实际应用建议
    printf("示例7: 实际应用建议\n");
    
    printf("使用fadvise64的最佳实践:\n");
    printf("1. 在文件打开后尽早设置建议\n");
    printf("2. 根据实际访问模式选择合适的建议\n");
    printf("3. 对于大文件效果更明显\n");
    printf("4. 不要过度使用,避免增加系统负担\n");
    printf("5. 在长时间运行的应用中适时调整\n");
    printf("6. 测试不同建议对性能的影响\n\n");
    
    printf("常见应用场景:\n");
    printf("- 大文件处理和分析\n");
    printf("- 数据库系统\n");
    printf("- 日志处理系统\n");
    printf("- 备份和归档工具\n");
    printf("- 媒体播放器\n");
    printf("- 科学计算应用\n\n");
    
    // 示例8: 与相关函数的对比
    printf("示例8: 与相关函数的对比\n");
    
    printf("fadvise64 vs madvise:\n");
    printf("fadvise64:\n");
    printf("  - 针对文件I/O\n");
    printf("  - 影响文件缓存策略\n");
    printf("  - 在文件描述符上操作\n\n");
    
    printf("madvise:\n");
    printf("  - 针对内存映射\n");
    printf("  - 影响内存管理策略\n");
    printf("  - 在内存地址上操作\n\n");
    
    printf("fadvise64 vs readahead:\n");
    printf("fadvise64:\n");
    printf("  - 更通用的建议机制\n");
    printf("  - 支持多种访问模式\n");
    printf("  - 可以指定文件区域\n\n");
    
    printf("readahead:\n");
    printf("  - 专门用于预读\n");
    printf("  - 立即执行预读操作\n");
    printf("  - 较为直接但不够灵活\n\n");
    
    // 清理资源
    close(fd);
    unlink("test_fadvise64.dat");
    
    printf("总结:\n");
    printf("fadvise64是Linux提供的文件访问优化机制\n");
    printf("通过向内核提供访问建议来优化性能\n");
    printf("支持多种访问模式的优化\n");
    printf("是处理大文件和特定访问模式的重要工具\n");
    printf("需要根据实际应用场景合理使用\n");
    
    return 0;
}
发表在 linux文章 | 留下评论

exit系统调用及示例

好的,我们继续按照您的列表顺序,介绍下一个函数。


1. 函数介绍

exit 是一个 C 标准库函数(而非直接的系统调用,但它会调用底层的 _exit 系统调用),用于终止调用它的当前进程

你可以把 exit 想象成主角在电影结尾谢幕并优雅退场

  • 主角(当前进程)完成了它的表演(执行了所有代码)。
  • 它调用 exit,告诉导演(操作系统):“我的戏演完了,现在我要离开了。”
  • 在正式退场前,主角可能会鞠躬致谢(执行清理工作),然后离开舞台(进程终止)。

exit 不仅会终止进程,还会执行一些标准的清理(cleanup)操作,然后将控制权交还给操作系统。


2. 函数原型

#include <stdlib.h> // 必需 (C 标准库)

void exit(int status);

注意exit 是 C 标准库函数。其底层通常会调用 Linux 系统调用 _exit


3. 功能

  • 终止进程: 立即终止调用 exit 的进程。
  • 执行清理: 在终止进程之前,exit 会执行一系列标准的清理操作:
    1. 调用退出处理函数: 按照与注册时相反的顺序(后注册先调用),调用所有通过 atexit 或 on_exit 注册的函数。
    2. 刷新并关闭标准 I/O 流: 自动刷新所有输出流(如 stdoutstderr)的缓冲区,确保所有待写数据都被写出。然后关闭所有标准 I/O 流。
  • 返回状态码: 将 status 参数作为进程的退出状态(exit status)返回给父进程
    • 按照惯例,0 表示成功非 0 值通常表示某种错误或异常
  • 不返回exit 函数永远不会返回到调用它的函数。一旦调用,进程即终止。

4. 参数

  • int status: 这是进程的退出状态码
    • 这是一个整数值,它会被传递给父进程(通常是启动该进程的 shell 或父进程)。
    • 惯例:
      • EXIT_SUCCESS (通常定义为 0): 表示程序成功执行完毕。
      • EXIT_FAILURE (通常定义为 1): 表示程序执行失败
      • 自定义值: 你可以使用 0-255 范围内的任何整数来表示特定的错误类型(例如,2 表示配置错误,3 表示文件未找到等)。超出 0-255 范围的值会被模 256 处理。

5. 返回值

  • voidexit 函数没有返回值,因为它永远不会返回

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

  • _exit: 这是一个直接的 Linux 系统调用。它立即终止进程,不执行任何标准 I/O 缓冲区刷新或 atexit 注册函数的调用。它只关闭文件描述符并返回 status 给父进程。在 fork 之后的子进程中,如果 exec 失败,通常推荐使用 _exit 而非 exit
  • atexit: 用于注册在进程正常终止(通过 exit)时要调用的函数。
  • on_exit: 类似于 atexit,但注册的函数可以接收 status 和一个用户提供的参数。
  • return from main: 在 main 函数中执行 return status; 等价于调用 exit(status)
  • abort: 立即异常终止进程,通常会产生核心转储(core dump)文件。

7. 示例代码

示例 1:基本的 exit 使用和 atexit 注册清理函数

这个例子演示了 exit 如何终止进程,并展示 atexit 注册的清理函数是如何被调用的。

// exit_atexit.c
#include <stdlib.h> // exit, atexit, EXIT_SUCCESS, EXIT_FAILURE
#include <stdio.h>  // printf, perror

// 定义两个清理函数
void cleanup_function_1(void) {
    printf("Cleanup function 1 is running.\n");
}

void cleanup_function_2(void) {
    printf("Cleanup function 2 is running.\n");
}

int main() {
    printf("Main function started.\n");

    // 1. 注册清理函数
    // 注意注册顺序: 2 -> 1
    if (atexit(cleanup_function_1) != 0) {
        perror("atexit for function 1 failed");
        // 即使注册失败,程序也可以继续,但这不是好习惯
        exit(EXIT_FAILURE);
    }

    if (atexit(cleanup_function_2) != 0) {
        perror("atexit for function 2 failed");
        exit(EXIT_FAILURE);
    }

    printf("Cleanup functions registered.\n");

    // 2. 执行一些工作
    printf("Performing some work in main...\n");
    for (int i = 0; i < 3; ++i) {
        printf("  Work step %d\n", i + 1);
    }

    // 3. 刷新 stdout 缓冲区 (可选,exit 会自动做)
    fflush(stdout);

    // 4. 正常退出,触发清理
    printf("Main function finished. Calling exit(EXIT_SUCCESS).\n");
    exit(EXIT_SUCCESS); // 等价于 return EXIT_SUCCESS; from main

    // --- 下面的代码永远不会执行 ---
    printf("This line will never be printed.\n");
}

代码解释:

  1. 定义了两个简单的清理函数 cleanup_function_1 和 cleanup_function_2,它们只是打印一条消息。
  2. 在 main 函数中,使用 atexit() 注册这两个清理函数。
    • 注意注册顺序:先注册 cleanup_function_1,再注册 cleanup_function_2
  3. 执行一些模拟工作。
  4. 调用 exit(EXIT_SUCCESS)
  5. 关键: 程序不会打印 “This line will never be printed.”。
  6. 关键exit 会按注册的相反顺序调用清理函数。因此,会先打印 “Cleanup function 2 is running.”,然后是 “Cleanup function 1 is running.”。
  7. exit 会自动刷新 stdout 的缓冲区。
  8. 进程终止,返回状态码 0 给父进程。

示例 2:exit 与 _exit 的区别 (在 fork 子进程中)

这个例子通过对比演示了在 fork 子进程中使用 exit 和 _exit 的区别。

// exit_vs__exit.c
#include <sys/socket.h> // fork
#include <unistd.h>     // _exit, fork
#include <stdio.h>      // printf, perror
#include <stdlib.h>     // exit

int main() {
    pid_t pid;

    // 打印一些内容到 stdout,但不换行,数据会留在缓冲区
    printf("Parent process (PID: %d) printing without newline. Buffer content: ");

    pid = fork();

    if (pid == -1) {
        perror("fork failed");
        exit(EXIT_FAILURE); // 父进程失败,使用 exit

    } else if (pid == 0) {
        // --- 子进程 ---
        printf("This is child process (PID: %d).\n", getpid());

        // 子进程打印到 stdout,数据会留在缓冲区
        printf("Child is about to terminate. ");

        // --- 关键区别 ---
        // 使用 exit: 会刷新 stdio 缓冲区
        // printf("Child calling exit(EXIT_SUCCESS).\n");
        // exit(EXIT_SUCCESS);

        // 使用 _exit: 不会刷新 stdio 缓冲区
        printf("Child calling _exit(EXIT_SUCCESS).\n");
        _exit(EXIT_SUCCESS); // 子进程推荐使用 _exit

    } else {
        // --- 父进程 ---
        printf("Parent continues after fork.\n");
        printf("Parent process (PID: %d) finished.\n", getpid());
        // 父进程正常退出,会刷新自己的缓冲区
        exit(EXIT_SUCCESS);
    }

    // 这行代码不会被执行
    return 0;
}

代码解释:

  1. 父进程首先打印一条消息到 stdout,但没有换行符 (\n)。在大多数系统上,stdout 在连接到终端时是行缓冲的,这意味着没有换行符的数据会暂时保存在stdio 缓冲区中,而不会立即显示在屏幕上。
  2. 调用 fork() 创建子进程。
  3. 在子进程中:
    • 打印一条消息 “This is child process …”。
    • 再打印一条消息 “Child is about to terminate. “(同样没有换行符)。
    • 关键: 调用 _exit(EXIT_SUCCESS)
      • _exit 会立即终止子进程。
      • 不会刷新 stdio 缓冲区。
      • 因此,子进程中打印但未刷新的 “Child is about to terminate. ” 不会出现在输出中。
  4. 在父进程中:
    • 打印消息。
    • 调用 exit(EXIT_SUCCESS)
    • exit 会刷新父进程的 stdio 缓冲区。
    • 因此,父进程中打印但未刷新的 “Parent process (PID: …) printing without newline. Buffer content: ” 会因为 exit 的刷新操作而被打印出来。
    • 然后打印 “Parent continues …” 和 “Parent process … finished.”。

运行结果:

Parent process (PID: 12345) printing without newline. Buffer content: This is child process (PID: 12346).
Child is about to terminate. Child calling _exit(EXIT_SUCCESS).
Parent continues after fork.
Parent process (PID: 12345) finished.

分析:

  • 父进程的缓冲区内容 “Buffer content: ” 被打印了,因为父进程的 exit 调用刷新了它。
  • 子进程的缓冲区内容 “Child is about to terminate. ” 没有被打印,因为子进程调用的 _exit 没有刷新缓冲区。

如果子进程调用 exit(EXIT_SUCCESS):

  • 子进程的 exit 也会尝试刷新缓冲区。
  • 这会导致 “Child is about to terminate. ” 被打印。
  • 但是,如果父进程也在运行并且也调用 exit,两个进程都试图刷新 stdout,可能会导致输出混乱或重复(因为它们共享了 fork 时的缓冲区状态)。虽然这个简单例子可能看不出问题,但在更复杂的情况下,这可能导致不可预测的行为。
  • 因此,在 fork 的子进程中,如果后续调用 exec 失败需要退出,强烈推荐使用 _exit 以避免这种潜在的 stdio 状态混乱。

重要提示与注意事项:

  1. 永不返回exit 调用后,当前进程立即终止,函数不返回。
  2. 清理工作exit 会执行重要的清理工作(atexit 函数、刷新 stdio)。这是它与 _exit 的主要区别。
  3. _exit 在子进程中: 在 fork 之后的子进程中,如果需要在 exec 失败后退出,应使用 _exit 而非 exit,以避免刷新共享的 stdio 缓冲区。
  4. main 中的 return: 在 main 函数中,return status; 等价于 exit(status);
  5. 状态码: 使用 EXIT_SUCCESS 和 EXIT_FAILURE 宏比直接使用数字更具可读性和可移植性。
  6. atexit 注册顺序atexit 注册的函数在 exit 时按后进先出(LIFO)的顺序被调用。
  7. stdio 缓冲区: 理解 exit 会刷新缓冲区,而 _exit 不会,对于避免输出混乱至关重要。

总结:

exit 是 C 程序终止的标准方式。它不仅终止进程,还负责任地执行清理工作,确保资源得到释放,输出得到刷新。理解其与系统调用 _exit 的区别,尤其是在多进程编程中,对于编写健壮的程序非常重要。

发表在 linux文章 | 留下评论

exit_group系统调用及示例

exit_group – 终止线程组

函数介绍

exit_group是一个Linux系统调用,用于终止整个线程组(进程组),而不仅仅是当前线程。它是多线程程序中用于整体退出的重要机制。

函数原型

#include <sys/syscall.h>
#include <unistd.h>

void exit_group(int status);

功能

终止调用线程所属的整个线程组,所有线程都会退出。

参数

  • int status: 退出状态码,传递给父进程

返回值

  • 无返回值(函数不返回)

特殊限制

  • 是Linux特有的系统调用
  • 需要通过syscall调用
  • 终止整个线程组,不只是当前线程

相似函数

  • exit(): 终止当前进程(单线程环境)
  • _exit(): 立即终止当前进程
  • pthread_exit(): 终止当前线程

示例代码

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <pthread.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>

// 系统调用包装
static void exit_group_wrapper(int status) {
    syscall(__NR_exit_group, status);
}

// 线程函数
void* thread_function(void* arg) {
    int thread_id = *(int*)arg;
    
    printf("线程 %d 启动\n", thread_id);
    
    // 模拟工作
    sleep(1);
    
    if (thread_id == 2) {
        printf("线程 %d 调用exit_group,整个线程组将退出\n", thread_id);
        exit_group_wrapper(42); // 整个进程组退出
        // 下面的代码不会执行
        printf("这行代码不会被执行\n");
    }
    
    // 其他线程继续工作
    sleep(2);
    printf("线程 %d 完成工作\n", thread_id);
    
    return NULL;
}

int main() {
    printf("=== Exit_group 函数示例 ===\n");
    printf("当前进程PID: %d\n", getpid());
    
    // 示例1: 多线程环境中的exit_group
    printf("\n示例1: 多线程环境中的exit_group\n");
    
    pthread_t threads[3];
    int thread_ids[3] = {1, 2, 3};
    
    // 创建多个线程
    for (int i = 0; i < 3; i++) {
        if (pthread_create(&threads[i], NULL, thread_function, &thread_ids[i]) != 0) {
            perror("创建线程失败");
            exit(EXIT_FAILURE);
        }
        printf("创建线程 %d\n", thread_ids[i]);
    }
    
    // 等待线程(实际上不会等到,因为线程2会调用exit_group)
    printf("主线程等待子线程...\n");
    
    // 这里程序会因为exit_group而终止,不会执行到下面
    for (int i = 0; i < 3; i++) {
        pthread_join(threads[i], NULL);
    }
    
    printf("所有线程完成\n"); // 这行不会执行
    return 0; // 这行也不会执行
}

// 单独的测试函数
void test_exit_group_behavior() {
    printf("\n=== Exit_group 行为测试 ===\n");
    
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork失败");
        return;
    }
    
    if (pid == 0) {
        // 子进程
        printf("子进程PID: %d\n", getpid());
        
        // 创建多个线程
        pthread_t threads[2];
        
        // 线程1
        pthread_create(&threads[0], NULL, [](void* arg) -> void* {
            printf("线程1开始工作\n");
            sleep(3); // 比主线程工作时间长
            printf("线程1完成工作\n"); // 这行可能不会执行
            return NULL;
        }, NULL);
        
        // 线程2(主线程模拟)
        printf("主线程工作1秒后调用exit_group\n");
        sleep(1);
        
        printf("调用exit_group(100)\n");
        exit_group_wrapper(100);
        
        // 这些代码不会执行
        printf("这行不会被执行\n");
        pthread_join(threads[0], NULL);
        
    } else {
        // 父进程等待子进程
        int status;
        pid_t result = waitpid(pid, &status, 0);
        if (result != -1) {
            if (WIFEXITED(status)) {
                int exit_code = WEXITSTATUS(status);
                printf("子进程通过exit_group退出,退出码: %d\n", exit_code);
            } else if (WIFSIGNALED(status)) {
                int signal = WTERMSIG(status);
                printf("子进程被信号 %d 终止\n", signal);
            }
        }
    }
}

// exit vs exit_group 对比
void compare_exit_functions() {
    printf("\n=== Exit vs Exit_group 对比 ===\n");
    
    printf("exit() 行为:\n");
    printf("  - 单线程: 终止整个进程\n");
    printf("  - 多线程: 终止调用线程,其他线程继续运行\n");
    printf("  - 执行清理函数\n");
    printf("  - 刷新缓冲区\n\n");
    
    printf("exit_group() 行为:\n");
    printf("  - 单线程: 终止整个进程\n");
    printf("  - 多线程: 终止整个线程组(所有线程)\n");
    printf("  - 不执行清理函数\n");
    printf("  - 不刷新缓冲区\n");
    printf("  - 立即终止\n\n");
    
    printf("pthread_exit() 行为:\n");
    printf("  - 终止调用线程\n");
    printf("  - 其他线程继续运行\n");
    printf("  - 如果是最后一个线程,终止进程\n\n");
}

// 实际应用场景演示
void demonstrate_real_world_usage() {
    printf("\n=== 实际应用场景 ===\n");
    
    printf("exit_group的典型使用场景:\n");
    printf("1. 多线程程序的整体错误处理\n");
    printf("2. 资源严重不足时的紧急退出\n");
    printf("3. 接收到致命信号时\n");
    printf("4. 线程检测到不可恢复的错误\n\n");
    
    // 模拟错误处理场景
    printf("错误处理示例:\n");
    printf("void handle_critical_error(int error_code) {\n");
    printf("    log_error(\"严重错误: %%d\\n\", error_code);\n");
    printf("    // 通知所有线程立即退出\n");
    printf("    exit_group(error_code);\n");
    printf("}\n\n");
    
    printf("信号处理示例:\n");
    printf("void signal_handler(int sig) {\n");
    printf("    if (sig == SIGSEGV || sig == SIGBUS) {\n");
    printf("        // 段错误,立即退出整个进程\n");
    printf("        exit_group(128 + sig);\n");
    printf("    }\n");
    printf("}\n\n");
}

// 测试exit_group与信号的关系
void test_exit_group_with_signals() {
    printf("\n=== Exit_group 与信号 ===\n");
    
    printf("exit_group与信号的关系:\n");
    printf("1. exit_group不被信号中断\n");
    printf("2. 调用后立即终止,不处理待处理信号\n");
    printf("3. 退出状态通过wait机制传递\n");
    printf("4. 不执行信号处理程序\n\n");
    
    // 演示信号处理
    printf("信号处理中的使用:\n");
    printf("在信号处理程序中使用exit_group:\n");
    printf("void sigsegv_handler(int sig) {\n");
    printf("    write(STDERR_FILENO, \"段错误!\\n\", 8);\n");
    printf("    exit_group(128 + SIGSEGV);\n");
    printf("}\n\n");
}

int main_comprehensive_test() {
    printf("=== Exit_group 完整测试 ===\n");
    
    // 运行各个测试
    test_exit_group_behavior();
    compare_exit_functions();
    demonstrate_real_world_usage();
    test_exit_group_with_signals();
    
    printf("\n=== 总结 ===\n");
    printf("exit_group的特点:\n");
    printf("1. Linux特有系统调用\n");
    printf("2. 终止整个线程组\n");
    printf("3. 立即终止,不执行清理\n");
    printf("4. 不刷新缓冲区\n");
    printf("5. 适用于紧急退出场景\n\n");
    
    printf("使用场景:\n");
    printf("1. 多线程程序整体退出\n");
    printf("2. 严重错误的紧急处理\n");
    printf("3. 信号处理中的快速退出\n");
    printf("4. 资源不足时的优雅退出\n\n");
    
    printf("注意事项:\n");
    printf("1. 需要通过syscall调用\n");
    printf("2. 不执行atexit注册的函数\n");
    printf("3. 不刷新标准I/O缓冲区\n");
    printf("4. 所有线程立即终止\n");
    printf("5. 慎重使用,可能导致数据丢失\n\n");
    
    printf("与相关函数的区别:\n");
    printf("- exit(): 执行清理,适用于正常退出\n");
    printf("- _exit(): 立即退出,但只影响当前线程\n");
    printf("- exit_group(): 立即退出整个线程组\n");
    printf("- pthread_exit(): 只退出当前线程\n\n");
    
    return 0;
}
发表在 linux文章 | 留下评论

create_module系统调用及示例

关于 create_module 和 delete_module 的内核版本历史

create_module 废弃时间

  • Linux 2.6 内核(2003年左右)开始逐步废弃
  • Linux 2.6.8 版本后完全移除
  • 最后支持的内核版本:Linux 2.4.x

delete_module 变化时间

  • Linux 2.6 内核开始改变行为
  • 从系统调用转变为更安全的模块管理机制
  • 现代系统中仍然存在,但行为更加受限

详细历史说明

/*
 * Linux 内核模块管理演进历史:
 * 
 * Linux 2.0-2.4 (1996-2003):
 * - 使用 create_module() 分配内核内存
 * - 使用 delete_module() 卸载模块
 * - 相对简单的模块加载机制
 * 
 * Linux 2.6+ (2003年至今):
 * - 引入 init_module() 替代 create_module()
 * - delete_module() 仍然可用但更加安全
 * - 模块签名验证机制
 * - 更严格的权限控制
 * 
 * 现代 Linux (3.0+):
 * - 模块加载通过 finit_module() 系统调用
 * - delete_module() 保留但增加安全检查
 * - 强制模块签名(某些发行版)
 */

现代替代方案

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("=== 现代内核模块管理 ===\n");
    printf("Linux内核版本演进:\n");
    printf("- 2.4及以前: create_module/delete_module\n");
    printf("- 2.6开始: init_module/delete_module\n");
    printf("- 3.0+: fini_module/delete_module\n\n");
    
    printf("create_module废弃时间: Linux 2.6.8 (2004年)\n");
    printf("最后支持版本: Linux 2.4.37 (2009年停止维护)\n\n");
    
    printf("现代替代方案:\n");
    printf("1. 用户空间工具:\n");
    printf("   - insmod: 加载模块\n");
    printf("   - rmmod: 卸载模块\n");
    printf("   - lsmod: 列出模块\n\n");
    
    printf("2. 系统调用:\n");
    printf("   - init_module(): 加载模块\n");
    printf("   - finit_module(): 文件描述符版本的init_module\n");
    printf("   - delete_module(): 卸载模块(仍在使用)\n\n");
    
    printf("3. 程序化使用:\n");
    printf("   - libkmod库提供高级API\n");
    printf("   - modprobe命令处理依赖关系\n");
    
    return 0;
}

总结

  • create_module: Linux 2.6.8 (2004年) 后完全废弃
  • delete_module: 仍在使用,但在现代内核中有更多安全限制
  • 对于现代Linux系统编程,应该使用用户空间工具或libkmod库来管理内核模块
发表在 linux文章 | 留下评论

dup3系统调用及示例

1. 函数介绍

dup3 是 Linux 系统调用,是 dup2 的扩展版本。它用于将一个已存在的文件描述符复制到指定的目标文件描述符,类似于 dup2,但提供了额外的标志参数来控制复制行为。

这个函数的主要优势是可以设置文件描述符标志,最常用的是 O_CLOEXEC 标志,该标志使得复制的文件描述符在执行 exec 系列函数时自动关闭,避免了文件描述符泄漏到新程序中。


2. 函数原型

#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>

int dup3(int oldfd, int newfd, int flags);

3. 功能

  • 将文件描述符 oldfd 复制到指定的文件描述符 newfd
  • 如果 newfd 已经打开,会先将其关闭
  • 可以设置额外的文件描述符标志
  • 如果 oldfd 等于 newfd,则返回错误(与 dup2 不同)

4. 参数

  • int oldfd: 要被复制的原始文件描述符
  • int newfd: 目标文件描述符编号
  • int flags: 控制标志,可以是以下值的按位或组合:
    • O_CLOEXEC: 设置执行时关闭标志(FD_CLOEXEC)
    • 0: 不设置任何特殊标志(等同于 dup2 的行为)

5. 返回值

  • 成功时: 返回 newfd
  • 失败时: 返回 -1,并设置 errno
    • EBADFoldfd 或 newfd 不是有效的文件描述符
    • EINVALflags 参数无效,或 oldfd 等于 newfd
    • EMFILE: 进程打开的文件描述符数量达到上限

6. 相似函数

  • dup(): 复制文件描述符到最小可用编号
  • dup2(): 复制文件描述符到指定编号(不支持标志)
  • fcntl(): 更通用的文件描述符控制函数

7. 示例代码

示例 1:基本的 dup3 使用

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

int main() {
    int fd1, fd2, fd3;
    
    printf("=== Dup3 基本使用演示 ===\n");
    
    // 1. 打开测试文件
    fd1 = open("test_dup3.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd1 == -1) {
        perror("打开文件失败");
        exit(EXIT_FAILURE);
    }
    printf("打开文件获得描述符: %d\n", fd1);
    
    // 2. 使用 dup3 复制文件描述符(无特殊标志)
    fd2 = dup3(fd1, 10, 0);
    if (fd2 == -1) {
        perror("dup3 失败");
        close(fd1);
        exit(EXIT_FAILURE);
    }
    printf("使用 dup3(%d, 10, 0) 复制,获得描述符: %d\n", fd1, fd2);
    
    // 3. 使用 dup3 复制并设置 O_CLOEXEC 标志
    fd3 = dup3(fd1, 15, O_CLOEXEC);
    if (fd3 == -1) {
        perror("dup3 带 O_CLOEXEC 失败");
        close(fd1);
        close(fd2);
        exit(EXIT_FAILURE);
    }
    printf("使用 dup3(%d, 15, O_CLOEXEC) 复制,获得描述符: %d\n", fd1, fd3);
    
    // 4. 验证 O_CLOEXEC 标志是否设置
    int flags = fcntl(fd3, F_GETFD);
    if (flags != -1) {
        if (flags & FD_CLOEXEC) {
            printf("描述符 %d 已设置 FD_CLOEXEC 标志\n", fd3);
        } else {
            printf("描述符 %d 未设置 FD_CLOEXEC 标志\n", fd3);
        }
    }
    
    // 5. 验证文件描述符共享性
    const char *message1 = "通过 fd1 写入\n";
    const char *message2 = "通过 fd2 写入\n";
    const char *message3 = "通过 fd3 写入\n";
    
    write(fd1, message1, strlen(message1));
    write(fd2, message2, strlen(message2));
    write(fd3, message3, strlen(message3));
    
    // 6. 读取验证
    lseek(fd1, 0, SEEK_SET);
    char buffer[256];
    ssize_t bytes_read = read(fd1, buffer, sizeof(buffer) - 1);
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("\n读取到的数据:\n%s", buffer);
    }
    
    // 7. 清理资源
    close(fd1);
    close(fd2);
    close(fd3);
    unlink("test_dup3.txt");
    
    return 0;
}

示例 2:O_CLOEXEC 标志的重要性

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

void demonstrate_cloexec_flag() {
    printf("=== O_CLOEXEC 标志演示 ===\n");
    
    // 创建测试文件
    int fd = open("cloexec_test.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return;
    }
    
    write(fd, "测试数据", 8);
    
    // 使用 dup3 设置 O_CLOEXEC 标志
    int fd_with_cloexec = dup3(fd, 10, O_CLOEXEC);
    if (fd_with_cloexec == -1) {
        perror("dup3 设置 O_CLOEXEC 失败");
        close(fd);
        return;
    }
    
    // 使用 dup2 不设置 O_CLOEXEC 标志
    int fd_without_cloexec = dup2(fd, 15);
    if (fd_without_cloexec == -1) {
        perror("dup2 失败");
        close(fd);
        close(fd_with_cloexec);
        return;
    }
    
    printf("创建了两个描述符:\n");
    printf("  %d: 带 O_CLOEXEC 标志\n", fd_with_cloexec);
    printf("  %d: 不带 O_CLOEXEC 标志\n", fd_without_cloexec);
    
    // 创建子进程执行新程序
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程
        printf("子进程 PID: %d\n", getpid());
        
        // 检查文件描述符是否仍然打开
        if (fcntl(fd_with_cloexec, F_GETFD) == -1) {
            printf("带 O_CLOEXEC 的描述符 %d 已自动关闭\n", fd_with_cloexec);
        } else {
            printf("带 O_CLOEXEC 的描述符 %d 仍然打开\n", fd_with_cloexec);
        }
        
        if (fcntl(fd_without_cloexec, F_GETFD) == -1) {
            printf("不带 O_CLOEXEC 的描述符 %d 已关闭\n", fd_without_cloexec);
        } else {
            printf("不带 O_CLOEXEC 的描述符 %d 仍然打开\n", fd_without_cloexec);
        }
        
        // 执行新程序(这里用 ls 作为示例)
        execl("/bin/ls", "ls", "-l", "cloexec_test.txt", NULL);
        perror("execl 失败");
        exit(EXIT_FAILURE);
    } else if (pid > 0) {
        // 父进程
        wait(NULL);
        printf("父进程继续执行\n");
    } else {
        perror("fork 失败");
    }
    
    // 清理
    close(fd);
    close(fd_with_cloexec);
    close(fd_without_cloexec);
    unlink("cloexec_test.txt");
}

int main() {
    printf("Dup3 函数演示\n\n");
    
    // 基本使用演示
    system("gcc -o basic_dup3 basic_dup3.c");
    system("./basic_dup3");
    
    printf("\n");
    
    // O_CLOEXEC 标志演示
    demonstrate_cloexec_flag();
    
    printf("\n=== 总结 ===\n");
    printf("dup3 的优势:\n");
    printf("1. 支持设置 O_CLOEXEC 标志,防止文件描述符泄漏\n");
    printf("2. 原子性操作,避免了 dup2 + fcntl 的竞态条件\n");
    printf("3. 当 oldfd == newfd 时返回错误,行为更明确\n\n");
    
    printf("使用建议:\n");
    printf("- 优先使用 dup3 而不是 dup2\n");
    printf("- 在可能执行 exec 的场景中使用 O_CLOEXEC\n");
    printf("- 注意检查返回值和错误处理\n");
    
    return 0;
}

示例 3:错误处理演示

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

void demonstrate_dup3_errors() {
    printf("=== Dup3 错误处理演示 ===\n");
    
    // 1. 无效的文件描述符
    printf("1. 使用无效的文件描述符:\n");
    int result = dup3(999, 10, 0);
    if (result == -1) {
        printf("   错误: %s\n", strerror(errno));
        if (errno == EBADF) {
            printf("   说明: 文件描述符 999 无效\n");
        }
    }
    
    // 2. 无效的标志
    printf("\n2. 使用无效的标志:\n");
    int fd = open("/dev/null", O_RDWR);
    if (fd != -1) {
        result = dup3(fd, 10, 0x1000); // 无效标志
        if (result == -1) {
            printf("   错误: %s\n", strerror(errno));
            if (errno == EINVAL) {
                printf("   说明: 标志参数无效\n");
            }
        }
        close(fd);
    }
    
    // 3. oldfd 等于 newfd
    printf("\n3. oldfd 等于 newfd:\n");
    fd = open("/dev/null", O_RDWR);
    if (fd != -1) {
        result = dup3(fd, fd, 0);
        if (result == -1) {
            printf("   错误: %s\n", strerror(errno));
            if (errno == EINVAL) {
                printf("   说明: dup3 不允许 oldfd 等于 newfd\n");
                printf("   对比: dup2 在这种情况下会返回 newfd\n");
            }
        } else {
            printf("   意外成功: %d\n", result);
        }
        close(fd);
    }
    
    // 4. 文件描述符数量达到上限
    printf("\n4. 文件描述符数量达到上限的模拟:\n");
    printf("   这种情况很难模拟,但会返回 EMFILE 错误\n");
}

int main() {
    demonstrate_dup3_errors();
    
    printf("\n=== Dup 系列函数对比 ===\n");
    printf("函数     | 目标描述符 | 支持标志 | oldfd==newfd 行为\n");
    printf("---------|------------|----------|------------------\n");
    printf("dup      | 自动分配   | 否       | N/A\n");
    printf("dup2     | 指定       | 否       | 返回 newfd\n");
    printf("dup3     | 指定       | 是       | 返回错误\n");
    
    return 0;
}

编译和运行说明

# 编译示例
gcc -o dup3_basic dup3_basic.c
gcc -o dup3_cloexec dup3_cloexec.c
gcc -o dup3_errors dup3_errors.c

# 运行示例
./dup3_basic
./dup3_cloexec
./dup3_errors

重要注意事项

  1. Linux 特定dup3 是 Linux 特定的系统调用,在其他 Unix 系统上可能不可用
  2. 标志支持: 主要优势是支持 O_CLOEXEC 标志,提高程序安全性
  3. 原子操作dup3 是原子操作,避免了 dup2 + fcntl 组合可能的竞态条件
  4. 错误处理: 当 oldfd 等于 newfd 时,dup3 返回错误,而 dup2 返回 newfd
  5. 兼容性: 如果需要跨平台兼容性,应该使用 dup2 或 fcntl
发表在 linux文章 | 留下评论

dup-dup2系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 dup 和 dup2 函数,它们用于复制一个已存在的文件描述符 (file descriptor)。


1. 函数介绍

dup 和 dup2 是 Linux 系统调用,它们的功能是创建一个指向同一文件表项 (open file description) 的新文件描述符

简单来说,当你调用 dup 或 dup2 时,你得到的是一个别名副本,这个新文件描述符和原始文件描述符指向同一个打开的文件,共享文件的:

  • 文件偏移量 (file offset): 通过一个描述符读写会改变文件位置,通过另一个描述符读写会从新的位置开始。
  • 状态标志 (status flags): 如 O_APPENDO_NONBLOCK 等。
  • 文件锁 (file locks): 通过任何一个描述符获取的锁,对另一个描述符也有效。

它们最常见的用途是重定向标准输入、标准输出或标准错误。例如,将一个程序的输出重定向到文件,而不是终端。

你可以把文件描述符想象成一个指向文件的“把手”。dup 就像是给这个“把手”又做了一个一模一样的复制品。你拿着任何一个“把手”都能操作同一个文件,而且它们的状态是同步的。


2. 函数原型

#include <unistd.h> // 必需

// 复制文件描述符 (返回新的最小可用 fd)
int dup(int oldfd);

// 复制文件描述符到指定的新 fd
int dup2(int oldfd, int newfd);

3. 功能

  • dup(int oldfd):
    • 复制一个已存在的文件描述符 oldfd
    • 内核会在当前进程中选择最小的未使用的文件描述符号码作为新的描述符。
    • 新的文件描述符和 oldfd 指向同一个文件表项。
  • dup2(int oldfd, int newfd):
    • 复制文件描述符 oldfd,并强制使复制得到的新文件描述符的号码为 newfd
    • 如果 newfd 已经打开(指向另一个文件),dup2 会在复制前先关闭 newfd(相当于先调用 close(newfd))。
    • 如果 oldfd 和 newfd 相同,dup2 什么都不做,直接返回 newfd

4. 参数

  • dup:
    • int oldfd: 要被复制的现有有效文件描述符。
  • dup2:
    • int oldfd: 要被复制的现有有效文件描述符。
    • int newfd: 请求的新文件描述符号码。
      • 如果 newfd 已经打开,它会被关闭。
      • 如果 newfd 等于 oldfd,则不执行任何操作。

5. 返回值

  • 成功时:
    • 返回新的文件描述符号码。
    • 对于 dup,这个号码是当前进程中最小的可用号码。
    • 对于 dup2,这个号码就是请求的 newfd
  • 失败时:
    • 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EBADF oldfd 或 newfd 无效,EMFILE 进程打开的文件描述符已达上限等)。

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

  • fcntldup(oldfd) 等价于 fcntl(oldfd, F_DUPFD, 0)fcntl 提供了更灵活的复制方式,例如 F_DUPFD_CLOEXEC 可以在复制时设置 FD_CLOEXEC 标志。
  • closedup2 在复制前如果 newfd 已打开,会隐式调用 close(newfd)
  • open: 用于获取最初的文件描述符。
  • readwrite: 对复制后的文件描述符进行操作。

7. 示例代码

示例 1:使用 dup 和 dup2 重定向标准输出

这个例子演示了如何使用 dup 和 dup2 来保存原始标准输出,然后将标准输出重定向到一个文件,最后再恢复标准输出。

#include <unistd.h>  // dup, dup2, close, write
#include <fcntl.h>   // open, O_WRONLY, O_CREAT, O_TRUNC
#include <stdio.h>   // printf, perror
#include <stdlib.h>  // exit

int main() {
    int saved_stdout;   // 用于保存原始标准输出的文件描述符
    int file_fd;        // 用于重定向输出的文件描述符

    // 1. 保存原始的标准输出 (stdout, 文件描述符 1)
    saved_stdout = dup(STDOUT_FILENO);
    if (saved_stdout == -1) {
        perror("dup saved_stdout");
        exit(EXIT_FAILURE);
    }
    printf("Saved original stdout to fd %d\n", saved_stdout);

    // 2. 打开一个文件用于重定向
    file_fd = open("output_redirection.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (file_fd == -1) {
        perror("open file for redirection");
        close(saved_stdout); // 清理
        exit(EXIT_FAILURE);
    }
    printf("Opened file 'output_redirection.txt' with fd %d\n", file_fd);

    // 3. 将标准输出重定向到文件
    // 方法一:使用 dup2
    if (dup2(file_fd, STDOUT_FILENO) == -1) {
        perror("dup2 redirect stdout");
        close(file_fd);
        close(saved_stdout);
        exit(EXIT_FAILURE);
    }
    printf("Standard output redirected to file.\n");
    // 注意:从现在开始,printf 的输出将写入到文件中,而不是终端!

    // 4. 输出一些内容到文件
    printf("This line goes to the file.\n");
    printf("This line also goes to the file.\n");

    // 5. 恢复标准输出
    // 使用 dup2 将保存的原始 stdout 描述符复制回 STDOUT_FILENO
    if (dup2(saved_stdout, STDOUT_FILENO) == -1) {
        perror("dup2 restore stdout");
        // 清理
        close(file_fd);
        close(saved_stdout);
        exit(EXIT_FAILURE);
    }
    printf("Standard output restored to terminal.\n");
    // 注意:从现在开始,printf 的输出将重新显示在终端上!

    // 6. 再输出一些内容到终端
    printf("This line goes back to the terminal.\n");

    // 7. 关闭所有打开的文件描述符
    // close(file_fd) 实际上关闭了文件表项的一个引用
    // dup2 在重定向时已经关闭了原来的 STDOUT_FILENO 对应的文件表项引用
    // 所以这里只需要关闭 file_fd 和我们自己保存的 saved_stdout
    if (close(file_fd) == -1) {
        perror("close file_fd");
    }
    // 关闭 saved_stdout 也会关闭它指向的文件表项(即原始的终端 stdout)
    if (close(saved_stdout) == -1) {
        perror("close saved_stdout");
    }

    printf("All file descriptors closed.\n");
    return 0;
}

代码解释:

  1. 调用 dup(STDOUT_FILENO) 创建原始标准输出(文件描述符 1)的一个副本,并将其保存在 saved_stdout 中。这个副本让我们可以稍后恢复标准输出。
  2. 使用 open 创建或打开一个名为 output_redirection.txt 的文件,用于接收重定向的输出。
  3. 调用 dup2(file_fd, STDOUT_FILENO)。这会:
    • 关闭当前的 STDOUT_FILENO(文件描述符 1)。
    • 将 file_fd 复制一份,并使这个新副本的号码为 1 (即 STDOUT_FILENO)。
    • 现在,文件描述符 1 和 file_fd 都指向 output_redirection.txt 文件。
  4. 执行 printf。因为标准输出已经被重定向,所以这些内容会写入到 output_redirection.txt 文件中。
  5. 调用 dup2(saved_stdout, STDOUT_FILENO) 来恢复标准输出。这会将之前保存的原始终端输出描述符复制回文件描述符 1。
  6. 再次执行 printf。现在标准输出已经恢复,内容会显示在终端上。
  7. 最后,使用 close 关闭所有打开的文件描述符。

示例 2:dup2 自动关闭目标文件描述符

这个例子重点演示 dup2 在目标文件描述符已打开时自动关闭它的行为。

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    int fd1, fd2;
    char buffer[100];
    ssize_t bytes_read;

    // 1. 打开两个不同的文件
    fd1 = open("file1.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd1 == -1) {
        perror("open file1.txt");
        exit(EXIT_FAILURE);
    }
    write(fd1, "Content of file 1\n", 18);

    fd2 = open("file2.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd2 == -1) {
        perror("open file2.txt");
        close(fd1);
        exit(EXIT_FAILURE);
    }
    write(fd2, "Content of file 2\n", 18);

    printf("Initially:\n");
    printf("  fd1 points to 'file1.txt' (fd=%d)\n", fd1);
    printf("  fd2 points to 'file2.txt' (fd=%d)\n", fd2);

    // 2. 使用 dup2 将 fd1 复制到 fd2
    // 这会自动关闭 fd2 当前指向的 'file2.txt'
    printf("\nCalling dup2(fd1, fd2)...\n");
    if (dup2(fd1, fd2) == -1) {
        perror("dup2(fd1, fd2)");
        close(fd1);
        close(fd2);
        exit(EXIT_FAILURE);
    }

    printf("After dup2(fd1, fd2):\n");
    printf("  fd1 still points to 'file1.txt' (fd=%d)\n", fd1);
    printf("  fd2 now ALSO points to 'file1.txt' (fd=%d)\n", fd2);
    printf("  'file2.txt' has been closed automatically.\n");

    // 3. 验证:向 fd2 写入数据,应该出现在 'file1.txt' 中
    write(fd2, "Data written via fd2 after dup2\n", 32);

    // 4. 关闭文件描述符
    // 关闭 fd1 和 fd2 实际上是关闭同一个文件表项的两个引用
    // 内核会在最后一个引用关闭时才真正关闭文件
    close(fd1);
    close(fd2); // 这个 close 实际上是关闭 fd1/fd2 共同指向的文件表项

    // 5. 读取 file1.txt 来验证内容
    printf("\nContents of 'file1.txt' after operations:\n");
    int read_fd = open("file1.txt", O_RDONLY);
    if (read_fd != -1) {
        while ((bytes_read = read(read_fd, buffer, sizeof(buffer) - 1)) > 0) {
            buffer[bytes_read] = '\0';
            printf("%s", buffer);
        }
        close(read_fd);
    }

    // 6. 检查 file2.txt 是否为空或被截断 (因为它被 dup2 自动关闭了)
    printf("\nContents of 'file2.txt' (should be empty or just initial content if not truncated):\n");
    read_fd = open("file2.txt", O_RDONLY);
    if (read_fd != -1) {
        while ((bytes_read = read(read_fd, buffer, sizeof(buffer) - 1)) > 0) {
            buffer[bytes_read] = '\0';
            printf("%s", buffer);
        }
        close(read_fd);
    }
    if (bytes_read == 0) {
         printf("(file2.txt is empty)\n");
    }

    return 0;
}

代码解释:

  1. 打开两个文件 file1.txt 和 file2.txt,分别得到文件描述符 fd1 和 fd2
  2. 向两个文件写入不同的初始内容。
  3. 调用 dup2(fd1, fd2)。关键点在于:
    • fd2 当前是打开的,指向 file2.txt
    • dup2 会自动关闭 fd2
    • 然后,它将 fd1(指向 file1.txt)复制一份,并使这个副本的号码为 fd2
  4. 现在,fd1 和 fd2 都指向 file1.txt
  5. 向 fd2 写入数据,数据会出现在 file1.txt 中,证明了 fd2 现在确实指向 file1.txt
  6. 关闭 fd1 和 fd2。由于它们指向同一个文件表项,文件只会在最后一个引用关闭时才真正关闭。
  7. 读取 file1.txt 和 file2.txt 的内容来验证操作结果。file1.txt 应该包含所有写入的内容,而 file2.txt 可能为空(因为它在 dup2 时被关闭了,如果它之前的内容没有被 O_TRUNC 重新截断,则可能还保留着)。

示例 3:dup 选择最小可用文件描述符

这个例子演示 dup 如何选择最小的可用文件描述符。

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    int fd, new_fd1, new_fd2;

    // 1. 打开一个文件
    fd = open("test_dup.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    printf("Original file descriptor: %d\n", fd);

    // 2. 复制文件描述符 (使用 dup)
    new_fd1 = dup(fd);
    if (new_fd1 == -1) {
        perror("dup 1");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("First dup() returned new fd: %d\n", new_fd1);

    // 3. 再次复制
    new_fd2 = dup(fd);
    if (new_fd2 == -1) {
        perror("dup 2");
        close(fd);
        close(new_fd1);
        exit(EXIT_FAILURE);
    }
    printf("Second dup() returned new fd: %d\n", new_fd2);

    // 4. 写入数据到所有描述符,验证它们指向同一文件
    write(fd, "Written via fd\n", 15);
    write(new_fd1, "Written via new_fd1\n", 20);
    write(new_fd2, "Written via new_fd2\n", 20);

    printf("Data written via all three file descriptors.\n");

    // 5. 关闭所有描述符
    close(fd);
    close(new_fd1);
    close(new_fd2);

    printf("All file descriptors closed.\n");
    return 0;
}

代码解释:

  1. 打开一个文件,得到文件描述符 fd(假设为 3)。
  2. 调用 dup(fd)。因为 0, 1, 2 (stdin, stdout, stderr) 通常已被占用,所以 dup 会返回下一个最小的可用描述符,比如 4。
  3. 再次调用 dup(fd)。现在 3, 4 已被占用,所以会返回 5。
  4. 向 fdnew_fd1new_fd2 写入数据,验证它们都写入了同一个文件。
  5. 关闭所有文件描述符。

总结:

dup 和 dup2 是用于复制文件描述符的强大工具,它们在实现输入/输出重定向、保存和恢复标准流、以及在进程管理(如 fork 后)中非常有用。理解它们的关键在于掌握新旧描述符共享文件表项的特性,以及 dup2 自动关闭目标描述符的行为。

发表在 linux文章 | 留下评论

epoll_create1系统调用及示例

epoll_create1 – 创建epoll实例(扩展版)

函数介绍

epoll_create1epoll_create的扩展版本,支持额外的标志位参数,提供了更多的控制选项。

函数原型

#include <sys/epoll.h>

int epoll_create1(int flags);

功能

创建一个新的epoll实例,支持额外的标志位控制。

参数

  • int flags: 控制标志
    • 0: 与epoll_create(size)相同
    • EPOLL_CLOEXEC: 设置文件描述符在exec时自动关闭

返回值

  • 成功时返回epoll文件描述符
  • 失败时返回-1,并设置errno

特殊限制

  • 需要Linux 2.6.27以上内核支持

相似函数

  • epoll_create(): 基础版本
  • poll()select(): 传统多路复用函数

示例代码

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

int main() {
    int epfd1, epfd2;
    
    printf("=== Epoll_create1 函数示例 ===\n");
    
    // 示例1: 基本使用
    printf("\n示例1: 基本使用\n");
    
    epfd1 = epoll_create1(0);
    if (epfd1 == -1) {
        perror("epoll_create1(0) 失败");
    } else {
        printf("成功创建epoll实例(无标志): %d\n", epfd1);
        close(epfd1);
    }
    
    // 示例2: 使用EPOLL_CLOEXEC标志
    printf("\n示例2: 使用EPOLL_CLOEXEC标志\n");
    
    epfd2 = epoll_create1(EPOLL_CLOEXEC);
    if (epfd2 == -1) {
        perror("epoll_create1(EPOLL_CLOEXEC) 失败");
    } else {
        printf("成功创建epoll实例(带CLOEXEC): %d\n", epfd2);
        
        // 验证CLOEXEC标志是否设置
        int flags = fcntl(epfd2, F_GETFD);
        if (flags != -1) {
            if (flags & FD_CLOEXEC) {
                printf("FD_CLOEXEC标志已正确设置\n");
            } else {
                printf("FD_CLOEXEC标志未设置\n");
            }
        }
        
        close(epfd2);
    }
    
    // 示例3: 错误处理
    printf("\n示例3: 错误处理\n");
    
    int invalid_fd = epoll_create1(-1);
    if (invalid_fd == -1) {
        if (errno == EINVAL) {
            printf("无效标志错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 示例4: 与epoll_create对比
    printf("\n示例4: 与epoll_create对比\n");
    
    int fd1 = epoll_create(10);
    int fd2 = epoll_create1(0);
    
    if (fd1 != -1 && fd2 != -1) {
        printf("epoll_create返回: %d\n", fd1);
        printf("epoll_create1(0)返回: %d\n", fd2);
        printf("两者功能基本相同\n");
        
        close(fd1);
        close(fd2);
    }
    
    // 示例5: 实际应用演示
    printf("\n示例5: 实际应用演示\n");
    
    // 推荐的现代用法
    int epfd = epoll_create1(EPOLL_CLOEXEC);
    if (epfd != -1) {
        printf("推荐用法: epoll_create1(EPOLL_CLOEXEC) = %d\n", epfd);
        printf("优点: 避免文件描述符泄漏到子进程\n");
        close(epfd);
    }
    
    printf("\nEPOLL_CLOEXEC的优势:\n");
    printf("1. 原子性设置标志,避免竞态条件\n");
    printf("2. 防止文件描述符泄漏到exec的程序\n");
    printf("3. 提高程序安全性\n");
    printf("4. 减少系统调用次数\n\n");
    
    printf("总结:\n");
    printf("epoll_create1是现代Linux编程推荐的epoll创建函数\n");
    printf("EPOLL_CLOEXEC标志提供了更好的安全性\n");
    printf("在支持的系统上应优先使用epoll_create1\n");
    
    return 0;
}
发表在 linux文章 | 留下评论

epoll_create系统调用及示例

epoll_create – 创建epoll实例

函数介绍

epoll_create系统调用用于创建一个epoll实例,返回一个文件描述符,用于后续的epoll操作。epoll是Linux特有的I/O多路复用机制,比传统的select和poll更高效。

函数原型

#include <sys/epoll.h>
#include <sys/syscall.h>
#include <unistd.h>

int epoll_create(int size);

功能

创建一个新的epoll实例,用于监控多个文件描述符的I/O事件。

参数

  • int size: 建议的内核为该epoll实例分配的事件数(Linux 2.6.8后被忽略)

返回值

  • 成功时返回epoll文件描述符(非负整数)
  • 失败时返回-1,并设置errno

特殊限制

  • 需要Linux 2.5.44以上内核支持
  • 每个进程可创建的epoll实例数量受系统限制

相似函数

  • epoll_create1(): 现代版本,支持标志位
  • poll(): 传统的轮询机制
  • select(): 传统的多路复用机制

示例代码

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

// 系统调用包装
static int epoll_create_wrapper(int size) {
    return syscall(__NR_epoll_create, size);
}

int main() {
    int epfd;
    
    printf("=== Epoll_create 函数示例 ===\n");
    
    // 示例1: 基本的epoll实例创建
    printf("\n示例1: 基本的epoll实例创建\n");
    
    epfd = epoll_create_wrapper(10); // size参数在现代内核中被忽略
    if (epfd == -1) {
        perror("epoll_create 失败");
        exit(EXIT_FAILURE);
    }
    
    printf("成功创建epoll实例,文件描述符: %d\n", epfd);
    
    // 检查epoll文件描述符是否有效
    if (fcntl(epfd, F_GETFD) != -1) {
        printf("epoll文件描述符验证成功\n");
    }
    
    // 关闭epoll实例
    if (close(epfd) == -1) {
        perror("关闭epoll实例失败");
    } else {
        printf("成功关闭epoll实例\n");
    }
    
    // 示例2: 多个epoll实例创建
    printf("\n示例2: 多个epoll实例创建\n");
    
    int epfds[5];
    int created_count = 0;
    
    for (int i = 0; i < 5; i++) {
        epfds[i] = epoll_create_wrapper(1);
        if (epfds[i] != -1) {
            printf("创建第%d个epoll实例: %d\n", i+1, epfds[i]);
            created_count++;
        } else {
            printf("创建第%d个epoll实例失败: %s\n", i+1, strerror(errno));
            break;
        }
    }
    
    // 关闭所有创建的epoll实例
    for (int i = 0; i < created_count; i++) {
        if (close(epfds[i]) == -1) {
            printf("关闭epoll实例%d失败: %s\n", epfds[i], strerror(errno));
        } else {
            printf("关闭epoll实例%d成功\n", epfds[i]);
        }
    }
    
    // 示例3: 错误处理演示
    printf("\n示例3: 错误处理演示\n");
    
    // 使用负数作为size参数
    epfd = epoll_create_wrapper(-1);
    if (epfd == -1) {
        if (errno == EINVAL) {
            printf("负数size参数错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 检查系统资源限制
    printf("\n系统epoll相关限制:\n");
    FILE *fp = fopen("/proc/sys/fs/epoll/max_user_watches", "r");
    if (fp != NULL) {
        char line[256];
        if (fgets(line, sizeof(line), fp)) {
            printf("最大用户监视数量: %s", line);
        }
        fclose(fp);
    }
    
    // 示例4: epoll vs select/poll对比说明
    printf("\n示例4: epoll优势说明\n");
    printf("epoll相比select/poll的优势:\n");
    printf("1. 文件描述符数量无限制(受系统资源限制)\n");
    printf("2. O(1)时间复杂度的事件通知\n");
    printf("3. 内存使用更高效\n");
    printf("4. 支持边缘触发和水平触发模式\n");
    printf("5. 更好的可扩展性\n\n");
    
    // 示例5: 实际使用场景
    printf("示例5: 实际使用场景\n");
    printf("epoll的典型应用场景:\n");
    printf("1. 高并发网络服务器\n");
    printf("2. 实时数据处理系统\n");
    printf("3. 聊天服务器和即时通讯\n");
    printf("4. 代理服务器和负载均衡\n");
    printf("5. 监控和日志收集系统\n\n");
    
    // 示例6: 性能考虑
    printf("示例6: 性能考虑\n");
    printf("epoll性能优化建议:\n");
    printf("1. 合理设置epoll_wait的maxevents参数\n");
    printf("2. 避免频繁添加/删除监视的文件描述符\n");
    printf("3. 使用边缘触发模式提高效率\n");
    printf("4. 批量处理事件\n");
    printf("5. 及时关闭不需要的epoll实例\n\n");
    
    printf("总结:\n");
    printf("epoll_create是创建epoll实例的基础函数\n");
    printf("虽然现代推荐使用epoll_create1,但epoll_create仍然广泛支持\n");
    printf("返回的文件描述符需要妥善管理\n");
    printf("epoll是Linux高性能网络编程的重要工具\n");
    
    return 0;
}
发表在 linux文章 | 留下评论

epoll_ctl系统调用及示例

epoll_ctl – 控制epoll实例

函数介绍

epoll_ctl系统调用用于控制epoll实例,可以添加、修改或删除要监视的文件描述符。

函数原型

#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

功能

对epoll实例进行控制操作,管理要监视的文件描述符。

参数

  • int epfd: epoll实例的文件描述符
  • int op: 操作类型
    • EPOLL_CTL_ADD: 添加文件描述符到监视集合
    • EPOLL_CTL_MOD: 修改已监视文件描述符的设置
    • EPOLL_CTL_DEL: 从监视集合中删除文件描述符
  • int fd: 要控制的文件描述符
  • struct epoll_event *event: 指向epoll_event结构体的指针

返回值

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

特殊限制

  • 需要有效的epoll文件描述符
  • 文件描述符必须是有效的
  • 某些操作需要文件描述符已在监视集合中

相似函数

  • epoll_wait(): 等待epoll事件
  • poll(): 传统轮询控制

示例代码

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

int main() {
    int epfd, sockfd;
    struct epoll_event ev;
    
    printf("=== Epoll_ctl 函数示例 ===\n");
    
    // 创建epoll实例
    epfd = epoll_create1(EPOLL_CLOEXEC);
    if (epfd == -1) {
        perror("epoll_create1 失败");
        exit(EXIT_FAILURE);
    }
    printf("创建epoll实例: %d\n", epfd);
    
    // 创建测试用的socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("创建socket失败");
        close(epfd);
        exit(EXIT_FAILURE);
    }
    printf("创建测试socket: %d\n", sockfd);
    
    // 设置socket为非阻塞模式
    int flags = fcntl(sockfd, F_GETFL, 0);
    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
    printf("设置socket为非阻塞模式\n");
    
    // 示例1: 添加文件描述符到epoll监视集合
    printf("\n示例1: 添加文件描述符到epoll\n");
    
    ev.events = EPOLLIN | EPOLLOUT | EPOLLET; // 读事件、写事件、边缘触发
    ev.data.fd = sockfd;
    
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev) == -1) {
        perror("EPOLL_CTL_ADD 失败");
    } else {
        printf("成功添加socket %d 到epoll监视集合\n", sockfd);
        printf("监视事件: EPOLLIN | EPOLLOUT | EPOLLET\n");
    }
    
    // 示例2: 修改已监视的文件描述符
    printf("\n示例2: 修改已监视的文件描述符\n");
    
    ev.events = EPOLLIN | EPOLLET; // 只监视读事件和边缘触发
    ev.data.fd = sockfd;
    
    if (epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev) == -1) {
        if (errno == ENOENT) {
            printf("修改失败,文件描述符未在监视集合中: %s\n", strerror(errno));
        } else {
            perror("EPOLL_CTL_MOD 失败");
        }
    } else {
        printf("成功修改socket %d 的监视设置\n", sockfd);
        printf("新监视事件: EPOLLIN | EPOLLET\n");
    }
    
    // 示例3: 删除文件描述符
    printf("\n示例3: 删除文件描述符\n");
    
    if (epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL) == -1) {
        if (errno == ENOENT) {
            printf("删除失败,文件描述符不在监视集合中: %s\n", strerror(errno));
        } else {
            perror("EPOLL_CTL_DEL 失败");
        }
    } else {
        printf("成功从epoll监视集合中删除socket %d\n", sockfd);
    }
    
    // 示例4: 错误处理演示
    printf("\n示例4: 错误处理演示\n");
    
    // 使用无效的epoll文件描述符
    if (epoll_ctl(-1, EPOLL_CTL_ADD, sockfd, &ev) == -1) {
        if (errno == EBADF) {
            printf("无效epoll文件描述符错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 使用无效的操作类型
    if (epoll_ctl(epfd, 999, sockfd, &ev) == -1) {
        if (errno == EINVAL) {
            printf("无效操作类型错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 使用无效的文件描述符
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, -1, &ev) == -1) {
        if (errno == EBADF) {
            printf("无效文件描述符错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 示例5: epoll_event结构说明
    printf("\n示例5: epoll_event结构说明\n");
    printf("struct epoll_event 结构体:\n");
    printf("  uint32_t events;  // 事件类型\n");
    printf("  epoll_data_t data; // 用户数据\n\n");
    
    printf("常用事件类型:\n");
    printf("  EPOLLIN:    可读事件\n");
    printf("  EPOLLOUT:   可写事件\n");
    printf("  EPOLLPRI:   紧急数据可读\n");
    printf("  EPOLLERR:   错误条件\n");
    printf("  EPOLLHUP:   挂起事件\n");
    printf("  EPOLLET:    边缘触发模式\n");
    printf("  EPOLLONESHOT: 一次性事件\n");
    printf("  EPOLLRDHUP: 对端关闭连接\n\n");
    
    // 示例6: epoll_data_t联合体说明
    printf("epoll_data_t联合体(可存储不同类型的数据):\n");
    printf("  void *ptr;    // 指针\n");
    printf("  int fd;       // 文件描述符\n");
    printf("  uint32_t u32; // 32位无符号整数\n");
    printf("  uint64_t u64; // 64位无符号整数\n\n");
    
    // 演示使用用户数据
    printf("示例6: 使用用户数据\n");
    
    struct epoll_event event_with_data;
    event_with_data.events = EPOLLIN;
    event_with_data.data.fd = sockfd;
    
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event_with_data) == 0) {
        printf("使用文件描述符作为用户数据: %d\n", event_with_data.data.fd);
        
        // 删除文件描述符
        epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
    }
    
    // 示例7: 实际应用场景
    printf("\n示例7: 实际应用场景\n");
    
    printf("服务器程序中的典型使用流程:\n");
    printf("1. 创建epoll实例\n");
    printf("2. 添加监听socket到epoll\n");
    printf("3. 在事件循环中:\n");
    printf("   a. 调用epoll_wait等待事件\n");
    printf("   b. 处理就绪事件\n");
    printf("   c. 根据需要添加/修改/删除监视的文件描述符\n\n");
    
    printf("常见操作模式:\n");
    printf("监听socket: EPOLLIN | EPOLLET\n");
    printf("客户端socket: EPOLLIN | EPOLLOUT | EPOLLET\n");
    printf("一次性事件: EPOLLIN | EPOLLONESHOT\n\n");
    
    // 清理资源
    close(sockfd);
    close(epfd);
    
    printf("总结:\n");
    printf("epoll_ctl是管理epoll监视集合的核心函数\n");
    printf("支持添加、修改、删除三种基本操作\n");
    printf("正确使用事件类型和用户数据很重要\n");
    printf("需要妥善处理各种错误情况\n");
    
    return 0;
}
发表在 linux文章 | 留下评论

epoll_pwait系统调用及示例

epoll_pwait – 带信号掩码的epoll等待

函数介绍

epoll_pwaitepoll_wait的扩展版本,支持在等待期间设置临时的信号掩码,提供了更精确的信号控制能力。

函数原型

#include <sys/epoll.h>
#include <signal.h>

int epoll_pwait(int epfd, struct epoll_event *events,
                int maxevents, int timeout,
                const sigset_t *sigmask);

功能

等待epoll事件,同时可以指定在等待期间临时应用的信号掩码。

参数

  • int epfd: epoll实例的文件描述符
  • struct epoll_event *events: 用于存储就绪事件的数组
  • int maxevents: events数组的最大元素数
  • int timeout: 超时时间(毫秒)
  • const sigset_t *sigmask: 临时信号掩码(NULL表示不改变)

返回值

  • 成功时返回就绪事件的数量
  • 失败时返回-1,并设置errno

特殊限制

  • 需要Linux 2.6.19以上内核支持
  • 原子性地设置信号掩码和等待事件

相似函数

  • epoll_wait(): 基础版本
  • pselect(): 带信号掩码的select版本
  • ppoll(): 带信号掩码的poll版本

示例代码

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>

int main() {
    int epfd, sockfd, nfds;
    struct epoll_event ev, events[10];
    sigset_t mask, orig_mask;
    
    printf("=== Epoll_pwait 函数示例 ===\n");
    
    // 创建epoll实例
    epfd = epoll_create1(EPOLL_CLOEXEC);
    if (epfd == -1) {
        perror("epoll_create1 失败");
        exit(EXIT_FAILURE);
    }
    printf("创建epoll实例: %d\n", epfd);
    
    // 创建测试用的socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("创建socket失败");
        close(epfd);
        exit(EXIT_FAILURE);
    }
    printf("创建测试socket: %d\n", sockfd);
    
    // 设置socket为非阻塞模式
    int flags = fcntl(sockfd, F_GETFL, 0);
    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
    
    // 添加socket到epoll监视集合
    ev.events = EPOLLIN | EPOLLOUT;
    ev.data.fd = sockfd;
    
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev) == -1) {
        perror("epoll_ctl 添加失败");
        close(sockfd);
        close(epfd);
        exit(EXIT_FAILURE);
    }
    printf("添加socket到epoll监视集合\n");
    
    // 示例1: 基本使用(不改变信号掩码)
    printf("\n示例1: 基本使用(不改变信号掩码)\n");
    
    nfds = epoll_pwait(epfd, events, 10, 1000, NULL);
    if (nfds == -1) {
        if (errno == EINTR) {
            printf("等待被信号中断\n");
        } else {
            perror("epoll_pwait 失败");
        }
    } else {
        printf("等待完成,就绪事件数: %d\n", nfds);
    }
    
    // 示例2: 设置临时信号掩码
    printf("\n示例2: 设置临时信号掩码\n");
    
    // 初始化信号掩码
    sigemptyset(&mask);
    sigaddset(&mask, SIGUSR1);
    sigaddset(&mask, SIGUSR2);
    
    printf("设置临时阻塞SIGUSR1和SIGUSR2信号\n");
    
    nfds = epoll_pwait(epfd, events, 10, 2000, &mask);
    if (nfds == -1) {
        if (errno == EINTR) {
            printf("等待被未阻塞的信号中断\n");
        } else {
            perror("epoll_pwait 失败");
        }
    } else {
        printf("等待完成,就绪事件数: %d\n", nfds);
    }
    
    // 示例3: 与普通epoll_wait对比
    printf("\n示例3: 与epoll_wait对比\n");
    
    printf("epoll_wait vs epoll_pwait的区别:\n");
    printf("epoll_wait:\n");
    printf("  - 使用当前进程的信号掩码\n");
    printf("  - 无法在调用中临时改变信号掩码\n\n");
    
    printf("epoll_pwait:\n");
    printf("  - 可以指定临时信号掩码\n");
    printf("  - 原子性地设置掩码和等待\n");
    printf("  - 避免竞态条件\n\n");
    
    // 示例4: 原子性操作演示
    printf("示例4: 原子性操作演示\n");
    
    printf("说明epoll_pwait的原子性优势:\n");
    printf("传统方式(非原子性):\n");
    printf("  1. sigprocmask(SIG_BLOCK, &mask, &orig_mask);\n");
    printf("  2. epoll_wait(epfd, events, 10, -1);\n");
    printf("  3. sigprocmask(SIG_SETMASK, &orig_mask, NULL);\n");
    printf("  问题: 在步骤1和2之间可能收到信号\n\n");
    
    printf("epoll_pwait方式(原子性):\n");
    printf("  epoll_pwait(epfd, events, 10, -1, &mask);\n");
    printf("  优势: 设置掩码和等待是原子操作\n\n");
    
    // 示例5: 错误处理演示
    printf("示例5: 错误处理演示\n");
    
    // 使用无效的信号掩码
    nfds = epoll_pwait(epfd, events, 10, 1000, (sigset_t*)-1);
    if (nfds == -1) {
        if (errno == EINVAL) {
            printf("无效信号掩码错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 其他错误与epoll_wait相同
    nfds = epoll_pwait(-1, events, 10, 1000, &mask);
    if (nfds == -1) {
        if (errno == EBADF) {
            printf("无效epoll文件描述符错误处理正确\n");
        }
    }
    
    // 示例6: 实际应用场景
    printf("\n示例6: 实际应用场景\n");
    
    printf("多线程服务器中的信号处理:\n");
    printf("场景: 主线程处理网络事件,信号处理线程处理信号\n");
    printf("要求: 主线程在epoll_wait期间不被某些信号中断\n\n");
    
    printf("实现方案:\n");
    printf("sigset_t mask;\n");
    printf("sigemptyset(&mask);\n");
    printf("sigaddset(&mask, SIGUSR1);  // 阻塞用户信号\n");
    printf("sigaddset(&mask, SIGALRM);  // 阻塞定时器信号\n");
    printf("epoll_pwait(epfd, events, MAX_EVENTS, -1, &mask);\n\n");
    
    // 示例7: 信号掩码操作演示
    printf("示例7: 信号掩码操作演示\n");
    
    // 创建复杂的信号掩码
    sigset_t complex_mask;
    sigemptyset(&complex_mask);
    
    // 添加多个信号到掩码
    int signals[] = {SIGINT, SIGTERM, SIGUSR1, SIGUSR2, SIGALRM};
    const char *signal_names[] = {"SIGINT", "SIGTERM", "SIGUSR1", "SIGUSR2", "SIGALRM"};
    
    printf("创建包含以下信号的掩码:\n");
    for (int i = 0; i < 5; i++) {
        sigaddset(&complex_mask, signals[i]);
        printf("  %s\n", signal_names[i]);
    }
    
    printf("使用复杂信号掩码进行epoll_pwait\n");
    nfds = epoll_pwait(epfd, events, 10, 1000, &complex_mask);
    if (nfds != -1) {
        printf("epoll_pwait成功完成\n");
    }
    
    // 示例8: 线程安全考虑
    printf("\n示例8: 线程安全考虑\n");
    
    printf("在多线程环境中的使用:\n");
    printf("1. 每个线程可以有自己的epoll实例\n");
    printf("2. 每个线程可以设置不同的信号掩码\n");
    printf("3. 避免线程间的信号处理冲突\n");
    printf("4. 提高信号处理的精确性\n\n");
    
    printf("线程特定的信号掩码设置:\n");
    printf("线程1: 阻塞SIGUSR1, SIGUSR2\n");
    printf("线程2: 阻塞SIGALRM, SIGVTALRM\n");
    printf("线程3: 不阻塞任何信号\n\n");
    
    // 示例9: 性能和安全性
    printf("示例9: 性能和安全性\n");
    
    printf("epoll_pwait的优势:\n");
    printf("1. 原子性操作,避免竞态条件\n");
    printf("2. 更精确的信号控制\n");
    printf("3. 提高程序的可靠性\n");
    printf("4. 简化信号处理逻辑\n\n");
    
    printf("使用建议:\n");
    printf("1. 在需要精确信号控制时使用epoll_pwait\n");
    printf("2. 合理设计信号掩码\n");
    printf("3. 注意信号处理的线程安全性\n");
    printf("4. 在多线程环境中谨慎使用\n\n");
    
    // 清理资源
    epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
    close(sockfd);
    close(epfd);
    
    printf("总结:\n");
    printf("epoll_pwait是epoll_wait的增强版本\n");
    printf("支持临时信号掩码设置\n");
    printf("提供原子性的信号控制\n");
    printf("适用于需要精确信号处理的场景\n");
    printf("在现代Linux系统中推荐使用\n");
    
    return 0;
}
发表在 linux文章 | 留下评论

epoll_wait系统调用及示例

epoll_wait – 等待epoll事件

函数介绍

epoll_wait系统调用用于等待epoll实例中的文件描述符就绪事件。它是epoll机制的核心函数,用于高效地等待多个文件描述符的I/O事件。

函数原型

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event *events,
               int maxevents, int timeout);

功能

等待epoll实例中的事件就绪,返回就绪的事件数组。

参数

  • int epfd: epoll实例的文件描述符
  • struct epoll_event *events: 用于存储就绪事件的数组
  • int maxevents: events数组的最大元素数(必须大于0)
  • int timeout: 超时时间(毫秒)
    • -1: 永久等待
    • 0: 立即返回(轮询)
    • >0: 等待指定毫秒数

返回值

  • 成功时返回就绪事件的数量(0表示超时)
  • 失败时返回-1,并设置errno

特殊限制

  • maxevents必须大于0
  • 需要有效的epoll文件描述符
  • 可能被信号中断

相似函数

  • epoll_pwait(): 支持信号掩码的版本
  • poll(): 传统轮询等待
  • select(): 传统多路复用等待

示例代码

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>

// 信号处理标志
volatile sig_atomic_t signal_received = 0;

void signal_handler(int sig) {
    signal_received = 1;
    printf("接收到信号 %d\n", sig);
}

int main() {
    int epfd, sockfd, nfds;
    struct epoll_event ev, events[10];
    
    printf("=== Epoll_wait 函数示例 ===\n");
    
    // 设置信号处理
    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);
    
    // 创建epoll实例
    epfd = epoll_create1(EPOLL_CLOEXEC);
    if (epfd == -1) {
        perror("epoll_create1 失败");
        exit(EXIT_FAILURE);
    }
    printf("创建epoll实例: %d\n", epfd);
    
    // 创建测试用的socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("创建socket失败");
        close(epfd);
        exit(EXIT_FAILURE);
    }
    printf("创建测试socket: %d\n", sockfd);
    
    // 设置socket为非阻塞模式
    int flags = fcntl(sockfd, F_GETFL, 0);
    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
    
    // 添加socket到epoll监视集合
    ev.events = EPOLLIN | EPOLLOUT;
    ev.data.fd = sockfd;
    
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev) == -1) {
        perror("epoll_ctl 添加失败");
        close(sockfd);
        close(epfd);
        exit(EXIT_FAILURE);
    }
    printf("添加socket到epoll监视集合\n");
    
    // 示例1: 带超时的等待
    printf("\n示例1: 带超时的等待\n");
    
    printf("等待1秒...\n");
    nfds = epoll_wait(epfd, events, 10, 1000); // 1秒超时
    
    if (nfds == -1) {
        if (errno == EINTR) {
            printf("等待被信号中断\n");
        } else {
            perror("epoll_wait 失败");
        }
    } else if (nfds == 0) {
        printf("等待超时(1秒内无事件)\n");
    } else {
        printf("就绪事件数量: %d\n", nfds);
        for (int i = 0; i < nfds; i++) {
            printf("  事件 %d: fd=%d, events=0x%x\n", 
                   i, events[i].data.fd, events[i].events);
        }
    }
    
    // 示例2: 立即返回的轮询
    printf("\n示例2: 立即返回的轮询\n");
    
    nfds = epoll_wait(epfd, events, 10, 0); // 立即返回
    
    if (nfds == -1) {
        perror("epoll_wait 轮询失败");
    } else {
        printf("轮询结果: %d 个事件就绪\n", nfds);
    }
    
    // 示例3: 永久等待(演示信号中断)
    printf("\n示例3: 永久等待(演示信号中断)\n");
    printf("请在5秒内按Ctrl+C发送SIGINT信号\n");
    
    alarm(5); // 5秒后发送SIGALRM
    nfds = epoll_wait(epfd, events, 10, -1); // 永久等待
    
    if (nfds == -1) {
        if (errno == EINTR) {
            printf("永久等待被信号中断\n");
        } else {
            perror("epoll_wait 失败");
        }
    } else {
        printf("永久等待返回: %d 个事件\n", nfds);
    }
    alarm(0); // 取消alarm
    
    // 示例4: 错误处理演示
    printf("\n示例4: 错误处理演示\n");
    
    // 使用无效的epoll文件描述符
    nfds = epoll_wait(-1, events, 10, 1000);
    if (nfds == -1) {
        if (errno == EBADF) {
            printf("无效epoll文件描述符错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 使用无效的maxevents
    nfds = epoll_wait(epfd, events, 0, 1000);
    if (nfds == -1) {
        if (errno == EINVAL) {
            printf("无效maxevents错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 使用NULL事件数组
    nfds = epoll_wait(epfd, NULL, 10, 1000);
    if (nfds == -1) {
        if (errno == EFAULT) {
            printf("NULL事件数组错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 示例5: 事件处理演示
    printf("\n示例5: 事件处理演示\n");
    
    // 模拟不同类型的事件
    printf("常见事件类型处理:\n");
    
    for (int i = 0; i < 10; i++) {
        events[i].events = 0;
        events[i].data.fd = i;
    }
    
    // 设置一些模拟事件
    events[0].events = EPOLLIN;
    events[1].events = EPOLLOUT;
    events[2].events = EPOLLIN | EPOLLOUT;
    events[3].events = EPOLLERR;
    events[4].events = EPOLLHUP;
    
    printf("模拟事件处理:\n");
    for (int i = 0; i < 5; i++) {
        printf("  fd %d: ", events[i].data.fd);
        if (events[i].events & EPOLLIN) printf("可读 ");
        if (events[i].events & EPOLLOUT) printf("可写 ");
        if (events[i].events & EPOLLERR) printf("错误 ");
        if (events[i].events & EPOLLHUP) printf("挂起 ");
        printf("\n");
    }
    
    // 示例6: 性能考虑
    printf("\n示例6: 性能考虑\n");
    
    printf("epoll_wait性能优化建议:\n");
    printf("1. 合理设置maxevents参数\n");
    printf("   - 不要过大浪费内存\n");
    printf("   - 不要过小频繁调用\n");
    printf("2. 使用适当的超时时间\n");
    printf("   - 根据应用需求选择\n");
    printf("3. 批量处理就绪事件\n");
    printf("   - 减少系统调用次数\n");
    printf("4. 避免在事件处理中阻塞\n");
    printf("   - 保持事件循环响应性\n\n");
    
    // 示例7: 实际服务器循环演示
    printf("示例7: 实际服务器循环演示\n");
    
    printf("典型的服务器事件循环:\n");
    printf("while (running) {\n");
    printf("    int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);\n");
    printf("    if (nfds == -1) {\n");
    printf("        if (errno == EINTR) continue;  // 被信号中断\n");
    printf("        else break;  // 真正的错误\n");
    printf("    }\n");
    printf("    \n");
    printf("    for (int i = 0; i < nfds; i++) {\n");
    printf("        if (events[i].data.fd == listen_sock) {\n");
    printf("            // 处理新连接\n");
    printf("        } else {\n");
    printf("            // 处理已连接socket的数据\n");
    printf("        }\n");
    printf("    }\n");
    printf("}\n\n");
    
    // 示例8: 超时时间说明
    printf("示例8: 超时时间说明\n");
    printf("timeout参数说明:\n");
    printf("  -1: 永久等待,直到有事件或被信号中断\n");
    printf("   0: 立即返回,不阻塞(轮询模式)\n");
    printf("  >0: 等待指定毫秒数\n\n");
    
    printf("超时时间选择建议:\n");
    printf("实时应用: 较小的超时值(1-100ms)\n");
    printf("批处理应用: 较大的超时值(1000ms以上)\n");
    printf("交互应用: 中等超时值(100-500ms)\n\n");
    
    // 清理资源
    epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
    close(sockfd);
    close(epfd);
    
    printf("总结:\n");
    printf("epoll_wait是epoll机制的核心等待函数\n");
    printf("支持灵活的超时控制\n");
    printf("正确处理信号中断很重要\n");
    printf("合理的maxevents和timeout设置影响性能\n");
    printf("是构建高性能网络服务器的基础\n");
    
    return 0;
}
发表在 linux文章 | 留下评论

eventfd系统调用及示例

eventfd – 创建事件文件描述符

函数介绍

eventfd系统调用用于创建一个事件文件描述符,用于用户空间程序之间的事件通知。它提供了一个简单的计数器机制,可以用于线程间或进程间的同步。

函数原型

#include <sys/eventfd.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdint.h>

int eventfd(unsigned int initval, int flags);

功能

创建一个事件文件描述符,内部维护一个64位无符号整数计数器,用于事件通知和同步。

参数

  • unsigned int initval: 计数器的初始值
  • int flags: 控制标志
    • 0: 基本模式
    • EFD_CLOEXEC: 设置执行时关闭标志
    • EFD_NONBLOCK: 设置非阻塞模式
    • EFD_SEMAPHORE: 信号量模式(每次读取递减1而不是重置为0)

返回值

  • 成功时返回事件文件描述符(非负整数)
  • 失败时返回-1,并设置errno

特殊限制

  • 需要Linux 2.6.22以上内核支持
  • 计数器值有最大限制(0xfffffffffffffffeULL)

相似函数

  • eventfd2(): 现代版本,更好的标志支持
  • pipe(): 管道机制
  • signalfd(): 信号文件描述符

示例代码

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

// 系统调用包装(如果glibc不支持)
static int eventfd_wrapper(unsigned int initval, int flags) {
    return syscall(__NR_eventfd2, initval, flags);
}

// 线程函数
void* thread_function(void* arg) {
    int efd = *(int*)arg;
    uint64_t value = 1;
    
    printf("  子线程: 准备发送事件通知\n");
    
    // 发送事件通知
    if (write(efd, &value, sizeof(value)) != sizeof(value)) {
        perror("  子线程: 写入eventfd失败");
    } else {
        printf("  子线程: 成功发送事件通知\n");
    }
    
    return NULL;
}

int main() {
    int efd;
    uint64_t value;
    pthread_t thread;
    
    printf("=== Eventfd 函数示例 ===\n");
    
    // 示例1: 基本使用
    printf("\n示例1: 基本使用\n");
    
    efd = eventfd_wrapper(0, 0);
    if (efd == -1) {
        perror("eventfd 创建失败");
        exit(EXIT_FAILURE);
    }
    printf("成功创建eventfd,文件描述符: %d\n", efd);
    
    // 检查文件描述符属性
    int flags = fcntl(efd, F_GETFD);
    if (flags != -1) {
        printf("eventfd文件描述符验证成功\n");
    }
    
    // 关闭eventfd
    close(efd);
    printf("关闭eventfd\n");
    
    // 示例2: 基本的事件通知
    printf("\n示例2: 基本的事件通知\n");
    
    efd = eventfd_wrapper(0, 0);
    if (efd == -1) {
        perror("eventfd 创建失败");
        exit(EXIT_FAILURE);
    }
    printf("创建eventfd: %d\n", efd);
    
    // 启动线程发送事件
    if (pthread_create(&thread, NULL, thread_function, &efd) != 0) {
        perror("创建线程失败");
        close(efd);
        exit(EXIT_FAILURE);
    }
    
    printf("主线程: 等待事件通知...\n");
    
    // 等待事件通知
    ssize_t bytes_read = read(efd, &value, sizeof(value));
    if (bytes_read == sizeof(value)) {
        printf("主线程: 收到事件通知,计数器值: %lu\n", value);
    } else {
        perror("主线程: 读取eventfd失败");
    }
    
    // 等待线程结束
    pthread_join(thread, NULL);
    
    close(efd);
    
    // 示例3: 使用标志位
    printf("\n示例3: 使用标志位\n");
    
    // 使用EFD_CLOEXEC标志
    efd = eventfd_wrapper(0, EFD_CLOEXEC);
    if (efd != -1) {
        printf("创建带EFD_CLOEXEC标志的eventfd: %d\n", efd);
        
        // 验证标志是否设置
        flags = fcntl(efd, F_GETFD);
        if (flags != -1 && (flags & FD_CLOEXEC)) {
            printf("EFD_CLOEXEC标志已正确设置\n");
        }
        close(efd);
    }
    
    // 使用EFD_NONBLOCK标志
    efd = eventfd_wrapper(0, EFD_NONBLOCK);
    if (efd != -1) {
        printf("创建带EFD_NONBLOCK标志的eventfd: %d\n", efd);
        
        // 尝试非阻塞读取(应该失败)
        bytes_read = read(efd, &value, sizeof(value));
        if (bytes_read == -1) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                printf("非阻塞读取正确返回EAGAIN: %s\n", strerror(errno));
            }
        }
        close(efd);
    }
    
    // 示例4: 信号量模式
    printf("\n示例4: 信号量模式\n");
    
    efd = eventfd_wrapper(3, EFD_SEMAPHORE);
    if (efd != -1) {
        printf("创建信号量模式eventfd,初始值: 3\n");
        
        // 多次读取,每次递减1
        for (int i = 0; i < 5; i++) {
            bytes_read = read(efd, &value, sizeof(value));
            if (bytes_read == sizeof(value)) {
                printf("第%d次读取,获得值: %lu\n", i+1, value);
            } else {
                if (errno == EAGAIN) {
                    printf("第%d次读取,无可用事件: %s\n", i+1, strerror(errno));
                    break;
                }
            }
        }
        
        close(efd);
    }
    
    // 示例5: 错误处理演示
    printf("\n示例5: 错误处理演示\n");
    
    // 使用无效的初始值(虽然eventfd允许大值,但有上限)
    efd = eventfd_wrapper(0xffffffff, 0);
    if (efd != -1) {
        printf("使用大初始值创建eventfd成功: %d\n", efd);
        close(efd);
    }
    
    // 尝试写入无效值
    efd = eventfd_wrapper(0, 0);
    if (efd != -1) {
        uint64_t invalid_value = 0xffffffffffffffffULL; // 最大值
        ssize_t result = write(efd, &invalid_value, sizeof(invalid_value));
        if (result == -1) {
            printf("写入最大值失败: %s\n", strerror(errno));
        } else {
            printf("写入最大值成功\n");
        }
        close(efd);
    }
    
    // 示例6: 计数器溢出处理
    printf("\n示例6: 计数器溢出处理\n");
    
    efd = eventfd_wrapper(0, 0);
    if (efd != -1) {
        // 写入接近最大值的数据
        uint64_t large_value = 0xfffffffffffffffeULL; // 接近最大值
        if (write(efd, &large_value, sizeof(large_value)) == sizeof(large_value)) {
            printf("写入大值成功\n");
            
            // 再次写入会导致溢出
            uint64_t add_value = 2;
            ssize_t result = write(efd, &add_value, sizeof(add_value));
            if (result == -1) {
                if (errno == EAGAIN) {
                    printf("计数器溢出,写入失败: %s\n", strerror(errno));
                }
            }
        }
        close(efd);
    }
    
    // 示例7: 实际应用场景
    printf("\n示例7: 实际应用场景\n");
    
    printf("eventfd的典型应用场景:\n");
    printf("1. 线程池任务通知\n");
    printf("2. 异步I/O完成通知\n");
    printf("3. 事件驱动编程\n");
    printf("4. 进程间简单通信\n");
    printf("5. 与epoll配合使用\n\n");
    
    // 演示与epoll配合使用
    printf("与epoll配合使用的示例:\n");
    printf("int epfd = epoll_create1(EPOLL_CLOEXEC);\n");
    printf("int efd = eventfd(0, EFD_CLOEXEC);\n");
    printf("struct epoll_event ev;\n");
    printf("ev.events = EPOLLIN;\n");
    printf("ev.data.fd = efd;\n");
    printf("epoll_ctl(epfd, EPOLL_CTL_ADD, efd, &ev);\n");
    printf("// 在其他线程中: write(efd, &value, sizeof(value));\n");
    printf("// 在事件循环中: epoll_wait(epfd, events, maxevents, timeout);\n\n");
    
    // 示例8: 性能优势
    printf("示例8: 性能优势\n");
    printf("eventfd相比传统机制的优势:\n");
    printf("1. 更少的系统调用\n");
    printf("2. 更小的内存占用\n");
    printf("3. 更快的通知速度\n");
    printf("4. 更简单的API\n");
    printf("5. 更好的可扩展性\n\n");
    
    printf("与pipe的对比:\n");
    printf("pipe: 需要两个文件描述符,缓冲区较大\n");
    printf("eventfd: 只需要一个文件描述符,固定8字节计数器\n\n");
    
    printf("总结:\n");
    printf("eventfd是Linux提供的轻量级事件通知机制\n");
    printf("适用于简单的同步和通知场景\n");
    printf("支持多种模式和标志位\n");
    printf("与epoll等机制配合使用效果更佳\n");
    printf("是现代Linux编程的重要工具\n");
    
    return 0;
}

49. eventfd2 – 创建事件文件描述符(扩展版)

函数介绍

eventfd2eventfd的扩展版本,提供了更好的标志位支持和错误处理。它是现代Linux系统推荐使用的eventfd创建函数。

函数原型

#include <sys/eventfd.h>

int eventfd2(unsigned int initval, int flags);

功能

创建一个事件文件描述符,功能与eventfd相同但接口更现代。

参数

  • unsigned int initval: 计数器的初始值
  • int flags: 控制标志(支持更多标志位)

返回值

  • 成功时返回事件文件描述符
  • 失败时返回-1,并设置errno

特殊限制

  • 需要Linux 2.6.27以上内核支持
  • 某些旧系统可能不支持

相似函数

  • eventfd(): 基础版本
  • pipe(): 管道机制

示例代码

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

int main() {
    int efd1, efd2;
    
    printf("=== Eventfd2 函数示例 ===\n");
    
    // 示例1: 基本使用对比
    printf("\n示例1: 基本使用对比\n");
    
    // 使用eventfd
    efd1 = eventfd(0, 0);
    if (efd1 != -1) {
        printf("eventfd创建成功: %d\n", efd1);
        close(efd1);
    } else {
        printf("eventfd创建失败: %s\n", strerror(errno));
    }
    
    // 使用eventfd2
    efd2 = eventfd2(0, 0);
    if (efd2 != -1) {
        printf("eventfd2创建成功: %d\n", efd2);
        close(efd2);
    } else {
        printf("eventfd2创建失败: %s\n", strerror(errno));
        printf("说明: 系统可能不支持eventfd2\n");
    }
    
    // 示例2: 标志位支持
    printf("\n示例2: 标志位支持\n");
    
    // EFD_CLOEXEC标志
    efd2 = eventfd2(0, EFD_CLOEXEC);
    if (efd2 != -1) {
        printf("使用EFD_CLOEXEC标志创建成功: %d\n", efd2);
        
        // 验证标志设置
        int flags = fcntl(efd2, F_GETFD);
        if (flags != -1 && (flags & FD_CLOEXEC)) {
            printf("EFD_CLOEXEC标志验证成功\n");
        }
        close(efd2);
    }
    
    // EFD_NONBLOCK标志
    efd2 = eventfd2(0, EFD_NONBLOCK);
    if (efd2 != -1) {
        printf("使用EFD_NONBLOCK标志创建成功: %d\n", efd2);
        
        // 测试非阻塞特性
        uint64_t value;
        ssize_t result = read(efd2, &value, sizeof(value));
        if (result == -1) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                printf("非阻塞读取正确返回EAGAIN\n");
            }
        }
        close(efd2);
    }
    
    // EFD_SEMAPHORE标志
    efd2 = eventfd2(5, EFD_SEMAPHORE);
    if (efd2 != -1) {
        printf("使用EFD_SEMAPHORE标志创建成功,初始值: 5\n");
        
        // 测试信号量模式
        for (int i = 0; i < 7; i++) {
            uint64_t read_value;
            ssize_t result = read(efd2, &read_value, sizeof(read_value));
            if (result == sizeof(read_value)) {
                printf("第%d次读取成功,值: %lu\n", i+1, read_value);
            } else {
                if (errno == EAGAIN) {
                    printf("第%d次读取失败,无可用资源: %s\n", i+1, strerror(errno));
                    break;
                }
            }
        }
        close(efd2);
    }
    
    // 示例3: 组合标志
    printf("\n示例3: 组合标志\n");
    
    // 组合多个标志
    efd2 = eventfd2(0, EFD_CLOEXEC | EFD_NONBLOCK);
    if (efd2 != -1) {
        printf("组合标志创建成功: %d\n", efd2);
        
        // 验证所有标志
        int flags = fcntl(efd2, F_GETFD);
        if (flags != -1) {
            if (flags & FD_CLOEXEC) {
                printf("EFD_CLOEXEC标志已设置\n");
            }
        }
        
        // 测试非阻塞特性
        uint64_t value;
        if (read(efd2, &value, sizeof(value)) == -1) {
            if (errno == EAGAIN) {
                printf("EFD_NONBLOCK标志生效\n");
            }
        }
        
        close(efd2);
    }
    
    // 示例4: 错误处理
    printf("\n示例4: 错误处理\n");
    
    // 使用无效标志
    efd2 = eventfd2(0, 0x1000); // 无效标志
    if (efd2 == -1) {
        if (errno == EINVAL) {
            printf("无效标志错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 在不支持的系统上
    printf("在不支持eventfd2的系统上会返回ENOSYS错误\n");
    
    // 示例5: 与eventfd的差异
    printf("\n示例5: 与eventfd的差异\n");
    
    printf("eventfd vs eventfd2:\n");
    printf("eventfd:\n");
    printf("  - 较老的接口\n");
    printf("  - 标志位支持有限\n");
    printf("  - 在所有支持eventfd的系统上可用\n\n");
    
    printf("eventfd2:\n");
    printf("  - 现代接口\n");
    printf("  - 更好的标志位支持\n");
    printf("  - 原子性设置标志\n");
    printf("  - 需要Linux 2.6.27+\n\n");
    
    // 示例6: 实际应用推荐
    printf("示例6: 实际应用推荐\n");
    
    printf("现代应用推荐使用模式:\n");
    printf("#ifdef EFD_CLOEXEC\n");
    printf("    int efd = eventfd2(0, EFD_CLOEXEC | EFD_NONBLOCK);\n");
    printf("#else\n");
    printf("    int efd = eventfd(0, 0);\n");
    printf("    fcntl(efd, F_SETFD, FD_CLOEXEC);\n");
    printf("    fcntl(efd, F_SETFL, fcntl(efd, F_GETFL) | O_NONBLOCK);\n");
    printf("#endif\n\n");
    
    // 示例7: 兼容性处理
    printf("示例7: 兼容性处理\n");
    
    // 兼容性包装函数
    printf("兼容性处理示例:\n");
    printf("int create_eventfd(unsigned int initval, int flags) {\n");
    printf("#ifdef __NR_eventfd2\n");
    printf("    int fd = eventfd2(initval, flags);\n");
    printf("    if (fd >= 0 || errno != ENOSYS)\n");
    printf("        return fd;\n");
    printf("#endif\n");
    printf("    // 回退到eventfd\n");
    printf("    fd = eventfd(initval, 0);\n");
    printf("    if (fd >= 0) {\n");
    printf("        // 手动设置标志\n");
    printf("        if (flags & EFD_CLOEXEC)\n");
    printf("            fcntl(fd, F_SETFD, FD_CLOEXEC);\n");
    printf("        if (flags & EFD_NONBLOCK)\n");
    printf("            fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);\n");
    printf("    }\n");
    printf("    return fd;\n");
    printf("}\n\n");
    
    // 示例8: 性能考虑
    printf("示例8: 性能考虑\n");
    printf("eventfd2性能优势:\n");
    printf("1. 原子性标志设置,避免竞态条件\n");
    printf("2. 减少系统调用次数\n");
    printf("3. 更好的错误处理\n");
    printf("4. 现代内核优化\n\n");
    
    printf("使用建议:\n");
    printf("1. 优先使用eventfd2\n");
    printf("2. 提供eventfd回退方案\n");
    printf("3. 合理使用标志位\n");
    printf("4. 注意资源清理\n\n");
    
    printf("总结:\n");
    printf("eventfd2是eventfd的现代替代品\n");
    printf("提供了更好的标志位支持\n");
    printf("在支持的系统上应优先使用\n");
    printf("需要考虑向后兼容性\n");
    printf("是构建高性能应用的重要工具\n");
    
    return 0;
}
发表在 linux文章 | 留下评论

execveat系统调用及示例

execveat系统调用及示例

探索execveat系统调用的深入解析与实用示例,掌握Linux编程技巧。立即学习,提升你的代码能力!


1. 函数介绍

execveat 是一个 Linux 系统调用(内核版本 >= 3.19),它是 execve 函数族的一员。它的核心功能是在当前进程中执行一个新的程序,从而替换当前进程的镜像(代码、数据、堆栈等)。

简单来说,execveat 就像用一个新灵魂替换旧灵魂

  • 你的身体(进程)还在,但里面的思想、记忆、行为(程序代码和数据)被完全替换成另一个人(新程序)的。
  • 旧程序的所有状态(局部变量、堆栈)都消失了。
  • 新程序从它的 main 函数开始执行。

execveat 相比于 execve 的独特之处在于它引入了目录文件描述符dirfd)和路径解析标志flags),使得程序执行可以相对于一个已打开的目录进行,或者直接执行一个已打开的文件描述符所指向的文件。这提供了更灵活和安全的程序执行方式,尤其是在处理复杂路径或受限环境(如容器)时。


2. 函数原型

#include <unistd.h> // 必需

int execveat(int dirfd, const char *pathname,
             char *const argv[], char *const envp[], int flags);

3. 功能

  • 执行新程序: 终止调用进程的当前程序,并使用由 pathname(结合 dirfd 和 flags)指定的可执行文件来替换当前进程的内存镜像。
  • 传递参数和环境: 将新的命令行参数 (argv) 和环境变量 (envp) 传递给新程序。
  • 灵活的路径解析: 通过 dirfd 和 flags 参数,提供了比 execve 更灵活的路径解析方式。

4. 参数

  • int dirfd: 一个目录文件描述符,用作解析 pathname 的起始点
    • 如果 pathname 是相对路径(例如 "subdir/myprogram"),则相对于 dirfd 指向的目录进行查找。
    • 如果 pathname 是绝对路径(例如 "/usr/bin/ls"),则 dirfd 被忽略。
    • 可以传入特殊的值 AT_FDCWD,表示使用当前工作目录作为起始点(此时行为类似于 execve)。
  • const char *pathname: 指向要执行的可执行文件的路径名。这个路径名会根据 dirfd 和 flags 进行解析。
  • char *const argv[]: 一个字符串数组(向量),用于传递给新程序的命令行参数
    • 数组的每个元素都是一个指向以空字符 (\0) 结尾的字符串的指针。
    • 数组必须以 NULL 指针结束
    • argv[0] 通常是程序的名字(惯例,但不是强制)。
    • 例如:char *argv[] = {"myprogram", "--verbose", "input.txt", NULL};
  • char *const envp[]: 一个字符串数组(向量),用于设置新程序的环境变量
    • 数组的每个元素都是一个形如 "NAME=VALUE" 的字符串。
    • 数组必须以 NULL 指针结束
    • 例如:char *envp[] = {"PATH=/usr/bin:/bin", "HOME=/home/user", NULL};
    • 可以使用全局变量 environ 来传递当前进程的环境。
  • int flags: 控制路径解析行为的标志位。可以是以下值的按位或组合:
    • 0: 默认行为。pathname 被当作普通路径名处理,相对于 dirfd 解析。
    • AT_EMPTY_PATH: 如果 pathname 是一个空字符串 (""),则 execveat 会尝试执行 dirfd 本身所引用的文件。dirfd 必须是一个有效的、指向可执行文件的文件描述符
    • AT_SYMLINK_NOFOLLOW: 如果 pathname 解析过程中遇到符号链接(symbolic link),则不跟随该链接,而是尝试执行符号链接文件本身。注意:并非所有文件系统都支持执行符号链接文件本身,通常会失败。

5. 返回值

  • 成功时永不返回。因为 execveat 成功执行后,当前进程的代码和数据已经被新程序完全替换,从新程序的 main 函数开始执行。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EACCES 权限不足,ENOENT 文件未找到,EINVAL 参数无效,ENOMEM 内存不足,ELIBBAD 动态链接库损坏等)。

重要只有在 execveat 调用失败时,它才会返回 -1。如果成功,控制流就不会回到调用 execveat 的代码处。


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

  • execve: 最基础的 exec 函数,功能与 execveat 相同,但不支持 dirfd 和 flags 参数。
  • execlexeclpexecleexecvexecvpexecve 的各种包装函数,提供了不同的参数传递方式(如使用可变参数列表 ... 或自动搜索 PATH 环境变量)。
  • fork: 通常与 execveat/execve 配合使用。fork 创建子进程,然后在子进程中调用 execveat/execve 来执行新程序。
  • system: 一个更高级的库函数,内部通过 fork + execve (/bin/sh -c command) 来执行 shell 命令。
  • posix_spawn: POSIX 标准定义的、更现代和可移植的创建和执行子进程的方式,功能强大且避免了 fork 的开销(在某些系统上)。

7. 示例代码

示例 1:基本的 execveat 使用

这个例子演示了如何使用 execveat 来执行一个简单的程序(/bin/echo)。

// execveat_basic.c
#define _GNU_SOURCE // For AT_FDCWD
#include <unistd.h>   // execveat
#include <fcntl.h>    // open, O_RDONLY, AT_FDCWD
#include <stdio.h>    // perror, printf
#include <stdlib.h>   // exit
#include <errno.h>    // errno

int main() {
    // 1. 准备 execveat 的参数
    const char *pathname = "/bin/echo";
    char *const argv[] = {"echo", "Hello,", "from", "execveat!", NULL};
    // 使用当前进程的环境变量
    extern char **environ;
    char *const envp[] = {NULL}; // 或者直接传递 environ

    printf("About to execute '/bin/echo' using execveat...\n");

    // 2. 调用 execveat
    // dirfd = AT_FDCWD: 使用当前工作目录解析绝对路径
    // pathname = "/bin/echo": 要执行的程序
    // argv: 传递给新程序的参数
    // environ: 传递给新程序的环境变量
    // flags = 0: 默认行为
    if (execveat(AT_FDCWD, pathname, argv, environ, 0) == -1) {
        // 3. 如果 execveat 返回,说明执行失败
        perror("execveat failed");
        exit(EXIT_FAILURE);
    }

    // 4. 这行代码永远不会被执行,因为 execveat 成功后不会返回
    printf("This line will never be printed if execveat succeeds.\n");

    return 0; // 这行也不会被执行
}

代码解释:

  1. 定义要执行的程序路径 pathname ("/bin/echo")。
  2. 准备传递给新程序的命令行参数 argv 数组。argv[0] 通常是程序名 "echo",后面跟着要传递的参数 "Hello,""from""execveat!"非常重要:数组必须以 NULL 结尾。
  3. 准备环境变量 envp。这里为了简化,传递一个只包含 NULL 的数组,表示不传递任何环境变量。实际应用中通常传递 extern char **environ 来继承当前进程的环境。
  4. 调用 execveat(AT_FDCWD, pathname, argv, environ, 0)
    • AT_FDCWD: 使用当前工作目录作为路径解析起点。
    • pathname: 要执行的程序的绝对路径。
    • argv: 参数向量。
    • environ: 环境变量向量。
    • 0: 默认标志。
  5. 关键: 检查 execveat 的返回值。如果它返回 -1,说明执行失败,打印错误信息并退出。
  6. 如果 execveat 成功,当前进程的代码被 /bin/echo 程序替换,从 echo 的 main 函数开始执行,打印 "Hello, from execveat!",然后 echo 程序退出,整个进程也随之结束。
  7. main 函数中 execveat 之后的代码(包括 return 0;永远不会被执行

示例 2:使用 execveat 和 AT_EMPTY_PATH 执行已打开的文件

这个例子演示了如何使用 AT_EMPTY_PATH 标志来执行一个已经通过文件描述符打开的可执行文件。

// execveat_empty_path.c
#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main() {
    const char *binary_path = "/bin/ls"; // 我们将执行 ls 命令
    int binary_fd;
    char *const argv[] = {"ls", "-l", "/tmp", NULL}; // ls -l /tmp
    extern char **environ;

    printf("Opening executable file '%s'...\n", binary_path);

    // 1. 打开可执行文件,获取文件描述符
    binary_fd = open(binary_path, O_RDONLY);
    if (binary_fd == -1) {
        perror("open executable file");
        exit(EXIT_FAILURE);
    }
    printf("Executable file opened successfully. File descriptor: %d\n", binary_fd);

    // 2. 准备 execveat 的参数
    // 注意:pathname 是空字符串 ""
    const char *pathname = "";

    printf("About to execute using execveat with AT_EMPTY_PATH...\n");
    printf("Command will be: ls -l /tmp\n");

    // 3. 调用 execveat
    // dirfd = binary_fd: 指向已打开的可执行文件的文件描述符
    // pathname = "": 空字符串
    // argv: 传递给 ls 的参数
    // environ: 环境变量
    // flags = AT_EMPTY_PATH: 告诉 execveat 执行 dirfd 指向的文件
    if (execveat(binary_fd, pathname, argv, environ, AT_EMPTY_PATH) == -1) {
        // 4. 如果 execveat 返回,说明执行失败
        perror("execveat with AT_EMPTY_PATH failed");
        // 可能的原因:内核版本 < 3.19, binary_fd 无效, 文件不可执行等
        close(binary_fd); // 关闭文件描述符
        exit(EXIT_FAILURE);
    }

    // 5. 这行代码永远不会被执行
    printf("This line will never be printed if execveat succeeds.\n");

    // close(binary_fd); // 这行也不会被执行,因为 execveat 成功后进程已替换
    return 0;
}

代码解释:

  1. 定义要执行的二进制文件路径 binary_path ("/bin/ls")。
  2. 使用 open(binary_path, O_RDONLY) 以只读方式打开该可执行文件,并获得文件描述符 binary_fd
  3. 准备 execveat 的参数:
    • pathname 被设置为空字符串 ""
    • argv 设置为执行 ls -l /tmp 所需的参数。
    • envp 传递 environ
  4. 关键步骤: 调用 execveat(binary_fd, "", argv, environ, AT_EMPTY_PATH)
    • binary_fd: 之前打开的可执行文件的文件描述符。
    • "": 空字符串 pathname
    • AT_EMPTY_PATH: 这个标志告诉 execveat:忽略 pathname,直接执行 dirfd(即 binary_fd)所指向的文件。
  5. 检查返回值。如果成功,当前进程被 /bin/ls 程序替换,并执行 ls -l /tmp 命令。
  6. 如果失败,打印错误信息,关闭 binary_fd 并退出。

示例 3:execveat 与 fork 结合使用

这个例子演示了 execveat 如何与 fork 结合,创建一个子进程并在其中执行新程序。

// execveat_with_fork.c
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/wait.h> // waitpid
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main() {
    pid_t pid;
    int status;
    const char *pathname = "/bin/date"; // 执行 date 命令
    char *const argv[] = {"date", "+%Y-%m-%d %H:%M:%S", NULL};
    extern char **environ;

    printf("Parent process (PID: %d) is about to fork.\n", getpid());

    // 1. 创建子进程
    pid = fork();
    if (pid == -1) {
        // fork 失败
        perror("fork");
        exit(EXIT_FAILURE);
    }

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

        // 2. 在子进程中调用 execveat 执行新程序
        printf("Child process executing '/bin/date'...\n");

        if (execveat(AT_FDCWD, pathname, argv, environ, 0) == -1) {
            // 3. execveat 失败
            perror("execveat in child failed");
            _exit(EXIT_FAILURE); // 子进程使用 _exit 退出
        }

        // 4. 这行代码在子进程中永远不会被执行
        printf("This line in child will never be printed.\n");

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

        // 5. 父进程等待子进程结束
        printf("Parent process waiting for child to finish...\n");
        if (waitpid(pid, &status, 0) == -1) {
            perror("waitpid");
            exit(EXIT_FAILURE);
        }

        // 6. 检查子进程退出状态
        if (WIFEXITED(status)) {
            printf("Child process (PID: %d) exited normally with status %d.\n",
                   pid, WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            printf("Child process (PID: %d) was killed by signal %d.\n",
                   pid, WTERMSIG(status));
        } else {
            printf("Child process (PID: %d) exited abnormally.\n", pid);
        }

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

    return 0;
}

代码解释:

  1. 定义要执行的程序 "/bin/date" 及其参数。
  2. 调用 fork() 创建子进程。
  3. fork 返回后
    • 子进程 (pid == 0):
      • 调用 execveat(AT_FDCWD, pathname, argv, environ, 0) 执行 /bin/date 命令。
      • 如果 execveat 成功,子进程的代码被替换,date 命令开始执行并打印当前日期时间。
      • date 命令执行完毕后,子进程退出。
      • 如果 execveat 失败,打印错误信息并使用 _exit(EXIT_FAILURE) 退出(在子进程中推荐使用 _exit 而非 exit)。
    • 父进程 (pid > 0):
      • 打印子进程 PID。
      • 调用 waitpid(pid, &status, 0) 等待子进程结束。
      • waitpid 返回后,检查子进程的退出状态 status
      • 使用 WIFEXITEDWEXITSTATUSWIFSIGNALEDWTERMSIG 等宏来判断子进程是正常退出还是被信号终止,并获取退出码或信号号。
      • 父进程打印相关信息并退出。

重要提示与注意事项:

  1. 永不返回execveat 成功时永远不会返回到调用者。这是 exec 系列函数的根本特性。
  2. 失败处理必须检查 execveat 的返回值。如果返回 -1,表示执行失败,需要进行错误处理(如打印错误信息、退出等)。
  3. argv 和 envp 必须以 NULL 结尾: 这是 execve 系列函数的严格要求。
  4. AT_EMPTY_PATH: 这是 execveat 相比 execve 的关键新增功能,允许通过文件描述符直接执行文件,这在容器技术和安全沙箱中非常有用。
  5. AT_SYMLINK_NOFOLLOW: 尝试执行符号链接本身,但支持有限。
  6. dirfd 的使用: 提供了更灵活的路径解析能力,尤其是在受限或复杂目录结构中。
  7. 与 fork 结合: 这是创建新进程并执行新程序的经典模式(fork-and-exec 模式)。
  8. 内核版本: 需要 Linux 内核 3.19 或更高版本。
  9. 权限: 执行文件需要有可执行权限 (x)。
  10. environ: 使用 extern char **environ; 可以方便地传递当前进程的环境变量给新程序。

总结:

execveat 是 execve 的强大扩展,通过引入 dirfd 和 flags 参数,提供了更灵活、更安全的程序执行方式。它允许程序基于已打开的目录或文件描述符来定位和执行可执行文件,这在现代 Linux 系统编程,特别是容器和沙箱技术中具有重要意义。理解其参数和与 fork 的结合使用是掌握 Linux 进程控制的基础。

execve系统调用及示例

linux命名空间系统调用及示例

发表在 linux文章 | 留下评论