- 手机:16182416600
- 电话:095-36671770
- Q Q:950431554
- 邮箱:admin@bttuoxin.com
- 地址:江苏省南通市彝良县滔筑大楼9390号
揭开高性能服务器底层面纱
来源:hth华体会网页版 发布时间:2023-05-01 13:26nbsp; 点击量:
揭开高性能服务器底层面纱一、前言我们经常听说高性能服务器,那什么是高性能服务器;用明白话来解释就是说处置惩罚事件快,效率高,占用服务器资源少,多路复用等等集万千痛爱于一身;可是,往往要想做到高性能,这是很是难的,需要一个好的优秀的架构和底层接口。这篇文章只限于linux平台,对于windows平台下,可以去参考下IOCP的用法,这里就不多说了~现在主流的高性能服务器底层都是封装了EPOLL接口,使用epoll举行事件处置惩罚,为什么epoll可以作为高性能服务器底层事件处置惩罚?那就让我们从源码下手,来揭开面纱~获取epoll源码请在微信民众号-后台服务器开发,回复“epoll源码”获取二、源码解读两个至关重要的结构体eventpoll结构体:/**此结构体存储在file->private_data中*//*eventpoll结构体是epoll的焦点内里存放着许多信息,主要包罗1.structrb_rootrbr;这是一颗红黑树的根节点,代表着一颗红黑树,红黑树下面挂的是我们感兴趣的socket的事件,当我们挪用epoll_ctl向epoll添加感兴趣的socket事件时,系统将我们的通报的信息封装成structepitem结构体,然后挂到这颗红黑树的相应节点上2.structlist_headrdllist;这是一个双向链表,这个双向链表中存放的是停当的事件当我们挪用epoll_wait的时候这些事件会返回给用户3.structfile*file;文件结构指针,指向epoll文件*/structeventpoll{//自旋锁,在kernel内部用自旋锁加锁,就可以同时多线(进)程对此结构体举行操作//主要是掩护ready_listspinlock_tlock;//这个互斥锁是为了保证在eventloop使用对应的文件形貌符的时候,文件形貌符不会被移除掉structmutexmtx;//epoll_wait使用的等候行列,和历程叫醒有关wait_queue_head_twq;//file->poll使用的等候行列,和历程叫醒有关wait_queue_head_tpoll_wait;//停当的形貌符行列,双向链表structlist_headrdllist;//通过红黑树来组织当前epoll关注的文件形貌符structrb_rootrbr;//在向用户空间传输停当事件的时候,将同时发生事件的文件形貌符链入到这个链内外面structepitem*ovflist;//对应的userstructuser_struct*user;//对应的文件形貌符structfile*file;//下面两个是用于环路检测的优化intvisited;structlist_headvisited_list_link;};epitem结构体//对应于一个加入到epoll的文件structepitem{//挂载到eventpoll的红黑树节点structrb_noderbn;//挂载到eventpoll.rdllist的节点structlist_headrdllink;//毗连到ovflist的指针structepitem*next;/*文件形貌符信息fd+file,红黑树的key*/structepoll_filefdffd;/*Numberofactivewaitqueueattachedtopolloperations*/intnwait;//当前文件的等候行列(eppoll_entry)列表//同一个文件上可能会监视多种事件,//这些事件可能属于差别的wait_queue中//(取决于对应文件类型的实现),//所以需要使用链表structlist_headpwqlist;//当前epitem的所有者structeventpoll*ep;/*Listheaderusedtolinkthisitemtothe"structfile"itemslist*/structlist_headfllink;/*epoll_ctl传入的用户数据*/structepoll_eventevent;};int epoll_create(int size);作用:挪用epoll_create方法建立一个epoll的句柄源码:SYSCALL_DEFINE1(epoll_create,int,size){if(size<=0)return-EINVAL;returndo_epoll_create(0);}从源码来看,其实size这个参数并没有什么作用,只要大于0就可以了~我从其他地方获取资料说的是:以前底层实现是哈希表,现在是红黑树,为了兼容所以才保留了这个参数,也不知道真假,权当相识一下~接着看下do_epoll_createstaticintdo_epoll_create(intflags){interror,fd;structeventpoll*ep=NULL;structfile*file;/*ChecktheEPOLL_*constantforconsistency.*/BUILD_BUG_ON(EPOLL_CLOEXEC!=O_CLOEXEC);if(flags&~EPOLL_CLOEXEC)return-EINVAL;/**Createtheinternaldatastructure("structeventpoll").*/error=ep_alloc(&ep);if(error<0)returnerror;/**Createsalltheitemsneededtosetupaneventpollfile.Thatis,*afilestructureandafreefiledescriptor.*///获取尚未被使用的文件形貌符,即形貌符数组的槽位fd=get_unused_fd_flags(O_RDWR|(flags&O_CLOEXEC));if(fd<0){error=fd;gotoout_free_ep;}//建立一个名叫[eventpoll]的文件,并返回其文件结构指针,这个文件代表着epoll实例file=anon_inode_getfile("[eventpoll]",&eventpoll_fops,ep,O_RDWR|(flags&O_CLOEXEC));if(IS_ERR(file)){error=PTR_ERR(file);gotoout_free_fd;}ep->file=file;//将file填入到对应的文件形貌符数组的槽内里fd_install(fd,file);returnfd;out_free_fd:put_unused_fd(fd);out_free_ep:ep_free(ep);returnerror;}这里error = ep_alloc(&ep);是分配eventpoll结构并举行的初始化操作;综上所述,epoll建立文件的历程,做了初始化和文件关联等;int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);作用:epoll的事件注册函数源码:SYSCALL_DEFINE4(epoll_ctl,int,epfd,int,op,int,fd,structepoll_event__user*,event){structepoll_eventepds;//错误处置惩罚:如果是删除,且epoll_event结构不为NULL则报错//如果是更改或者添加那就需要把从用户空间将epoll_event结构copy到内核空间if(ep_op_has_event(op)&&//复制用户空间数据到内核copy_from_user(&epds,event,sizeof(structepoll_event)))return-EFAULT;returndo_epoll_ctl(epfd,op,fd,&epds,false);}我们看下函数do_epoll_ctl:intdo_epoll_ctl(intepfd,intop,intfd,structepoll_event*epds,boolnonblock){interror;intfull_check=0;structfdf,tf;structeventpoll*ep;structepitem*epi;structeventpoll*tep=NULL;//省略校验历程.....epi=ep_find(ep,tf.file,fd);error=-EINVAL;switch(op){//增加caseEPOLL_CTL_ADD:if(!epi){epds->events|=EPOLLERR|EPOLLHUP;error=ep_insert(ep,epds,tf.file,fd,full_check);}elseerror=-EEXIST;break;//删除caseEPOLL_CTL_DEL:if(epi)error=ep_remove(ep,epi);elseerror=-ENOENT;break;//修改caseEPOLL_CTL_MOD:if(epi){if(!(epi->event.events&EPOLLEXCLUSIVE)){epds->events|=EPOLLERR|EPOLLHUP;error=ep_modify(ep,epi,epds);}}elseerror=-ENOENT;break;}if(tep!=NULL)mutex_unlock(&tep->mtx);mutex_unlock(&ep->mtx);error_tgt_fput:if(full_check){clear_tfile_check_list();mutex_unlock(&epmutex);}fdput(tf);error_fput:fdput(f);error_return:returnerror;}在do_epoll_ctl函数中,做的更多的是是对文件形貌符的校验,然后凭据传入的fd添加进去而且监视,这里就看一下增加的操作吧~//往epollfd内里添加一个监听fdstaticintep_insert(structeventpoll*ep,conststructepoll_event*event,structfile*tfile,intfd,intfull_check){interror,pwake=0;__poll_trevents;longuser_watches;structepitem*epi;structep_pqueueepq;lockdep_assert_irqs_enabled();user_watches=atomic_long_read(&ep->user->epoll_watches);if(unlikely(user_watches>=max_user_watches))return-ENOSPC;//分配和初始化epi结构体if(!(epi=kmem_cache_alloc(epi_cache,GFP_KERNEL)))return-ENOMEM;/*Iteminitializationfollowhere...*/INIT_LIST_HEAD(&epi->rdllink);INIT_LIST_HEAD(&epi->fllink);INIT_LIST_HEAD(&epi->pwqlist);//将epoll工具挂载到该fd的epitem结构的ep成员中epi->ep=ep;//设置被监控的文件形貌符及其对应的文件工具到epitem的ffd成员中ep_set_ffd(&epi->ffd,tfile,fd);//生存fd感兴趣的事件工具epi->event=*event;epi->nwait=0;epi->next=EP_UNACTIVE_PTR;if(epi->event.events&EPOLLWAKEUP){error=ep_create_wakeup_source(epi);if(error)gotoerror_create_wakeup_source;}else{RCU_INIT_POINTER(epi->ws,NULL);}/*Initializethepolltableusingthequeuecallback*/epq.epi=epi;//将ep_ptable_queue_proc注册到epq.pt中。init_poll_funcptr(&epq.pt,ep_ptable_queue_proc);/**Attachtheitemtothepollhooksandgetcurrenteventbits.*Wecansafelyusethefile*herebecauseitsusagecounthas*beenincreasedbythecallerofthisfunction.Notethatafter*thisoperationcompletes,thepollcallbackcanstarthitting*thenewitem.*///内部会挪用ep_ptable_queue_proc,在文件对应的waitqueuehead上//注册回调函数,并返回当前文件的状态revents=ep_item_poll(epi,&epq.pt,1);/**Wehavetocheckifsomethingwentwrongduringthepollwaitqueue*installprocess.Namelyanallocationforawaitqueuefaileddue*highmemorypressure.*/error=-ENOMEM;if(epi->nwait<0)gotoerror_unregister;/*Addthecurrentitemtothelistofactiveepollhookforthisfile*///把epitem插入到f_ep_links链表的尾部spin_lock(&tfile->f_lock);list_add_tail_rcu(&epi->fllink,&tfile->f_ep_links);spin_unlock(&tfile->f_lock);/**AddthecurrentitemtotheRBtree.AllRBtreeoperationsare*protectedby"mtx",andep_insert()iscalledwith"mtx"held.*///将该epitem插入到ep的红黑树中ep_rbtree_insert(ep,epi);/*nowcheckifwe'vecreatedtoomanybackpaths*/error=-EINVAL;if(full_check&&reverse_path_check())gotoerror_remove_epi;/*Wehavetodropthenewiteminsideouritemlisttokeeptrackofit*/write_lock_irq(&ep->lock);/*recordNAPIIDofnewitemifpresent*/ep_set_busy_poll_napi_id(epi);/*Ifthefileisalready"ready"wedropitinsidethereadylist*///如果要监视的文件状态已经停当而且还没有加入到停当行列中,则将当前的epitem加入到停当if(revents&&!ep_is_linked(epi)){list_add_tail(&epi->rdllink,&ep->rdllist);ep_pm_stay_awake(epi);/*Notifywaitingtasksthateventsareavailable*/if(waitqueue_active(&ep->wq))//通知sys_epoll_wait,挪用回调函数叫醒sys_epoll_wait历程wake_up(&ep->wq);if(waitqueue_active(&ep->poll_wait))pwake++;}write_unlock_irq(&ep->lock);atomic_long_inc(&ep->user->epoll_watches);/*Wehavetocallthisoutsidethelock*/if(pwake)ep_poll_safewake(ep,NULL);return0;error_remove_epi:spin_lock(&tfile->f_lock);list_del_rcu(&epi->fllink);spin_unlock(&tfile->f_lock);rb_erase_cached(&epi->rbn,&ep->rbr);error_unregister:ep_unregister_pollwait(ep,epi);/**Weneedtodothisbecauseaneventcouldhavebeenarrivedonsome*allocatedwaitqueue.Notethatwedon'tcareabouttheep->ovflist*list,sincethatisused/cleanedonlyinsideasectionboundby"mtx".*Andep_insert()iscalledwith"mtx"held.*/write_lock_irq(&ep->lock);if(ep_is_linked(epi))list_del_init(&epi->rdllink);write_unlock_irq(&ep->lock);wakeup_source_unregister(ep_wakeup_source(epi));error_create_wakeup_source:kmem_cache_free(epi_cache,epi);returnerror;}这里做的更多的是对事件的一个绑定和挂载操作,如果这个socket有事件停当,则会挪用ep_poll_callback函数,这个函数卖力将事件加入停当行列并叫醒epoll_wait;int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);作用:等候在epoll监控的事件中已经发生的事件。源码;SYSCALL_DEFINE4(epoll_wait,int,epfd,structepoll_event__user*,events,int,maxevents,int,timeout){returndo_epoll_wait(epfd,events,maxevents,timeout);}直接去看下do_epoll_wait函数吧~staticintdo_epoll_wait(intepfd,structepoll_event__user*events,intmaxevents,inttimeout){interror;structfdf;structeventpoll*ep;/*Themaximumnumberofeventmustbegreaterthanzero*/if(maxevents<=0||maxevents>EP_MAX_EVENTS)return-EINVAL;/*Verifythattheareapassedbytheuseriswriteable*/if(!access_ok(events,maxevents*sizeof(structepoll_event)))return-EFAULT;/*Getthe"structfile*"fortheeventpollfile*///获取epoll的structfile//再通过对应的structfile获得eventpollf=fdget(epfd);if(!f.file)return-EBADF;/**Wehavetocheckthatthefilestructureunderneaththefd*theuserpassedtous_is_aneventpollfile.*/error=-EINVAL;if(!is_file_epoll(f.file))gotoerror_fput;/**Atthispointitissafetoassumethatthe"private_data"contains*ourowndatastructure.*///凭据private_data获得eventpoll结构ep=f.file->private_data;/*Timetofishforevents...*///等候事件的到来error=ep_poll(ep,events,maxevents,timeout);error_fput:fdput(f);returnerror;}看来焦点在ep_poll函数呀~去看看吧staticintep_poll(structeventpoll*ep,structepoll_event__user*events,intmaxevents,longtimeout){intres=0,eavail,timed_out=0;u64slack=0;wait_queue_entry_twait;ktime_texpires,*to=NULL;lockdep_assert_irqs_enabled();//如果停当链表为空则阻塞直到timeoutif(timeout>0){structtimespec64end_time=ep_set_mstimeout(timeout);slack=select_estimate_accuracy(&end_time);to=&expires;*to=timespec64_to_ktime(end_time);//非阻塞}elseif(timeout==0){/**Avoidtheunnecessarytriptothewaitqueueloop,ifthe*callerspecifiedanonblockingoperation.Westillneed*lockbecausewecouldraceandnotseeanepibeingadded*tothereadylistwhileinirqcallback.Thusincorrectly*returning0backtouserspace.*/timed_out=1;write_lock_irq(&ep->lock);eavail=ep_events_available(ep);write_unlock_irq(&ep->lock);gotosend_events;}fetch_events://是否有停当事件,或正在扫描处置惩罚eventpoll中的rdllist链表if(!ep_events_available(ep))ep_busy_loop(ep,timed_out);eavail=ep_events_available(ep);if(eavail)gotosend_events;/**Busypolltimedout.DropNAPIIDfornow,wecanadd*itbackinwhenwehavemovedasocketwithavalidNAPI*IDontothereadylist.*/ep_reset_busy_poll_napi_id(ep);do{/**Internallyinit_wait()usesautoremove_wake_function(),*thuswaitentryisremovedfromthewaitqueueoneach*wakeup.Whyitisimportant?Incaseofseveralwaiters*eachnewwakeupwillhitthenextwaiter,givingitthe*chancetoharvestnewevent.Otherwisewakeupcanbe*lost.Thisisalsogoodperformance-wise,becauseon*normalwakeuppathnoneedtocall__remove_wait_queue()*explicitly,thusep->lockisnottaken,whichhaltsthe*eventdelivery.*/init_wait(&wait);write_lock_irq(&ep->lock);/**Barrierlessvariant,waitqueue_active()iscalledunder*thesamelockonwakeupep_poll_callback()side,soit*issafetoavoidanexplicitbarrier.*///执行ep_poll_callback()叫醒时应当需要将当前历程叫醒,//这就是我们将任务状态设置为TASK_INTERRUPTIBLE的原因。
__set_current_state(TASK_INTERRUPTIBLE);/**Dothefinalcheckunderthelock.ep_scan_ready_list()*playswithtwolists(->rdllistand->ovflist)andthere*isalwaysaracewhenbothlistsareemptyforshort*periodoftimealthougheventsarepending,solockis*important.*/eavail=ep_events_available(ep);if(!eavail){if(signal_pending(current))res=-EINTR;else__add_wait_queue_exclusive(&ep->wq,&wait);}write_unlock_irq(&ep->lock);if(eavail||res)break;if(!schedule_hrtimeout_range(to,slack,HRTIMER_MODE_ABS)){timed_out=1;break;}/*Wewerewokenup,thusgoandtrytoharvestsomeevents*/eavail=1;}while(0);//醒来__set_current_state(TASK_RUNNING);if(!list_empty_careful(&wait.entry)){write_lock_irq(&ep->lock);__remove_wait_queue(&ep->wq,&wait);write_unlock_irq(&ep->lock);}send_events:if(fatal_signal_pending(current)){/**Alwaysshort-circuitforfatalsignalstoallow*threadstomakeatimelyexitwithoutthechanceof*findingmoreeventsavailableandfetching*repeatedly.*/res=-EINTR;}/**Trytotransfereventstouserspace.Incaseweget0eventsand*there'sstilltimeoutleftover,wegotryingagaininsearchof*moreluck.*//*如果一切正常,有event发生,就开始准备数据copy给用户空间了...*/if(!res&&eavail&&!(res=ep_send_events(ep,events,maxevents))&&!timed_out)gotofetch_events;returnres;}ep_send_events()函数将用户传入的内存简朴封装到ep_send_events_data结构中,然后挪用ep_scan_ready_list()迁就绪行列中的事件传入用户空间的内存。用户空间会见这个效果,举行处置惩罚。staticintep_send_events(structeventpoll*ep,structepoll_event__user*events,intmaxevents){structep_send_events_dataesed;esed.maxevents=maxevents;esed.events=events;ep_scan_ready_list(ep,ep_send_events_proc,&esed,0,false);returnesed.res;}static__poll_tep_send_events_proc(structeventpoll*ep,structlist_head*head,void*priv){structep_send_events_data*esed=priv;__poll_trevents;structepitem*epi,*tmp;structepoll_event__user*uevent=esed->events;structwakeup_source*ws;poll_tablept;init_poll_funcptr(&pt,NULL);esed->res=0;/**Wecanloopwithoutlockbecausewearepassedataskprivatelist.*Itemscannotvanishduringtheloopbecauseep_scan_ready_list()is*holding"mtx"duringthiscall.*/lockdep_assert_held(&ep->mtx);list_for_each_entry_safe(epi,tmp,head,rdllink){if(esed->res>=esed->maxevents)break;/**Activateep->wsbeforedeactivatingepi->wstoprevent*triggeringauto-suspendhere(incasewereactiveepi->ws*below).**Thiscouldberearrangedtodelaythedeactivationofepi->ws*instead,butthenepi->wswouldtemporarilybeoutofsync*withep_is_linked().*/ws=ep_wakeup_source(epi);if(ws){if(ws->active)__pm_stay_awake(ep->ws);__pm_relax(ws);}list_del_init(&epi->rdllink);/**Iftheeventmaskintersectthecaller-requestedone,*delivertheeventtouserspace.Again,ep_scan_ready_list()*isholdingep->mtx,sonooperationscomingfromuserspace*canchangetheitem.*/revents=ep_item_poll(epi,&pt,1);if(!revents)continue;//把当前事件和用户传入的数据copy到用户空间if(__put_user(revents,&uevent->events)||__put_user(epi->event.data,&uevent->data)){//复制失败把epi重新插入到ready链表list_add(&epi->rdllink,head);ep_pm_stay_awake(epi);if(!esed->res)esed->res=-EFAULT;return0;}esed->res++;uevent++;if(epi->event.events&EPOLLONESHOT)epi->event.events&=EP_PRIVATE_BITS;elseif(!(epi->event.events&EPOLLET)){/**IfthisfilehasbeenaddedwithLevel*Triggermode,weneedtoinsertbackinside*thereadylist,sothatthenextcallto*epoll_wait()willcheckagaintheevents*availability.Atthispoint,noonecaninsert*intoep->rdllistbesidesus.Theepoll_ctl()*callersarelockedoutby*ep_scan_ready_list()holding"mtx"andthe*pollcallbackwillqueuetheminep->ovflist.*/list_add_tail(&epi->rdllink,&ep->rdllist);ep_pm_stay_awake(epi);}}return0;}看到__put_user就知道,从内核拷贝数据到用户空间使用了__put_user函数,和所谓的共享内存没有一点关系,现在博客上面有许多错误,望大家修正~三、总结请大家原谅我水平和时间有限,这次阅读epoll的源码起源于在网上看到内核与用户态数据拷贝使用的方法存在争议,所以找来epoll的源码举行了大略的阅读,后续还会抽时间精读一下,不外这次虽然是大略的阅读了epoll的源码,可是收获也许多,接下来就简朴做下总结~(这个总结有我自己看源码得来的,也有从网络上搜集的资料,如果错误,请大家不惜见教)epoll_createepoll_create传入参数的时候,只要保证参数大于0就可以,这个参数时无用的初始化等候行列和初始化停当链表,另有初始化红黑树的头结点分配eventpoll结构并举行的初始化操作;epoll_ctl将epoll_event结构拷贝到内核空间中,而且判断加入的fd是否支持poll结(epoll,poll,selectI/O多路复用必须支持poll操作).ep = f.file->private_data;获取event_poll工具;通过op判断事件的修改、添加、删除操作首先在eventpoll结构中的红黑树查找是否已经存在了相对应的fd,没找到就支持插入操作,否则报重复的错误,另有修改,删除操作。
插入操作时,会建立一个与fd对应的epitem结构,而且初始化相关成员,并指定挪用poll_wait时的回调函数用于数据停当时叫醒历程,(其内部,初始化设备的等候行列,将该历程注册到等候行列)完成这一步,epitem就跟这个socket关联起来了, 当它有状态变化时,会通过ep_poll_callback()来通知.最后挪用加入的fd的fileoperation->poll函数(最后会挪用poll_wait操作)用于完注册操作,将epitem结构添加到红黑树中。epoll_wait判断eventpoll工具的链表是否为空,是否需要操作;初始化一个等候行列,把自己挂上去,设置自己的历程状态若是可睡眠状态.判断是否有信号到来(有的话直接被中断醒来,),如果没有那就挪用schedule_timeout举行睡眠,如果超时或者被叫醒,首先从自己初始化的等候行列删除,然后开始拷贝资源给用户空间了拷贝资源则是先把停当事件链表转移到中间链表,然后挨个遍历拷贝到用户空间,而且挨个判断其是否为水平触发,是的话再次插入到停当链表用户态和内核态拷贝数据方式用户态拷贝数据到内核态,是挪用了函数:copy_from_user内核态数据拷贝到用户态,挪用了函数:__put_user这里注意,很多多少博客上面的说拷贝数据使用的是共享内存,是错误的,千万别信哈~~~~ET和LT模式差别的原理elseif(!(epi->event.events&EPOLLET)){/**IfthisfilehasbeenaddedwithLevel*Triggermode,weneedtoinsertbackinside*thereadylist,sothatthenextcallto*epoll_wait()willcheckagaintheevents*availability.Atthispoint,noonecaninsert*intoep->rdllistbesidesus.Theepoll_ctl()*callersarelockedoutby*ep_scan_ready_list()holding"mtx"andthe*pollcallbackwillqueuetheminep->ovflist.*/list_add_tail(&epi->rdllink,&ep->rdllist);ep_pm_stay_awake(epi);}这里会判断事件类型是否包罗了EPOLLET位,如果不包罗的话就会将该事件对应的epitem工具重新加入到epoll的rdllist链表中,用户态法式下次挪用epoll_wait()返回时就又能获取该epitem了;等到下一次epoll_wait时, 会立刻返回, 并通知给用户空间;epoll 为什么高效(相比select)泉源:https://www.cnblogs.com/apprentice89/p/3234677.html仅从上面的挪用方式就可以看出epoll比select/poll的一个优势:select/poll每次挪用都要通报所要监控的所有fd给select/poll系统挪用(这意味着每次挪用都要将fd列表从用户态拷贝到内核态,当fd数目许多时,这会造成低效)。而每次挪用epoll_wait时(作用相当于挪用select/poll),不需要再通报fd列表给内核,因为已经在epoll_ctl中将需要监控的fd告诉了内核(epoll_ctl不需要每次都拷贝所有的fd,只需要举行增量式操作)。
所以,在挪用epoll_create之后,内核已经在内核态开始准备数据结构存放要监控的fd了。每次epoll_ctl只是对这个数据结构举行简朴的维护。
此外,内核使用了slab机制,为epoll提供了快速的数据结构: 在内核里,一切皆文件。所以,epoll向内核注册了一个文件系统,用于存储上述的被监控的fd。当你挪用epoll_create时,就会在这个虚拟的epoll文件系统里建立一个file结点。固然这个file不是普通文件,它只服务于epoll。
epoll在被内核初始化时(操作系统启动),同时会开发出epoll自己的内核高速cache区,用于安置每一个我们想监控的fd,这些fd会以红黑树的形式生存在内核cache里,以支持快速的查找、插入、删除。这个内核高速cache区,就是建设一连的物理内存页,然后在之上建设slab层,简朴的说,就是物理上分配好你想要的size的内存工具,每次使用时都是使用空闲的已分配好的工具。epoll的第三个优势在于:当我们挪用epoll_ctl往里塞入百万个fd时,epoll_wait仍然可以飞快的返回,并有效的将发生事件的fd给我们用户。
这是由于我们在挪用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储以后epoll_ctl传来的fd外,还会再建设一个list链表,用于存储准备停当的事件,当epoll_wait挪用时,仅仅视察这个list链内外有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后纵然链表没数据也返回。所以,epoll_wait很是高效。而且,通常情况下纵然我们要监控百万计的fd,大多一次也只返回很少量的准备停当fd而已,所以,epoll_wait仅需要从内核态copy少量的fd到用户态而已。
那么,这个准备停当list链表是怎么维护的呢?当我们执行epoll_ctl时,除了把fd放到epoll文件系统里file工具对应的红黑树上之外,还会给内核中断处置惩罚法式注册一个回调函数,告诉内核,如果这个fd的中断到了,就把它放到准备停当list链内外。所以,当一个fd(例如socket)上有数据到了,内核在把设备(例如网卡)上的数据copy到内核中后就来把fd(socket)插入到准备停当list链内外了。四、往期精彩汇总GDB 多线程之旅肝!动态计划C++使用锁注意事项呕心沥血的递归muduo源码剖析学习总结。
本文关键词:揭开,高性能,服务器,底层,面纱,揭开,高性能,hth华体会最新网站
本文来源:hth华体会最新网站-www.bttuoxin.com
推荐新闻 MORE+
- 揭开高性能服务器底层面纱 2023-05-01
- 图解微服务注册中心设计原理与思考并基 2023-05-01
- 企业共识三十五条 2023-05-01
- 前端教程丨手把手教你用 Next.js 搭建小我 2023-05-01
- hth华体会网页版-养老金入市 成交量是否 2023-05-01
- 3部大片同步北美上映 这个11月可以大饱 2023-04-29
- 达科塔约翰逊新恐怖片《阴风阵阵》发海 2023-04-29
- 影帝梁朝伟主演最新电影《猎狐行动》女 2023-04-29
- 漳州餐饮店招聘服务员 2023-04-29
- 庄浪县农村信用社招聘5人通告 2023-04-29