点击上方蓝字【囧囧妹】一起学习,一起成长!
一、开篇
示例代码我都放在了https://gitee.com/sunnyshare/linux-examplecode.git。
二、epoll_wait
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;long slack = 0;wait_queue_t wait;ktime_t expires, *to = NULL;如果timeout大于0就设置超时时间if (timeout > 0) {struct timespec end_time = ep_set_mstimeout(timeout);slack = select_estimate_accuracy(&end_time);to = &expires;*to = timespec_to_ktime(end_time);} else if (timeout == 0) {timeout==0也就是不用等待,此时会跳过中间过程直接执行check_eventstimed_out = 1;spin_lock_irqsave(&ep->lock, flags);goto check_events;}fetch_events:spin_lock_irqsave(&ep->lock, flags);检测是否rdlist是否为空if (!ep_events_available(ep)) {将当前进程的默认唤醒回调添加到wait队列上init_waitqueue_entry(&wait, current);将wait队列wq队列上__add_wait_queue_exclusive(&ep->wq, &wait);for (;;) {//当前任务设置为可中断set_current_state(TASK_INTERRUPTIBLE);//就绪链表不为空或者超时则跳出if (ep_events_available(ep) || timed_out)break;//收到未决信号跳出if (signal_pending(current)) {res = -EINTR;break;}spin_unlock_irqrestore(&ep->lock, flags);//让出cpu,进入休眠if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS))timed_out = 1;spin_lock_irqsave(&ep->lock, flags);}//将wait从wq队列删除掉__remove_wait_queue(&ep->wq, &wait);//当前任务设置为运行态__set_current_state(TASK_RUNNING);}check_events://如果超时为0则直接跳转到此处进行就绪链表是否为空判断//如果超时>0则从上面for(;;)中跳出到此处eavail = ep_events_available(ep);spin_unlock_irqrestore(&ep->lock, flags);//res==0且有事件就绪&&ep_send_events返回就绪数量不为0&&未超时if (!res && eavail &&!(res = ep_send_events(ep, events, maxevents)) && !timed_out)goto fetch_events;//上述条件有一个不成立则继续执行fetch_eventsreturn res;//返回就绪的事件数量}
static int ep_send_events(struct eventpoll *ep,struct epoll_event __user *events, int maxevents){struct ep_send_events_data esed;esed.maxevents = maxevents;esed.events = events;return ep_scan_ready_list(ep, ep_send_events_proc, &esed, 0, false);}
//扫描就绪链表static int ep_scan_ready_list(struct eventpoll *ep,int (*sproc)(struct eventpoll *,struct list_head *, void *),void *priv, int depth, bool ep_locked){int error, pwake = 0;unsigned long flags;struct epitem *epi, *nepi;//初始化链表LIST_HEAD(txlist);if (!ep_locked)mutex_lock_nested(&ep->mtx, depth);spin_lock_irqsave(&ep->lock, flags);//将就绪链表连接到txlistlist_splice_init(&ep->rdllist, &txlist);ep->ovflist = NULL;spin_unlock_irqrestore(&ep->lock, flags);error = (*sproc)(ep, &txlist, priv);spin_lock_irqsave(&ep->lock, flags);//当执行sproc回调时,可能有些事件重新进入了poll回调,这里要重新将他们插入就绪链表for (nepi = ep->ovflist; (epi = nepi) != NULL;nepi = epi->next, epi->next = EP_UNACTIVE_PTR) {//查看该epitem是否已经在就绪链表if (!ep_is_linked(&epi->rdllink)) {list_add_tail(&epi->rdllink, &ep->rdllist);ep_pm_stay_awake(epi);}}ep->ovflist = EP_UNACTIVE_PTR;//将未拷贝到用户空间的就绪链表重新连接到ep就绪链表list_splice(&txlist, &ep->rdllist);__pm_relax(ep->ws);//就绪链表不为空则再次激活epoll_wait//为什么会不为空呢?//当进行就绪链表拷贝到用户空间的时,极有可能发生事件的触发再次添加到就绪链表//当拷贝过程中出现错误或者没有全部拷贝到用户空间if (!list_empty(&ep->rdllist)) {if (waitqueue_active(&ep->wq))wake_up_locked(&ep->wq);if (waitqueue_active(&ep->poll_wait))pwake++;}spin_unlock_irqrestore(&ep->lock, flags);if (!ep_locked)mutex_unlock(&ep->mtx);if (pwake)ep_poll_safewake(&ep->poll_wait);return error;}
//将就绪事件和数据拷贝给用户空间static int ep_send_events_proc(struct eventpoll *ep, struct list_head *head,void *priv){struct ep_send_events_data *esed = priv;int eventcnt;unsigned int revents;struct epitem *epi;struct epoll_event __user *uevent;struct wakeup_source *ws;poll_table pt;init_poll_funcptr(&pt, NULL);//遍历就绪链表eventcnt记录就绪的事件fd数量for (eventcnt = 0, uevent = esed->events;!list_empty(head) && eventcnt < esed->maxevents;) {epi = list_first_entry(head, struct epitem, rdllink);ws = ep_wakeup_source(epi);if (ws) {if (ws->active)__pm_stay_awake(ep->ws);__pm_relax(ws);}//将epi从链表中删除list_del_init(&epi->rdllink);//获取最新的事件数据revents = ep_item_poll(epi, &pt);//如果有事件发生将events拷贝到用户空间if (revents) {if (__put_user(revents, &uevent->events) ||__put_user(epi->event.data, &uevent->data)) {//拷贝发生错误则将epi重新挂载到就绪链表list_add(&epi->rdllink, head);ep_pm_stay_awake(epi);return eventcnt ? eventcnt : -EFAULT;}eventcnt++;uevent++;if (epi->event.events & EPOLLONESHOT)epi->event.events &= EP_PRIVATE_BITS;else if (!(epi->event.events & EPOLLET)) {//如果是ET模式,epitem不会再次添加到就绪链表,除非事件再次发生//如果是LT模式,则会将epitem重新添加到就绪链表,并唤醒对应的资源,//以此来达到下次再次epoll_wait时会将上次未处理事件再次返回list_add_tail(&epi->rdllink, &ep->rdllist);ep_pm_stay_awake(epi);}}}//返回的是就绪事件数量return eventcnt;}
文章转载自囧囧妹,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




