1、epoll()

本文深入解析epoll(), epoll_wait()等函数的使用方法与核心思想,通过实例代码展示如何利用epoll提高I/O效率。

1、epoll()

  epoll()是Linux特有的I/O复用函数,它的实现与使用上和select()、poll()、有很大差异。

  epoll()用一组函数来完成任务,而不是单个函数;其次,epoll()把文件描述放到内核事件表中,只需一个额外的文件描述符,来标识内核中唯一的这个事件表。

  需要使用的API:

  int epoll_create(int size);

  int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

需要使用的结构体信息:

typedef union epoll_data {
  void        *ptr;
  int          fd;  //一般情况下,都用的是这个文件描述符
  uint32_t     u32;
  uint64_t     u64;
} epoll_data_t;

struct epoll_event {
  uint32_t     events;      /* Epoll events */
  epoll_data_t data;        /* User data variable */
};

  int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

2、epoll_wait()

  关键:对epoll_wait()函数的核心理解

  (1)、返回值:事件表中就绪客户端的个数;

  (2)、参数events:将事件表中的就绪客户端的信息放到了events数组中。

3、epoll()的核心思想

  :就是创建一个内核事件表,存放所监听客户端的套接字和当前的事件,在利用epoll_wait()函数查找就绪的套接字,最后经过增加、删除、修改利用epoll_ctl()函数进行;当然了,这其中还有一批搭配使用的宏;

3、代码实现

(1)、utili.h

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

#include<sys/select.h>

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT  8787
#define LISTEN_QUEUE 5
#define SIZE 10
#define BUFFER_SIZE 256

#include<poll.h>
#define OPEN_MAX 1000

#include<sys/epoll.h>
#define FDSIZE      1000
#define EPOLLEVENTS 100

(2)、ser.c

#include"../utili.h"

static int socket_bind(const char *ip, int port){
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addrSer;
    addrSer.sin_family = AF_INET;
    //addrSer.sin_addr.s_addr = inet_addr(ip);
    inet_pton(AF_INET, ip, &addrSer.sin_addr);
    addrSer.sin_port = htons(port);
    bind(listenfd, (struct sockaddr*)&addrSer, sizeof(struct sockaddr));
    return listenfd;
}

static void add_event(int epollfd, int fd, int state){
    struct epoll_event ev;
    ev.events = state;
    ev.data.fd = fd;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
}

static void delete_event(int epollfd, int fd, int state){
    struct epoll_event ev;
    ev.events = state;
    ev.data.fd = fd;
    epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev);
}
static void modify_event(int epollfd, int fd, int state){
    struct epoll_event ev;
    ev.events = state;
    ev.data.fd = fd;
    epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev);
}

static void handle_accept(int epollfd, int listenfd){
    int clifd;
    struct sockaddr_in addrCli;
    socklen_t len = sizeof(struct sockaddr);
    clifd = accept(listenfd, (struct sockaddr*)&addrCli, &len);
    if(clifd != -1){
        add_event(epollfd, clifd, EPOLLIN);
    }
}

static void do_read(int epollfd,  int fd, char *buf){
    int nread = read(fd, buf, BUFFER_SIZE);
    if(nread == -1){
        close(fd);
        delete_event(epollfd, fd, EPOLLIN);
    }else{
        printf("read msg:>%s\n",buf);
        modify_event(epollfd, fd, EPOLLOUT);
    }
}
static void do_write(int epollfd, int fd, char *buf){
    int nwrite = write(fd, buf, strlen(buf)+1);
    if(nwrite == -1){
        close(fd);
        delete_event(epollfd, fd, EPOLLOUT);
    } else{
        modify_event(epollfd, fd , EPOLLIN);
    }
    memset(buf, 0, BUFFER_SIZE);

}

static void handle_events(int epollfd, struct epoll_event *events, int num,
                            int listenfd, char *buf){
    int i;
    int fd;
    for(i=0; i<num; ++i){
        fd = events[i].data.fd;
        if((fd==listenfd) && (events[i].events&EPOLLIN)) //根据其结果分别进入三种状态
            handle_accept(epollfd, listenfd);  //申请与服务器连接
        else if(events[i].events & EPOLLIN)
            do_read(epollfd, fd, buf);  //只读
        else if(events[i].events & EPOLLOUT)        
            do_write(epollfd, fd, buf);  //只写
    }
}


static void do_epoll(int listenfd){
    int ret;
    char buffer[BUFFER_SIZE];
    struct epoll_event events[EPOLLEVENTS];
    int epollfd = epoll_create(FDSIZE);
    add_event(epollfd, listenfd, EPOLLIN);
    for(;;){
        //select poll
        ret = epoll_wait(epollfd, events, EPOLLEVENTS, -1);
        handle_events(epollfd, events, ret, listenfd, buffer);
    }
    close(epollfd);
}

int main(void){
    int listenfd;
    listenfd = socket_bind(SERVER_IP, SERVER_PORT);
    listen(listenfd, LISTEN_QUEUE);
    do_epoll(listenfd);
    return 0;
}

(3)、cli.c

#include"../utili.h"

static void add_event(int epollfd, int fd, int state){
    struct epoll_event ev;
    ev.events = state;
    ev.data.fd = fd;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
}

static void delete_event(int epollfd, int fd, int state){
    struct epoll_event ev;
    ev.events = state;
    ev.data.fd = fd; 
    epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev);
}

static void modify_event(int epollfd, int fd, int state){
    struct epoll_event ev; 
    ev.events = state;
    ev.data.fd = fd; 
    epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev);
}



static void do_read(int epollfd,  int fd, int sockfd, char *buf){
    int nread = read(fd, buf, BUFFER_SIZE);
    if(nread == -1) {
        close(fd);
        delete_event(epollfd, fd, EPOLLIN);
    }else{
        if(fd == STDIN_FILENO)
            add_event(epollfd, fd, EPOLLIN);
        else{
            delete_event(epollfd, fd, EPOLLIN);
            add_event(epollfd, STDOUT_FILENO, EPOLLOUT);
        }
    }
    printf("Ser :>%s", buf);
}

static void do_write(int epollfd, int fd, int sockfd, char *buf){
    int nwrite = write(fd, buf, strlen(buf)+1);
    if(nwrite == -1){
        perror("write");
        close(fd);
    }else{
        if(fd == STDOUT_FILENO){
            delete_event(epollfd, fd, EPOLLOUT);
        }else{
            modify_event(epollfd, fd, EPOLLIN);
        }
    }    
    memset(buf, 0, BUFFER_SIZE);
}


static void handle_events(int epollfd, struct epoll_event *events, int num,
                            int sockfd, char *buf){
    int i;
    int fd;
    for(i=0; i<num; ++i){
        fd = events[i].data.fd;
        if(events[i].events & EPOLLIN)
            do_read(epollfd, fd, sockfd, buf);
        else if(events[i].events, fd, sockfd, buf)
            do_write(epollfd, fd, sockfd, buf);
    }
}
static void handle_connection(int sockfd){
    struct epoll_event events[EPOLLEVENTS];
    int epollfd = epoll_create(FDSIZE);
    add_event(epollfd, STDIN_FILENO, EPOLLIN);

    int ret;
    char buffer[BUFFER_SIZE];
    for(;;){
        ret = epoll_wait(epollfd, events, EPOLLEVENTS, -1);
        handle_events(epollfd, events, ret, sockfd, buffer);
    }
    close(epollfd);
}

int main(void){
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addrSer;
    addrSer.sin_family = AF_INET;
    addrSer.sin_port = htons(SERVER_PORT);
    addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);
    connect(sockfd, (struct sockaddr*)&addrSer, sizeof(struct sockaddr));

    handle_connection(sockfd);
    close(sockfd);
    return 0;
}

运行结果

  服务器端就是等待客户端的使用;

客户端1

wKioL1fpqXei35d_AAAZ3YfRI20994.png-wh_50

客户端2

wKioL1fpqbCTD-LIAAAW7mAZK28187.png-wh_50

利用epoll()函数,不用轮询每个套接字,效率更高效一些;

<think> 我们正在分析的是在WSL2系统(内核版本5.15.167.4-microsoft-standard-WSL2)中执行`strace -p 1`命令的输出内容及其含义,以及可能遇到的问题。 首先,我们需要理解`strace -p 1`命令的作用: - `strace`:一个用于跟踪系统调用和信号的诊断、调试工具。 - `-p 1`:指定附加到进程ID为1的进程(即init进程,在Linux系统中通常是所有进程的父进程)。 在标准的Linux系统中,进程1通常是`systemd`、`init`或其他初始化系统。然而,在WSL2环境中,情况有所不同。 ### WSL2中进程1的特殊性 在WSL2中,进程1是`/init`,它是WSL特有的初始化进程,由微软提供。它的主要职责包括: 1. 启动WSL的系统服务(如`getty`、`sshd`等) 2. 管理Windows与Linux的进程间通信 3. 处理进程信号转发 ### 执行`strace -p 1`可能遇到的输出和问题 当我们尝试在WSL2中执行`strace -p 1`时,可能会观察到以下情况: #### 1. **权限问题** - 错误信息:`attach: ptrace(PTRACE_SEIZE, 1): Operation not permitted` - 原因:默认情况下,WSL2允许用户空间进程使用`ptrace`(因为`ptrace_scope`默认为0),但附加到进程1需要`root`权限。 - 解决方案:使用`sudo`运行命令: ```bash sudo strace -p 1 ``` #### 2. **输出内容分析** 成功附加后,输出将显示进程1的系统调用。典型输出可能包括: ```text strace: Process 1 attached restart_syscall(<... resuming interrupted read ...>) = ? ``` - **常见系统调用**: - `read`/`write`:进程1通常会通过标准输入输出进行通信(尽管在后台运行)。 - `wait4`:等待子进程的状态变化。 - `rt_sigreturn`:处理来自内核的信号。 - `epoll_wait`:使用epoll机制等待I/O事件(高性能I/O复用)。 - `restart_syscall`:重启被信号中断的系统调用。 在WSL2中,进程1(即`/init`)主要处理来自Windows主机的请求和子进程管理,因此其系统调用活动相对较低。可能长时间阻塞在某个系统调用上(如`epoll_wait`或`pause`)。 #### 3. **可能遇到的问题** - **系统调用被过滤**:WSL2内核可能限制某些系统调用的跟踪(如与虚拟机通信相关的调用)。 - **输出停滞**:进程1大部分时间处于休眠状态(等待事件),因此`strace`可能长时间无输出。 - **性能影响**:持续跟踪进程1可能会显著增加系统负载(尤其在高频率事件场景)。 - **中断跟踪**:按`Ctrl+C`停止跟踪时,可能看到中断信号(`SIGINT`)被传递到进程1。 #### 4. **内核差异导致的异常** WSL2内核经过微软定制,某些系统调用的行为可能与标准Linux不同: - **未实现的系统调用**:部分系统调用在WSL2中返回`ENOSYS`(功能未实现)。 - **虚拟化相关调用**:如`ioctl`操作可能涉及Hyper-V通信,这些调用细节对用户不可见。 ### 诊断技巧 1. **过滤系统调用**: ```bash sudo strace -p 1 -e trace=!futex,pause # 排除futex和pause调用 ``` 2. **统计调用频率**: ```bash sudo strace -p 1 -c # 按Ctrl+C后显示统计 ``` ### 示例输出解释 假设我们获得以下输出片段: ```text epoll_wait(3, [], 1024, 0) = 0 epoll_wait(3, [], 1024, 1000) = 0 wait4(-1, 0x7ffc5fb8b1f0, WNOHANG, NULL) = 0 rt_sigreturn({mask=[]}) = 43 ``` - `epoll_wait`:进程1在等待I/O事件(超时设置1000毫秒)。 - `wait4`:非阻塞方式检查是否有子进程退出(无子进程退出时返回0)。 - `rt_sigreturn`:从信号处理程序返回(信号编号43在WSL中可能是自定义信号)。 ### 注意事项 - **安全风险**:在生产环境中跟踪关键进程可能导致稳定性问题。 - **替代工具**:对于WSL2内部的进程调试,建议使用Windows端工具(如Process Monitor)或WSL专用调试器。 --- 相关问题 1. 如何在WSL2中调试其他关键系统进程(如`/sbin/init`)? 2. WSL2内核与标准Linux内核在系统调用实现上有哪些主要差异? 3. 如何分析`strace`输出的系统调用性能瓶颈? 4. 在WSL2中是否有替代`strace`的工具用于系统跟踪? </think> ### `strace -p 1` 在 WSL2 中的输出解析 在 WSL2(内核版本 `5.15.167.4-microsoft-standard-WSL2`)中执行 `sudo strace -p 1` 命令时,进程 `1` 是 WSL2 特有的 **`/init` 初始化进程**(非标准 Linux 的 `systemd` 或 `init`)。其输出反映了 WSL2 架构的特殊行为: #### 典型输出及含义 ```bash strace: Process 1 attached restart_syscall(<... resuming interrupted read ...>) = ? ``` 1. **`restart_syscall`** - 表示进程在系统调用被中断后自动重启 - 公式:$$ \text{被信号中断的系统调用} \xrightarrow{\text{内核自动处理}} \text{restart\_syscall} $$ - 常见于进程阻塞在 I/O 操作时(如`read`/`write`) 2. **阻塞状态** - 长时间无新输出表明进程处于 **休眠等待状态**: $$ \text{/init} \xrightarrow{\text{epoll\_wait}} \text{等待事件触发} $$ - 这是正常设计:WSL2 的 `/init` 在无任务时主动休眠以节省资源[^1] --- ### 可能遇到的问题及原因 #### 问题 1:命令无输出或卡死 ```bash # 执行后无任何输出,无法退出 sudo strace -p 1 ``` - **原因**: WSL2 的 `/init` 进程在空闲时进入深度休眠 $$ \text{进程状态} = \text{TASK\_INTERRUPTIBLE} \quad (\text{可中断睡眠}) $$ - **解决方案**: 在 **另一个终端** 触发 WSL2 活动(如启动进程)唤醒 `/init`: ```bash # 新终端窗口执行 echo "wakeup" > /dev/null ``` #### 问题 2:权限错误 ```bash attach: ptrace(PTRACE_SEIZE, 1): Operation not permitted ``` - **原因**: 未用 `sudo` 或 WSL2 的 `ptrace` 限制未完全解除 - **解决方案**: ```bash # 确保使用 root 权限 sudo strace -p 1 # 检查 ptrace 权限 echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope ``` #### 问题 3:意外信号中断 ```bash --- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=0, si_uid=0} --- ``` - **原因**: Windows 主机重启/关机信号通过 Hyper-V 层转发到 WSL2[^2] - **应对**: 此信号表示 WSL2 即将关闭,需保存工作 --- ### WSL2 架构特殊性导致的差异 | 行为 | 标准 Linux | WSL2 | 原因 | |---------------------|--------------------------|------------------------|--------------------------| | **进程 1 的身份** | `systemd`/`init` | `/init` | 微软定制初始化系统 | | **系统调用模式** | 高频调度 | 低频休眠 | 轻量化设计[^3] | | **信号来源** | 内核/其他进程 | Windows 主机 + Hyper-V | 虚拟化架构 | | `ptrace_scope` 文件 | 存在 | 不存在 | 内核禁用 YAMA 模块 | --- ### 最佳实践建议 1. **动态跟踪技巧** ```bash # 1. 启动后台跟踪 sudo strace -p 1 -o /tmp/init.log & # 2. 触发 WSL2 活动(例如启动进程) sleep 5 # 3. 查看日志 tail -f /tmp/init.log ``` 2. **替代调试方案** ```bash # 使用内核日志观察行为 dmesg -w | grep -i init # 进程树分析 pstree -p -s 1 ``` 3. **关键系统调用解析** ```text clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD...) = 1234 ``` - `clone`:创建新进程(如启动 Linux 子进程) - `CLONE_CHILD_SETTID`:设置子进程 TID - `SIGCHLD`:子进程终止时通知父进程 --- 相关问题 1. 如何在不中断服务的情况下调试 WSL2 的关键系统进程? 2. WSL2 的 `/init` 进程与标准 Linux 的 `init` 有何本质区别? 3. 哪些工具可以监控 WSL2 与 Windows 主机的跨系统通信? 4. 如何通过自定义内核编译启用 WSL2 的完整 `ptrace` 功能?[^3] [^1]: WSL2 的轻量化设计导致进程常驻内存而非持续活跃 [^2]: Hyper-V 虚拟化层负责 Windows 与 WSL2 的信号转发 [^3]: 微软官方文档确认 WSL2 内核默认禁用部分安全模块
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值