Select & Epoll原理

预备知识

等待队列

等待队列有一个等待队列头,其他加入这个等待队列的需要加在这个头上。

需要加入等待队列的话,可以调用封装好的sleep_on(wait_queue_head_t *q)。这个sleep_on()函数其中调用了__add_wait_queue(q, &wait),然后调用了schedule_timeout()去调度进程并让当前进程睡眠,最后调用__remove_wait_queue(q, &wait)。即睡眠回来的进程把自己从等待队列头上去掉,然后往下执行。

对一个等待队列头调用wake_up(wait_queue_head_t *q)的话,就是去队列中调用挂在等待队列的实体注册的回调函数。wake_up_all(wait_queue_head_t *q)则是遍历这个等待队列,调用所有的实体的回调函数。默认的回调函数是default_wake_function(),这个函数实际上就是去调用try_to_wake_up()唤醒等待队列上的进程。然后注册到等待队列上的进程启动,并回到sleep_on(),并且调用__remove_wait_queue(q, &wait)。

一般说来,当设备有响应的时候,会触发一个硬中断。在硬中断处理函数中会调用其wake_up()或者wake_up_all()函数。

static unsigned int poll(struct file* file, poll_table* wait)

一个设备的f_op->poll()函数干两件事。

一、调用poll_wait()。一般情况下就是poll_wait(file, &dev->r_wait, p)和poll_wait(file, &dev->w_wait, p)。其中dev->r_wait和dev->w_wait就是相应设备的等待队列的队列头。

二、对设备读写状态进行检查,有就给mask置位,最后返回mask。

static inline void poll_wait(struct file* filp, wait_queue_head_t* wait_address, poll_table* p)

f_op->poll()调用的poll_wait()一般只做一件事,就是判定p和wait_address是否为null,如果都不是,那么调用p->qproc(filp, wait_address, p)。一般来说,这个pqroc()函数的行为是1.设置等待队列被唤醒之后所执行的函数,2.把实体加入到相应设备的等待队列中。

Select

结构

struct poll_wqueues来表达一次select()。这个结构里面最重要的一项是struct poll_table pt。poll_wqueues里面还有struct poll_table_entry的链表和数组。

struct poll_table_entry表示一个需要监听的事件,并且是挂到驱动设备的一个或多个等待队列的实体。

函数

select()最后要调用do_select()。do_select()做如下几件事。

一、定义一个poll_wqueues并调用poll_initwait(&table)初始化这个poll_wqueues。poll_initwait(&table)中最关键的初始化操作是init_poll_funcptr(&pwq->pt, __pollwait)。即设置table.pt.qproc = __pollwait。

二、死循环。首先,遍历。遍历所有用户需要监听的fd,对于每个fd:1.调用mask = (*f_op->poll)(file, retval ? NULL : wait)。其中wait就是我们的table.pt。2.查看mask是否置位,来改变返回的位图和retval。其次,检查。检查(retval的值 | timeout | 当前进程是否信号要处理),如果满足任意一个,那么退出死循环。都不满足的话,调用schedule_timeout()让出cpu并且睡眠。

三、调用poll_freewait(&table),这个函数:释放poll_wqueues,并且移除设备的等待队列上的所有实体。然后返回retval。

__pollwait()函数和我们上面说的一样。这个函数的行为是设置等待队列被唤醒之后所执行的函数为pollwake()(实际上这个函数本质上就是default_wake_function())并且加入到fd相应设备的等待队列中

注意对poll()的调用参数。如果select()发现retval不为0的话,就不执行__pollwait()了,而只是去检查设备的状态。

Epoll

结构

struct eventpoll来表达一次epoll()。这个结构体里面有红黑树的根rb_root rbr,有一个链表list_head rdllist,还有一个链表struct epitem* ovflist,有一个等待队列wait_queue_head_t wq

struct epitem代表一个用户注册的实例,其中有一个红黑树节点和一个链表节点,还有用户空间的数据。

struct epoll_entry代表一个需要加入设备等待队列的实体,其中有一个base指针指向epitem。

函数

epoll_create()调用系统调用sys_epoll_create()。这个系统调用继续调用sys_epoll_create1(0)。即这个create1()函数舍弃了用户传来的size变量。这个sys_epoll_create1()函数接着调用ep_alloc()进行初始化结构(红黑树等),然后调用anon_inode_getfd()函数创建一个匿名的文件,然后调用fd_install()给文件安排一个fd。

epoll_ctl()调用系统调用sys_epoll_ctl()。我们主要讨论插入的情况。sys_epoll_ctl()先判定这个要插入的fd是否已经在红黑树中,如果存在了的话就不插入了,而是返回一个错误。如果不存在,可以正常插入,那么调用ep_insert()函数。

ep_insert()函数做的事情如下。

一、调用init_poll_funcptr()函数注册ep_ptable_queue_proc()函数。

二、把这个fd初始化一个struct epitem并且加入到一个红黑树中。

三、首先,调用当前交给epoll_ctl()的fd的revents = poll(tfile, &epq.pt)。检查设备是否满足条件。

四、如果设备有响应,且这个fd没加入eventpoll的队列中的话,就直接加入到eventpoll的队列中。否则什么也不干。

这个ep_ptable_queue_proc()函数设置设备的等待队列的callback为ep_poll_callback(),并且把epoll_entry加入fd对应的设备的等待队列

ep_poll_callback()做的事情就是,1.判断是不是有事件发生,如果没有就退出。2.unlikely判断ovflist能不能加入(ep_scan_ready_list()是否正在执行,是否正在向用户空间传数据),如果能的话就把epitem加入到ovflist中。3.如果不能加入ovflist,那么判断当前epitem是否在rdllist中,如果不在那么就把响应的epitem加入到struct eventpoll->rdllist中。4.如果ep->wq上有等待实体,那么唤醒ep->wq等待队列。

对于epoll_wait()函数,epoll_wait()调用ep_poll()函数。ep_poll()也是一个死循环,死循环里做以下事情。

一、检查eventpoll->rdllist里有没有元素,如果没有的话就1.把current加到eventpoll的等待队列里(ep->wq)。2.执行一个死循环,死循环里检查(链表是否是空 | timeout | 当前进程是否信号要处理),如果满足任意一个,那么退出死循环。死循环里还调用schedule_timeout()重新调度让出cpu并睡眠。3.调用__remove_wait_queue(&ep->wq, &wait)。

二、调用ep_send_events()把元素传递给内核空间。如果还没获取到,那么继续死循环,直到获取到为止。

ep_send_events()调用ep_scan_ready_list()。ep_scan_ready_list()做以下几件事。1.置位ovflist声明其可访问。2.调用ep_send_events_proc()。3.在ep_send_events_proc()执行的过程中,有可能会有新的epitem加入到eventpoll->ovflist中,所以要遍历ovflist,把在ovflist中的,在rdllist中没有的epitem加入到rdllist中。4.复位ovflist声明其不可访问。5.如果rdllist不为空(LT水平触发 | 传输rdllist出现错误 | 新的ovflist加入rdllist)那么如果ep->wq上有等待实体,则唤醒ep->wq。

ep_send_events_proc()这个函数遍历eventpoll->rdllist。对其中每一项,1.移除rddlist。2.调用f_op->poll(epi->ffd.file, NULL)。即,这个时候不再调用ep_ptable_queue_proc(),只是单纯的去检查,不改变设备的等待队列,所以也就不用从队列里移除或者再添加。2.如果通过了(确实有响应),那么就传递给用户空间数据,如果传输失败再把当前epitem加回到eventpoll->rdllist。3.传递完,检查events的标志,如果是ONESHOT那么把标志全部清零(除了PRIVATE_BITS);如果是LT水平触发,那么就把这个epitem再添加到eventpoll->rdllist中;ET触发什么都不干。

实际流程

Select直接获取

Select先设置wait = __pollwait,然后去遍历rset,分别调用poll(file, retval ? NULL : wait)。假如执行到第3个fd,发现设备有回应,即retval不为0了。那么对于前3个fd,poll分别注册了它们的pollwait回调函数并且加入到了等待队列头当中。然后到了第4个,retval已经不为0了,以后的poll只执行检查。执行完,位图和retval也都设置好了,这时死循环break,那么把前3个fd从设备的等待队列头中摘除,然后高高兴兴地返回retval就完事。

Select睡眠获取

Select先设置wait = __pollwait,然后去遍历rset,分别调用poll(file, retval ? NULL : wait)。然而没有任何fd有poll的相应,那么当前进程就去睡眠。如果有设备响应了任何一个fd,调用了wake_up(&head)。wake_up(&head)去调用default_wake_funtion()。这时在Select中睡眠的进程被唤醒,从死循环头部开始执行,以下流程就类似于Select的直接获取,不直接说了。但是要注意,如果是第3个fd开始响应的,那么前3个fd会被挂在等待队列头两次。不过最后都会被一起移除的。

Epoll直接获取

epoll_create()先初始化红黑树、创建文件并返回epoll的描述符。

epoll_ctl()判断这个注册的fd没在红黑树中,那么调用ep_insert()。ep_insert()设置wait = ep_ptable_queue_proc,然后把epitem加到红黑树中,然后调用poll(file, wait),设置callback为ep_poll_callback,然后把entry加入到设备的等待队列头中。这时检查有没有响应,有响应直接加入到rdllist中。

epoll_wait()检查rdllist是否为空。发现rdllist不为空之后,直接通过ep_send_events()返回rdllist。

Epoll睡眠获取

上述epoll_wait()函数发现rdllist是空的,那么就去睡觉。当设备有响应的时候,硬件设备执行wake_up(&dev->r_wait)。这个wake_up()调用等待的实体的ep_poll_callback(),这个ep_poll_callback()函数把准备好的epitem加入到struct eventpoll->rdllist或ovflist中,并且调用wake_up(&ep->wq)。这个wake_up()调用default_wake_function(),唤醒current进程。进程回到epoll_wait(),把current移除等待队列ep->wq,然后调用ep_send_events()。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容