首页 > 编程知识 正文

简述手动变速器的工作原理,redis epoll原理

时间:2023-05-04 19:41:22 阅读:113625 作者:4875

这篇文章包含以下内容。 epoll是如何工作的,本文不包含以下内容。 epoll的使用方法epoll的缺陷epoll的实现原理通过视频进行了说明。 C/C Linux服务器开发高级体系结构学习视频点击: C/C Linux服务器开发/Linux后台架构师-学习视频教程基于epoll原理分析和reactor模型应用linux epoll

我喜欢像epoll一样好用,原理不深也有很大帮助的东西。 即使那个可能已经旧了

select和poll的缺点epoll对于需要处理数以万计的连接的web服务APP应用程序来说可以说是革命性的。 在典型的本机APP下,select和poll可能很容易使用,但在C10K这样的高并发web场景中,select和poll是无法处理的。

他们的

intselect(intnfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout ); int poll (结构pollfd * FDS,nfds_t nfds,int timeout ); 有共同点。 用户必须将API的文件描述符集合打包并作为参数传递。 每次调用时,此集合都从用户空间3358www.Sina.com/传递到内核空间。 这是因为内核不记得这个集合。 对于大多数APP应用,这是足够浪费的。 由于APP应用程序需要监视的描述符在大部分时间内几乎没有变化,因此可能会有变化,但不会很大。

与此相对,epoll的改善、epoll的改善也是其实现方法,需要完成以下两点

添加描述符---内核可以记录用户对哪个文件中的哪个事件感兴趣。 事件发生---内核可以记录哪个文件中的哪个事件实际发生,并且可以在用户来获取时向用户提供结果。 既然追加描述符有记忆,那么内核当然需要数据结构。 此数据结构简单来说就像下图中的epoll_instance,它有一个链表头。 链表中的元素epoll_item是用户添加的,每个元素都包含描述符fd和感兴趣的事件组合event

发生的事件有多种类型,但POLLIN显示的监控事件是用户使用最多的事件。 例如:

当TCP套接字收到消息时,它是可读的; pipe收到对方发送的数据后,它将变为可读;与timerfd对应的计时器超时后,它将变为可读; 现在,需要将这些拷贝事件与上一个epoll_instance相关联。 在linux上,每个文件描述符的内核都对应一个结构文件结构,该结构文件具有指向不同数据结构的private_data指针,具体取决于文件的实际类型。

最有用的方法是在epoll_item中添加指向结构文件的指针,在结构文件中添加指向epoll item的指针。

要记录发生事件的文件,请将readylist添加到epoll_instance,将指针返回到结构文件,将指针返回到private_data指针指向的各种数据结构,然后返回到epoll item

然后,用户可以通过从系统调用中读取就绪列表来知道哪些文件可用。

那么,以上是我磕头想出来的epoll的大致工作方法,其中一定含有很多缺陷。

但是,实际的epoll的实现思想与上面相同,叙述如下

了解有关C/C Linux后端开发网络基础原理的信息: Linux、Nginx、ZeroMQ、MySQL、Redis、线程池、MongoDB、ZK、Linux内核、CDN、P2P、-

与上述epoll_instance一样,要创建epoll实例,必须具有在内核中保存用户注册条目的数据结构。 此结构在内核中是结构事件池,并且当用户使用epoll_create(2)或epoll_create1) 2时,内核FS

error=EP_alloc(EP; 在此结构中,重要的部分是几个链表,但刚创建实例时它是空的,以后可以看到它的作用

epoll_create ()最终将向用户返回文件描述符,以便用户以后可以使用可读。 因此,在创建可读后,内核将被拆分

配一个文件描述符fd和对应的struct file结构

fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep, O_RDWR | (flags & O_CLOEXEC));

最后就是把它们和刚才的epoll 实例 关联起来,然后向用户返回fd

ep->file = file;fd_install(fd, file);return fd;

完成后,epoll 实例 就成这样了。

 

向 epoll 实例添加一个文件描述符

用户可以通过 epoll_ctl(2)向 epoll 实例 添加要监控的描述符和感兴趣的事件。如同前面的epoll item,内核实际创建的是一个叫struct epitem的结构作为注册表项。如下图所示

 

为了在描述符很多时的也能有较高的搜索效率, epoll 实例 以红黑树的形式来组织每个struct epitem (取代上面例子中链表)。struct epitem结构中ffd是用来记录关联文件的字段, 同时它也作为该表项添加到红黑树上的Key

rdllink的作用是当fd对应的文件准备好 (关心的事件发生) 时,内核会将它作为挂载点挂接到epoll 实例中ep->rdllist链表上
fllink的作用是作为挂载点挂接到fd对应的文件的file->f_tfile_llink链表上,一般这个链表最多只有一个元素,除非发生了dup。
pwqlist是一个链表头,用来连接 poll wait queue。虽然它是链表,但其实链表上最多只会再挂接一个元素。

创建struct epitem的代码在fs/evnetpoll.c的ep_insert()中

if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL))) return -ENOMEM;

之后会进行各个字段初始化

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);epi->event = *event;epi->nwait = 0;epi->next = EP_UNACTIVE_PTR;

然后是设置局部变量epq

struct ep_pqueue epq;epq.epi = epi;init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);

epq的数据结构是struct ep_pqueue, 它是poll table的一层包装 (加了一个struct epitem* 的指针)

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

poll table包含一个函数和一个事件掩码

typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);typedef struct poll_table_struct { poll_queue_proc _qproc; unsigned long _key; }poll_table;

这个poll table用在哪里呢 ? 答案是, 用在了struct file_operations的poll操作 (这和本文开始说的select`poll`不是一个东西)

 

struct file_operations { unsigned int (*poll)(struct file*, struct poll_table_struct*); }

不同的文件有不同poll实现方式, 但一般它们的实现方式差不多是下面这种形式

static unsigned int XXXX_poll(struct file *file, poll_table *wait){ 私有数据 = file->private_data; unsigned int events = 0; poll_wait(file, &私有数据->wqh, wait); if (文件可读了) events |= POLLIN; return events;}

它们主要实现两个功能

XXX放到文件私有数据的等待队列上 (一般file->private_data中都有一个等待队列头wait_queue_head_t wqh), 至于XXX是啥, 各种类型文件实现各异, 取决于poll_table参数查询是否真的有事件了, 若有则返回.

有兴趣的读者可以 timerfd_poll() 或者 pipe_poll() 它们的实现

 

poll_wait的实现很简单, 就是调用poll_table中设置的函数, 将文件私有的等待队列当作了参数.

static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p){ if (p && p->_qproc && wait_address) p->_qproc(filp, wait_address, p);}

回到 ep_insert()

所以这里设置的poll_table就是ep_ptable_queue_proc().

然后

revents = ep_item_poll(epi, &epq.pt)

看其实现可以看到, 其实就是主动去调用文件的poll函数. 这里以 TCP socket文件为例好了 (毕竟网络应用是最广泛的)

unsigned int tcp_poll(struct file *file, struct socket *sock, poll_table *wait) { sock_poll_wait(file, sk_sleep(sk), wait); }

可以看到, 最终还是调用到了poll_wait(), 所以注册的ep_ptable_queue_proc()会执行

struct epitem *epi = ep_item_from_epqueue(pt); struct eppoll_entry *pwq; pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL)

这里面, 又分配了一个struct eppoll_entry结构. 其实它和struct epitem 结构是一一对应的.

随后就是一些初始化

init_waitqueue_func_entry(&pwq->wait, ep_poll_callback); pwq->whead = whead; pwq->base = epi; add_wait_queue(whead, &pwq->wait) list_add_tail(&pwq->llink, &epi->pwqlist); epi->nwait++;

这其中比较重要的是设置pwd->wait.func = ep_poll_callback。

现在, struct epitem 和struct eppoll_entry的关系就像下面这样

 

文件可读之后

对于 TCP socket, 当收到对端报文后, 最初设置的sk->sk_data_ready函数将被调用

void sock_init_data(struct socket *sock, struct sock *sk){ sk->sk_data_ready = sock_def_readable; }

经过层层调用, 最终会调用到 __wake_up_common 这里面会遍历挂在socket.wq上的等待队列上的函数

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, &q->task_list, task_list) { unsigned flags = curr->flags; if (curr->func(curr, mode, wake_flags, key) && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive) break; }}

于是, 顺着图中的这条红色轨迹, 就会调用到我们设置的ep_poll_callback, 那么接下来就是要让epoll实例能够知有文件已经可读了

 

先从入参中取出当前表项epi和ep

struct epitem *epi = ep_item_from_wait(wait); struct eventpoll *ep = epi->ep;

再把epi挂到ep的就绪队列

if (!ep_is_linked(&epi->rdllink)) { list_add_tail(&epi->rdllink, &ep->rdllist) }

接着唤醒阻塞在 (如果有) 该epoll实例的用户.

waitqueue_active(&ep->wq) 用户获取事件

谁有可能阻塞在epoll实例的等待队列上呢? 当然就是使用epoll_wait来从epoll实例获取发生了感兴趣事件的的描述符的用户.
epoll_wait会调用到ep_poll()函数.

if (!ep_events_available(ep)) { init_waitqueue_entry(&wait, current); __add_wait_queue_exclusive(&ep->wq, &wait);

如果没有事件, 我们就将自己挂在epoll实例的等待队列上然后睡去.....
如果有事件, 那么我们就要将事件返回给用户

ep_send_events(ep, events, maxevents)

 

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。