<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<atom:link href="http://gentoo-zh.org/extern.php?action=feed&amp;tid=460&amp;type=rss" rel="self" type="application/rss+xml" />
		<title><![CDATA[Gentoo中文社区 / linux源码解读（十七）：红黑树在内核的应用——epoll]]></title>
		<link>http://www.gentoo-zh.org/viewtopic.php?id=460</link>
		<description><![CDATA[linux源码解读（十七）：红黑树在内核的应用——epoll 最近发表的帖子。]]></description>
		<lastBuildDate>Tue, 11 Oct 2022 02:48:34 +0000</lastBuildDate>
		<generator>FluxBB</generator>
		<item>
			<title><![CDATA[linux源码解读（十七）：红黑树在内核的应用——epoll]]></title>
			<link>http://www.gentoo-zh.org/viewtopic.php?pid=467#p467</link>
			<description><![CDATA[<p>1、简单介绍一下epoll的出现的背景：这里以java代码为例，最原始的server代码如下：</p><div class="codebox"><pre><code>while(true){
         ServerSocket ss = new ServerSocket(8888);
         System.out.println(&quot;启动服务器....&quot;);
         Socket s = ss.accept();//阻塞点
         System.out.println(&quot;客户端:&quot;+s.getInetAddress().getLocalHost()+&quot;已连接到服务器&quot;);
         
         BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
         //读取客户端发送来的消息
         String mess = br.readLine();//阻塞2
         System.out.println(&quot;客户端：&quot;+mess);
         BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
         bw.write(mess+&quot;\n&quot;);
         bw.flush();  

}</code></pre></div><p>&#160; 单线程中，一个死循环不停地接受客户端的连接和数据；上述代码有两个“卡点”：第一个是accept函数，server在8888端口监听有没有client来连接，如果没有就一直阻塞，代码没法往下继续执行！第二个是readLine函数，server和client建立连接后要等client发送数据；如果没收到client的数据也一直阻塞，后面的代码还是没法执行！这种代码的缺陷就很明显了：单线程同时只能处理一个client的连接和收数据，如果没有就一直阻塞，其他client的连接请求无法处理！</p><p>&#160; 既然上述问题是单线程导致的，改成多线程不就解决了？每个线程单独accept和readLine，就算阻塞也只阻塞单个线程；但凡有新client连接，server就单独开个线程去accept和readLine，client之间互不影响，问题完美解决？？？？？(￣▽￣)&quot;&#160; 如果真有这么简单，就没epoll啥事了！多线程也有缺陷：client连接后不见的会持续发数据，但是server却要分配线程去监听，同时做好接收数据的准备；server这边单独开一个sockt（linux操作系统底层用的是fd表示）+线程会明显会消耗server的硬件资源；如果有大量的client连接后却不发送数据，server会因为分配了大量的线程+socket，让cpu（大量线程之间轮询，产生上下文切换）甚至内存被打满（这不就是DDOS攻击么？），整个服务器没法继续干活了，这可咋整？</p><p>&#160; 2、回顾上面的问题，本质都是因为accept和readLine阻塞引起的，如果改成单线程+非阻塞能不能解决了？epoll孕育而生！</p><p>&#160; &#160; （1）还是按照以往的思路，先看看有哪些相关的结构体：eventpoll，包含了红黑树的root节点和readList链表！既然包含了root节点，肯定需要先生成该结构体实例的！</p><div class="codebox"><pre><code>struct eventpoll {
  ...
  /*红黑树的根节点，这棵树中存储着所有添加到epoll中的事件，
  也就是这个epoll监控的事件*/
  struct rb_root rbr;
  /*双向链表rdllist保存着将要通过epoll_wait返回给用户的、满足条件的事件*/
  struct list_head rdllist;
  ...
};</code></pre></div><p>&#160; &#160;epitem：组成了红黑树的节点。和epoll这种业务相关的字段就是epoll_event了！</p><div class="codebox"><pre><code>struct epitem {
  ...
  //红黑树节点
  struct rb_node rbn;
  //双向链表节点
  struct list_head rdllink;
  //事件句柄等信息
  struct epoll_filefd ffd;
  //指向其所属的eventepoll对象
  struct eventpoll *ep;
  //期待的事件类型
/* The structure that describe the interested events and the source fd */
  struct epoll_event event;
   //下一个epitem实例
   struct epitem *next;  ...
}; // 这里包含每一个事件对应着的信息。</code></pre></div><p>复制代码</p><p>&#160; 从源码的英文注释看，epoll_event结构体就两个字段：由事件和fd组成的，让事件和fd产生映射关系，避免事件和fd张冠李戴！比如明明是进程A的socket收到了数据，却错误映射到了进程B的socket就尴尬了！</p><div class="codebox"><pre><code>struct epoll_event {
    __u32 events;
    __u64 data;
} EPOLL_PACKED;</code></pre></div><p>&#160; 这3个结构体之间的脉络关系如下：这里标注了3个函数，也正是这3个函数构建了红黑树和两个双向链表！<br /><span class="postimg"><img src="https://img2022.cnblogs.com/blog/2052730/202201/2052730-20220121114418332-1796426900.png" alt="FluxBB bbcode 测试" /></span></p><p>&#160; &#160; &#160; &#160; 注意：这个图上有两个链表，task链表组成的struct字段如下：最核心的就是第2个和第3个字段了！当进程没收到数据时，会进入这个wait_queue；当网卡收到数据后，会通过中断通知cpu来取数据，再执行第三个回调函数func！</p><div class="codebox"><pre><code>struct __wait_queue {
    unsigned int        flags;
    void            *private;//等待队列的task_struct指针
    wait_queue_func_t    func;//进程唤醒后执行的回调函数
    struct list_head    task_list;
};</code></pre></div><p>&#160; &#160; &#160;这个结构体实例之间的关系：<br /><span class="postimg"><img src="https://img2022.cnblogs.com/blog/2052730/202201/2052730-20220121194816164-1005931440.png" alt="FluxBB bbcode 测试" /></span> </p><p>&#160; &#160; &#160;（2）既然红黑树的根节点是在eventpoll结构体中，那么肯定要先生成eventpoll实例，这个是在epoll_create函数中实现的，如下：</p><div class="codebox"><pre class="vscroll"><code>/*
 * Open an eventpoll file descriptor.
 */
SYSCALL_DEFINE1(epoll_create1, int, flags)
{
    int error, fd;
    struct eventpoll *ep = NULL;
    struct file *file;

    /* Check the EPOLL_* constant for consistency.  */
    BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);

    if (flags &amp; ~EPOLL_CLOEXEC)
        return -EINVAL;
    /*
     * Create the internal data structure (&quot;struct eventpoll&quot;).
       创建一个eventpoll实例，里面初始化红黑树、链表的节点
     */
    error = ep_alloc(&amp;ep);
    if (error &lt; 0)
        return error;
    /*
     * Creates all the items needed to setup an eventpoll file. That is,
     * a file structure and a free file descriptor.
     */
    fd = get_unused_fd_flags(O_RDWR | (flags &amp; O_CLOEXEC));
    if (fd &lt; 0) {
        error = fd;
        goto out_free_ep;
    }
    file = anon_inode_getfile(&quot;[eventpoll]&quot;, &amp;eventpoll_fops, ep,
                 O_RDWR | (flags &amp; O_CLOEXEC));
    if (IS_ERR(file)) {
        error = PTR_ERR(file);
        goto out_free_fd;
    }
    ep-&gt;file = file;
    fd_install(fd, file);
    return fd;

out_free_fd:
    put_unused_fd(fd);
out_free_ep:
    ep_free(ep);
    return error;
}</code></pre></div><p>&#160; 既然是create函数，核心功能就是生成eventpoll结构体的实例，这个是通过调用ep_alloc实现的！</p><div class="codebox"><pre class="vscroll"><code>/*生成eventpoll实例*/
static int ep_alloc(struct eventpoll **pep)
{
    int error;
    struct user_struct *user;
    struct eventpoll *ep;

    user = get_current_user();
    error = -ENOMEM;
    ep = kzalloc(sizeof(*ep), GFP_KERNEL);
    if (unlikely(!ep))
        goto free_uid;

    spin_lock_init(&amp;ep-&gt;lock);
    mutex_init(&amp;ep-&gt;mtx);
    init_waitqueue_head(&amp;ep-&gt;wq);
    init_waitqueue_head(&amp;ep-&gt;poll_wait);
    INIT_LIST_HEAD(&amp;ep-&gt;rdllist);//readList第一个节点初始化
    ep-&gt;rbr = RB_ROOT;//红黑树根节点
    ep-&gt;ovflist = EP_UNACTIVE_PTR;
    ep-&gt;user = user;

    *pep = ep;

    return 0;

free_uid:
    free_uid(user);
    return error;
}</code></pre></div><p>&#160; 红黑树的根节点、链表头节点都生成后，接下来就是构建整颗树以及链表了，这些都是在epoll_ctl中实现的；先看看epoll操作的类型：主要有下面3种：增加、删除和修改！<br />static const char *epoll_ctl_ops[] = { &quot;ADD&quot;, &quot;DEL&quot;, &quot;MOD&quot;, };</p><p>&#160; 这还是个系统调用，核心代码如下（函数开始有很多容错的代码忽略了，避免影响对主干代码的理解）：就是个switch结构，3种不同的options对应3种不同的操作！本质上就是让内核知道用户关注哪些事件；事件发生的时候需要回调哪些函数！</p><div class="codebox"><pre class="vscroll"><code>/*
 * The following function implements the controller interface for
 * the eventpoll file that enables the insertion/removal/change of
 * file descriptors inside the interest set.
 */
SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
        struct epoll_event __user *, event)
{
    int error;
    int full_check = 0;
    struct fd f, tf;
    struct eventpoll *ep;
    struct epitem *epi;
    struct epoll_event epds;
    struct eventpoll *tep = NULL;
        ..........
        /*
     * Try to lookup the file inside our RB tree, Since we grabbed &quot;mtx&quot;
     * above, we can be sure to be able to use the item looked up by
     * ep_find() till we release the mutex. 在红黑树中根据fd找到epi结构体
     */
    epi = ep_find(ep, tf.file, fd);

    error = -EINVAL;
    switch (op) {
    case EPOLL_CTL_ADD:
        if (!epi) {
            epds.events |= POLLERR | POLLHUP;
            /*epoll节点插入，包括list节点和红黑树节点*/
            error = ep_insert(ep, &amp;epds, tf.file, fd, full_check);
        } else
            error = -EEXIST;
        if (full_check)
            clear_tfile_check_list();
        break;
    case EPOLL_CTL_DEL:
        if (epi)
            /*epoll节点删除，包括list节点和红黑树节点*/
            error = ep_remove(ep, epi);
        else
            error = -ENOENT;
        break;
    case EPOLL_CTL_MOD:
        if (epi) {
            if (!(epi-&gt;event.events &amp; EPOLLEXCLUSIVE)) {
                epds.events |= POLLERR | POLLHUP;
                /*epoll节点更改，包括list节点和红黑树节点*/
                error = ep_modify(ep, epi, &amp;epds);
            }
        } else
            error = -ENOENT;
        break;
    }
        ..........
}</code></pre></div><p>&#160; &#160; &#160; &#160; &#160;从上面的代码可知，其实核心的函数是ep_insert、ep_remove、ep_modify!&#160; 先看看ep_insert函数(为了突出重点，省略末尾的容错代码)，主要干了这么几件事：</p><p>&#160; &#160; 构建wait_queue队列（通过链表实现）<br />&#160; &#160; 构建红黑树<br />&#160; &#160; 注册唤醒task后的回调函数</p><div class="codebox"><pre class="vscroll"><code>/*
 * Must be called with &quot;mtx&quot; held.
 */
static int ep_insert(struct eventpoll *ep, struct epoll_event *event,
             struct file *tfile, int fd, int full_check)
{
    int error, revents, pwake = 0;
    unsigned long flags;
    long user_watches;
    struct epitem *epi;
    struct ep_pqueue epq;

    user_watches = atomic_long_read(&amp;ep-&gt;user-&gt;epoll_watches);
    if (unlikely(user_watches &gt;= max_user_watches))
        return -ENOSPC;
    if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
        return -ENOMEM;

    /* Item initialization follow here ... */
    INIT_LIST_HEAD(&amp;epi-&gt;rdllink);//初始化readyList链表头
    INIT_LIST_HEAD(&amp;epi-&gt;fllink);
    INIT_LIST_HEAD(&amp;epi-&gt;pwqlist);
    epi-&gt;ep = ep;
    ep_set_ffd(&amp;epi-&gt;ffd, tfile, fd);
    epi-&gt;event = *event;
    epi-&gt;nwait = 0;
    epi-&gt;next = EP_UNACTIVE_PTR;
    if (epi-&gt;event.events &amp; EPOLLWAKEUP) {
        error = ep_create_wakeup_source(epi);
        if (error)
            goto error_create_wakeup_source;
    } else {
        RCU_INIT_POINTER(epi-&gt;ws, NULL);
    }

    /* Initialize the poll table using the queue callback */
    epq.epi = epi;
    /*注册回到函数*/
    init_poll_funcptr(&amp;epq.pt, ep_ptable_queue_proc);

    /*
     * Attach the item to the poll hooks and get current event bits.
     * We can safely use the file* here because its usage count has
     * been increased by the caller of this function. Note that after
     * this operation completes, the poll callback can start hitting
     * the new item.
     */
    revents = ep_item_poll(epi, &amp;epq.pt);

    /*
     * We have to check if something went wrong during the poll wait queue
     * install process. Namely an allocation for a wait queue failed due
     * high memory pressure.
     */
    error = -ENOMEM;
    if (epi-&gt;nwait &lt; 0)
        goto error_unregister;

    /* Add the current item to the list of active epoll hook for this file */
    spin_lock(&amp;tfile-&gt;f_lock);
    list_add_tail_rcu(&amp;epi-&gt;fllink, &amp;tfile-&gt;f_ep_links);
    spin_unlock(&amp;tfile-&gt;f_lock);

    /*
     * Add the current item to the RB tree. All RB tree operations are
     * protected by &quot;mtx&quot;, and ep_insert() is called with &quot;mtx&quot; held.
       epitem节点就是在这里插入红黑树的
     */
    ep_rbtree_insert(ep, epi);

    /* now check if we&#039;ve created too many backpaths */
    error = -EINVAL;
    if (full_check &amp;&amp; reverse_path_check())
        goto error_remove_epi;

    /* We have to drop the new item inside our item list to keep track of it */
    spin_lock_irqsave(&amp;ep-&gt;lock, flags);

    /* If the file is already &quot;ready&quot; we drop it inside the ready list 
    */
    if ((revents &amp; event-&gt;events) &amp;&amp; !ep_is_linked(&amp;epi-&gt;rdllink)) {
        list_add_tail(&amp;epi-&gt;rdllink, &amp;ep-&gt;rdllist);//把当前event加入readyList的尾部
        ep_pm_stay_awake(epi);

        /* Notify waiting tasks that events are available 
        如果网卡收到数据，需要唤醒等待的task，并执行实现设置好的回调函数
        */
        if (waitqueue_active(&amp;ep-&gt;wq))
            wake_up_locked(&amp;ep-&gt;wq);//唤醒进程后执行的回调函数
        if (waitqueue_active(&amp;ep-&gt;poll_wait))
            pwake++;
    }

    spin_unlock_irqrestore(&amp;ep-&gt;lock, flags);

    atomic_long_inc(&amp;ep-&gt;user-&gt;epoll_watches);

    /* We have to call this outside the lock */
    if (pwake)
        ep_poll_safewake(&amp;ep-&gt;poll_wait);

    return 0;
}</code></pre></div><p>&#160; &#160;相比增加，删除节点就容易多了：直接从红黑树和readyList删除</p><div class="codebox"><pre class="vscroll"><code>/*
 * Removes a &quot;struct epitem&quot; from the eventpoll RB tree and deallocates
 * all the associated resources. Must be called with &quot;mtx&quot; held.
 */
static int ep_remove(struct eventpoll *ep, struct epitem *epi)
{
    unsigned long flags;
    struct file *file = epi-&gt;ffd.file;

    /*
     * Removes poll wait queue hooks. We _have_ to do this without holding
     * the &quot;ep-&gt;lock&quot; otherwise a deadlock might occur. This because of the
     * sequence of the lock acquisition. Here we do &quot;ep-&gt;lock&quot; then the wait
     * queue head lock when unregistering the wait queue. The wakeup callback
     * will run by holding the wait queue head lock and will call our callback
     * that will try to get &quot;ep-&gt;lock&quot;.
     */
    ep_unregister_pollwait(ep, epi);

    /* Remove the current item from the list of epoll hooks */
    spin_lock(&amp;file-&gt;f_lock);
    list_del_rcu(&amp;epi-&gt;fllink);
    spin_unlock(&amp;file-&gt;f_lock);

    rb_erase(&amp;epi-&gt;rbn, &amp;ep-&gt;rbr);//从红黑树删除

    spin_lock_irqsave(&amp;ep-&gt;lock, flags);
    if (ep_is_linked(&amp;epi-&gt;rdllink))
        list_del_init(&amp;epi-&gt;rdllink);//从readyList删除
    spin_unlock_irqrestore(&amp;ep-&gt;lock, flags);

    wakeup_source_unregister(ep_wakeup_source(epi));
    /*
     * At this point it is safe to free the eventpoll item. Use the union
     * field epi-&gt;rcu, since we are trying to minimize the size of
     * &#039;struct epitem&#039;. The &#039;rbn&#039; field is no longer in use. Protected by
     * ep-&gt;mtx. The rcu read side, reverse_path_check_proc(), does not make
     * use of the rbn field.
     */
    call_rcu(&amp;epi-&gt;rcu, epi_rcu_free);

    atomic_long_dec(&amp;ep-&gt;user-&gt;epoll_watches);

    return 0;
}</code></pre></div><p>&#160; 最后就是epoll_wait函数了: 也是个系统调用。为了突出主干，省略了前面的容错代码，核心就是调用了ep_poll函数；</p><div class="codebox"><pre><code>/*
 * Implement the event wait interface for the eventpoll file. It is the kernel
 * part of the user space epoll_wait(2).
 */
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
        int, maxevents, int, timeout)
{
    int error;
    struct fd f;
    struct eventpoll *ep;
        ..........
     /*
     * At this point it is safe to assume that the &quot;private_data&quot; contains
     * our own data structure.
     */
    ep = f.file-&gt;private_data;

    /* Time to fish for events ... */
    error = ep_poll(ep, events, maxevents, timeout);

}</code></pre></div><p>&#160; ep_poll函数的实现逻辑也不复杂：在超时允许的时间内查看readyList；如果不为空说明有事件准备好了，然后把这些事件推送到用户空间！</p><div class="codebox"><pre class="vscroll"><code>/**
 * ep_poll - Retrieves ready events, and delivers them to the caller supplied
 *           event buffer.
 *           找到ready的事件
 * @ep: Pointer to the eventpoll context.
 * @events: Pointer to the userspace buffer where the ready events should be
 *          stored.
 * @maxevents: Size (in terms of number of events) of the caller event buffer.
 * @timeout: Maximum timeout for the ready events fetch operation, in
 *           milliseconds. If the @timeout is zero, the function will not block,
 *           while if the @timeout is less than zero, the function will block
 *           until at least one event has been retrieved (or an error
 *           occurred).
 *
 * Returns: Returns the number of ready events which have been fetched, or an
 *          error code, in case of error.
 */
static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
           int maxevents, long timeout)
{
    int res = 0, eavail, timed_out = 0;
    unsigned long flags;
    u64 slack = 0;
    wait_queue_t wait;
    ktime_t expires, *to = NULL;

    if (timeout &gt; 0) {
        struct timespec64 end_time = ep_set_mstimeout(timeout);

        slack = select_estimate_accuracy(&amp;end_time);
        to = &amp;expires;
        *to = timespec64_to_ktime(end_time);
    } else if (timeout == 0) {
        /*
         * Avoid the unnecessary trip to the wait queue loop, if the
         * caller specified a non blocking operation.
         */
        timed_out = 1;
        spin_lock_irqsave(&amp;ep-&gt;lock, flags);
        goto check_events;
    }

fetch_events:
    spin_lock_irqsave(&amp;ep-&gt;lock, flags);

    if (!ep_events_available(ep)) {
        /* 如果没有任何事件发生，就让出cpu；一旦有事件到达，会被ep_poll_callback函数唤醒！
         * We don&#039;t have any available event to return to the caller.
         * We need to sleep here, and we will be wake up by
         * ep_poll_callback() when events will become available.
         */
        init_waitqueue_entry(&amp;wait, current);//把当前进程放入等待队列，并注册唤醒回调函数
        __add_wait_queue_exclusive(&amp;ep-&gt;wq, &amp;wait);

        for (;;) {
            /*
             * We don&#039;t want to sleep if the ep_poll_callback() sends us
             * a wakeup in between. That&#039;s why we set the task state
             * to TASK_INTERRUPTIBLE before doing the checks.
             */
            set_current_state(TASK_INTERRUPTIBLE);
            if (ep_events_available(ep) || timed_out)//如果有ready的事件，或已经等待超时就跳出死循环
                break;
            if (signal_pending(current)) {
                res = -EINTR;
                break;
            }

            spin_unlock_irqrestore(&amp;ep-&gt;lock, flags);            //主动让出cpu，进入随眠状态；等有事件发生时，进程切换回来继续执行for循环，到上面的break代码跳出
            if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS))
                timed_out = 1;

            spin_lock_irqsave(&amp;ep-&gt;lock, flags);
        }

        __remove_wait_queue(&amp;ep-&gt;wq, &amp;wait);//移除wait_queue
        __set_current_state(TASK_RUNNING);//唤醒后进程状态设置为running
    }
check_events:
    /* Is it worth to try to dig for events ? */
    eavail = ep_events_available(ep);//遍历readyList，发现不为空哦，说明有事件产生了！

    spin_unlock_irqrestore(&amp;ep-&gt;lock, flags);

    /*
     * Try to transfer events to user space. In case we get 0 events and
     * there&#039;s still timeout left over, we go trying again in search of
     * more luck. 把ready的事件拷贝到用户空间，让上层应用继续处理
     */
    if (!res &amp;&amp; eavail &amp;&amp;
        !(res = ep_send_events(ep, events, maxevents)) &amp;&amp; !timed_out)
        goto fetch_events;

    return res;
}</code></pre></div><p>&#160; &#160; &#160;上面epoll_create、epoll_ctl和epoll_wait看起来很多，其实用起来很简单，demo代码如下（完整的用例见文章末尾）：先用epoll_create创建epoll实例，再用epoll_ctl添加fd和事件的映射，个人觉得epoll思路的精髓就体现在epoll_wait这里了：这函数可以设置等待（或则阻塞）的时长。如果执行的时候发现readyList有准备好的事件，或者说超时就返回继续执行，不会一直阻塞在这里傻等；再加上这里是单线程循环执行，完美解决了文章开头的多线程+阻塞的问题！<br /><span class="postimg"><img src="https://img2022.cnblogs.com/blog/2052730/202201/2052730-20220121212731766-987749629.png" alt="FluxBB bbcode 测试" /></span> </p><p>&#160; 当网卡收到数据，也就是有事件发生时，会挨个唤醒wait_queue的task，然后执行每个task的回调函数，执行回调函数的名称是ep_poll_callback，其调用的核心代码如下：</p><div class="codebox"><pre><code>/*
 * The core wakeup function. Non-exclusive wakeups (nr_exclusive == 0) just
 * wake everything up. If it&#039;s an exclusive wakeup (nr_exclusive == small +ve
 * number) then we wake all the non-exclusive tasks and one exclusive task.
 *
 * There are circumstances in which we can try to wake a task which has already
 * started to run but is not in state TASK_RUNNING. try_to_wake_up() returns
 * zero in this (rare) case, and we handle it by continuing to scan the queue.
   执行等待队列中每个task被唤醒后的回调函数
 */
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
            int nr_exclusive, int wake_flags, void *key)
{
    wait_queue_t *curr, *next;

    list_for_each_entry_safe(curr, next, &amp;q-&gt;task_list, task_list) {
        unsigned flags = curr-&gt;flags;
        /*执行回调函数*/
        if (curr-&gt;func(curr, mode, wake_flags, key) &amp;&amp;
                (flags &amp; WQ_FLAG_EXCLUSIVE) &amp;&amp; !--nr_exclusive)
            break;
    }
}</code></pre></div><p>&#160; &#160; &#160; &#160;windows下用vmware+kali+qemu+vscode调试的效果如下：<br /><span class="postimg"><img src="https://img2022.cnblogs.com/blog/2052730/202201/2052730-20220123210550733-1126511337.png" alt="FluxBB bbcode 测试" /></span> </p><p>&#160; &#160; &#160; &#160; epoll回调函数断点：左上方能看到各个变量的值，左下方能看到函数的调用栈！这个在逆向特别有用：比如在malloc断下，就能查到是哪个函数在分配内存，进而存放加密数据了！<br /><span class="postimg"><img src="https://img2022.cnblogs.com/blog/2052730/202201/2052730-20220123214946877-117014663.png" alt="FluxBB bbcode 测试" /></span> </p><p>&#160; &#160;3、epoll的一个完整的demo用例，方便直观理解epoll的功能：</p><div class="codebox"><pre class="vscroll"><code>#include &lt;iostream&gt;  
#include &lt;sys/socket.h&gt;  
#include &lt;sys/epoll.h&gt;  
#include &lt;netinet/in.h&gt;  
#include &lt;arpa/inet.h&gt;  
#include &lt;fcntl.h&gt;  
#include &lt;unistd.h&gt;  
#include &lt;stdio.h&gt;  
#include &lt;errno.h&gt;  
  
using namespace std;  
  
#define MAXLINE 100  
#define OPEN_MAX 100  
#define LISTENQ 20  
#define SERV_PORT 5000  
#define INFTIM 1000  
  
void setnonblocking(int sock)  
{  
    int opts;  
     opts=fcntl(sock,F_GETFL);  
    if(opts&lt;0)  
    {  
        perror(&quot;fcntl(sock,GETFL)&quot;);  
        exit(1);  
    }  
     opts = opts|O_NONBLOCK;  
    if(fcntl(sock,F_SETFL,opts)&lt;0)  
    {  
        perror(&quot;fcntl(sock,SETFL,opts)&quot;);  
        exit(1);  
    }  
}  
  
int main(int argc, char* argv[])  
{  
    int i, maxi, listenfd, connfd, sockfd,epfd,nfds, portnumber;  
    ssize_t n;  
    char line[MAXLINE];  
    socklen_t clilen;  
    string szTemp(&quot;&quot;);  
  
    if ( 2 == argc )  
    {  
        if( (portnumber = atoi(argv[1])) &lt; 0 )  
        {  
            fprintf(stderr,&quot;Usage:%s portnumber\a\n&quot;,argv[0]);  
            return 1;  
        }  
    }  
    else  
    {  
        fprintf(stderr,&quot;Usage:%s portnumber\a\n&quot;,argv[0]);  
        return 1;  
    }  
  
    //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件  
    struct epoll_event ev, events[20];  
      
    //创建一个epoll的句柄，size用来告诉内核这个监听的数目一共有多大  
    epfd = epoll_create(256); //生成用于处理accept的epoll专用的文件描述符  
      
    struct sockaddr_in clientaddr;  
    struct sockaddr_in serveraddr;  
    listenfd = socket(AF_INET, SOCK_STREAM, 0);  
      
    //把socket设置为非阻塞方式  
    //setnonblocking(listenfd);  
  
    //设置与要处理的事件相关的文件描述符  
    //要监听的fd
    ev.data.fd=listenfd;  
      
    //设置要处理的事件类型  
    //要监听的事件：收到数据后可读、边缘触发
    ev.events=EPOLLIN|EPOLLET;  
  
    //注册epoll事件 ，也就上述要是监听fd+监听的事件，通过ev传入
    //本质就是在红黑树上增删改查
    epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&amp;ev);  
      
    bzero(&amp;serveraddr, sizeof(serveraddr)); /*配置Server socket的相关信息 */  
    serveraddr.sin_family = AF_INET;  
    char *local_addr=&quot;127.0.0.1&quot;;  
    inet_aton(local_addr,&amp;(serveraddr.sin_addr));//htons(portnumber);  
    serveraddr.sin_port=htons(portnumber);  
    bind(listenfd,(sockaddr *)&amp;serveraddr, sizeof(serveraddr));  
    listen(listenfd, LISTENQ);  
      
    maxi = 0;  
      
    for ( ; ; ) {  
          
        //等待epoll事件的发生  
        //返回需要处理的事件数目nfds，如返回0表示已超时。  
        nfds=epoll_wait(epfd,events,20,500);  
          
        //处理所发生的所有事件  
        for(i=0; i &lt; nfds; ++i)  
        {  
            //如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口，建立新的连接。  
            if(events[i].data.fd == listenfd)  
            {  
                //收到了用户建立连接的请求再调用accept，避免阻塞浪费时间
                connfd = accept(listenfd,(sockaddr *)&amp;clientaddr, &amp;clilen);  
                if(connfd &lt; 0)  
                {  
                    perror(&quot;connfd &lt; 0&quot;);  
                    exit(1);  
                }  
                //setnonblocking(connfd);  
                char *str = inet_ntoa(clientaddr.sin_addr);  
                cout &lt;&lt; &quot;accapt a connection from &quot; &lt;&lt; str &lt;&lt; endl;  
                  
                //设置用于读操作的文件描述符  
                  ev.data.fd=connfd;  
                  
                //设置用于注册的读操作事件  
                  ev.events=EPOLLIN|EPOLLET;  
  
                //注册ev  
                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&amp;ev); /* 添加新建立的fd，同样需要监控这个fd有没有收发数据、边缘触发等 */  
            }  
            //如果是已经连接的用户，并且收到数据，那么进行读入。  
            else if(events[i].events&amp;EPOLLIN)  
            {  
                cout &lt;&lt; &quot;EPOLLIN&quot; &lt;&lt; endl;  
                if ( (sockfd = events[i].data.fd) &lt; 0)  
                    continue;  
                //网卡收到了数据再调用recv把数据拷贝到用户空间，避免阻塞浪费时间
                if ( (n = recv(sockfd, line, sizeof(line), 0)) &lt; 0)   
                {    
                    // Connection Reset:你连接的那一端已经断开了，而你却还试着在对方已断开的socketfd上读写数据！  
                    if (errno == ECONNRESET)  
                    {  
                        close(sockfd);  
                        events[i].data.fd = -1;  
                    }   
                    else  
                        std::cout&lt;&lt;&quot;readline error&quot;&lt;&lt;std::endl;  
                }   
                else if (n == 0) //读入的数据为空  
                {  
                    close(sockfd);  
                    events[i].data.fd = -1;  
                }  
                  
                szTemp = &quot;&quot;;  
                szTemp += line;  
                szTemp = szTemp.substr(0,szTemp.find(&#039;\r&#039;)); /* remove the enter key */  
                memset(line,0,100); /* clear the buffer */  
                //line[n] = &#039;\0&#039;;  
                cout &lt;&lt; &quot;Readin: &quot; &lt;&lt; szTemp &lt;&lt; endl;  
                  
                //设置用于写操作的文件描述符  
                ev.data.fd=sockfd;  
                  
                //设置用于注册的写操作事件  
                ev.events=EPOLLOUT|EPOLLET;  
                  
                //修改sockfd上要处理的事件为EPOLLOUT，即fd上可写了、可以发送数据了
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&amp;ev); /* 修改红黑树 */  
  
            }  
            else if(events[i].events&amp;EPOLLOUT) // 如果有数据发送  
            {  
                sockfd = events[i].data.fd;  
                szTemp = &quot;Server:&quot; + szTemp + &quot;\n&quot;;  
                send(sockfd, szTemp.c_str(), szTemp.size(), 0); 
                  
                //设置用于读操作的文件描述符  
                ev.data.fd=sockfd;  
                 
                //设置用于注册的读操作事件  
                ev.events=EPOLLIN|EPOLLET;  
                  
                //修改sockfd上要处理的事件为EPOLIN ，即fd上可读了、可以接收数据了
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&amp;ev); /* 修改红黑树 */  
            }  
        } //(over)处理所发生的所有事件  
    } //(over)等待epoll事件的发生  
      
    close(epfd);  
    return 0;  
}</code></pre></div><br /><br /><p>总结：</p><p>1、红黑树的作用：当有事件发生时，可以快速根据fd查找epitem（找到得epiterm会组成链表传递给用户空间做进一步处理），比遍历链表快多了！</p><p>2、内核中链表适用的场景：用来做队列或栈，存储的每个节点都要处理（说白了就是需要遍历），不存在查找的需求场景！</p><p>3、epoll事件底层最终是中断触发的：当网卡收到数据后，通过中断通知操作系统来取数据，进而触发epoll事件！</p><p> </p><p> </p><p> </p><p>参考：</p><p>1、https://www.bilibili.com/video/BV15z4y1m7Gt/?spm_id_from=333.788.recommend_more_video.-1&#160; epoll源码剖析</p><p>2、https://os.51cto.com/article/649405.html epoll原理</p><p>3、https://www.bilibili.com/video/BV1Sq4y1q7Gv?zw&#160; &#160; &#160;<a href="https://zhuanlan.zhihu.com/p/445453676" rel="nofollow">https://zhuanlan.zhihu.com/p/445453676</a>&#160; &#160; linux内核调试环境搭建</p><p>4、https://blog.csdn.net/Eunice_fan1207/article/details/99674021&#160; Linux内核剖析-----IO复用函数epoll内核源码剖析</p><p>5、https://www.cnblogs.com/carekee/articles/2760693.html&#160; epoll用例</p>]]></description>
			<author><![CDATA[dummy@example.com (batsom)]]></author>
			<pubDate>Tue, 11 Oct 2022 02:48:34 +0000</pubDate>
			<guid>http://www.gentoo-zh.org/viewtopic.php?pid=467#p467</guid>
		</item>
	</channel>
</rss>
