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


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 进程控制的基础。

发表在 linux文章 | 留下评论

execve系统调用及示例

好的,我们继续按照您的列表顺序,介绍下一个函数是 execve


1. 函数介绍

execve 是 Linux 系统编程中一组被称为 exec 函数族的核心成员之一。它的功能是用一个新的程序镜像(program image)

你可以把 execve 想象成彻底的身份转变

  • 你是一个人(当前运行的进程)。
  • 你决定彻底改变自己,变成另一个人(一个全新的程序)。
  • 你喝下了一瓶神奇药水(调用 execve)。
  • 瞬间,你的外表、记忆、技能、思维方式全部变成了那个人的(新的程序代码、数据、堆栈)。
  • 不再是原来的你,而是完全变成了新程序的实例。
  • 你的身份(PID)可能保持不变,但你的“灵魂”(程序代码)已经彻底替换。

execve(以及整个 exec 函数族)是实现程序执行的根本机制。当你在 shell 中输入命令(如 lsgrepgcc)并按回车时,shell 实际上是通过 fork 创建一个子进程,然后在子进程中调用 execve 来运行你指定的程序。


2. 函数原型

#include <unistd.h> // 必需

int execve(const char *pathname, char *const argv[], char *const envp[]);

3. 功能

  • 替换进程镜像: 用由 pathname 指定的新程序的镜像完全替换调用 execve 的当前进程的镜像。
  • 加载新程序: 内核会加载 pathname 指定的可执行文件。
  • 初始化新程序: 内核会为新程序分配内存,将程序代码和数据加载到内存中,初始化堆栈,并设置程序计数器(PC)指向程序的入口点(通常是 main 函数)。
  • 传递参数和环境: 将 argv 指定的命令行参数和 envp 指定的环境变量传递给新程序。
  • 开始执行: 从新程序的入口点开始执行新程序。

4. 参数

  • const char *pathname: 这是一个指向以空字符结尾的字符串的指针,该字符串包含了要执行的新程序的路径名
    • 这个路径名必须指向一个有效的、可执行的文件。
    • 它可以是绝对路径(如 /bin/ls)或相对路径(如 ./my_program)。
  • char *const argv[]: 这是一个指针数组,数组中的每个元素都是一个指向以空字符结尾的字符串的指针。这些字符串构成了传递给新程序的命令行参数
    • 惯例argv[0] 通常是程序的名字(或调用它的名字)。
    • 结尾: 数组的最后一个元素必须是 NULL,以标记参数列表的结束。
    • 例如:char *args[] = { "ls", "-l", "/home", NULL };
  • char *const envp[]: 这也是一个指针数组,数组中的每个元素都是一个指向以空字符结尾的字符串的指针。这些字符串定义了新程序的环境变量
    • 格式: 每个字符串的格式通常是 NAME=VALUE
    • 结尾: 数组的最后一个元素必须是 NULL,以标记环境变量列表的结束。
    • 例如:char *env_vars[] = { "HOME=/home/user", "PATH=/usr/bin:/bin", NULL };
    • 获取当前环境: 在 C 程序中,可以通过全局变量 extern char **environ; 来访问当前进程的环境变量列表。如果你想让新程序继承当前进程的所有环境变量,可以将 environ 作为 envp 参数传递。

5. 返回值

  • 成功时execve 永远不会返回。如果调用成功,当前进程的镜像就被新程序完全替代,执行从新程序的入口点开始。
  • 失败时: 如果 execve 调用失败(例如,文件不存在、权限不足、文件不是有效的可执行格式等),它会返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EACCES 权限不足,ENOENT 文件不存在,EINVAL 文件格式无效等)。

关键理解点execve 的成功调用是**“不归之路”**。一旦成功,调用 execve 的代码就不再存在了。


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

execve 是 exec 函数族中最底层、最通用的函数。其他 exec 函数都是基于 execve 或与其紧密相关的变体:

  • execlint execl(const char *path, const char *arg, ..., (char *)NULL);
    • 参数以列表(list)形式传递,而不是数组。
    • 最后一个参数必须是 (char *)NULL
    • 使用当前进程的 environ 作为环境。
  • execlpint execlp(const char *file, const char *arg, ..., (char *)NULL);
    • 与 execl 类似,但会在 PATH 环境变量指定的目录中搜索可执行文件。
  • execleint execle(const char *path, const char *arg, ..., (char *)NULL, char *const envp[]);
    • 与 execl 类似,但允许指定自定义的环境变量数组 envp
  • execvint execv(const char *path, char *const argv[]);
    • 参数以数组(vector)形式传递。
    • 使用当前进程的 environ 作为环境。
  • execvpint execvp(const char *file, char *const argv[]);
    • 与 execv 类似,但会在 PATH 环境变量指定的目录中搜索可执行文件。
  • execvpeint execvpe(const char *file, char *const argv[], char *const envp[]); (GNU 扩展)
    • 与 execvp 类似,但允许指定自定义的环境变量数组 envp

7. 示例代码

示例 1:使用 execve 执行 /bin/ls

这个例子演示了如何使用最底层的 execve 函数来执行 /bin/ls -l /tmp 命令。

// execve_ls.c
#include <unistd.h> // execve
#include <stdio.h>  // perror, printf
#include <stdlib.h> // exit

// 全局变量 environ,指向当前进程的环境变量数组
extern char **environ;

int main() {
    // 1. 定义要执行的程序路径
    char *pathname = "/bin/ls";

    // 2. 定义命令行参数数组 (argv)
    // 注意: argv[0] 通常是程序名,数组必须以 NULL 结尾
    char *argv[] = { "ls", "-l", "/tmp", NULL };

    // 3. 定义环境变量数组 (envp)
    // 为了简化,我们让新程序继承当前进程的所有环境变量
    // 通过传递全局变量 environ
    char **envp = environ; // 或者可以构造一个自定义的 envp 数组

    printf("About to execute: %s %s %s\n", argv[0], argv[1], argv[2]);

    // --- 关键: 调用 execve ---
    // 如果成功,execve 永远不会返回
    execve(pathname, argv, envp);

    // --- 如果代码执行到这里,说明 execve 失败了 ---
    perror("execve failed");
    // 打印错误信息后,程序继续执行下面的代码

    printf("This line will only be printed if execve fails.\n");
    exit(EXIT_FAILURE); // 因此,如果 execve 失败,应该显式退出
}

代码解释:

  1. 定义要执行的程序的完整路径 pathname ("/bin/ls")。
  2. 定义命令行参数数组 argv。它是一个 char * 数组。
    • argv[0] 设置为 "ls"(程序名)。
    • argv[1] 设置为 "-l"(第一个参数)。
    • argv[2] 设置为 "/tmp"(第二个参数)。
    • 关键: 数组的最后一个元素必须是 NULL,以标记参数列表结束。
  3. 定义环境变量数组 envp。这里为了简化,直接使用了全局变量 environ,它指向当前进程的环境变量列表,从而使新程序继承所有环境变量。
  4. 调用 execve(pathname, argv, envp)
  5. 关键: 如果 execve 成功,它会用 ls 程序替换当前进程,ls 程序开始执行,并且永远不会返回到 execve 之后的代码。
  6. 关键: 如果 execve 失败(例如,/bin/ls 文件不存在或不可执行),它会返回 -1,并设置 errno
  7. 因此,execve 之后的代码只有在失败时才会执行。这里打印错误信息并调用 exit(EXIT_FAILURE) 退出程序。

示例 2:使用 execve 执行自定义程序并传递自定义环境变量

这个例子演示了如何执行一个自定义程序,并向其传递一组自定义的环境变量。

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

// 假设你有一个简单的 C 程序 my_program.c 如下,并已编译为 my_program:
/*
// my_program.c
#include <stdio.h>
#include <stdlib.h> // getenv

int main(int argc, char *argv[], char *envp[]) {
    printf("--- My Custom Program Started ---\n");
    printf("Arguments received (argc=%d):\n", argc);
    for (int i = 0; i < argc; ++i) {
        printf("  argv[%d]: %s\n", i, argv[i]);
    }

    // 打印特定的环境变量
    char *my_env = getenv("MY_CUSTOM_ENV");
    char *lang_env = getenv("LANG");
    printf("\nEnvironment variables:\n");
    printf("  MY_CUSTOM_ENV: %s\n", my_env ? my_env : "(not set)");
    printf("  LANG: %s\n", lang_env ? lang_env : "(not set)");

    printf("--- My Custom Program Finished ---\n");
    return 42;
}
*/

int main() {
    char *pathname = "./my_program"; // 假设 my_program 在当前目录

    // 1. 定义命令行参数
    char *argv[] = { "my_program_alias", "arg1", "arg2 with spaces", NULL };

    // 2. 定义自定义环境变量
    // 注意:数组必须以 NULL 结尾
    char *envp[] = {
        "MY_CUSTOM_ENV=Hello_From_Execve",
        "LANG=C",
        "PATH=/usr/local/bin:/usr/bin:/bin", // 覆盖 PATH
        NULL
    };

    printf("Parent process preparing to execve '%s' with custom environment.\n", pathname);

    // --- 关键: 调用 execve 并传递自定义环境 ---
    execve(pathname, argv, envp);

    // --- 如果执行到这里,说明 execve 失败 ---
    perror("execve failed");
    printf("Failed to execute '%s'. Make sure it exists and is executable.\n", pathname);
    exit(EXIT_FAILURE);
}

如何测试:

  1. 首先,创建并编译 my_program.c# 创建 my_program.c (内容如上注释所示) gcc -o my_program my_program.c chmod +x my_program # 确保可执行
  2. 编译并运行 execve_custom.cgcc -o execve_custom execve_custom.c ./execve_custom

代码解释:

  1. 定义要执行的程序路径 pathname ("./my_program")。
  2. 定义命令行参数 argv,包括一个别名和两个参数。
  3. 关键: 定义一个自定义的环境变量数组 envp
    • 它包含三个环境变量:MY_CUSTOM_ENVLANGPATH
    • 重要: 数组以 NULL 结尾。
  4. 调用 execve(pathname, argv, envp)
  5. 如果成功,my_program 将被执行,并接收 argv 和 envp 中定义的参数和环境变量。
  6. my_program 会打印接收到的参数和特定的环境变量值,证明 execve 正确传递了它们。
  7. my_program 执行完毕后(返回 42),整个进程(包括 execve_custom)就结束了。

示例 3:fork + execve 经典范式

这个例子演示了 Unix/Linux 系统编程中最经典、最常用的模式:fork 创建子进程,然后在子进程中调用 execve 执行新程序。

// fork_execve.c
#include <sys/socket.h> // fork, wait
#include <sys/wait.h>   // wait
#include <unistd.h>     // execve, fork
#include <stdio.h>      // perror, printf
#include <stdlib.h>     // exit

extern char **environ;

int main() {
    pid_t pid;
    char *pathname = "/bin/date"; // 执行 date 命令
    char *argv[] = { "date", "+%Y-%m-%d %H:%M:%S", NULL };
    char **envp = environ;

    printf("Parent process (PID: %d) is about to fork.\n", getpid());

    // 1. 创建子进程
    pid = fork();

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

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

        // 2. 在子进程中调用 execve 执行新程序
        printf("Child (PID: %d) is about to execve '%s'.\n", getpid(), pathname);
        execve(pathname, argv, envp);

        // --- 如果代码执行到这里,说明 execve 失败 ---
        perror("execve failed in child");
        printf("Child process (PID: %d) exiting due to execve failure.\n", getpid());
        // 子进程失败时应使用 _exit,而不是 exit
        _exit(EXIT_FAILURE);

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

        // 3. 父进程等待子进程结束
        int status;
        printf("Parent (PID: %d) is waiting for child (PID: %d) to finish...\n", getpid(), pid);

        if (waitpid(pid, &status, 0) == -1) {
            perror("waitpid failed");
            exit(EXIT_FAILURE);
        }

        // 4. 检查子进程的退出状态
        if (WIFEXITED(status)) {
            int exit_code = WEXITSTATUS(status);
            printf("Parent: Child (PID: %d) exited normally with status %d.\n", pid, exit_code);
        } else if (WIFSIGNALED(status)) {
            int sig = WTERMSIG(status);
            printf("Parent: Child (PID: %d) was terminated by signal %d.\n", pid, sig);
        } else {
            printf("Parent: Child (PID: %d) did not exit normally.\n", pid);
        }

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

    return 0;
}

代码解释:

  1. 定义要执行的程序路径 (/bin/date) 和参数 (date +%Y-%m-%d %H:%M:%S)。
  2. 调用 fork() 创建子进程。
  3. 在子进程中 (pid == 0):
    • 调用 execve(pathname, argv, envp) 执行 date 命令。
    • 如果 execve 成功,子进程从此处消失,date 命令开始执行。
    • 如果 execve 失败,打印错误信息并调用 _exit(EXIT_FAILURE) 退出子进程。强调: 在 fork 的子进程中,失败时应使用 _exit 而非 exit
  4. 在父进程中 (pid > 0):
    • 打印信息。
    • 调用 waitpid(pid, &status, 0) 等待特定的子进程 (pid) 结束。
    • waitpid 返回后,检查子进程的退出状态 status
      • WIFEXITED(status): 检查子进程是否正常退出(通过 exit 或 return)。
      • WEXITSTATUS(status): 获取子进程的退出码。
      • WIFSIGNALED(status): 检查子进程是否被信号终止。
      • WTERMSIG(status): 获取终止子进程的信号编号。
    • 根据退出状态打印相应信息。
    • 父进程结束。

重要提示与注意事项:

  1. 永不返回execve 成功时永远不会返回。这是其最根本的特性。
  2. 失败处理execve 失败时返回 -1。必须检查返回值并处理错误,因为程序会继续执行 execve 之后的代码。
  3. _exit vs exit: 在 fork 之后的子进程中,如果 execve 失败并需要退出,应调用 _exit() 而不是 exit()。因为 exit() 会执行一些清理工作(如调用 atexit 注册的函数、刷新 stdio 缓冲区),这在子进程中可能导致意外行为(例如,缓冲区被刷新两次)。
  4. 参数和环境数组argv 和 envp 数组必须以 NULL 指针结尾。忘记 NULL 会导致未定义行为。
  5. argv[0]: 按惯例,argv[0] 应该是程序的名字。虽然可以是任意字符串,但很多程序会使用它来确定自己的行为。
  6. 环境变量envp 数组定义了新程序的完整环境。它不会自动继承父进程的环境,除非你显式地传递 environ
  7. PATH 搜索execve 不会在 PATH 环境变量中搜索可执行文件。它要求 pathname 是一个完整的路径。如果需要 PATH 搜索功能,应使用 execvp 或 execvpe
  8. 权限: 调用进程必须对 pathname 指定的文件具有执行权限。
  9. 文件描述符execve 不会关闭当前进程中打开的文件描述符(除非它们设置了 FD_CLOEXEC 标志)。新程序会继承这些文件描述符。
  10. exec 函数族选择:
    • 需要最精确控制(指定完整路径、自定义环境):使用 execve
    • 需要 PATH 搜索:使用 execvp 或 execvpe
    • 参数较少且希望列表形式:使用 execl 或 execlp
    • 一般推荐:execv 或 execvp,因为它们使用数组形式,更灵活且不易出错。

总结:

execve 是 Linux 系统中执行新程序的核心机制。它通过完全替换当前进程的内存镜像来启动一个新的程序。理解其参数(路径、参数数组、环境数组)和永不返回的特性对于掌握进程执行和 Unix/Linux 编程范式至关重要。它通常与 fork 结合使用,形成创建并运行新进程的经典模式。虽然有更高级的 exec 函数变体,但 execve 是它们的基础。

发表在 linux文章 | 留下评论

C语言高级编程技巧与最佳实践

目录

  1. 宏定义与预处理技巧
  2. 内存管理高级技巧
  3. 函数指针与回调机制
  4. 数据结构设计
  5. 并发与多线程
  6. 错误处理与异常机制
  7. 性能优化技巧
  8. 调试与测试技巧
  9. 跨平台编程
  10. 安全编程实践

宏定义与预处理技巧

1. 条件编译与平台检测

// 平台检测
#if defined(_WIN32) || defined(_WIN64)
    #define PLATFORM_WINDOWS
#elif defined(__linux__)
    #define PLATFORM_LINUX
#elif defined(__APPLE__)
    #define PLATFORM_MACOS
#endif

// 编译器检测
#if defined(__GNUC__)
    #define COMPILER_GCC
#elif defined(_MSC_VER)
    #define COMPILER_MSVC
#endif

// 版本检测
#if __STDC_VERSION__ >= 201112L
    #define C11_SUPPORTED
#endif

2. 强大的宏技巧

// 字符串化和连接
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define CONCAT(a, b) a##b

// 获取数组长度
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

// 容器of宏(从成员指针获取容器指针)
#define container_of(ptr, type, member) ({          \
    void *__mptr = (void *)(ptr);                    \
    ((type *)(__mptr - offsetof(type, member))); })

// 最大最小值
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))

// 交换变量(不使用临时变量)
#define SWAP(a, b) do { typeof(a) temp = a; a = b; b = temp; } while(0)

// 编译时断言
#define STATIC_ASSERT(condition, message) \
    typedef char static_assertion_##message[(condition) ? 1 : -1]

// 可变参数宏
#define DEBUG_PRINT(fmt, ...) \
    fprintf(stderr, "[DEBUG] %s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)

3. 现代C语言特性

// C11泛型选择
#define generic_max(a, b) _Generic((a), \
    int: max_int, \
    float: max_float, \
    double: max_double \
)(a, b)

// 静态断言(C11)
_Static_assert(sizeof(int) >= 4, "int must be at least 4 bytes");

// 线程局部存储(C11)
_Thread_local int thread_var;

内存管理高级技巧

1. 内存池设计

typedef struct {
    void *memory;
    size_t size;
    size_t used;
    size_t block_size;
} memory_pool_t;

memory_pool_t* create_pool(size_t size, size_t block_size) {
    memory_pool_t *pool = malloc(sizeof(memory_pool_t));
    pool->memory = malloc(size);
    pool->size = size;
    pool->used = 0;
    pool->block_size = block_size;
    return pool;
}

void* pool_alloc(memory_pool_t *pool, size_t size) {
    if (pool->used + size > pool->size) return NULL;
    void *ptr = (char*)pool->memory + pool->used;
    pool->used += size;
    return ptr;
}

2. 智能指针模拟

typedef struct {
    void *ptr;
    void (*deleter)(void*);
    int *ref_count;
} smart_ptr_t;

smart_ptr_t make_smart_ptr(void *ptr, void (*deleter)(void*)) {
    smart_ptr_t sp = {ptr, deleter, malloc(sizeof(int))};
    *sp.ref_count = 1;
    return sp;
}

smart_ptr_t smart_ptr_copy(smart_ptr_t sp) {
    ++(*sp.ref_count);
    return sp;
}

void smart_ptr_free(smart_ptr_t *sp) {
    if (--(*sp->ref_count) == 0) {
        sp->deleter(sp->ptr);
        free(sp->ref_count);
    }
}

3. 内存对齐

// C11对齐
_Alignas(16) char aligned_buffer[256];

// 手动对齐
#define ALIGN_UP(x, align) (((x) + (align) - 1) & ~((align) - 1))
#define IS_ALIGNED(x, align) (((x) & ((align) - 1)) == 0)

void* aligned_malloc(size_t size, size_t alignment) {
    void *ptr = malloc(size + alignment - 1 + sizeof(void*));
    if (!ptr) return NULL;
    
    void **aligned_ptr = (void**)(((uintptr_t)ptr + sizeof(void*) + alignment - 1) & ~(alignment - 1));
    aligned_ptr[-1] = ptr;
    return aligned_ptr;
}

函数指针与回调机制

1. 面向对象风格编程

// 虚函数表模拟
typedef struct {
    void (*destroy)(void *self);
    void (*print)(void *self);
    int (*compare)(void *self, void *other);
} vtable_t;

typedef struct {
    vtable_t *vtable;
    // 具体数据
} object_t;

// 多态调用
#define CALL_METHOD(obj, method, ...) \
    ((obj)->vtable->method((obj), ##__VA_ARGS__))

2. 状态机实现

typedef enum {
    STATE_IDLE,
    STATE_RUNNING,
    STATE_PAUSED,
    STATE_STOPPED
} state_t;

typedef struct {
    state_t current_state;
    int (*handlers[4])(void *context, int event);
} state_machine_t;

int handle_idle(void *context, int event) {
    switch (event) {
        case EVENT_START:
            return STATE_RUNNING;
        default:
            return STATE_IDLE;
    }
}

3. 插件系统设计

typedef struct {
    const char *name;
    int version;
    int (*init)(void);
    void (*cleanup)(void);
    void* (*create_instance)(void);
} plugin_interface_t;

// 动态加载插件
#ifdef _WIN32
    #include <windows.h>
    #define LOAD_PLUGIN(name) LoadLibrary(name)
    #define GET_SYMBOL(handle, name) GetProcAddress(handle, name)
#else
    #include <dlfcn.h>
    #define LOAD_PLUGIN(name) dlopen(name, RTLD_LAZY)
    #define GET_SYMBOL(handle, name) dymbol(handle, name)
#endif

数据结构设计

1. 链表实现

typedef struct list_node {
    void *data;
    struct list_node *next;
    struct list_node *prev;
} list_node_t;

typedef struct {
    list_node_t head;
    size_t size;
    void (*destructor)(void*);
} list_t;

// 双向链表操作
void list_insert_after(list_t *list, list_node_t *node, void *data) {
    list_node_t *new_node = malloc(sizeof(list_node_t));
    new_node->data = data;
    new_node->next = node->next;
    new_node->prev = node;
    
    if (node->next) node->next->prev = new_node;
    node->next = new_node;
    list->size++;
}

2. 哈希表实现

typedef struct hash_entry {
    char *key;
    void *value;
    struct hash_entry *next;
} hash_entry_t;

typedef struct {
    hash_entry_t **buckets;
    size_t bucket_count;
    size_t size;
    unsigned int (*hash_func)(const char*);
} hash_table_t;

unsigned int djb2_hash(const char *str) {
    unsigned int hash = 5381;
    int c;
    while ((c = *str++)) hash = ((hash << 5) + hash) + c;
    return hash;
}

3. 环形缓冲区

typedef struct {
    char *buffer;
    size_t size;
    size_t read_pos;
    size_t write_pos;
    int full;
} ring_buffer_t;

int ring_buffer_write(ring_buffer_t *rb, const char *data, size_t len) {
    size_t available = rb->size - ring_buffer_size(rb);
    if (len > available) return -1;
    
    for (size_t i = 0; i < len; i++) {
        rb->buffer[rb->write_pos] = data[i];
        rb->write_pos = (rb->write_pos + 1) % rb->size;
        if (rb->write_pos == rb->read_pos) rb->full = 1;
    }
    return len;
}

并发与多线程

1. 线程安全的数据结构

#include <pthread.h>

typedef struct {
    int value;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
} thread_safe_counter_t;

void counter_increment(thread_safe_counter_t *counter) {
    pthread_mutex_lock(&counter->mutex);
    counter->value++;
    pthread_cond_signal(&counter->cond);
    pthread_mutex_unlock(&counter->mutex);
}

int counter_wait_for(thread_safe_counter_t *counter, int target) {
    pthread_mutex_lock(&counter->mutex);
    while (counter->value < target) {
        pthread_cond_wait(&counter->cond, &counter->mutex);
    }
    pthread_mutex_unlock(&counter->mutex);
    return counter->value;
}

2. 读写锁实现

typedef struct {
    pthread_mutex_t mutex;
    pthread_cond_t read_cond;
    pthread_cond_t write_cond;
    int readers;
    int writers;
    int waiting_writers;
} rwlock_t;

void rwlock_rdlock(rwlock_t *rwlock) {
    pthread_mutex_lock(&rwlock->mutex);
    while (rwlock->writers > 0 || rwlock->waiting_writers > 0) {
        pthread_cond_wait(&rwlock->read_cond, &rwlock->mutex);
    }
    rwlock->readers++;
    pthread_mutex_unlock(&rwlock->mutex);
}

3. 无锁编程

#include <stdatomic.h>

typedef struct {
    atomic_int value;
} atomic_counter_t;

void atomic_counter_increment(atomic_counter_t *counter) {
    atomic_fetch_add(&counter->value, 1);
}

int atomic_counter_get(atomic_counter_t *counter) {
    return atomic_load(&counter->value);
}

错误处理与异常机制

1. 错误码系统

typedef enum {
    ERROR_SUCCESS = 0,
    ERROR_INVALID_PARAM = -1,
    ERROR_OUT_OF_MEMORY = -2,
    ERROR_FILE_NOT_FOUND = -3,
    ERROR_PERMISSION_DENIED = -4
} error_code_t;

#define RETURN_ON_ERROR(expr) do { \
    error_code_t err = (expr); \
    if (err != ERROR_SUCCESS) return err; \
} while(0)

// 带上下文的错误处理
typedef struct {
    error_code_t code;
    const char *message;
    const char *file;
    int line;
} error_context_t;

2. 异常模拟机制

#include <setjmp.h>

typedef struct {
    jmp_buf jump_buffer;
    int error_code;
    const char *error_message;
} exception_context_t;

static __thread exception_context_t *current_exception = NULL;

#define TRY \
    do { \
        exception_context_t __exception_ctx; \
        __exception_ctx.error_code = 0; \
        if (setjmp(__exception_ctx.jump_buffer) == 0) { \
            current_exception = &__exception_ctx;

#define CATCH(error_var) \
        } else { \
            error_var = current_exception->error_code;

#define END_TRY \
        } \
        current_exception = NULL; \
    } while(0);

#define THROW(code, message) \
    do { \
        if (current_exception) { \
            current_exception->error_code = code; \
            current_exception->error_message = message; \
            longjmp(current_exception->jump_buffer, 1); \
        } \
    } while(0)

3. 资源管理RAII

typedef struct {
    void *resource;
    void (*cleanup)(void*);
} raii_guard_t;

#define RAII_VAR(type, name, init, cleanup_func) \
    type name = init; \
    raii_guard_t __guard_##name = {&name, (void(*)(void*))cleanup_func}; \
    __attribute__((cleanup(raii_cleanup))) raii_guard_t *__raii_##name = &__guard_##name;

static void raii_cleanup(raii_guard_t **guard) {
    if ((*guard)->resource && (*guard)->cleanup) {
        (*guard)->cleanup((*guard)->resource);
    }
}

性能优化技巧

1. 缓存友好的数据结构

// 结构体打包优化
struct __attribute__((packed)) packed_struct {
    char a;
    int b;
    short c;
};

// 缓存行对齐
#define CACHE_LINE_SIZE 64
struct __attribute__((aligned(CACHE_LINE_SIZE))) cache_aligned_struct {
    int data[16];
};

2. 分支预测优化

// 静态分支预测
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)

void optimized_function(int *array, size_t size) {
    if (unlikely(size == 0)) return;
    
    for (size_t i = 0; likely(i < size); i++) {
        process_element(array[i]);
    }
}

3. 内联汇编优化

// 获取时间戳计数器
static inline uint64_t rdtsc(void) {
    uint32_t lo, hi;
    __asm__ __volatile__("rdtsc" : "=a" (lo), "=d" (hi));
    return ((uint64_t)hi << 32) | lo;
}

// 内存屏障
#define MEMORY_BARRIER() __asm__ __volatile__("" ::: "memory")

4. SIMD优化

#ifdef __SSE2__
#include <emmintrin.h>

void vector_add(float *a, float *b, float *result, size_t n) {
    size_t i = 0;
    for (; i + 4 <= n; i += 4) {
        __m128 va = _mm_load_ps(&a[i]);
        __m128 vb = _mm_load_ps(&b[i]);
        __m128 vr = _mm_add_ps(va, vb);
        _mm_store_ps(&result[i], vr);
    }
    // 处理剩余元素
    for (; i < n; i++) {
        result[i] = a[i] + b[i];
    }
}
#endif

调试与测试技巧

1. 调试宏

#ifdef DEBUG
    #define DBG_PRINT(fmt, ...) \
        fprintf(stderr, "[DEBUG] %s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
    #define ASSERT(condition) \
        do { \
            if (!(condition)) { \
                fprintf(stderr, "Assertion failed: %s at %s:%d\n", \
                        #condition, __FILE__, __LINE__); \
                abort(); \
            } \
        } while(0)
#else
    #define DBG_PRINT(fmt, ...) do {} while(0)
    #define ASSERT(condition) do {} while(0)
#endif

// 性能计时
#define TIME_IT(code, result_var) \
    do { \
        clock_t start = clock(); \
        code; \
        result_var = ((double)(clock() - start)) / CLOCKS_PER_SEC; \
    } while(0)

2. 单元测试框架

typedef struct {
    const char *name;
    void (*test_func)(void);
    int passed;
    int failed;
} test_case_t;

#define TEST_CASE(name) \
    static void test_##name(void); \
    static test_case_t test_case_##name = {#name, test_##name, 0, 0}; \
    static void test_##name(void)

#define ASSERT_EQ(expected, actual) \
    do { \
        if ((expected) != (actual)) { \
            fprintf(stderr, "Assertion failed: %s != %s at %s:%d\n", \
                    #expected, #actual, __FILE__, __LINE__); \
            current_test->failed++; \
        } else { \
            current_test->passed++; \
        } \
    } while(0)

3. 内存泄漏检测

#ifdef DEBUG_MEMORY
static size_t total_allocated = 0;
static size_t allocation_count = 0;

void* debug_malloc(size_t size, const char *file, int line) {
    void *ptr = malloc(size + sizeof(size_t));
    if (ptr) {
        *(size_t*)ptr = size;
        total_allocated += size;
        allocation_count++;
        printf("ALLOC: %zu bytes at %s:%d\n", size, file, line);
        return (char*)ptr + sizeof(size_t);
    }
    return NULL;
}

void debug_free(void *ptr, const char *file, int line) {
    if (ptr) {
        size_t *size_ptr = (size_t*)((char*)ptr - sizeof(size_t));
        total_allocated -= *size_ptr;
        allocation_count--;
        printf("FREE: %zu bytes at %s:%d\n", *size_ptr, file, line);
        free(size_ptr);
    }
}

#define malloc(size) debug_malloc(size, __FILE__, __LINE__)
#define free(ptr) debug_free(ptr, __FILE__, __LINE__)
#endif

跨平台编程

1. 平台抽象层

// 线程抽象
#ifdef _WIN32
    #include <windows.h>
    typedef HANDLE thread_t;
    typedef CRITICAL_SECTION mutex_t;
    #define THREAD_CREATE(thread, func, arg) \
        (thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)func, arg, 0, NULL))
    #define THREAD_JOIN(thread) WaitForSingleObject(thread, INFINITE)
    #define MUTEX_INIT(mutex) InitializeCriticalSection(mutex)
    #define MUTEX_LOCK(mutex) EnterCriticalSection(mutex)
    #define MUTEX_UNLOCK(mutex) LeaveCriticalSection(mutex)
#else
    #include <pthread.h>
    typedef pthread_t thread_t;
    typedef pthread_mutex_t mutex_t;
    #define THREAD_CREATE(thread, func, arg) pthread_create(&thread, NULL, func, arg)
    #define THREAD_JOIN(thread) pthread_join(thread, NULL)
    #define MUTEX_INIT(mutex) pthread_mutex_init(mutex, NULL)
    #define MUTEX_LOCK(mutex) pthread_mutex_lock(mutex)
    #define MUTEX_UNLOCK(mutex) pthread_mutex_unlock(mutex)
#endif

2. 文件路径处理

#ifdef _WIN32
    #define PATH_SEPARATOR '\\'
    #define PATH_SEPARATOR_STR "\\"
#else
    #define PATH_SEPARATOR '/'
    #define PATH_SEPARATOR_STR "/"
#endif

char* join_path(const char *dir, const char *file) {
    size_t dir_len = strlen(dir);
    size_t file_len = strlen(file);
    char *result = malloc(dir_len + file_len + 2);
    
    strcpy(result, dir);
    if (dir[dir_len - 1] != PATH_SEPARATOR) {
        strcat(result, PATH_SEPARATOR_STR);
    }
    strcat(result, file);
    return result;
}

3. 字节序处理

// 网络字节序转换
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
    #define IS_BIG_ENDIAN 1
#else
    #define IS_BIG_ENDIAN 0
#endif

static inline uint32_t swap_endian_32(uint32_t val) {
    return ((val & 0x000000FF) << 24) |
           ((val & 0x0000FF00) << 8)  |
           ((val & 0x00FF0000) >> 8)  |
           ((val & 0xFF000000) >> 24);
}

#define hton32(x) (IS_BIG_ENDIAN ? (x) : swap_endian_32(x))
#define ntoh32(x) hton32(x)

安全编程实践

1. 缓冲区溢出防护

// 安全字符串操作
size_t safe_strncpy(char *dest, size_t dest_size, const char *src, size_t count) {
    if (dest_size == 0) return 0;
    
    size_t copy_len = (count < dest_size - 1) ? count : dest_size - 1;
    memcpy(dest, src, copy_len);
    dest[copy_len] = '\0';
    return copy_len;
}

// 格式化字符串安全检查
#define SAFE_PRINTF(buffer, size, format, ...) \
    do { \
        int __result = snprintf(buffer, size, format, ##__VA_ARGS__); \
        if (__result < 0 || (size_t)__result >= size) { \
            /* 处理溢出 */ \
            buffer[size - 1] = '\0'; \
        } \
    } while(0)

2. 输入验证

// 整数溢出检查
static inline int safe_add(int a, int b, int *result) {
    if ((b > 0 && a > INT_MAX - b) || (b < 0 && a < INT_MIN - b)) {
        return -1; // 溢出
    }
    *result = a + b;
    return 0;
}

// 指针验证
#define VALIDATE_PTR(ptr) \
    do { \
        if (!(ptr)) { \
            return ERROR_INVALID_PARAM; \
        } \
    } while(0)

3. 安全随机数

#include <time.h>
#include <stdlib.h>

// 密码学安全随机数(需要平台支持)
#ifdef __linux__
    #include <sys/random.h>
    int secure_random_bytes(void *buf, size_t len) {
        return getrandom(buf, len, 0) == (ssize_t)len ? 0 : -1;
    }
#else
    // 简单的伪随机数生成器
    static unsigned long long rand_state = 1;
    
    void srand64(unsigned long long seed) {
        rand_state = seed;
    }
    
    unsigned long long rand64(void) {
        rand_state = rand_state * 6364136223846793005ULL + 1;
        return rand_state;
    }
#endif

附录:最佳实践总结

编码规范

  1. 命名约定:使用清晰的命名,避免缩写
  2. 注释风格:使用Doxygen风格注释
  3. 错误处理:始终检查返回值
  4. 内存管理:遵循RAII原则
  5. 线程安全:明确标识线程安全函数

性能优化原则

  1. 先测量后优化:使用性能分析工具
  2. 算法优先:选择合适的数据结构和算法
  3. 避免过早优化:保持代码可读性
  4. 缓存友好:考虑数据局部性
  5. 编译器优化:合理使用编译器优化选项

安全编码原则

  1. 输入验证:永远不要信任外部输入
  2. 边界检查:防止缓冲区溢出
  3. 最小权限:使用最小必要权限
  4. 安全函数:使用安全的字符串函数
  5. 代码审查:定期进行安全代码审查

这份指南涵盖了C语言编程中的高级技巧和最佳实践,从基础的宏定义到复杂的并发编程,从性能优化到安全编码,希望能帮助您提升C语言编程水平。

发表在 linux文章 | 留下评论

PCAKET_HOST等相关宏定义介绍

好的,我们来用中文详细解释一下 PACKET_HOST:

核心含义:

PACKET_HOST 是 Linux 内核中定义的一个常量(通常在头文件 中),它表示捕获到的网络数据包的目的地是本机。

详细说明:

1. 适用场景:
◦ 当你使用 AF_PACKET / PF_PACKET 套接字(也称为 SOCK_RAW + ETH_P_ALL 或 SOCK_DGRAM)进行原始网络链路层数据包捕获时,会接触到它。

◦ 这种类型的套接字允许你的程序直接接收流经网络接口(网卡)的底层数据包,包括不是发给本机的包(如果你开启了混杂模式)。

2. 来源和作用:
◦ 当你的原始套接字使用 recvfrom(), recvmsg() 等系统调用接收一个数据包时,除了数据内容本身,你还会收到一个地址结构 struct sockaddr_ll。

◦ 这个 struct sockaddr_ll 结构体有一个非常重要的成员叫做 sll_pkttype。

◦ sll_pkttype 的值就是由 PACKET_HOST, PACKET_BROADCAST, PACKET_MULTICAST, PACKET_OTHERHOST, PACKET_OUTGOING 等常量表示的。

◦ sll_pkttype = PACKET_HOST 明确告诉你:这个刚刚捕获到的数据包是发往本机操作系统网络协议栈的! 也就是说,这个包的目标 MAC 地址是本机网卡的 MAC 地址(或者如果接口支持,是配置给该接口的多个 MAC 地址之一)。

3. 与其他包类型的区别:
◦ PACKET_BROADCAST: 广播包 (目标 MAC 地址为 FF:FF:FF:FF:FF:FF)。

◦ PACKET_MULTICAST: 多播包 (目标 MAC 地址属于多播地址范围)。

◦ PACKET_OTHERHOST: 目的地不是本机的包。在正常模式下,网卡会过滤掉这类包。只有开启了网卡的“混杂模式”(Promiscuous Mode),你的原始套接字才能接收到这种数据包。例如,捕获同一局域网中其他主机之间的通信就需要它。

◦ PACKET_OUTGOING: 表示此包是由本机发送出去的数据包(当套接字设置为捕获所有包 ETH_P_ALL 时可能也会收到本机发出的包)。

关键总结:

• PACKET_HOST 不是配置项,而是捕获包时的一个“标记”或“属性”。

• 它是 struct sockaddr_ll 中 sll_pkttype 字段的一个可能取值。

• 它的值明确表示:“捕获到的这个数据包本来是打算交给本机操作系统协议栈处理的”(例如,一个发送给你电脑 IP 地址的 TCP SYN 包,一个 ICMP echo request 包等)。

• 在编写抓包工具(如 tcpdump、Wireshark 的底层)或自定义网络监控/安全程序时,检查 sll_pkttype 的值(特别是与 PACKET_HOST, PACKET_OTHERHOST 的比较)是理解捕获包流的关键。

通俗比喻:

想象一个繁忙的邮局分拣中心:

• PACKET_HOST: 信件上写着你的名字和地址,是寄给你的。

• PACKET_BROADCAST: 信件上写着“此片区所有居民收”,是广播通知。

• PACKET_MULTICAST: 信件上写着“某兴趣小组成员收”,是多播通知。

• PACKET_OTHERHOST: 信件上写的是别人的名字地址,你本不该看到,但邮局工作人员(在特殊模式下)也让你看了。

在代码中如何识别:
#include
#include #include // 或 netinet/if_ether.h
// … 其他头文件 …

int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); // 创建原始套接字
// … 绑定到接口等操作 …

struct sockaddr_ll sll;
socklen_t sll_len = sizeof(sll);
unsigned char buffer[1500]; // 常见 MTU 大小的缓冲区

// 接收数据包
ssize_t packet_size = recvfrom(sock, buffer, sizeof(buffer), 0,
(struct sockaddr*)&sll, &sll_len);

if (packet_size > 0) {
// 检查包的“类型属性” sll_pkttype
switch(sll.sll_pkttype) {
case PACKET_HOST:
printf(“捕获到一个本机接收包!\n”);
// … 处理目的地址是本机的包 …
break;
case PACKET_BROADCAST:
printf(“捕获到一个广播包!\n”);
break;
case PACKET_MULTICAST:
printf(“捕获到一个多播包!\n”);
break;
case PACKET_OTHERHOST:
printf(“捕获到一个目的地是其他主机的包!\n”);
// … 处理目的地址不是本机的包 (混杂模式下捕获) …
break;
default:
printf(“捕获到一个未知类型的包 (%d)\n”, sll.sll_pkttype);
}
}

希望这个详细的中文解释能帮助你清晰理解 PACKET_HOST 的含义和作用!

发表在 linux文章 | 留下评论

creat系统调用及示例

creat – 创建文件

函数介绍

creat系统调用用于创建新文件或截断已存在的文件。它是一个简化的文件创建函数,等价于open(path, O_WRONLY|O_CREAT|O_TRUNC, mode)

函数原型

#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

int creat(const char *pathname, mode_t mode);

功能

创建新文件或清空已存在的文件,返回只写权限的文件描述符。

参数

  • const char *pathname: 要创建的文件路径名
  • mode_t mode: 文件权限模式(如0644)

返回值

  • 成功时返回文件描述符(非负整数)
  • 失败时返回-1,并设置errno

特殊限制

  • 文件总是以只写模式打开
  • 如果文件已存在,会被截断为0字节
  • 不支持追加模式等高级选项

相似函数

  • open(): 更灵活的文件打开函数
  • mkstemp(): 创建安全的临时文件

示例代码

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

int main() {
    int fd;
    
    printf("=== Creat函数示例 ===\n");
    
    // 示例1: 基本文件创建
    printf("示例1: 基本文件创建\n");
    fd = creat("test_creat.txt", 0644);
    if (fd == -1) {
        perror("创建文件失败");
    } else {
        printf("成功创建文件,fd=%d\n", fd);
        write(fd, "Hello from creat!", 17);
        close(fd);
    }
    
    // 示例2: 重复创建(会截断原文件)
    printf("示例2: 重复创建(截断原文件)\n");
    fd = creat("test_creat.txt", 0644);
    if (fd != -1) {
        printf("重新创建文件(原内容被清空)\n");
        write(fd, "New content", 11);
        close(fd);
    }
    
    // 示例3: 权限测试
    printf("示例3: 不同权限创建\n");
    int fd1 = creat("file_600.txt", 0600);  // 仅所有者读写
    int fd2 = creat("file_644.txt", 0644);  // 所有者读写,其他用户读
    int fd3 = creat("file_666.txt", 0666);  // 所有用户读写
    
    if (fd1 != -1) {
        printf("创建0600权限文件\n");
        close(fd1);
    }
    if (fd2 != -1) {
        printf("创建0644权限文件\n");
        close(fd2);
    }
    if (fd3 != -1) {
        printf("创建0666权限文件\n");
        close(fd3);
    }
    
    // 清理测试文件
    unlink("test_creat.txt");
    unlink("file_600.txt");
    unlink("file_644.txt");
    unlink("file_666.txt");
    
    return 0;
}
https://app-blog.csdn.net/csdn/aiChatNew

creat – 创建文件

函数介绍

函数原型

功能

参数

返回值

特殊限制

相似函数

示例代码

Markdown 1391 字数 99 行数 当前行 1, 当前列 3

HTML 1330 字数 77 段落

保存草稿 发布文章

发表在 linux文章 | 留下评论

sysinfo系统调用及示例

sysfs 和 sysinfo 函数详解

1. 函数介绍

sysfs

sysfs 是Linux内核提供的虚拟文件系统,用于导出内核对象的信息到用户空间。它以文件和目录的形式呈现系统硬件、驱动程序、设备状态等信息,是现代Linux系统管理和监控的重要接口。

sysinfo

sysinfo 是Linux系统调用,用于获取系统的整体统计信息,包括内存使用情况、系统负载、运行时间等。它提供了一种编程方式来获取系统状态信息。

2. 函数原型

sysfs

#include <sysfs/libsysfs.h>  // 需要安装libsysfs-dev包

// sysfs库函数(非系统调用)
struct sysfs_bus *sysfs_open_bus(const char *name);
struct sysfs_device *sysfs_open_device(const char *bus_name, const char *dev_name);
char *sysfs_get_device_attr(const char *devpath, const char *attr_name);

sysinfo

#include <sys/sysinfo.h>

int sysinfo(struct sysinfo *info);

3. 功能

sysfs

  • 提供内核对象的结构化信息访问
  • 导出硬件设备、驱动程序、总线等信息
  • 支持动态查询设备状态和属性

sysinfo

  • 获取系统内存使用统计
  • 查询系统负载和运行时间
  • 获取进程和用户统计信息

4. 参数

sysfs

  • *const char name: 总线或设备名称
  • *const char devpath: 设备路径
  • *const char attr_name: 属性名称

sysinfo

  • *struct sysinfo info: 指向sysinfo结构的指针

5. 返回值

sysfs

  • 成功: 返回相应的句柄或字符串
  • 失败: 返回NULL,并设置errno

sysinfo

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

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

sysfs相关:

  • opendir/readdir: 目录遍历
  • open/read: 文件读取
  • /sys/: sysfs挂载点

sysinfo相关:

  • getloadavg: 获取系统负载平均值
  • getrusage: 获取资源使用情况
  • /proc/: procfs文件系统

7. 示例代码

示例1:基础sysinfo使用

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

/**
 * 显示系统信息
 */
void show_system_info() {
    struct sysinfo si;
    time_t current_time;
    char time_str[64];
    
    printf("=== 系统信息 ===\n");
    
    // 获取系统信息
    if (sysinfo(&si) == -1) {
        perror("获取系统信息失败");
        return;
    }
    
    // 显示时间信息
    current_time = time(NULL);
    strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", localtime(&current_time));
    printf("当前时间: %s\n", time_str);
    
    // 显示启动时间
    time_t boot_time = current_time - si.uptime;
    strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", localtime(&boot_time));
    printf("系统启动时间: %s\n", time_str);
    printf("系统运行时间: %ld 天 %ld 小时 %ld 分钟\n",
           si.uptime / 86400,
           (si.uptime % 86400) / 3600,
           (si.uptime % 3600) / 60);
    
    // 显示负载信息
    printf("\n系统负载:\n");
    printf("  1分钟平均负载: %.2f\n", (double)si.loads[0] / (1 << SI_LOAD_SHIFT));
    printf("  5分钟平均负载: %.2f\n", (double)si.loads[1] / (1 << SI_LOAD_SHIFT));
    printf("  15分钟平均负载: %.2f\n", (double)si.loads[2] / (1 << SI_LOAD_SHIFT));
    
    // 显示内存信息
    printf("\n内存信息:\n");
    printf("  总内存: %.2f GB\n", si.totalram * si.mem_unit / (1024.0 * 1024.0 * 1024.0));
    printf("  可用内存: %.2f GB\n", si.freeram * si.mem_unit / (1024.0 * 1024.0 * 1024.0));
    printf("  共享内存: %.2f GB\n", si.sharedram * si.mem_unit / (1024.0 * 1024.0 * 1024.0));
    printf("  缓冲区内存: %.2f GB\n", si.bufferram * si.mem_unit / (1024.0 * 1024.0 * 1024.0));
    
    // 显示交换信息
    printf("\n交换信息:\n");
    printf("  总交换空间: %.2f GB\n", si.totalswap * si.mem_unit / (1024.0 * 1024.0 * 1024.0));
    printf("  可用交换空间: %.2f GB\n", si.freeswap * si.mem_unit / (1024.0 * 1024.0 * 1024.0));
    
    // 显示进程信息
    printf("\n进程信息:\n");
    printf("  当前进程数: %d\n", si.procs);
    
    printf("\n");
}

/**
 * 演示基础sysinfo使用方法
 */
int demo_sysinfo_basic() {
    printf("=== 基础sysinfo使用示例 ===\n");
    
    // 显示系统信息
    show_system_info();
    
    // 演示多次查询
    printf("连续查询系统信息:\n");
    for (int i = 0; i < 3; i++) {
        struct sysinfo si;
        
        if (sysinfo(&si) == 0) {
            printf("  第 %d 次查询:\n", i + 1);
            printf("    运行时间: %ld 秒\n", si.uptime);
            printf("    可用内存: %.2f MB\n", 
                   si.freeram * si.mem_unit / (1024.0 * 1024.0));
            printf("    当前进程: %d\n", si.procs);
        } else {
            printf("  第 %d 次查询失败: %s\n", i + 1, strerror(errno));
        }
        
        if (i < 2) sleep(2);  // 间隔查询
    }
    
    return 0;
}

int main() {
    return demo_sysinfo_basic();
}

示例2:内存监控工具

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

/**
 * 内存使用统计结构
 */
typedef struct {
    unsigned long total_ram_mb;
    unsigned long free_ram_mb;
    unsigned long used_ram_mb;
    unsigned long shared_ram_mb;
    unsigned long buffer_ram_mb;
    unsigned long total_swap_mb;
    unsigned long free_swap_mb;
    unsigned long used_swap_mb;
    double ram_usage_percent;
    double swap_usage_percent;
} memory_stats_t;

/**
 * 获取内存统计信息
 */
int get_memory_stats(memory_stats_t *stats) {
    struct sysinfo si;
    
    if (sysinfo(&si) == -1) {
        return -1;
    }
    
    // 转换为MB
    unsigned long mem_unit = si.mem_unit ? si.mem_unit : 1;
    
    stats->total_ram_mb = si.totalram * mem_unit / (1024 * 1024);
    stats->free_ram_mb = si.freeram * mem_unit / (1024 * 1024);
    stats->used_ram_mb = stats->total_ram_mb - stats->free_ram_mb;
    stats->shared_ram_mb = si.sharedram * mem_unit / (1024 * 1024);
    stats->buffer_ram_mb = si.bufferram * mem_unit / (1024 * 1024);
    stats->total_swap_mb = si.totalswap * mem_unit / (1024 * 1024);
    stats->free_swap_mb = si.freeswap * mem_unit / (1024 * 1024);
    stats->used_swap_mb = stats->total_swap_mb - stats->free_swap_mb;
    
    // 计算使用率
    stats->ram_usage_percent = stats->total_ram_mb > 0 ? 
        (double)stats->used_ram_mb / stats->total_ram_mb * 100 : 0;
    stats->swap_usage_percent = stats->total_swap_mb > 0 ? 
        (double)stats->used_swap_mb / stats->total_swap_mb * 100 : 0;
    
    return 0;
}

/**
 * 显示内存统计信息
 */
void show_memory_stats(const memory_stats_t *stats) {
    printf("内存统计信息:\n");
    printf("  物理内存:\n");
    printf("    总量: %lu MB\n", stats->total_ram_mb);
    printf("    已用: %lu MB (%.1f%%)\n", stats->used_ram_mb, stats->ram_usage_percent);
    printf("    可用: %lu MB\n", stats->free_ram_mb);
    printf("    共享: %lu MB\n", stats->shared_ram_mb);
    printf("    缓冲: %lu MB\n", stats->buffer_ram_mb);
    
    printf("  交换空间:\n");
    printf("    总量: %lu MB\n", stats->total_swap_mb);
    printf("    已用: %lu MB (%.1f%%)\n", stats->used_swap_mb, stats->swap_usage_percent);
    printf("    可用: %lu MB\n", stats->free_swap_mb);
}

/**
 * 内存监控警报
 */
void check_memory_alerts(const memory_stats_t *stats) {
    printf("\n内存警报检查:\n");
    
    // RAM使用率警报
    if (stats->ram_usage_percent > 90) {
        printf("  ⚠ 警告: RAM使用率过高 (%.1f%%)\n", stats->ram_usage_percent);
    } else if (stats->ram_usage_percent > 80) {
        printf("  ℹ 提示: RAM使用率较高 (%.1f%%)\n", stats->ram_usage_percent);
    } else {
        printf("  ✓ RAM使用率正常 (%.1f%%)\n", stats->ram_usage_percent);
    }
    
    // 交换空间使用率警报
    if (stats->swap_usage_percent > 80) {
        printf("  ⚠ 警告: 交换空间使用率过高 (%.1f%%)\n", stats->swap_usage_percent);
    } else if (stats->swap_usage_percent > 50) {
        printf("  ℹ 提示: 交换空间使用率较高 (%.1f%%)\n", stats->swap_usage_percent);
    } else {
        printf("  ✓ 交换空间使用率正常 (%.1f%%)\n", stats->swap_usage_percent);
    }
    
    // 内存不足警报
    if (stats->free_ram_mb < 100) {
        printf("  ⚠ 警告: 可用内存不足 (%lu MB)\n", stats->free_ram_mb);
    }
}

/**
 * 演示内存监控工具
 */
int demo_memory_monitor() {
    memory_stats_t stats;
    
    printf("=== 内存监控工具演示 ===\n");
    
    // 单次内存统计
    printf("1. 当前内存状态:\n");
    if (get_memory_stats(&stats) == 0) {
        show_memory_stats(&stats);
        check_memory_alerts(&stats);
    } else {
        printf("获取内存统计失败: %s\n", strerror(errno));
        return -1;
    }
    
    // 连续监控演示
    printf("\n2. 连续内存监控演示:\n");
    printf("监控5次,每次间隔3秒:\n");
    
    for (int i = 0; i < 5; i++) {
        if (get_memory_stats(&stats) == 0) {
            time_t now = time(NULL);
            char time_str[32];
            strftime(time_str, sizeof(time_str), "%H:%M:%S", localtime(&now));
            
            printf("[%s] 第 %d 次监控:\n", time_str, i + 1);
            printf("  RAM: %lu/%lu MB (%.1f%%)\n",
                   stats.used_ram_mb, stats.total_ram_mb, stats.ram_usage_percent);
            printf("  Swap: %lu/%lu MB (%.1f%%)\n",
                   stats.used_swap_mb, stats.total_swap_mb, stats.swap_usage_percent);
            
            // 检查警报
            if (stats.ram_usage_percent > 90 || stats.swap_usage_percent > 80) {
                printf("  ⚠ 触发内存警报\n");
            }
        } else {
            printf("第 %d 次监控失败: %s\n", i + 1, strerror(errno));
        }
        
        if (i < 4) sleep(3);
    }
    
    // 内存使用趋势分析
    printf("\n3. 内存使用趋势分析:\n");
    printf("历史数据分析:\n");
    
    unsigned long max_ram_usage = 0;
    unsigned long min_ram_usage = (unsigned long)-1;
    double total_ram_usage = 0;
    int sample_count = 0;
    
    for (int i = 0; i < 5; i++) {
        if (get_memory_stats(&stats) == 0) {
            if (stats.used_ram_mb > max_ram_usage) {
                max_ram_usage = stats.used_ram_mb;
            }
            if (stats.used_ram_mb < min_ram_usage) {
                min_ram_usage = stats.used_ram_mb;
            }
            total_ram_usage += stats.ram_usage_percent;
            sample_count++;
        }
        sleep(1);
    }
    
    if (sample_count > 0) {
        printf("  最大RAM使用: %lu MB\n", max_ram_usage);
        printf("  最小RAM使用: %lu MB\n", min_ram_usage);
        printf("  平均RAM使用率: %.1f%%\n", total_ram_usage / sample_count);
    }
    
    return 0;
}

int main() {
    return demo_memory_monitor();
}

示例3:系统负载监控

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

/**
 * 系统负载统计结构
 */
typedef struct {
    double load_avg_1min;
    double load_avg_5min;
    double load_avg_15min;
    unsigned long uptime_seconds;
    unsigned int process_count;
    time_t boot_time;
} system_load_stats_t;

/**
 * 获取系统负载统计
 */
int get_system_load_stats(system_load_stats_t *stats) {
    struct sysinfo si;
    
    if (sysinfo(&si) == -1) {
        return -1;
    }
    
    // 计算负载平均值
    stats->load_avg_1min = (double)si.loads[0] / (1 << SI_LOAD_SHIFT);
    stats->load_avg_5min = (double)si.loads[1] / (1 << SI_LOAD_SHIFT);
    stats->load_avg_15min = (double)si.loads[2] / (1 << SI_LOAD_SHIFT);
    
    // 系统运行时间
    stats->uptime_seconds = si.uptime;
    stats->boot_time = time(NULL) - si.uptime;
    
    // 进程数
    stats->process_count = si.procs;
    
    return 0;
}

/**
 * 显示系统负载统计
 */
void show_system_load_stats(const system_load_stats_t *stats) {
    char boot_time_str[64];
    char current_time_str[64];
    
    strftime(boot_time_str, sizeof(boot_time_str), "%Y-%m-%d %H:%M:%S", 
             localtime(&stats->boot_time));
    
    printf("系统负载统计:\n");
    printf("  系统启动时间: %s\n", boot_time_str);
    
    unsigned long days = stats->uptime_seconds / 86400;
    unsigned long hours = (stats->uptime_seconds % 86400) / 3600;
    unsigned long minutes = (stats->uptime_seconds % 3600) / 60;
    printf("  系统运行时间: %lu 天 %lu 小时 %lu 分钟\n", days, hours, minutes);
    
    printf("  系统负载平均值:\n");
    printf("    1分钟: %.2f\n", stats->load_avg_1min);
    printf("    5分钟: %.2f\n", stats->load_avg_5min);
    printf("    15分钟: %.2f\n", stats->load_avg_15min);
    
    printf("  当前进程数: %u\n", stats->process_count);
}

/**
 * 分析系统负载状态
 */
void analyze_system_load(const system_load_stats_t *stats, int cpu_count) {
    printf("\n系统负载分析:\n");
    
    // 1分钟负载分析
    double load_per_cpu_1min = stats->load_avg_1min / cpu_count;
    if (load_per_cpu_1min > 1.0) {
        printf("  ⚠ 1分钟负载警告: 每CPU负载 %.2f (> 1.0)\n", load_per_cpu_1min);
    } else if (load_per_cpu_1min > 0.7) {
        printf("  ℹ 1分钟负载提示: 每CPU负载 %.2f (较高)\n", load_per_cpu_1min);
    } else {
        printf("  ✓ 1分钟负载正常: 每CPU负载 %.2f\n", load_per_cpu_1min);
    }
    
    // 5分钟负载分析
    double load_per_cpu_5min = stats->load_avg_5min / cpu_count;
    if (load_per_cpu_5min > 1.0) {
        printf("  ⚠ 5分钟负载警告: 每CPU负载 %.2f (> 1.0)\n", load_per_cpu_5min);
    } else if (load_per_cpu_5min > 0.7) {
        printf("  ℹ 5分钟负载提示: 每CPU负载 %.2f (较高)\n", load_per_cpu_5min);
    } else {
        printf("  ✓ 5分钟负载正常: 每CPU负载 %.2f\n", load_per_cpu_5min);
    }
    
    // 15分钟负载分析
    double load_per_cpu_15min = stats->load_avg_15min / cpu_count;
    if (load_per_cpu_15min > 1.0) {
        printf("  ⚠ 15分钟负载警告: 每CPU负载 %.2f (> 1.0)\n", load_per_cpu_15min);
    } else if (load_per_cpu_15min > 0.7) {
        printf("  ℹ 15分钟负载提示: 每CPU负载 %.2f (较高)\n", load_per_cpu_15min);
    } else {
        printf("  ✓ 15分钟负载正常: 每CPU负载 %.2f\n", load_per_cpu_15min);
    }
    
    // 进程数分析
    if (stats->process_count > 1000) {
        printf("  ⚠ 进程数警告: 当前有 %u 个进程\n", stats->process_count);
    } else if (stats->process_count > 500) {
        printf("  ℹ 进程数提示: 当前有 %u 个进程\n", stats->process_count);
    } else {
        printf("  ✓ 进程数正常: 当前有 %u 个进程\n", stats->process_count);
    }
}

/**
 * 演示系统负载监控
 */
int demo_system_load_monitoring() {
    system_load_stats_t stats;
    int cpu_count = sysconf(_SC_NPROCESSORS_ONLN);
    
    printf("=== 系统负载监控演示 ===\n");
    printf("CPU核心数: %d\n", cpu_count);
    
    // 单次负载统计
    printf("\n1. 当前系统负载状态:\n");
    if (get_system_load_stats(&stats) == 0) {
        show_system_load_stats(&stats);
        analyze_system_load(&stats, cpu_count);
    } else {
        printf("获取系统负载统计失败: %s\n", strerror(errno));
        return -1;
    }
    
    // 连续负载监控
    printf("\n2. 连续负载监控演示:\n");
    printf("监控10次,每次间隔2秒:\n");
    
    double max_load_1min = 0, min_load_1min = 999;
    double total_load_1min = 0;
    int sample_count = 0;
    
    for (int i = 0; i < 10; i++) {
        if (get_system_load_stats(&stats) == 0) {
            time_t now = time(NULL);
            char time_str[32];
            strftime(time_str, sizeof(time_str), "%H:%M:%S", localtime(&now));
            
            printf("[%s] 第 %d 次监控:\n", time_str, i + 1);
            printf("  负载: %.2f %.2f %.2f\n", 
                   stats.load_avg_1min, stats.load_avg_5min, stats.load_avg_15min);
            printf("  进程: %u\n", stats.process_count);
            
            // 统计负载数据
            if (stats.load_avg_1min > max_load_1min) {
                max_load_1min = stats.load_avg_1min;
            }
            if (stats.load_avg_1min < min_load_1min) {
                min_load_1min = stats.load_avg_1min;
            }
            total_load_1min += stats.load_avg_1min;
            sample_count++;
        } else {
            printf("第 %d 次监控失败: %s\n", i + 1, strerror(errno));
        }
        
        if (i < 9) sleep(2);
    }
    
    // 负载趋势分析
    printf("\n3. 负载趋势分析:\n");
    if (sample_count > 0) {
        printf("  1分钟负载范围: %.2f - %.2f\n", min_load_1min, max_load_1min);
        printf("  1分钟负载平均: %.2f\n", total_load_1min / sample_count);
        printf("  负载波动幅度: %.2f\n", max_load_1min - min_load_1min);
    }
    
    // 负载等级判断
    printf("\n4. 负载等级判断:\n");
    if (sample_count > 0) {
        double avg_load = total_load_1min / sample_count;
        double load_per_cpu = avg_load / cpu_count;
        
        if (load_per_cpu > 1.5) {
            printf("  💥 系统负载极重: 每CPU平均负载 %.2f\n", load_per_cpu);
        } else if (load_per_cpu > 1.0) {
            printf("  ⚠ 系统负载较重: 每CPU平均负载 %.2f\n", load_per_cpu);
        } else if (load_per_cpu > 0.7) {
            printf("  ℹ 系统负载中等: 每CPU平均负载 %.2f\n", load_per_cpu);
        } else {
            printf("  ✓ 系统负载轻松: 每CPU平均负载 %.2f\n", load_per_cpu);
        }
    }
    
    return 0;
}

int main() {
    return demo_system_load_monitoring();
}

示例4:基础sysfs使用

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>

/**
 * 读取sysfs文件内容
 */
int read_sysfs_file(const char *path, char *buffer, size_t buffer_size) {
    int fd;
    ssize_t bytes_read;
    
    fd = open(path, O_RDONLY);
    if (fd == -1) {
        return -1;
    }
    
    bytes_read = read(fd, buffer, buffer_size - 1);
    if (bytes_read == -1) {
        close(fd);
        return -1;
    }
    
    buffer[bytes_read] = '\0';
    
    // 移除末尾的换行符
    if (bytes_read > 0 && buffer[bytes_read - 1] == '\n') {
        buffer[bytes_read - 1] = '\0';
    }
    
    close(fd);
    return 0;
}

/**
 * 显示CPU信息
 */
void show_cpu_info() {
    DIR *dir;
    struct dirent *entry;
    char path[256];
    char buffer[256];
    
    printf("=== CPU信息 ===\n");
    
    // 读取CPU基本信息
    if (read_sysfs_file("/sys/devices/system/cpu/online", buffer, sizeof(buffer)) == 0) {
        printf("在线CPU: %s\n", buffer);
    }
    
    if (read_sysfs_file("/sys/devices/system/cpu/offline", buffer, sizeof(buffer)) == 0) {
        if (strlen(buffer) > 0) {
            printf("离线CPU: %s\n", buffer);
        }
    }
    
    // 遍历CPU目录
    dir = opendir("/sys/devices/system/cpu");
    if (dir) {
        int cpu_count = 0;
        while ((entry = readdir(dir)) != NULL) {
            if (strncmp(entry->d_name, "cpu", 3) == 0 && 
                entry->d_name[3] >= '0' && entry->d_name[3] <= '9') {
                cpu_count++;
                
                // 读取CPU频率信息
                snprintf(path, sizeof(path), 
                        "/sys/devices/system/cpu/%s/cpufreq/scaling_cur_freq", 
                        entry->d_name);
                if (read_sysfs_file(path, buffer, sizeof(buffer)) == 0) {
                    long freq_khz = atol(buffer);
                    printf("CPU %s 当前频率: %.2f MHz\n", 
                           entry->d_name, freq_khz / 1000.0);
                }
                
                // 读取CPU在线状态
                snprintf(path, sizeof(path), 
                        "/sys/devices/system/cpu/%s/online", 
                        entry->d_name);
                if (read_sysfs_file(path, buffer, sizeof(buffer)) == 0) {
                    printf("CPU %s 在线状态: %s\n", 
                           entry->d_name, atoi(buffer) ? "在线" : "离线");
                }
            }
        }
        printf("CPU总数: %d\n", cpu_count);
        closedir(dir);
    }
    
    printf("\n");
}

/**
 * 显示内存信息
 */
void show_memory_info() {
    char buffer[256];
    
    printf("=== 内存信息 ===\n");
    
    // 读取内存块信息
    if (read_sysfs_file("/sys/devices/system/memory/block_size_bytes", buffer, sizeof(buffer)) == 0) {
        unsigned long block_size = strtoul(buffer, NULL, 16);
        printf("内存块大小: %lu 字节 (%.2f MB)\n", block_size, block_size / (1024.0 * 1024.0));
    }
    
    // 遍历内存块
    DIR *dir = opendir("/sys/devices/system/memory");
    if (dir) {
        struct dirent *entry;
        int online_count = 0, total_count = 0;
        
        while ((entry = readdir(dir)) != NULL) {
            if (strncmp(entry->d_name, "memory", 6) == 0) {
                total_count++;
                char path[256];
                snprintf(path, sizeof(path), 
                        "/sys/devices/system/memory/%s/state", entry->d_name);
                
                if (read_sysfs_file(path, buffer, sizeof(buffer)) == 0) {
                    if (strcmp(buffer, "online") == 0) {
                        online_count++;
                    }
                }
            }
        }
        
        printf("内存块总数: %d\n", total_count);
        printf("在线内存块: %d\n", online_count);
        printf("离线内存块: %d\n", total_count - online_count);
        
        closedir(dir);
    }
    
    printf("\n");
}

/**
 * 显示块设备信息
 */
void show_block_devices() {
    printf("=== 块设备信息 ===\n");
    
    DIR *dir = opendir("/sys/block");
    if (dir) {
        struct dirent *entry;
        
        printf("块设备列表:\n");
        while ((entry = readdir(dir)) != NULL) {
            if (entry->d_name[0] != '.') {
                char path[256];
                char buffer[256];
                
                // 读取设备大小
                snprintf(path, sizeof(path), "/sys/block/%s/size", entry->d_name);
                if (read_sysfs_file(path, buffer, sizeof(buffer)) == 0) {
                    unsigned long sectors = atol(buffer);
                    double size_gb = sectors * 512.0 / (1024.0 * 1024.0 * 1024.0);
                    printf("  %s: %.2f GB (%lu 扇区)\n", entry->d_name, size_gb, sectors);
                } else {
                    printf("  %s: (无法获取大小)\n", entry->d_name);
                }
                
                // 读取设备类型
                snprintf(path, sizeof(path), "/sys/block/%s/queue/rotational", entry->d_name);
                if (read_sysfs_file(path, buffer, sizeof(buffer)) == 0) {
                    if (atoi(buffer) == 1) {
                        printf("    类型: 机械硬盘\n");
                    } else {
                        printf("    类型: 固态硬盘\n");
                    }
                }
            }
        }
        
        closedir(dir);
    }
    
    printf("\n");
}

/**
 * 演示基础sysfs使用方法
 */
int demo_sysfs_basic() {
    printf("=== 基础sysfs使用示例 ===\n");
    
    // 检查sysfs是否挂载
    if (access("/sys", F_OK) == -1) {
        printf("sysfs未挂载或不可访问\n");
        return -1;
    }
    
    printf("sysfs挂载点: /sys\n");
    
    // 显示CPU信息
    show_cpu_info();
    
    // 显示内存信息
    show_memory_info();
    
    // 显示块设备信息
    show_block_devices();
    
    // 演示读取特定属性
    printf("=== 特定属性读取演示 ===\n");
    
    struct {
        const char *path;
        const char *description;
    } attributes[] = {
        {"/sys/class/dmi/id/product_name", "产品名称"},
        {"/sys/class/dmi/id/product_version", "产品版本"},
        {"/sys/class/dmi/id/bios_vendor", "BIOS厂商"},
        {"/sys/class/dmi/id/bios_version", "BIOS版本"},
        {"/sys/kernel/mm/transparent_hugepage/enabled", "透明大页状态"},
        {NULL, NULL}
    };
    
    for (int i = 0; attributes[i].path; i++) {
        char buffer[256];
        if (read_sysfs_file(attributes[i].path, buffer, sizeof(buffer)) == 0) {
            printf("%s: %s\n", attributes[i].description, buffer);
        } else {
            printf("%s: 无法读取 (%s)\n", attributes[i].description, strerror(errno));
        }
    }
    
    return 0;
}

int main() {
    return demo_sysfs_basic();
}

示例5:综合系统监控工具

#include <sys/sysinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <dirent.h>
#include <fcntl.h>

/**
 * 综合系统信息结构
 */
typedef struct {
    // 系统基本信息
    time_t current_time;
    time_t boot_time;
    unsigned long uptime_seconds;
    
    // 内存信息
    unsigned long total_ram_mb;
    unsigned long free_ram_mb;
    unsigned long used_ram_mb;
    unsigned long total_swap_mb;
    unsigned long free_swap_mb;
    unsigned long used_swap_mb;
    double ram_usage_percent;
    double swap_usage_percent;
    
    // 负载信息
    double load_avg_1min;
    double load_avg_5min;
    double load_avg_15min;
    unsigned int process_count;
    
    // 硬件信息
    int cpu_count;
    int online_cpu_count;
    char hostname[256];
} system_info_t;

/**
 * 获取综合系统信息
 */
int get_system_info(system_info_t *info) {
    struct sysinfo si;
    
    // 获取系统时间
    info->current_time = time(NULL);
    
    // 获取sysinfo信息
    if (sysinfo(&si) == -1) {
        return -1;
    }
    
    // 系统基本信息
    info->uptime_seconds = si.uptime;
    info->boot_time = info->current_time - si.uptime;
    
    // 内存信息
    unsigned long mem_unit = si.mem_unit ? si.mem_unit : 1;
    info->total_ram_mb = si.totalram * mem_unit / (1024 * 1024);
    info->free_ram_mb = si.freeram * mem_unit / (1024 * 1024);
    info->used_ram_mb = info->total_ram_mb - info->free_ram_mb;
    info->total_swap_mb = si.totalswap * mem_unit / (1024 * 1024);
    info->free_swap_mb = si.freeswap * mem_unit / (1024 * 1024);
    info->used_swap_mb = info->total_swap_mb - info->free_swap_mb;
    
    info->ram_usage_percent = info->total_ram_mb > 0 ? 
        (double)info->used_ram_mb / info->total_ram_mb * 100 : 0;
    info->swap_usage_percent = info->total_swap_mb > 0 ? 
        (double)info->used_swap_mb / info->total_swap_mb * 100 : 0;
    
    // 负载信息
    info->load_avg_1min = (double)si.loads[0] / (1 << SI_LOAD_SHIFT);
    info->load_avg_5min = (double)si.loads[1] / (1 << SI_LOAD_SHIFT);
    info->load_avg_15min = (double)si.loads[2] / (1 << SI_LOAD_SHIFT);
    info->process_count = si.procs;
    
    // 硬件信息
    info->cpu_count = sysconf(_SC_NPROCESSORS_ONLN);
    info->online_cpu_count = info->cpu_count;  // 简化处理
    
    // 主机名
    gethostname(info->hostname, sizeof(info->hostname) - 1);
    
    return 0;
}

/**
 * 显示综合系统信息
 */
void show_system_info(const system_info_t *info) {
    char time_str[64];
    char boot_time_str[64];
    
    printf("=========================================\n");
    printf("         系统监控报告\n");
    printf("=========================================\n");
    
    // 时间信息
    strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", localtime(&info->current_time));
    strftime(boot_time_str, sizeof(boot_time_str), "%Y-%m-%d %H:%M:%S", localtime(&info->boot_time));
    
    printf("主机名: %s\n", info->hostname);
    printf("当前时间: %s\n", time_str);
    printf("启动时间: %s\n", boot_time_str);
    
    unsigned long days = info->uptime_seconds / 86400;
    unsigned long hours = (info->uptime_seconds % 86400) / 3600;
    unsigned long minutes = (info->uptime_seconds % 3600) / 60;
    printf("运行时间: %lu 天 %lu 小时 %lu 分钟\n", days, hours, minutes);
    
    // CPU信息
    printf("\nCPU信息:\n");
    printf("  CPU核心数: %d\n", info->cpu_count);
    printf("  在线CPU数: %d\n", info->online_cpu_count);
    
    // 内存信息
    printf("\n内存信息:\n");
    printf("  物理内存: %lu MB / %lu MB (%.1f%%)\n", 
           info->used_ram_mb, info->total_ram_mb, info->ram_usage_percent);
    printf("  交换空间: %lu MB / %lu MB (%.1f%%)\n", 
           info->used_swap_mb, info->total_swap_mb, info->swap_usage_percent);
    
    // 负载信息
    printf("\n系统负载:\n");
    printf("  1分钟平均负载: %.2f\n", info->load_avg_1min);
    printf("  5分钟平均负载: %.2f\n", info->load_avg_5min);
    printf("  15分钟平均负载: %.2f\n", info->load_avg_15min);
    printf("  当前进程数: %u\n", info->process_count);
    
    // 系统健康状态
    printf("\n系统健康状态:\n");
    
    // 内存健康检查
    if (info->ram_usage_percent > 90) {
        printf("  ⚠ 内存使用率过高: %.1f%%\n", info->ram_usage_percent);
    } else if (info->ram_usage_percent > 80) {
        printf("  ℹ 内存使用率较高: %.1f%%\n", info->ram_usage_percent);
    } else {
        printf("  ✓ 内存使用率正常: %.1f%%\n", info->ram_usage_percent);
    }
    
    // 交换空间健康检查
    if (info->swap_usage_percent > 50) {
        printf("  ⚠ 交换空间使用过多: %.1f%%\n", info->swap_usage_percent);
    } else if (info->swap_usage_percent > 20) {
        printf("  ℹ 交换空间使用较多: %.1f%%\n", info->swap_usage_percent);
    } else {
        printf("  ✓ 交换空间使用正常: %.1f%%\n", info->swap_usage_percent);
    }
    
    // 负载健康检查
    double load_per_cpu = info->load_avg_1min / info->cpu_count;
    if (load_per_cpu > 1.5) {
        printf("  ⚠ 系统负载过重: 每CPU负载 %.2f\n", load_per_cpu);
    } else if (load_per_cpu > 1.0) {
        printf("  ℹ 系统负载中等: 每CPU负载 %.2f\n", load_per_cpu);
    } else {
        printf("  ✓ 系统负载正常: 每CPU负载 %.2f\n", load_per_cpu);
    }
    
    printf("=========================================\n\n");
}

/**
 * 实时监控模式
 */
void real_time_monitoring(int duration_seconds, int interval_seconds) {
    system_info_t info;
    time_t start_time = time(NULL);
    time_t current_time;
    int report_count = 0;
    
    printf("=== 实时系统监控 ===\n");
    printf("监控时长: %d 秒\n", duration_seconds);
    printf("监控间隔: %d 秒\n", interval_seconds);
    printf("开始监控...\n\n");
    
    while ((current_time = time(NULL)) - start_time < duration_seconds) {
        if (get_system_info(&info) == 0) {
            report_count++;
            printf("[第 %d 次监控报告] ", report_count);
            show_system_info(&info);
        } else {
            printf("获取系统信息失败: %s\n", strerror(errno));
        }
        
        // 等待下次监控
        sleep(interval_seconds);
    }
    
    printf("监控完成,共生成 %d 份报告\n", report_count);
}

/**
 * 演示综合系统监控工具
 */
int demo_comprehensive_monitor() {
    system_info_t info;
    
    printf("=== 综合系统监控工具演示 ===\n");
    
    // 单次系统信息获取
    printf("1. 当前系统状态:\n");
    if (get_system_info(&info) == 0) {
        show_system_info(&info);
    } else {
        printf("获取系统信息失败: %s\n", strerror(errno));
        return -1;
    }
    
    // 简短监控演示
    printf("2. 简短监控演示 (10秒):\n");
    real_time_monitoring(10, 3);
    
    // 系统信息统计
    printf("3. 系统信息统计:\n");
    
    // 收集统计信息
    unsigned long max_ram_used = 0, min_ram_used = (unsigned long)-1;
    double max_load_1min = 0, min_load_1min = 999;
    double total_ram_usage = 0, total_load_1min = 0;
    int sample_count = 0;
    
    printf("收集统计样本...\n");
    for (int i = 0; i < 5; i++) {
        if (get_system_info(&info) == 0) {
            // 更新最大最小值
            if (info.used_ram_mb > max_ram_used) max_ram_used = info.used_ram_mb;
            if (info.used_ram_mb < min_ram_used) min_ram_used = info.used_ram_mb;
            if (info.load_avg_1min > max_load_1min) max_load_1min = info.load_avg_1min;
            if (info.load_avg_1min < min_load_1min) min_load_1min = info.load_avg_1min;
            
            // 累加统计值
            total_ram_usage += info.ram_usage_percent;
            total_load_1min += info.load_avg_1min;
            sample_count++;
        }
        sleep(1);
    }
    
    // 显示统计结果
    if (sample_count > 0) {
        printf("\n统计结果 (%d 个样本):\n", sample_count);
        printf("  内存使用范围: %lu MB - %lu MB\n", min_ram_used, max_ram_used);
        printf("  内存使用率范围: %.1f%% - %.1f%%\n", 
               min_ram_used * 100.0 / info.total_ram_mb,
               max_ram_used * 100.0 / info.total_ram_mb);
        printf("  负载范围: %.2f - %.2f\n", min_load_1min, max_load_1min);
        printf("  平均内存使用率: %.1f%%\n", total_ram_usage / sample_count);
        printf("  平均1分钟负载: %.2f\n", total_load_1min / sample_count);
    }
    
    // 监控建议
    printf("\n=== 系统监控建议 ===\n");
    printf("1. 定期监控:\n");
    printf("   - 每5分钟检查一次关键指标\n");
    printf("   - 每小时生成详细报告\n");
    printf("   - 每天汇总统计信息\n");
    
    printf("\n2. 警报阈值:\n");
    printf("   - 内存使用率 > 90%% 触发警告\n");
    printf("   - 交换空间使用率 > 50%% 触发警告\n");
    printf("   - 每CPU负载 > 1.5 触发警告\n");
    printf("   - 进程数 > 1000 触发提醒\n");
    
    printf("\n3. 性能优化:\n");
    printf("   - 根据负载趋势调整资源配置\n");
    printf("   - 及时清理内存泄漏进程\n");
    printf("   - 优化高负载应用\n");
    printf("   - 合理配置交换空间\n");
    
    return 0;
}

int main() {
    return demo_comprehensive_monitor();
}

sysfs/sysinfo 使用注意事项

系统要求:

  1. 内核版本: 支持sysfs的Linux内核(2.6+)
  2. 权限要求: 通常不需要特殊权限
  3. 挂载要求: sysfs必须正确挂载(通常在/sys)

sysfs特点:

  1. 虚拟文件系统: 不占用磁盘空间
  2. 动态更新: 实时反映内核状态
  3. 层次结构: 按照设备类型组织
  4. 只读属性: 大部分文件是只读的

sysinfo特点:

  1. 轻量级: 系统调用开销小
  2. 实时性: 提供当前系统状态
  3. 标准化: 跨平台兼容性好
  4. 信息全面: 涵盖主要系统指标

错误处理:

  1. ENOENT: 文件或目录不存在
  2. EACCES: 权限不足
  3. EINVAL: 参数无效
  4. ENOMEM: 内存不足

性能考虑:

  1. 缓存利用: 适当缓存频繁访问的信息
  2. 批量读取: 减少系统调用次数
  3. 异步处理: 避免阻塞主线程
  4. 增量更新: 只更新变化的信息

安全考虑:

  1. 权限检查: 验证访问权限
  2. 输入验证: 验证读取的数据
  3. 资源限制: 避免过度消耗系统资源
  4. 日志记录: 记录重要操作

最佳实践:

  1. 信息整合: 综合多个信息源
  2. 趋势分析: 关注指标变化趋势
  3. 警报机制: 及时发现异常情况
  4. 可视化展示: 直观显示监控结果

sysfs目录结构

主要目录:

/sys/
├── block/          # 块设备信息
├── bus/            # 总线信息
├── class/          # 设备类信息
├── dev/            # 设备信息
├── devices/        # 设备树
├── firmware/       # 固件信息
├── fs/             # 文件系统信息
└── kernel/         # 内核信息

sysinfo结构详解

struct sysinfo:

struct sysinfo {
    long uptime;         // 系统运行时间(秒)
    unsigned long loads[3];  // 1,5,15分钟负载平均值
    unsigned long totalram;  // 总物理内存
    unsigned long freeram;   // 可用物理内存
    unsigned long sharedram; // 共享内存
    unsigned long bufferram; // 缓冲区内存
    unsigned long totalswap; // 总交换空间
    unsigned long freeswap;  // 可用交换空间
    unsigned short procs;    // 当前进程数
    unsigned long totalhigh; // 高端内存总量
    unsigned long freehigh;  // 可用高端内存
    unsigned int mem_unit;   // 内存单位
    char _f[20-2*sizeof(__kernel_ulong_t)-sizeof(__u32)]; // 填充
};

常见使用场景

1. 系统监控:

// 实时监控系统资源使用情况
struct sysinfo si;
sysinfo(&si);
double ram_usage = (si.totalram - si.freeram) * 100.0 / si.totalram;

2. 性能分析:

// 分析系统负载和性能瓶颈
double load_per_cpu = si.loads[0] / (1 << SI_LOAD_SHIFT) / cpu_count;

3. 资源管理:

// 根据系统资源动态调整应用行为
if (si.freeram * si.mem_unit < MIN_MEMORY_THRESHOLD) {
    // 内存不足,采取措施
}

4. 硬件信息查询:

// 通过sysfs查询硬件详细信息
char model[256];
read_sysfs_file("/sys/class/dmi/id/product_name", model, sizeof(model));

总结

sysfs 和 sysinfo 是Linux系统中重要的系统信息访问接口:

sysfs特点:

  1. 结构化信息: 提供详细的硬件和内核对象信息
  2. 动态更新: 实时反映系统状态变化
  3. 标准化接口: 统一的文件系统访问方式
  4. 丰富内容: 涵盖各类系统组件信息

sysinfo特点:

  1. 快速访问: 高效获取系统整体统计信息
  2. 标准API: 跨平台兼容的系统调用
  3. 全面指标: 包含内存、负载、进程等关键信息
  4. 实时监控: 适合构建监控和告警系统

通过合理使用这两个接口,可以构建功能强大的系统监控和管理工具,为系统运维和性能优化提供有力支持。在实际应用中,需要注意错误处理、性能优化和安全考虑等方面的问题。

发表在 linux文章 | 留下评论