epoll源码分析以及在Redis中的实现

Redis源码分析 Redis的网络模型是基于I/O多路复用程序来实现的。源码中包含四种多路复用函数库epoll、select、evport、kqueue。在程序编译时会根据系统自动选择这四种库其中之一。下面以epoll为例,来分析Redis的I/O模块的源码。 epoll系统调用方法 Redis网络事件处理模块的代码都是围绕epoll那三个系统方法来写的。先把这三个方法弄清楚,后面就不难了。 epfd = epoll_create(1024); 创建epoll实例 参数:表示该 epoll 实例最多可监听的 socket f 阅读详情

1.概述

这篇文章分析一下linux中epoll的实现原理,主要为了增强自己对网络调用的理解。业界使用epoll的框架比较多,随便就能列出来很多,比如jdk的nio在linux下的实现,以及netty、redis等涉及到长链接网络请求的地方,我们都可以直接使用epoll。文末会从redis源码简单看看如何使用epoll做IO多路复用实现高并发。

2.具体实现

参考官方文档描述:

The central concept of the epoll API is the epoll instance, an inkernel data structure which, from a user-space perspective, can be considered as a container for two lists

所以 其实就是epoll就是内核的一个数据结构。从用户空间的角度,其实就是两个链表。所以基本上就是维护两个链表就可以了。 理解完这段话,我们也就能理解Epoll提供了三个方法了:

  • create是初始化这个内核的数据结构。返回一个fd。众所周知,unix万物皆文件。所以,这里创建返回的是一个文件fd。每次操作我们只需要传入fd,内核便能拿到epoll对应的数据结构。
  • epoll_ctl就是对其中一个链表的操作。这个链表存放用户感兴趣的io事件。当然在注册事件的时候,会有一些其他的操作。后面详细解释
  • epoll_wait,则是返回就绪事件(感兴趣的事件)。然后让应用层去处理。
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

struct epoll_event {
    __uint32_t events;
    epoll_data_t data;
};

epoll_create方法

SYSCALL_DEFINE1(epoll_create1, int, flags)
{
    int error, fd;
    struct eventpoll *ep = NULL;
    struct file *file;
    // 创建内部数据结构eventpoll 
    error = ep_alloc(&ep);
    //查询未使用的fd
    fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));
    file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,
                 O_RDWR | (flags & O_CLOEXEC));

    ep->file = file;
    fd_install(fd, file);  //建立fd和file的关联关系
    return fd;
out_free_fd:
    put_unused_fd(fd);
out_free_ep:
    ep_free(ep);
    return error;
}

简单的说一下这个方法。首先这个方法返回一个文件描述符。我们使用这个文件描述符就可以找到对应的结构体。也就是一块内存区域。epoll的所有数据都会保存在这里。 这块内存区域用eventpoll结构体表示。 所以这个方法逻辑如下:

1.创建eventpoll结构体。初始化结构体相应的数据

2.查询一个未使用的fd,然后为epoll创建一个文件。将file的->private_data指向ep。创建文件的过程就不多说

3.将ep->file指向file。其实就是绑定一下。

4.将fd和file关联。这样我们就能通过fd找到对应的file。并找到ep对应的结构体(内存区域) 这里再说一下 file的private_data其实在设备驱动程序中非常重要,它可以指向一块自定义的数据结构。这也就是保证了一个设备驱动程序可以适配多个设备的原因。因为不同的设备可能存在不同的属性。epoll这里用private_data指向自己的数据结构是完全没有问题的。

eventpoll结构体内容如下。后面遇到详细说。

struct eventpoll {
    spinlock_t lock;
    struct mutex mtx;
    wait_queue_head_t wq; //sys_epoll_wait()使用的等待队列
    wait_queue_head_t poll_wait; //file->poll()使用的等待队列
    struct list_head rdllist; //所有准备就绪的文件描述符列表
    struct rb_root rbr; //用于储存已监控fd的红黑树根节点
    
    struct epitem *ovflist; //用于监听文件的结构。如果rdllist被锁定,临时事件会被连接到这里
    struct wakeup_source *ws; // 当ep_scan_ready_list运行时使用wakeup_source
    struct user_struct *user; //创建eventpoll描述符的用户
    struct file *file;
    int visited;           //用于优化循环检测检查
    struct list_head visited_list_link;
};

epoll_ctl方法

该方法主要就是对监听事件进行增删改。

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;
    error = -EFAULT;
    //如果不是删除操作,将用户空间的epoll_event 拷贝到内核
    if (ep_op_has_event(op) &&
        copy_from_user(&epds, event, sizeof(struct epoll_event)))
    f = fdget(epfd); //epfd对应的文件
    tf = fdget(fd); //fd对应的文件.
    ...
    ep = f.file->private_data; // 取出epoll_create过程创建的ep
    ...
    epi = ep_find(ep, tf.file, fd); //ep红黑树中查看该fd
    switch (op) {
    case EPOLL_CTL_ADD:
        if (!epi) {
            epds.events |= POLLERR | POLLHUP;
            error = ep_insert(ep, &epds, tf.file, fd, full_check); 
        }
        if (full_check)
            clear_tfile_check_list();
        break;
    case EPOLL_CTL_DEL:
        if (epi)
            error = ep_remove(ep, epi); 
        break;
    case EPOLL_CTL_MOD:
        if (epi) {
            epds.events |= POLLERR | POLLHUP;
            error = ep_modify(ep, epi, &epds); 
        }
        break;
    }
    mutex_unlock(&ep->mtx);
    fdput(tf);
    fdput(f);
    ...
    return error;
}

分享更多关于C/C++ Linux后端开发网络底层原理知识学习提升 点击 学习资料 获取,完善技术栈,内容知识点包括Linux,Nginx,ZeroMQ,MySQL,Redis,线程池,MongoDB,ZK,Linux内核,CDN,P2P,epoll,Docker,TCP/IP,协程,DPDK等等。

 

上面省略了一些判断的代码。主要核心就是根据不同的事件类型执行不同的函数。

epoll调用ep_find从红黑树拿到对应的epi。如果已经存在,则不需要进行add。如果不存在,也不可进行remove和modify操作。整个过程会加锁。因为是红黑树,所以查找以及插入性能都是logn级别。所以对于高并发场景,也是能实现快速注册监听的。下面我们分别看一下这三个操作的逻辑。

ep_insert操作

顾名思义,就是加入监听事件。

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; //[小节2.4.5]


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

    //构造并填充epi结构体
    INIT_LIST_HEAD(&epi->rdllink);
    INIT_LIST_HEAD(&epi->fllink);
    INIT_LIST_HEAD(&epi->pwqlist);
    epi->ep = ep;
    ep_set_ffd(&epi->ffd, tfile, fd); // 将tfile和fd都赋值给ffd
    epi->event = *event;
    epi->nwait = 0;
    epi->next = EP_UNACTIVE_PTR;
    if (epi->event.events & EPOLLWAKEUP) {
        error = ep_create_wakeup_source(epi);
    } else {
        RCU_INIT_POINTER(epi->ws, NULL);
    }
    epq.epi = epi;
    //设置轮询回调函数
    init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
    //执行poll方法
    revents = ep_item_poll(epi, &epq.pt);
    spin_lock(&tfile->f_lock);
    list_add_tail_rcu(&epi->fllink, &tfile->f_ep_links);
    spin_unlock(&tfile->f_lock);
    ep_rbtree_insert(ep, epi); //将将当前epi添加到RB树
    spin_lock_irqsave(&ep->lock, flags);
    //事件就绪 并且 epi的就绪队列有数据
    if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
        list_add_tail(&epi->rdllink, &ep->rdllist);
        ep_pm_stay_awake(epi);

        //唤醒正在等待文件就绪,即调用epoll_wait的进程
        if (waitqueue_active(&ep->wq))
            wake_up_locked(&ep->wq);
        if (waitqueue_active(&ep->poll_wait))
            pwake++;
    }
    spin_unlock_irqrestore(&ep->lock, flags);
    atomic_long_inc(&ep->user->epoll_watches);


    if (pwake)
        ep_poll_safewake(&ep->poll_wait); //唤醒等待eventpoll文件就绪的进程
    return 0;
...
}

1.首先会初始化一个epi,对于目标文件的监听都是需要通过epi去维护。一个文件的监听对应一个epi。并且保存在ep的红黑树中。

struct epitem {
    union {
        struct rb_node rbn; //RB树节点将此结构链接到eventpoll RB树
        struct rcu_head rcu; //用于释放结构体epitem
    };


    struct list_head rdllink; //时间的就绪队列,主要就是链接到eventpoll的rdllist
    struct epitem *next; //配合eventpoll中的ovflist一起使用来保持单向链的条目
    struct epoll_filefd ffd; //该结构 监听的文件描述符信息,每一个socket fd都会对应一个epitem 。就是通过这个结构关联
    int nwait; //附加到poll轮询中的活跃等待队列数

    struct list_head pwqlist; //用于保存被监听文件的等待队列
    struct eventpoll *ep;  //epi所属的ep
    struct list_head fllink; //主要是为了实现一个文件被多个epoll监听。将该结构链接到文件的f_ep_link。
    struct wakeup_source __rcu *ws; //设置EPOLLWAKEUP时使用的wakeup_source
    struct epoll_event event; //监控的事件和文件描述符
};

2.初始化结束后,会将文件的fd以及对应文件指针绑定到epi的ffd中。主要作用就是将fd和改epi绑定起来。

struct epoll_filefd {
    struct file *file;
    int fd;
} __packed;

3.为epq的pt(其实就是一个poll_table)注册对应的函数ep_ptable_queue_proc。

struct ep_pqueue {
    poll_table pt;
    struct epitem *epi;
};

typedef struct poll_table_struct {
    poll_queue_proc _qproc;
    unsigned long _key;
} poll_table;

这里epq是一个结构体,里面绑定了epi以及poll_table。poll_table主要注册了ep_ptable_queue_proc函数。_key用于记录事件。 所以epq就保存了epi,以及对应的ep_ptable_queue_proc。后续执行回调函数的时候,我们可以通过poll_table的地址拿到对应的epq,最终拿到对应的epi,这也就是定义这个结构的目的。

4.调用ep_item_poll方法。这个方法我简单说一下。他会调用文件系统的poll方法。

static inline unsigned int ep_item_poll(struct epitem *epi, poll_table *pt)
{
    pt->_key = epi->event.events;、
    return epi->ffd.file->f_op->poll(epi->ffd.file, pt) & epi->event.events;
}

不同的驱动程序,都会有自己的poll方法,如果是TCP套接字,这个poll方法就是tcp_poll。在TCP中,会周期性的调用这个方法,调用频率取决于协议栈中断频率的设置。一旦有事件到达后,对应的tcp_poll方法被调用,tcp_poll方法会回调用sock_poll_wait(),该方法会调用这里注册的ep_ptable_queue_proc方法。epoll其实就是通过此机制实现将自己的回调函数加入到文件的waitqueue中的。这也是ep_ptable_queue_proc的目的。

5.调用ep_item_poll后会返回revents。也就是该fd触发的事件。如果有我们感兴趣的事件,会将其插入到ep的rdllist中。如果有进程正在等待文件的就绪状态,也就是调用epoll_wait睡眠的进程。那么会唤醒等待进程。

 if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
        list_add_tail(&epi->rdllink, &ep->rdllist);
        ep_pm_stay_awake(epi);

        //唤醒正在等待文件就绪,即调用epoll_wait的进程
        if (waitqueue_active(&ep->wq))
            wake_up_locked(&ep->wq);
        if (waitqueue_active(&ep->poll_wait))
            pwake++;
    }

ep_ptable_queue_proc方法

整个过程其实就是通过文件的poll方法绑定ep_ptable_queue_proc函数 。当该文件描述符对应的文件有事件到达后,回调用这个函数

Ps:其中file就是对应文件的结构体。当然 多个fd可以指向通过file结构。多个file可以同时指向同一个innode节点。在linux中,一个文件的内容是通过innode去定义描述的。file只是我们对文件操作的时候创建出来的。这个概念大家需要清楚。

static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,
                 poll_table *pt)
{
    struct epitem *epi = ep_item_from_epqueue(pt);
    struct eppoll_entry *pwq;  


    if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
        //初始化回调方法
        init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
        pwq->whead = whead;
        pwq->base = epi;
        //将ep_poll_callback放入等待队列whead
        add_wait_queue(whead, &pwq->wait);
        //将llink 放入epi->pwqlist的尾部
        list_add_tail(&pwq->llink, &epi->pwqlist);
        epi->nwait++;
    } else {
        epi->nwait = -1; //标记错误发生
    }
}

static inline void
init_waitqueue_func_entry(wait_queue_t *q, wait_queue_func_t func)
{
    q->flags    = 0;
    q->private    = NULL;
    q->func        = func;
}

ep_ptable_queue_proc有三个参数。 file就是监听文件的指针,whead为该fd对应的设备等待队列。pt就是我们当时调用文件的poll传入的东西。

在ep_ptable_queue_proc,引入了eppoll_entry。也就是pwq。pwq主要完成epi和epi事件发生时callback函数的关联。

从上面代码可以看出。首先根据pt拿到对应的epi。然后通过pwq将三者关联。

最后通过add_wait_queue方法,将eppoll_entry挂在到fd的设备等待队列上。也就是注册epoll的回调函数。

所以这个方法的主要目标就是将eppoll_entry挂在到fd的设备等待队列上。当设备有硬件数据到达时,硬件中断处理函数会唤醒该队列上等待的进程时,会调用唤醒函数ep_poll_callback。

分享更多关于C/C++ Linux后端开发网络底层原理知识学习提升 点击 学习资料 获取,完善技术栈,内容知识点包括Linux,Nginx,ZeroMQ,MySQL,Redis,线程池,MongoDB,ZK,Linux内核,CDN,P2P,epoll,Docker,TCP/IP,协程,DPDK等等。

 

ep_poll_callback方法

这个函数主要功能就是,当被监听文件的事件就绪,将文件对应的epi加入到就绪队列。当应用层调用epoll_wait()的时候,内核会将就绪队列的事件拷贝到用户空间。报告给应用。

static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
    int pwake = 0;
    unsigned long flags;
    struct epitem *epi = ep_item_from_wait(wait);
    struct eventpoll *ep = epi->ep;
    spin_lock_irqsave(&ep->lock, flags);
    if (unlikely(ep->ovflist != EP_UNACTIVE_PTR)) {
        if (epi->next == EP_UNACTIVE_PTR) {
            epi->next = ep->ovflist;
            ep->ovflist = epi;
            if (epi->ws) {
                __pm_stay_awake(ep->ws);
            }
        }
        goto out_unlock;
    }
    //如果此文件已在就绪列表中,很快就会退出
    if (!ep_is_linked(&epi->rdllink)) {
        //将epi就绪事件 插入到ep就绪队列
        list_add_tail(&epi->rdllink, &ep->rdllist);
        ep_pm_stay_awake_rcu(epi);
    }
    // 如果活跃,唤醒eventpoll等待队列和 ->poll()等待队列
    if (waitqueue_active(&ep->wq))
        wake_up_locked(&ep->wq);  //当队列不为空,则唤醒进程
    if (waitqueue_active(&ep->poll_wait))
        pwake++;
out_unlock:
    spin_unlock_irqrestore(&ep->lock, flags);
    if (pwake)
        ep_poll_safewake(&ep->poll_wait);


    if ((unsigned long)key & POLLFREE) {
        list_del_init(&wait->task_list); //删除相应的wait
        smp_store_release(&ep_pwq_from_wait(wait)->whead, NULL);
    }
    return 1;
}

//判断等待队列是否为空
static inline int waitqueue_active(wait_queue_head_t *q)
{
    return !list_empty(&q->task_list);
}

3.epoll实现总结

透过现象看本质,其实epoll的灵魂就是ep_item_poll和ep_poll_callback。

epoll依赖虚拟文件系统的ep_item_poll。将ep_poll_callback注册到对应文件的waitqueue中。当对应文件有数据到来。注册的函数就会被调用。epoll的回调会将对应文件的epi加入到就绪队列。

当用户调用epoll_wait(),epoll会加锁,将队列数据传递到用户空间,这个时间到的事件会被挂到ovflist中。

4.Redis使用Epoll

具体实现在ae_epoll.c中

typedef struct aeApiState {

    // epoll_event 实例描述符
    int epfd;
    // 事件槽
    struct epoll_event *events;

} aeApiState;

aeApiCreate方法

redis在初始化server的时候会调用aeCreateEventLoop方法。aeCreateEventLoop回调用aeApiCreate去创建epoll实例。

static int aeApiCreate(aeEventLoop *eventLoop) {
    aeApiState *state = zmalloc(sizeof(aeApiState));


    if (!state) return -1;
    state->events = zmalloc(sizeof(struct kevent)*eventLoop->setsize);
    if (!state->events) {
        zfree(state);
        return -1;
    }
    state->kqfd = kqueue();
    if (state->kqfd == -1) {
        zfree(state->events);
        zfree(state);
        return -1;
    }
    eventLoop->apidata = state;
    return 0;    
}

aeApiAddEvent方法

这个方法是关联事件到epoll,所以会调用epoll的ctl方法

static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
    aeApiState *state = eventLoop->apidata;
    struct epoll_event ee;

    /* If the fd was already monitored for some event, we need a MOD
     * operation. Otherwise we need an ADD operation. 
     *
     * 如果 fd 没有关联任何事件,那么这是一个 ADD 操作。
     * 如果已经关联了某个/某些事件,那么这是一个 MOD 操作。
     */
    int op = eventLoop->events[fd].mask == AE_NONE ?
            EPOLL_CTL_ADD : EPOLL_CTL_MOD;

    // 注册事件到 epoll
    ee.events = 0;
    mask |= eventLoop->events[fd].mask; /* Merge old events */
    if (mask & AE_READABLE) ee.events |= EPOLLIN;
    if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;
    ee.data.u64 = 0; /* avoid valgrind warning */
    ee.data.fd = fd;

    if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1;
    return 0;
}

这个方法在redis服务创建一个新的客户端的时候会调用。会注册这个客户端的读事件。

当redis需要给客户端写数据的时候会调用prepareClientToWrite方法。这个方法主要是注册对应fd的写事件。

如果注册失败,redis就不会将数据写入缓冲。

如果对应套件字可写,那么redis的事件循环就会将缓冲区新数据写入socket。

aeMain方法

Redis事件处理器的主循环。

void aeMain(aeEventLoop *eventLoop) {

    eventLoop->stop = 0;

    while (!eventLoop->stop) {

        // 如果有需要在事件处理前执行的函数,那么运行它
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);

        // 开始处理事件
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
    }
}

这个方法最终会调用epoll_wait()获取对应事件并执行。

 

深入理解IO多路复用:从select、poll到epoll,Redis为何选择epoll 在高并发网络编程中,IO多路复用是支撑海量连接的关键技术。它允许单个线程同时监听多个文件描述符,避免阻塞式IO带来的资源浪费。从最基础的select开始,其存在描述符上限、O(n)扫描和多次拷贝等缺陷;poll虽突破了数量限制,但仍需全量遍历。epoll通过红黑树管理关注集合、就绪链表和回调机制,实现了O(1)复杂度的事件通知,成为Linux下高性能服务器的首选。Redis基于单线程模型和事件驱动框架,在底层封装了select、epoll等不同实现,最终选择epoll(LT模式)来管理数万并发连接,从而将C 阅读详情

相关推荐

浅谈NIO和Epoll实现原理

1 前置知识 1.1 socket是什么? 就是传输控制层tcp连接通信。 1.2 fd是什么? fd既file descriptor-文件描述符。Java中用对象代表输入输出流等...在Linux系统中不是面向对象的,是一切皆文件的,拿文件来代表输入输出流。其实是一个数值,例如1,2,3,4...0是标准输入,1是标准输出,2是错误输出... 相关视频讲解: C++后台开发学习视频 支撑亿级IO的底层基石 epoll实战揭秘 任何进程在操作系统中都有自己IO对应的文件描述符,参考如下:

Linuxhus的博客 4466

epoll监听文件_怎么使用epoll机制监听文件变化并读取文件

展开全部epoll的接口62616964757a686964616fe58685e5aeb931333363386165非常简单,一共就三个函数:1. int epoll_create(int size);创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会...

weixin_32306771的博客 1828

从linux源码看epoll及epoll实战揭秘

从linux源码看epoll 前言 在linux的高性能网络编程中,绕不开的就是epoll。和select、poll等系统调用相比,epoll在需要监视大量文件描述符并且其中只有少数活跃的时候,表现出无可比拟的优势。epoll能让内核记住所关注的描述符,并在对应的描述符事件就绪的时候,在epoll的就绪链表中添加这些就绪元素,并唤醒对应的epoll等待进程。 本文就是笔者在探究epoll源码过程中,对kernel将就绪描述符添加到epoll并唤醒对应进程的一次源码分析(基于linux-2.6.32内核版

Linuxhus的博客 710

epoll使用详解(精髓)

epoll - I/O event notification facility 在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。 相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明: #define __FD_SETSIZE 1024

XMJYever的博客 1231

详解 epoll 原理【Redis,Netty,Nginx实现高性能IO的核心原理】

epoll原理剖析:epoll原理剖析以及reactor模型应用 redis源码分析:redis源码分析及driver现实 Nginx模块开发:Nginx源码从模块开发入手,3个项目弄透nginx模块开发 【Redis,Netty,Nginx 等实现高性能IO的核心原理】 I/O 输入输出(input/output)的对象可以是文件(file), 网络(socket),进程之间的管道(pipe)。在linux系统中,都用文件描述符(fd)来表示。 I/O 多路复用(multiplexin

Linuxhus的博客 451

在Kintex-7 FPGA上实现皮秒级时间测量:手把手教你搭建CARRY4进位链TDC(附源码与约束)

本文详细介绍了在Kintex-7 FPGA上利用CARRY4进位链实现皮秒级时间测量(TDC)的完整方案。从CARRY4的物理特性分析到硬件架构设计,再到校准方法与精度提升技巧,手把手教你构建测量精度达53ps的TDC系统,并附源码与约束文件,助力激光雷达、高能物理等精密计时应用开发。

weixin_42550052的博客 444

epoll监听文件_epoll源码分析以及在Redis中的实现

1.概述这篇文章分析一下linux中epoll的实现原理,主要为了增强自己对网络调用的理解。业界使用epoll的框架比较多,随便就能列出来很多,比如jdk的nio在linux下的实现,以及netty、redis等涉及到长链接网络请求的地方,我们都可以直接使用epoll。文末会从redis源码简单看看如何使用epoll做IO多路复用实现高并发。2.具体实现参考官方文档描述:The central c...

weixin_42524750的博客 273

使用electron静默打印

使用electron静默打印 1.使用electron打印的理由 很多情况下程序中使用的打印都是用户无感知的。并且想要灵活的控制打印内容,往往需要借助打印机给我们提供的api再进行开发,这种开发方式非常繁琐,并且开发难度较大。 electron提供的打印api可以非常灵活的控制打印设置的显示,并且可以通过html来书写打印内容。 2.api ele...

w1366352655的博客 945

epoll原理_Epoll的简介及原理

epoll 全称 eventpoll,是 linux 内核实现IO多路复用(IO multiplexing)的一个实现。IO多路复用的意思是在一个操作里同时监听多个输入输出源,在其中一个或多个输入输出源可用的时候返回,然后对其的进行读写操作。在 linux 中,和 epoll 类似的有 select 和 poll。网上很多文章着重关注 epoll 与 select 的区别,或者关注 epoll 中...

weixin_39603117的博客 517

YOLOv8验证的命令以及计算coco指标

好久不看代码,都不知道怎么验证的了,来记录一下。1、计算coco指标。

毕竟是Shy哥 1481

避坑指南:DeepSeek Function calling流式输出中的常见问题与解决方案

本文深入探讨了在DeepSeek Function Calling中实现流式输出时常见的参数拼接、函数依赖管理和错误处理等核心挑战。针对流式响应中参数碎片化的问题,提供了健壮的参数拼接方案与状态管理策略。同时,文章详细阐述了如何管理函数调用顺序、处理条件性执行,并构建了分阶段的错误处理与重试机制,旨在帮助开发者构建稳定、高效的流式AI应用。

weixin_29062671的博客 203

有关epoll的EPOLLOUT

epoll使用的资料网上一大把,EPOLLIN(读)监听事件的类型,大家一般使用起来一般没有什么疑问,无非是监听某个端口,一旦客户端连接有数据发送,它马上通知服务端有数据,一般用一个回调的读函数,从这个相关的socket接口读取数据就行了。但是有关EPOLLOUT(写)监听的使用,网上的资料却讲得不够明白,理解起来有点麻烦。因为监听一般都是被动操作,客户端有数据上来需要读写(被动的读操作,EPOL

tanlijun37的专栏 9650

告别仿真失败!用Sigrity 2022 SystemSI搞定DDR4信号质量仿真的保姆级流程

本文详细介绍了如何使用Sigrity 2022 SystemSI工具进行DDR4信号完整性仿真,提供了一套避免仿真失败、提高仿真精度和加速仿真流程的保姆级方法。从模型验证、设计数据预处理到拓扑构建和参数提取,再到高级调试技巧和仿真加速方案,帮助硬件工程师系统性地解决DDR4信号质量仿真中的常见问题。

weixin_42537499的博客 343

Redis 源码分析-Redis 中的事件驱动

select、poll、epoll 的实现是怎么样的,每个机制中的关键函数都有哪些参数以及参数的含义是什么,三者都有什么优缺点,Redis 是如何实现 IO 多路复用的

qq_56517253的博客 1056

运放单电源供电和双电源供电的实际应用案例

文章摘要:本文对比分析了运放单电源与双电源供电的典型应用场景及电路设计要点。单电源供电适用于低成本、小体积设备,如便携音箱(5V供电+中点偏置)、工业4-20mA信号转换(12V供电+电压跟随)和物联网传感器(3.3V低功耗设计);双电源供电则适合高精度要求场景,如Hi-Fi音响(±15V对称输出)、医疗设备(±5V微弱信号放大)和精密仪器(±15V宽动态范围)。文末提供常见运放选型对照表,涵盖LM324、TL082等型号的供电参数与核心特性,为不同应用场景提供选型参考。(149字)

qq_37999850的博客 609

Lammps实现结冰匀相成核

Lammps实现结冰匀相成核

Rockyoung 3846

epoll详解_Redis详解(2)——Redis是如何做到单线程服务10000客户端的

前言在Redis的配置文件中,可以指定同时连接的最大客户端数目,这个数目默认为10000,但是我们又提到Redis是单线程服务客户端请求的,那么Redis是如何做到单线程服务这10000个客户端的呢?那就是大龙:Redis详解(一)——为什么我们都需要了解Redis​zhuanlan.zhihu.com中提到的IO多路复用技术。如果只是给大家拽技术名词,那我的专栏就没什么存在的价值了。所以本篇我们...

weixin_39759890的博客 284

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

解析Redis网络模型的源码

Redis的网络模型是基于I/O多路复用程序来实现的。源码中包含四种多路复用函数库epoll、select、evport、kqueue。在程序编译时会根据系统自动选择这四种库其中之一。下面以epoll为例,来分析Redis的I/O模块的源码。 epoll系统调用方法 Redis网络事件处理模块的代码都是围绕epoll那三个系统方法来写的。先把这三个方法弄清楚,后面就不难了。 epfd = epoll_create(1024); 创建epoll实例 参数:表示该 epoll 实例最多可监听的 socket f

qingyangcc123的博客 354

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

【Redis-6.0.8】事件循环器AE(上)

1.aeMain流程阅读 1.1 aeMain() voidaeMain(aeEventLoop*eventLoop){ eventLoop->stop=0; while(!eventLoop->stop){ aeProcessEvents(eventLoop,AE_ALL_EVENTS| AE_CALL_BEFORE_SLEEP| ...

Edidaughter的博客 1429

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

redis源码剖析之异步事件驱动框架

想要分析源码,我觉得可以把它当成一个整体,或者一个函数,有输入和对应的输出。我们从输入开始分析流程。先撇开main函数,在gdb上打印下线程的堆栈是个好想法,让我们看看它都在做什么。 可以看得到它其中一条线程阻塞在epoll_wait(我的系统是ubuntu,因此选择了epoll)。在这里检测套接字事件,等待client请求。还有个线程池,创建了3条线程等待处理任务。 #ifdef HAVE_E...

明月如霜丶 861

浅析Redis②:命令处理之epoll实现(中)

Redis的非阻塞的核心epoll是如何实现的?

wtopps的专栏 1648

redis源码分析之ae_epoll.c

#include <sys/epoll.h> typedef struct aeApiState { //epoll 监听fd int epfd; //epoll wait产生的事件列表 struct epoll_event *events; } aeApiState; /** * 创建epoll * @param eventLoop * @r...

小羽毛的博客 521

【Linux深入】epoll源码剖析

引入 之前讲了select、poll、epoll的区别,由于许多应用中都用到了epoll,例如Netty、Redis等等,所以就来深入学习一下,现在我们就来剖析一下epoll的源码 我先来剖析理解epoll源码的基础:主要的数据结构,然后再来解析epoll主要的三个方法:epoll_create()、epoll_ctl()、epoll_wait()。 主要的数据结构 1.eventpoll ...

paradox_1_0的博客 392

浅析Redis①:命令处理核心源码分析(上)

毫无疑问,Redis已经成为我们日常开发中最长使用的缓存数据库,Redis如此高效的原因,是因为采用了非阻塞I/O模型来处理命令请求,这是我们耳熟能详的事情了,那么Redis具体是如何实现非阻塞I/O的呢?Redis是如何接收命令请求,并执行命令,再返回给客户端的呢?我们来一起探究。 本篇是Redis源码分析系列的第一篇,我们来一起看一下Redis处理命令的核心实现机制。

wtopps的专栏 1421
上一篇: 手写网络协议栈-协议封装,netmap,dpdk网卡数据抓取,柔性数组
下一篇: 【纯干货】你一定还不懂的,mysql缓存问题的解决方案
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值