epoll源码解析翻译------说使用了mmap的都是骗子

本文探讨了epoll、select和poll在处理I/O事件上的差异,指出epoll并未直接使用mmap技术,但提到了dpdk和netmap如何利用内核态到用户态的内存映射以提高性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文地址

//https://www.cnblogs.com/l2017/p/10830391.html

//https://blog.csdn.net/li_haoren

select poll epoll这三个都是对poll机制的封装。

只是select跟poll傻了点

epoll里并没有找到mmap相关的代码。并没有用到内核态内存映射到用户态的技术。但这个技术是存在的。dpdk,跟netmap(绕过内核的tcp/ip协议栈,在用户态协议栈处理。减少中断,上下文切换等开销)就有用到内核态内存映射到用户态技术

这个图不是我的,不知道从哪偷来的。

 

 

//好像是 linux-5.2
//https://elixir.bootlin.com/linux/v5.2-rc5/source/fs/eventpoll.c
//参考
//https://www.cppfans.org/1418.html
//https://cloud.tencent.com/developer/article/1005481
//http://blog.chinaunix.net/uid-26339466-id-3292595.html
//https://titenwang.github.io/2017/10/05/implementation-of-epoll/
//https://www.jianshu.com/p/aa486512e989
//https://blog.csdn.net/Jammg/article/details/51854436
//https://www.cnblogs.com/lshs/p/6038904.html
//http://blog.lucode.net/linux/epoll-tutorial.html
 
//sys/epoll.h
#ifndef __sigset_t_defined
#define __sigset_t_defined
typedef __sigset_t sigset_t;
#endif
 
/*
错误标志
EINVAL : 无效的标志
EMFILE : 用户打开的文件超过了限制
ENFILE : 系统打开的文件超过了限制
ENOMEM : 没有足够的内存完成当前操作
EBADF : epfd或者fd不是一个有效的文件描述符
EEXIST : op为EPOLL_CTL_ADD,但fd已经被监控
EINVAL : epfd是无效的epoll文件描述符
ENOENT : op为EPOLL_CTL_MOD或者EPOLL_CTL_DEL,并且fd未被监控
ENOMEM : 没有足够的内存完成当前操作
ENOSPC : epoll实例超过了/proc/sys/fs/epoll/max_user_watches中限制的监听数量
EBADF : epfd不是一个有效的文件描述符
EFAULT : events指向的内存无权访问
EINTR : 在请求事件发生或者过期之前,调用被信号打断
EINVAL : epfd是无效的epoll文件描述符
*/
//本文地址 <a href="https://www.cnblogs.com/l2017/p/10830391.html">https://www.cnblogs.com/l2017/p/10830391.html</a><br>//<a href="https://blog.csdn.net/li_haoren">https://blog.csdn.net/li_haoren</a>
//要传递给epoll_create2的标志,就是那个代替了size的flags
enum
{
    EPOLL_CLOEXEC = 02000000,
#define EPOLL_CLOEXEC EPOLL_CLOEXEC
    //EPOLL_NONBLOCK 它是fd的一个标识说明,用来设置文件close-on-exec状态的。
    //当close-on-exec状态为0时,调用exec时,fd不会被关闭;
    //状态非零时则会被关闭,这样做可以防止fd泄露给执行exec后的进程。
    EPOLL_NONBLOCK = 04000
#define EPOLL_NONBLOCK EPOLL_NONBLOCK
    //创建的epfd会设置为非阻塞
};
 
enum EPOLL_EVENTS
{
    EPOLLIN = 0x001,
    //表示关联的fd可以进行读操作了。(包括对端Socket正常关闭)
#define EPOLLIN EPOLLIN
    EPOLLPRI = 0x002,
    //表示关联的fd有紧急优先事件可以进行读操作了。
#define EPOLLPRI EPOLLPRI
    EPOLLOUT = 0x004,
    //表示关联的fd可以进行写操作了。
#define EPOLLOUT EPOLLOUT
    EPOLLRDNORM = 0x040,
#define EPOLLRDNORM EPOLLRDNORM
    EPOLLRDBAND = 0x080,
#define EPOLLRDBAND EPOLLRDBAND
    EPOLLWRNORM = 0x100,
#define EPOLLWRNORM EPOLLWRNORM
    EPOLLWRBAND = 0x200,
#define EPOLLWRBAND EPOLLWRBAND
    EPOLLMSG = 0x400,
#define EPOLLMSG EPOLLMSG
    EPOLLERR = 0x008,
    //表示关联的fd发生了错误,
    //这个事件是默认的  后续代码有 epds.events |= EPOLLERR | EPOLLHUP;
#define EPOLLERR EPOLLERR
    EPOLLHUP = 0x010,
    //表示关联的fd挂起了,
    //这个事件是默认的  后续代码有 epds.events |= EPOLLERR | EPOLLHUP;
#define EPOLLHUP EPOLLHUP
    EPOLLRDHUP = 0x2000,
    //表示套接字关闭了连接,或者关闭了正写一半的连接。
#define EPOLLRDHUP EPOLLRDHUP
    EPOLLONESHOT = (1 << 30),
    //设置关联的fd为one-shot的工作方式。
    //表示只监听一次事件,如果要再次监听,需要重置这个socket上的EPOLLONESHOT事件。
    //使用场合:多线程环境
    //如果主线程在epoll_wait返回了套接字conn,之后子线程1在处理conn,主线程回到epoll_wait,
    //但还没等到子线程1返回conn又可读了,此时主线程epoll_wait返回,又分配给另一个线程,此时两个线程同时使用一个套接字,这当然是不行的,
    //出现了两个线程同时操作一个socket的局面。
    //可以使用epoll的EPOLLONESHOT事件实现一个socket连接在任一时刻都被一个线程处理。
    //作用:
    //    对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多出发其上注册的一个可读,可写或异常事件,且只能触发一次。
    //使用:
    //    注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,
    //    该线程就应该立即重置这个socket上的EPOLLONESHOT事件,以确保这个socket下一次可读时,
    //    (使用该线程的没重置此套接字前即:主线程不允许返回任何关于此套接字的事件,这样就做到同一时刻只可能有一个线程处理该套接字)
    //    其EPOLLIN事件能被触发,进而让其他工作线程有机会继续处理这个sockt。
    //效果:
    //    尽管一个socket在不同事件可能被不同的线程处理,但同一时刻肯定只有一个线程在为它服务,这就保证了连接的完整性,从而避免了很多可能的竞态条件。
    //也可以使用add,并忽略epoll_ctl()返回的错误码EEXIST来重置。(??还没试过)
    //EPOLLONESHOT优先于水平触发(默认)的处理,即同时设置水平触发和EPOLLONESHOT并不会把epi添加到ready链表。
    //如果设置了EPOLLONESHOT标志位,则设置epi->event.events &= EP_PRIVATE_BITS,
    //其定义如下#define EP_PRIVATE_BITS (EPOLLWAKEUP | EPOLLONESHOT | EPOLLET),
    //后续根据EP_PRIVATE_BITS判断不再加入ep->rdllist或者ep->ovflist。
    //注意设置了EPOLLONESHOT触发一次后并没有删除epi,
    //因而通过epoll_ctl进行ADD操作后会提示File exists错误。
#define EPOLLONESHOT EPOLLONESHOT
    EPOLLET = (1 << 31)
    //设置关联的fd为ET的工作方式,epoll的默认工作方式是LT。(LT/ET触发)LT水平触发 ET边缘触发
#define EPOLLET EPOLLET
    //LT模式是epoll默认的工作方式
    //LT模式状态时,主线程正在epoll_wait等待事件时,请求到了,epoll_wait返回后没有去处理请求(recv),
    //那么下次epoll_wait时此请求还是会返回(立刻返回了);
    //而ET模式状态下,这次没处理,下次epoll_wait时将不返回(所以我们应该每次一定要处理)
    //本质的区别在设置了EPOLLET的fd在wait发送到用户空间之后,会重新挂回到就绪队列中。
    //等待下次wait返回(会重新查看每个socket是否真的有数据,并不是挂上去就绪队列了就返回)
    //可查找这段代码if (!(epi->event.events & EPOLLET)) 仔细研读 看清楚有个!
};
 
 
 
#define EPOLL_CTL_ADD 1   
// 注册目标fd到epfd中,同时关联内部event到fd上 
#define EPOLL_CTL_DEL 2   
// 从epfd中删除/移除已注册的fd,event可以被忽略,也可以为NULL
#define EPOLL_CTL_MOD 3       
// 修改已经注册到fd的监听事件
 
 
 
 
struct epoll_event
{
    uint32_t events;
    //指明了用户态应用程序感兴趣的事件类型,比如EPOLLIN和EPOLLOUT等
    epoll_data_t data;
} __attribute__((__packed__));
 
//提供给用户态应用程序使用,一般用于存储事件的上下文,比如在nginx中,该成员用于存放指向ngx_connection_t类型对象的指针
typedef union epoll_data
{
    void *ptr;//指定与fd相关的用户数据
    int fd; //指定事件所从属的目标文件描述符
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;
 
extern int epoll_create(int __size) __THROW;
//创建一个epoll实例。 返回新实例的fd。
//__size是历史遗留问题,以前用的是hash,需要给预期大小
//epoll_create()返回的fd应该用close()关闭。
extern int epoll_create1(int __flags) __THROW;
//与epoll_create相同,但带有FLAGS参数。 无用的SIZE参数已被删除。
//例如 EPOLL_CLOEXEC 标志
//epoll_create()返回的fd应该用close()关闭。
 
 
extern int epoll_ctl(int __epfd, int __op, int __fd,
    struct epoll_event *__event) __THROW;
//epoll实例“epfd”。 成功时返回0,错误时返回-1(“errno”变量将包含特定错误代码)
//“op”参数是上面定义的EPOLL_CTL_ *常量之一。
//“fd”参数是操作的目标。
//“event”参数描述了调用者感兴趣的事件以及任何相关的用户数据。
 
 
extern int epoll_wait(int __epfd, struct epoll_event *__events,
    int __maxevents, int __timeout);
//等待epoll实例“epfd”上的事件。
//返回值为“events”缓冲区中返回的触发事件数。 如果出错,“errno”变量设置为特定错误代码,则返回-1。
//“events”参数是一个包含触发事件的缓冲区。
//“maxevents”是要返回的最大事件数(通常是“事件”的大小)。
//“timeout”参数指定最长等待时间(以毫秒为单位)(-1 ==无限)。
 
 
extern int epoll_pwait(int __epfd, struct epoll_event *__events,
    int __maxevents, int __timeout,
    __const __sigset_t *__ss);
//与epoll_wait相同,但线程的信号掩码暂时原子替换为作为参数提供的掩码。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
//***************************************************************************************************
//分隔符
//fs/eventpoll.c
 
 
//epoll有三级锁
// 1) epmutex (mutex)
// 2) ep->mtx (mutex)
// 3) ep->wq.lock (spinlock)
//获取顺序是上面的从1到3
//我们需要一个ep->wq.lock(spinlock)自旋锁,因为我们从poll回调内部
//操作对象,这可能是wake_up()触发的,而wake_up()又可能从中断请求上下文中调用。
//所以我们无法在poll回调中sleep,因此我们需要一个自旋锁。必须要非常小心使用的锁,
// 尤其是调用spin_lock_irqsave()的时候, 中断关闭, 不会发生进程调度,
// 被保护的资源其它CPU也无法访问。 这个锁是很强力的, 所以只能锁一些
// 非常轻量级的操作。
 
//在事件传输循环期间(从内核空间到用户空间),我们可能因为copy_to_user()而终于sleep,
//所以,我们需要一个允许我们sleep的锁。这个锁是mutex(ep->mtx)。它是
//在epoll_ctl(EPOLL_CTL_DEL)期间的事件传输循环期间和eventpoll_release_file()的事件传输循环期间获取。
//eventpoll_release_file是用来清除已经close的fd却还没DEL的(当一个fd被加入到epoll中,然后close(fd),而没有先调用epoll_ctl(EPOLL_CTL_DEL)时)
 
//然后我们还需要一个全局互斥锁来序列化eventpoll_release_file()和ep_free()。
//ep_free在epollfd被close时调用来清理资源
//https://www.cnblogs.com/l2017/
//这个互斥锁是在epoll文件清理路径中由ep_free()获取的,它也可以被eventpoll_release_file()获取,
//将epoll fd插入另一个epoll中也会获得。
//我们这样做是为了让我们遍历epoll树时确保这个插入不会创建epoll文件描述符的闭环,
//这可能导致死锁。我们需要一个全局互斥锁来防止两个fd同时插入(A插到B和B插到A)
//来竞争和构建一个循环,而不需要插入观察它是去。
//当一个epoll fd被添加到另一个epoll  fd时,有必要立即获得多个“ep-> mtx”。(最多4层嵌套)
//在这种情况下,我们总是按嵌套顺序获取锁(即在* epoll_ctl(e1,EPOLL_CTL_ADD,e2)之后,e1->mtx将始终在e2->mtx之前获取)。
//由于我们不允许epoll文件描述符的循环,这可以确保互斥锁是有序的。
//为了将这个嵌套传递给lockdep,当走遍epoll文件描述符的树时,我们使用当前的递归深度作为lockdep子键。
//可以删除“ep-> mtx”并使用全局mutex“epmutex”(与“ep-> wq.lock”一起)使其工作,
//但是“ep-> mtx”将使界面更具可扩展性。 需要持有“epmutex”的事件非常罕见,
//而对于正常操作,epoll私有“ep-> mtx”将保证更好的可扩展性。
 
//介绍RCU
//后面有些变量的命名带有rcu
//RCU(Read - Copy Update),顾名思义就是读 - 拷贝修改,它是基于其原理命名的。
//对于被RCU保护的共享数据结构,读者不需要获得任何锁就可以访问它,
//但写者在访问它时首先拷贝一个副本,然后对副本进行修改,
//最后使用一个回调(callback)机制在适当的时机把指向原来数据的指针重新指向新的被修改的数据。
//这个时机就是所有引用该数据的CPU都退出对共享数据的操作。
//用于释放内存
 
 
//介绍poll机制
 
//在用户空间应用程序向驱动程序请求数据时,有以下几种方式:
//1、不断查询,条件不满足的情况下就是死循环,非常耗cpu
//2、休眠唤醒的方式,如果条件不满足,应用程序则一直睡眠下去
//3、poll机制,如果条件不满足,休眠指定的时间,休眠时间内条件满足唤醒,条件一直不满足时间到达自动唤醒
//4、异步通知,应用程序注册信号处理函数,驱动程序发信号。类似于QT的信号与槽机制。
 
 
//在深入了解epoll的实现之前, 先来了解内核的3个方面.
//https://www.cnblogs.com/watson/p/3543320.html
// 1. 等待队列 waitqueue
//  我们简单解释一下等待队列:
//  队列头(wait_queue_head_t)往往是资源生产者,
//  队列成员(wait_queue_t)往往是资源消费者,
//  当头的资源ready后, 会逐个执行每个成员指定的回调函数,
//  来通知它们资源已经ready了, 等待队列大致就这个意思.
// 2. 内核的poll机制
//  被Poll的fd, 必须在实现上支持内核的Poll技术,
//  比如fd是某个字符设备,或者是个socket, 它必须实现
//  file_operations中的poll操作, 给自己分配有一个等待队列头.
//  主动poll fd的某个进程必须分配一个等待队列成员, 添加到
//  fd的对待队列里面去, 并指定资源ready时的回调函数.
//  用socket做例子, 它必须有实现一个poll操作, 这个Poll是
//  发起轮询的代码必须主动调用的, 该函数中必须调用poll_wait(),
//  poll_wait会将发起者作为等待队列成员加入到socket的等待队列中去.
//  这样socket发生状态变化时可以通过队列头逐个通知所有关心它的进程.
//  这一点必须很清楚的理解, 否则会想不明白epoll是如何
//  得知fd的状态发生变化的.
// 3. epollfd本身也是个fd, 所以它本身也可以被epoll,
//  (最多4层嵌套)EP_MAX_NESTS
 
//建议先了解内核的list数据结构以及,不然很多list相关的东西看起来会很奇怪
 
#define EP_PRIVATE_BITS (EPOLLWAKEUP | EPOLLONESHOT | EPOLLET | EPOLLEXCLUSIVE)
//如果设置了EPOLLONESHOT标志位,则设置epi->event.events &= EP_PRIVATE_BITS,
//后续根据EP_PRIVATE_BITS判断不再加入ep->rdllist或者ep->ovflist。
#define EPOLLINOUT_BITS (EPOLLIN | EPOLLOUT)
 
 
#define EPOLLEXCLUSIVE_OK_BITS (EPOLLINOUT_BITS | EPOLLERR | EPOLLHUP | \
                EPOLLWAKEUP | EPOLLET | EPOLLEXCLUSIVE)
 
#define EP_MAX_NESTS 4//指最多4层epoll嵌套
#define EP_MAX_EVENTS (INT_MAX / sizeof(struct epoll_event))
#define EP_UNACTIVE_PTR ((void *) -1L)
#define EP_ITEM_COST (sizeof(struct epitem) + sizeof(struct eppoll_entry))
 
//记录file跟fd
struct epoll_filefd {
    struct file *file;
    int fd;
};
 
//用于跟踪可能的嵌套调用的结构,用于过深的递归和循环周期,epoll嵌套
struct nested_call_node {
    struct list_head llink;
    void *cookie;
    void *ctx;
};
 
//此结构用作嵌套调用的收集器,以进行检查最大递归部分和循环周期,epoll嵌套
struct nested_calls {
    struct list_head tasks_call_list;
    spinlock_t lock;
};
 
 
//添加到eventpoll接口的每个文件描述符都有一个链接到“rbr”RB树的epitem结构。
//避免增加此结构的大小,因为服务器上可能有数千个这样的结构,我们不希望存在多个缓存行
//epitem 表示一个被监听的fd
struct epitem {
    union {
        struct rb_node rbn;
        //rb_node, 当使用epoll_ctl()将多个fds加入到某个epollfd时, 内核会用slab分配
        //多个epitem与fds对应, 而且它们以红黑树的形式组织起来,
        //tree的root保存在eventpoll rbr中.
        struct rcu_head rcu;
        //用于释放epitem结构,rcu前面有提到
    };
 
    struct list_head rdllink;
    //对应eventpoll的rdllist
    //当epitem对应的fd的存在已经ready的I/O事件,
    //则ep_poll_callback回调函数会将该epitem链接到eventpoll中的rdllist循环链表中去,
 
    struct epitem *next;
    //对应eventpoll的ovflist上
    //ovflist是一个临时的就绪单链表。充当单链表的next
 
    struct epoll_filefd ffd;
    //epitem对应的fd和struct file
 
    /* Number of active wait queue attached to poll operations */
    int nwait;
    //附加到poll轮询中的等待队列(eppoll_entry)个数
    //pwqlist里的个数
 
    /* List containing poll wait queues */
    struct list_head pwqlist;
    //对应eppoll_entry的llink
    //双向链表,保存着被监视文件对应的eppoll_entry(等同等待队列),
    //按道理应该是一个文件就只有一个等待队列。
    //但poll某些文件的时候,需要添加两次等待队列,如/dev/bsg/目录下面的文件。所有不是一个指针而是链表
 
    // 同一个文件上可能会监视多种事件, 
    // 这些事件可能属于不同的wait_queue中 
    // (取决于对应文件类型的实现), 
    // 所以需要使用链表
    struct eventpoll *ep;
    //当前epitem属于哪个eventpoll
 
    struct list_head fllink;
    //对应目标file的f_ep_links
    //双向链表,用来链接被监视的file。
    //被监控的file里有f_ep_link(list头),用来链接所有监视这个文件的epitem(list节点)结构,即把监听该file的epitem串起来
    //把一个file加到了两个epoll中(file 的 f_ep_links链表中会有两个epitem的fllink)
 
    /* wakeup_source used when EPOLLWAKEUP is set */
    struct wakeup_source __rcu* ws;
    //??
 
    struct epoll_event event;
    //注册的感兴趣的事件,也就是用户空间的epoll_event,这个数据是调用epoll_ctl时从用户态传递过来
};
 
/*
* This structure is stored inside the "private_data" member of the file
* structure and represents the main data structure for the eventpoll
* interface.
*
* Access to it is protected by the lock inside wq.
*/
//这个结构存储在file->private_data。是每个epoll fd(epfd)对应的主要数据结构
//eventpoll在epoll_create时创建。
struct eventpoll {
    /*
    * This mutex is used to ensure that files are not removed
    * while epoll is using them. This is held during the event
    * collection loop, the file cleanup path, the epoll file exit
    * code and the ctl operations.
    */
    struct mutex mtx;
    //防止这个结构在使用时被删除
    //添加, 修改或者删除监听fd的时候, 以及epoll_wait返回, 向用户空间传递数据时都会持有这个互斥锁,
    //所以在用户空间可以放心的在多个线程中同时执行epoll相关的操作, 内核级已经做了保护.
 
    wait_queue_head_t wq;
    //sys_epoll_wait()使用的等待队列,用于保存有哪些进程在等待这个epoll返回。
 
    /* Wait queue used by file->poll() */
    wait_queue_head_t poll_wait;
    //file->poll()使用的等待队列,这个用于epollfd本身被poll的时候??
 
    struct list_head rdllist;
    //对应epitem的rdllist
    //用于收集已经就绪了的epitem的对象(有个函数可用于结构内成员变量的地址得到该结构指针,后面会看到)
    //链表中的每个结点即为epitem中的rdllink,rdllist中链接的所有rdllink对应的epitem有事件ready
 
    struct rb_root_cached rbr;
    //用于管理所有epitem(fd)的红黑树(树根)
    //查找删除更改都是log(N)
 
    struct epitem *ovflist;
    //对应epitem的next
    //ovflist链表也是用来收集就绪了item对象的,epitem的next就是这个地方用来串成单链表的
    //是在对rdllink成员进行扫描操作获取就绪事件返还给用户态时被用来存放扫描期间就绪的事件的。
    //因为在对rellist扫描期间需要保证数据的一致性,如果此时又有新的就绪事件发生,那么就需要提供临时的空间来存储
 
    /* wakeup_source used when ep_scan_ready_list is running */
    struct wakeup_source *ws;
    //??
 
    struct user_struct *user;
    //这里保存了一些用户变量, 比如fd监听数量的最大值等等
 
    struct file *file;
    //存放对应的file,在create中被创建的那个
 
    /* used to optimize loop detection check */
    int visited;
    //??
    struct list_head visited_list_link;
    //??
};
/* Wait structure used by the poll hooks */
//完成一个epitem和ep_poll_callback的关联,同时eppoll_entry会被插入目标文件file的等待头队列中
 
//在ep_ptable_queue_proc函数中,引入了另外一个非常重要的数据结构eppoll_entry。
//eppoll_entry主要完成epitem和epitem事件发生时的callback(ep_poll_callback)函数之
//间的关联。首先将eppoll_entry的whead指向fd的设备等待队列(waitlist),
//然后初始化eppoll_entry的base变量指向epitem,最后通过add_wait_queue将epo
//ll_entry(wait)挂载到fd的设备等待队列上(waitlist)(把eppoll_entry添加到sk->sk_wq->wait的头部)。
//完成这个动作后,epoll_entry已经被挂载到fd的设备等待队列。
//然后还有一个动作必须完成,就是通过pwq->llink将eppoll_entry挂载到epitem的pwqlist尾部。
struct eppoll_entry {
    /* List header used to link this structure to the "struct epitem" */
    struct list_head llink;
    //对应epitem的pwqlist
    //把这个结构跟epitem连接起来,挂载到epitem的pwqlist尾部。
    /* The "base" pointer is set to the container "struct epitem" */
    struct epitem *base;
    //指向epitem
    /*
    * Wait queue item that will be linked to the target file wait
    * queue head.
    */
    wait_queue_entry_t wait;
    //挂载到fd的设备等待队列上,后面会指定跟ep_poll_callback绑定起来。为唤醒时的回调函数
    /* The wait queue head that linked the "wait" wait queue item */
    wait_queue_head_t *whead;
    //指向fd的设备等待队列头,用于将wait挂载到这个设备等待队列上面。
};
 
/* Wrapper struct used by poll queueing */
struct ep_pqueue {
    poll_table pt;
    //??
    struct epitem *epi;
};
 
 
 
/*
* Configuration options available inside /proc/sys/fs/epoll/
*/
/* Maximum number of epoll watched descriptors, per user */
static long max_user_watches __read_mostly;
//每个用户的epoll最大监听数
/*
* This mutex is used to serialize ep_free() and eventpoll_release_file().
*/
static DEFINE_MUTEX(epmutex);
 
/* Used to check for epoll file descriptor inclusion loops */
static struct nested_calls poll_loop_ncalls;
 
/* Slab cache used to allocate "struct epitem" */
static struct kmem_cache *epi_cache __read_mostly;
 
/* Slab cache used to allocate "struct eppoll_entry" */
static struct kmem_cache *pwq_cache __read_mostly;
 
/* Visited nodes during ep_loop_check(), so we can unset them when we finish */
static LIST_HEAD(visited_list);
 
/*
* List of files with newly added links, where we may need to limit the number
* of emanating paths. Protected by the epmutex.
*/
static LIST_HEAD(tfile_check_list);
 
 
static const struct file_operations eventpoll_fops;
 
//判断这个文件是不是epoll文件,利用文件可进行的操作判断
static inline int is_file_epoll(struct file *f)
{
    return f->f_op == &eventpoll_fops;
}
 
 
//设置epitem里的ffd,用做rbtree的键
static inline void ep_set_ffd(struct epoll_filefd *ffd,
    struct file *file, int fd)
{
    ffd->file = file;
    ffd->fd = fd;
}
 
//用于rbtree比较大小
static inline int ep_cmp_ffd(struct epoll_filefd *p1,
    struct epoll_filefd *p2)
{
    return (p1->file > p2->file ? +1 :
        (p1->file < p2->file ? -1 : p1->fd - p2->fd));
}
 
/* Tells us if the item is currently linked */
//监听该fd的就绪链表是否为空
//理解为这个epi是否就绪
static inline int ep_is_linked(struct epitem *epi)
{
    return !list_empty(&epi->rdllink);
}
 
//container_of 通过结构体变量中某个成员的首地址进而获得整个结构体变量的首地址。
//container_of(ptr, type, member)
//ptr : 表示结构体中member的地址
//type : 表示结构体类型
//member : 表示结构体中的成员
static inline struct eppoll_entry *ep_pwq_from_wait(wait_queue_entry_t *p)
{
    return container_of(p, struct eppoll_entry, wait);
}
 
/* Get the "struct epitem" from a wait queue pointer */
static inline struct epitem *ep_item_from_wait(wait_queue_entry_t *p)
{
    return container_of(p, struct eppoll_entry, wait)->base;
}
 
/* Get the "struct epitem" from an epoll queue wrapper */
static inline struct epitem *ep_item_from_epqueue(poll_table *p)
{
    return container_of(p, struct ep_pqueue, pt)->epi;
}
 
 
/* Tells if the epoll_ctl(2) operation needs an event copy from userspace */
//如果是删除事件则不需要event,判断op操作是什么
static inline int ep_op_has_event(int op)
{
    return op != EPOLL_CTL_DEL;
}
 
/* Initialize the poll safe wake up structure */
static void ep_nested_calls_init(struct nested_calls *ncalls)
{
    INIT_LIST_HEAD(&ncalls->tasks_call_list);
    spin_lock_init(&ncalls->lock);
}
 
/**
* ep_events_available - Checks if ready events might be available.
*
* @ep: Pointer to the eventpoll context.
*
* Returns: Returns a value different than zero if ready events are available,
*          or zero otherwise.
*/
//检查事件是否就绪
//返回:如果就绪事件可用,则返回不为零的值,否则返回零。
//如果ready链ep->rdllist非空或者ep->ovflist有效,则表示当前有关注的event发生
static inline int ep_events_available(struct eventpoll *ep)
{
    return !list_empty(&ep->rdllist) || ep->ovflist != EP_UNACTIVE_PTR;
}
 
 
static inline void ep_busy_loop(struct eventpoll *ep, int nonblock)
{
}
 
static inline void ep_reset_busy_poll_napi_id(struct eventpoll *ep)
{
}
 
static inline void ep_set_busy_poll_napi_id(struct epitem *epi)
{
}
 
 
/**
* ep_call_nested - Perform a bound (possibly) nested call, by checking
*                  that the recursion limit is not exceeded, and that
*                  the same nested call (by the meaning of same cookie) is
*                  no re-entered.
*
* @ncalls: Pointer to the nested_calls structure to be used for this call.
* @max_nests: Maximum number of allowed nesting calls.
* @nproc: Nested call core function pointer.
* @priv: Opaque data to be passed to the @nproc callback.
* @cookie: Cookie to be used to ident
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值