幼儿饰品瑜伽美体用品微软
投稿投诉
微软创意
爱情通信
用品婚姻
爱好看病
美体软件
影音星座
瑜伽周边
星座办公
饰品塑形
搞笑减肥
幼儿两性
智家潮品

从linux源码看epoll

  从linux源码看epoll前言
  在linux的高性能网络编程中,绕不开的就是epoll。和select、poll等系统调用相比,epoll在需要监视大量文件描述符并且其中只有少数活跃的时候,表现出无可比拟的优势。epoll能让内核记住所关注的描述符,并在对应的描述符事件就绪的时候,在epoll的就绪链表中添加这些就绪元素,并唤醒对应的epoll等待进程。本文就是笔者在探究epoll源码过程中,对kernel将就绪描述符添加到epoll并唤醒对应进程的一次源码分析(基于linux2。6。32内核版本)。由于篇幅所限,笔者聚焦于tcp协议下socket可读事件的源码分析。
  简单的epoll例子
  下面的例子,是从笔者本人用c语言写的dbproxy中的一段代码。由于细节过多,所以做了一些删减。intinitreactor(intlistenfd,intworkercount){
  。。。。。。
  创建多个epollfd,以充分利用多核
  for(i0;iworkercount;i){
  reactorworkerfdepollcreate(EPOLLMAXEVENTS);
  }
  epolladdlistenfdandaccept
  将accept后的事件加入到对应的epollfd中
  intclientfdaccept(listenfd,(structsockaddr)clientaddr,clientlen)));
  将连接描述符注册到对应的worker里面
  epollctl(reactorclientfd,EPOLLCTLADD,epifd,event);
  }
  reactor的worker线程
  staticvoidrwthreadfunc(voidarg){
  。。。。。。
  for(;;){
  epollwait等待事件触发
  intretvalepollwait(epfd,events,EPOLLMAXEVENTS,500);
  if(retval0){
  for(j0;jretval;j){
  处理读事件
  if(eventEPOLLIN){
  handlereadyreadconnection(conn);
  continue;
  }
  处理其它事件
  }
  }
  }
  。。。。。。
  }
  上述代码事实上就是实现了一个reactor模式中的accept与readwrite处理线程,如下图所示:
  epollcreate
  Unix的万物皆文件的思想在epoll里面也有体现,epollcreate调用返回一个文件描述符,此描述符挂载在anoninodefs(匿名inode文件系统)的根目录下面。让我们看下具体的epollcreate系统调用源码:SYSCALLDEFINE1(epollcreate,int,size)
  {
  if(size0)
  returnEINVAL;
  returnsysepollcreate1(0);
  }
  由上述源码可见,epollcreate的参数是基本没有意义的,kernel简单的判断是否为0,然后就直接就调用了sysepollcreate1。由于linux的系统调用是通过(SYSCALLDEFINE1,SYSCALLDEFINE2SYSCALLDEFINE6)定义的,那么sysepollcreate1对应的源码即是SYSCALLDEFINE(epollcreate1)。
  (注:受限于寄存器数量的限制,(80x86下的)kernel限制系统调用最多有6个参数。据ulk3所述,这是由于32位80x86寄存器的限制)
  接下来,我们就看下epollcreate1的源码:SYSCALLDEFINE1(epollcreate1,int,flags)
  {
  kzalloc(sizeof(ep),GFPKERNEL),用的是内核空间
  errorepalloc(ep);
  获取尚未被使用的文件描述符,即描述符数组的槽位
  fdgetunusedfdflags(ORDWR(flagsOCLOEXEC));
  在匿名inode文件系统中分配一个inode,并得到其file结构体
  且filefopeventpollfops
  且fileprivatedataep;
  fileanoninodegetfile(〔eventpoll〕,eventpollfops,ep,
  ORDWR(flagsOCLOEXEC));
  将file填入到对应的文件描述符数组的槽里面
  fdinstall(fd,file);
  epfilefile;
  returnfd;
  }
  最后epollcreate生成的文件描述符如下图所示:
  structeventpoll
  所有的epoll系统调用都是围绕eventpoll结构体做操作,现简要描述下其中的成员:
  此结构体存储在fileprivatedata中
  structeventpoll{
  自旋锁,在kernel内部用自旋锁加锁,就可以同时多线(进)程对此结构体进行操作
  主要是保护readylist
  spinlocktlock;
  这个互斥锁是为了保证在eventloop使用对应的文件描述符的时候,文件描述符不会被移除掉
  structmutexmtx;
  epollwait使用的等待队列,和进程唤醒有关
  waitqueueheadtwq;
  filepoll使用的等待队列,和进程唤醒有关
  waitqueueheadtpollwait;
  就绪的描述符队列
  structlistheadrdllist;
  通过红黑树来组织当前epoll关注的文件描述符
  structrbrootrbr;
  在向用户空间传输就绪事件的时候,将同时发生事件的文件描述符链入到这个链表里面
  structepitemovflist;
  对应的user
  structuserstructuser;
  对应的文件描述符
  structfilefile;
  下面两个是用于环路检测的优化
  intvisited;
  structlistheadvisitedlistlink;
  };
  本文讲述的是kernel是如何将就绪事件传递给epoll并唤醒对应进程上,因此在这里主要聚焦于(waitqueueheadtwq)等成员。
  epollctl(add)
  我们看下epollctl(EPOLLCTLADD)是如何将对应的文件描述符插入到eventpoll中的。借助于spinlock(自旋锁)和mutex(互斥锁),epollctl调用可以在多个KSE(内核调度实体,即进程线程)中并发执行。SYSCALLDEFINE4(epollctl,int,epfd,int,op,int,fd,
  structepolleventuser,event)
  {
  校验epfd是否是epoll的描述符
  此处的互斥锁是为了防止并发调用epollctl,即保护内部数据结构
  不会被并发的添加修改删除破坏
  mutexlocknested(epmtx,0);
  switch(op){
  caseEPOLLCTLADD:
  。。。
  插入到红黑树中
  errorepinsert(ep,epds,tfile,fd);
  。。。
  break;
  。。。。。。
  }
  mutexunlock(epmtx);
  }
  上述过程如下图所示:
  epinsert
  在epinsert中初始化了epitem,然后初始化了本文关注的焦点,即事件就绪时候的回调函数,代码如下所示:staticintepinsert(structeventpollep,structepolleventevent,
  structfiletfile,intfd)
  {
  初始化epitem
  epq。ptqprocepptablequeueproc
  initpollfuncptr(epq。pt,epptablequeueproc);
  在这里将回调函数注入
  reventstfilefoppoll(tfile,epq。pt);
  如果当前有事件已经就绪,那么一开始就会被加入到readylist
  例如可写事件
  另外,在tcp内部ack之后调用tcpcheckspace,最终调用sockdefwritespace来唤醒对应的epollwait下的进程
  if((reventseventevents)!epislinked(epirdllink)){
  listaddtail(epirdllink,eprdllist);
  wakeupep对应在epollwait下的进程
  if(waitqueueactive(epwq)){
  wakeuplocked(epwq);
  }
  。。。。。。
  }
  将epitem插入红黑树
  eprbtreeinsert(ep,epi);
  。。。。。。
  }
  tfilefoppoll的实现
  向kernel更底层注册回调函数的是tfilefoppoll(tfile,epq。pt)这一句,我们来看一下对于对应的socket文件描述符,其fdfilefoppoll的初始化过程:将accept后的事件加入到对应的epollfd中
  intclientfdaccept(listenfd,(structsockaddr)clientaddr,clientlen)));
  将连接描述符注册到对应的worker里面
  epollctl(reactorclientfd,EPOLLCTLADD,epifd,event);
  回顾一下上述userspace代码,fd即clientfd是由tcp的listenfd通过accept调用而来,那么我们看下accept调用链的关键路径:accept
  accept4
  sockattachfd(newsock,newfile,flagsONONBLOCK);
  initfile(file,。。。,socketfileops);
  filefopfop;
  filefopsocketfileops
  fdinstall(newfd,newfile);安装fd
  那么,由accept获得的clientfd的结构如下图所示:
  (注:由于是tcpsocket,所以这边sockopsinetstreamops,这个初始化的过程在我的另一篇博客从linux源码看socket的阻塞和非阻塞中,博客地址如下:
  https:my。oschina。netalchemystarblog1791017)
  既然知道了tfilefoppoll的实现,我们就可以看下此poll是如何将安装回调函数的。
  回调函数的安装
  kernel的调用路径如下:sockpolltfilefoppoll(tfile,epq。pt);
  sockopspoll
  tcppoll
  这边重要的是拿到了sksleep用于KSE(进程线程)的唤醒
  sockpollwait(file,sksksleep,wait);
  pollwait
  pqproc(filp,waitaddress,p);
  p为epq。pt,而且epq。ptqprocepptablequeueproc
  epptablequeueproc(filp,waitaddress,p);
  绕了一大圈之后,我们的回调函数的安装其实就是调用了eventpoll。c中的epptablequeueproc,而且向其中传递了sksksleep作为其waitqueue的head,其源码如下所示:staticvoidepptablequeueproc(structfilefile,waitqueueheadtwhead,
  polltablept)
  {
  取出当前clientfd对应的epitem
  structepitemepiepitemfromepqueue(pt);
  pwqwaitfunceppollcallback,用于回调唤醒
  注意,这边不是initwaitqueueentry,即没有将当前KSE(current,当前进程线程)写入到
  waitqueue当中,因为不一定是从当前安装的KSE唤醒,而应该是唤醒epollwait的KSE
  initwaitqueuefuncentry(pwqwait,eppollcallback);
  这边的whead是sksksleep,将当前的waitqueue链入到socket对应的sleep列表
  addwaitqueue(whead,pwqwait);
  }
  这样clientfd的结构进一步完善,如下图所示:
  eppollcallback函数是唤醒对应epollwait的地方,我们将在后面一起讲述。
  epollwait
  epollwait主要是调用了eppoll:SYSCALLDEFINE4(epollwait,int,epfd,structepolleventuser,events,
  int,maxevents,int,timeout)
  {
  检查epfd是否是epollcreate创建的fd
  调用eppoll
  erroreppoll(ep,events,maxevents,timeout);
  。。。
  }
  紧接着,我们看下eppoll函数:staticinteppoll(structeventpollep,structepolleventuserevents,
  intmaxevents,longtimeout)
  {
  。。。。。。
  retry:
  获取spinlock
  spinlockirqsave(eplock,flags);
  将当前taskstruct写入到waitqueue中以便唤醒
  wqentryfuncdefaultwakefunction;
  initwaitqueueentry(wait,current);
  WQFLAGEXCLUSIVE,排他性唤醒,配合SOREUSEPORT从而解决accept惊群问题
  wait。flagsWQFLAGEXCLUSIVE;
  链入到ep的waitqueue中
  addwaitqueue(epwq,wait);
  for(;;){
  设置当前进程状态为可打断
  setcurrentstate(TASKINTERRUPTIBLE);
  检查当前线程是否有信号要处理,有则返回EINTR
  if(signalpending(current)){
  resEINTR;
  break;
  }
  spinunlockirqrestore(eplock,flags);
  schedule调度,让出CPU
  jtimeoutscheduletimeout(jtimeout);
  spinlockirqsave(eplock,flags);
  }
  到这里,表明超时或者有事件触发等动作导致进程重新调度
  removewaitqueue(epwq,wait);
  设置进程状态为running
  setcurrentstate(TASKRUNNING);
  。。。。。。
  检查是否有可用事件
  eavail!listempty(eprdllist)epovflist!EPUNACTIVEPTR;
  。。。。。。
  向用户空间拷贝就绪事件
  epsendevents(ep,events,maxevents)
  }
  上述逻辑如下图所示:
  epsendevents
  epsendevents函数主要就是调用了epscanreadylist,顾名思义epscanreadylist就是扫描就绪列表:staticintepscanreadylist(structeventpollep,
  int(sproc)(structeventpoll,
  structlisthead,void),
  voidpriv,
  intdepth)
  {
  。。。
  将epfd的rdllist链入到txlist
  listspliceinit(eprdllist,txlist);
  。。。
  sprocepsendeventsproc
  error(sproc)(ep,txlist,priv);
  。。。
  处理ovflist,即在上面sproc过程中又到来的事件
  。。。
  }
  其主要调用了epsendeventsproc:staticintepsendeventsproc(structeventpollep,structlistheadhead,
  voidpriv)
  {
  for(eventcnt0,ueventesedevents;
  !listempty(head)eventcntesedmaxevents;){
  遍历readylist
  epilistfirstentry(head,structepitem,rdllink);
  listdelinit(epirdllink);
  readylist只是表明当前epi有事件,具体的事件信息还是得调用对应file的poll
  这边的poll即是tcppoll,根据tcp本身的信息设置掩码(mask)等信息上兴趣事件掩码,则可以得知当前事件是否是epollwait感兴趣的事件
  reventsepiffd。filefoppoll(epiffd。file,)
  epievent。events;
  if(revents){
  将event放入到用户空间
  处理ONESHOT逻辑
  如果不是边缘触发,则将当前的epi重新加回到可用列表中,这样就可以下一次继续触发poll,如果下一次poll的revents不为0,那么用户空间依旧能感知
  elseif(!(epievent。eventsEPOLLET)){
  listaddtail(epirdllink,eprdllist);
  }
  如果是边缘触发,那么就不加回可用列表,因此只能等到下一个可用事件触发的时候才会将对应的epi放到可用列表里面
  eventcnt
  }
  如poll出来的revents事件epollwait不感兴趣(或者本来就没有事件),那么也不会加回到可用列表
  。。。。。。
  }
  returneventcnt;
  }
  上述代码逻辑如下所示:
  事件到来添加到epoll就绪队列(rdllist)的过程
  经过上述章节的详述之后,我们终于可以阐述,tcp在数据到来时是怎么加入到epoll的就绪队列的了。
  可读事件到来
  首先我们看下tcp数据包从网卡驱动到kernel内部tcp协议处理调用链:
  step1:
  网络分组到来的内核路径,网卡发起中断后调用netifrx将事件挂入CPU的等待队列,并唤起软中断(softirq),再通过linux的软中断机制调用netrxaction,如下图所示:
  注:上图来自PLKA(深入Linux内核架构)
  step2:
  紧接着跟踪nextrxactionnextrxaction
  processbacklog
  。。。。。。
  packettypefunc在这里我们考虑iprcv
  ipprothandler在这里ipprot重载为tcpprotocol
  (handler即为tcpv4rcv)
  我们再看下对应的tcpv4rcvtcpv4rcv
  tcpv4dorcv
  tcprcvstateprocess
  tcpdataqueue
  skskdataready(sockdefreadable)
  wakeupinterruptiblesyncpoll(sksleep,。。。)
  wakeup
  wakeupcommon
  currfunc
  这里已经被epinsert添加为eppollcallback,而且设定了排它标识WQFLAGEXCLUSIVE
  eppollcallback
  这样,我们就看下最终唤醒epollwait的eppollcallback函数:staticinteppollcallback(waitqueuetwait,unsignedmode,intsync,voidkey)
  {
  获取wait对应的epitem
  structepitemepiepitemfromwait(wait);
  epitem对应的eventpoll结构体
  structeventpollepepiep;
  获取自旋锁,保护readylist等结构
  spinlockirqsave(eplock,flags);
  如果当前epi没有被链入ep的readylist,则链入
  这样,就把当前的可用事件加入到epoll的可用列表了
  if(!epislinked(epirdllink))
  listaddtail(epirdllink,eprdllist);
  如果有epollwait在等待的话,则唤醒这个epollwait进程
  对应的epwq是在epollwait调用的时候通过initwaitqueueentry(wait,current)而生成的
  其中的current即是对应调用epollwait的进程信息taskstruct
  if(waitqueueactive(epwq))
  wakeuplocked(epwq);
  }
  上述过程如下图所示:
  最后wakeuplocked调用wakeupcommon,然后调用了在initwaitqueueentry注册的defaultwakefunction,调用路径为:wakeuplocked
  wakeupcommon
  defaultwakefunction
  trywakeup(wakeupathread)
  activatetask
  enqueuetaskrunning
  将epollwait进程推入可运行队列,等待内核重新调度进程,然后epollwait对应的这个进程重新运行后,就从schedule恢复,继续下面的epsendevents(向用户空间拷贝事件并返回)。
  wakeup过程如下图所示:
  可写事件到来
  可写事件的运行过程和可读事件大同小异:
  首先,在epollctladd的时候预先会调用一次对应文件描述符的poll,如果返回事件里有可写掩码的时候直接调用wakeuplocked以唤醒对应的epollwait进程。
  然后,在tcp在底层驱动有数据到来的时候可能携带了ack从而可以释放部分已经被对端接收的数据,于是触发可写事件,这一部分的调用链为:tcpinput。c
  tcpv4rcv
  tcpv4dorcv
  tcprcvstateprocess
  tcpdatasndcheck
  tcpcheckspace
  tcpnewspace
  skskwritespace
  tcp下即是skstreamwritespace
  最后在此函数里面skstreamwritespace唤醒对应的epollwait进程voidskstreamwritespace(structsocksk)
  {
  即有13可写空间的时候才触发可写事件
  if(skstreamwspace(sk)skstreamminwspace(sk)sock){
  clearbit(SOCKNOSPACE,sockflags);
  if(sksksleepwaitqueueactive(sksksleep))
  wakeupinterruptiblepoll(sksksleep,POLLOUT
  POLLWRNORMPOLLWRBAND)
  。。。。。。
  }
  }
  关闭描述符(closefd)
  值得注意的是,我们在close对应的文件描述符的时候,会自动调用eventpollrelease将对应的file从其关联的epollfd中删除,kernel关键路径如下:closefd
  filpclose
  fput
  fput
  eventpollrelease
  epremove
  所以我们在关闭对应的文件描述符后,并不需要通过epollctldel来删掉对应epoll中相应的描述符。
  学习Linuxkernel源码组好的莫过于ULK3《深入理解Linux内核》
  总结
  epoll作为linux下非常优秀的事件触发机制得到了广泛的运用。其源码还是比较复杂的,本文只是阐述了epoll读写事件的触发机制,探究linuxkernel源码的过程非常快乐

李玫瑾教授再爱孩子,也不要给他报这3种兴趣班,费钱还没作用为了不让自家孩子输在起跑线上,哪家父母不是铆足了劲的带着孩子奔走于各种兴趣班、培训班的。生怕自家娃跑慢了一步,就比别家孩子落后一大截。因此,给孩子报兴趣班,还要多报兴趣班成为现……一日为员工,一辈子是大家庭的一份子最新一期的邵恒头条,主题是让离职员工成为一家公司的资产,内容有点意思。大部分公司很忌讳员工谈离职,公司可以开除你,但你不能主动跳槽抛弃公司。有些公司则想了很多招,去……教师招聘笔试语文学科模拟题古代文学必做选择题1。被苏轼赞为诗中有画,画中有诗的诗人是()A。王维B。孟浩然C。李白D。杜甫2。唐代诗人中有诗仙、诗圣、诗佛、诗雄美称的诗人依次是()A。王维岑参李白杜甫B……我的宝宝每次放假想妈妈带她去玩每次不管是放暑假还是什么假她爸爸在工地上班是没有假期的,只要要做事就是要上班的呀!但是她奶奶连喝水的都要我们端给他喝所以我不敢带孩子出去玩,可是宝宝一直哭一直闹,后面就叫宝宝们……教师招聘考试刷题2021121。由于反映活动的形式不同,知识可以分为陈述性知识与A程序性知识B实用的知识C直观的知识D可应用的知识〔A〕22。在实际教学过程中,知识直观的方式主要有实物直观、……虾皮补钙是真的吗?怎样吃能让虾皮发挥最大补钙作用?对于正在处于身体增长期的孩子而言,补钙是非常重要的,从食物中摄取钙质不仅安全,而且营养美味。相信很多家长都会说虾皮。然而,虾皮补钙的说法真的靠谱吗?怎样吃能让虾皮发挥它的最大补……尴尬的70后,无奈的70后,辛苦的70后,奔五的70后时光匆匆,一晃而过。人生在风风雨雨中穿行,转眼间,七零后的一代,已经站在了40岁的尾巴上。告别了曾经的风华岁月,历经沧桑,走进了知天命之年。不论我们如何感叹,……危险警告!孕妈经常熬夜,肚里的娃儿竟然会变丑都说年轻人不要老熬夜,12点就赶快去睡觉。经常熬夜会降低免疫力,容易生病、情绪不稳定、免疫力下降对但对于胎儿,孕妈熬夜晚睡对胎儿危害可就多的多了,也比你想的要严重得多。孕……会让你活着离开XXX?这话是不是太霸道了?还是太黑道了?能活着离开X地?都说医闹大多是因为患者的冲动,但想到没有,医闹的发生并非全是患者家属的错。这不满嘴匪气的说法来了:我会让你活着离开X地吗……妈妈,我想要这个,当孩子指着68块一斤的车厘子,你会怎么办导语:和大家分享一个这样的小故事吧。一个妈妈再去接孩子放学回家的时候路过水果店。妈妈跟小孩儿说家里没有水果啦,一会儿我们去挑一些水果吧。小孩儿听见了就非常开心,然后就一心希望着……烫青菜被换成豆芽菜顾客给差评,老板硬拗芽菜也是青菜你觉得豆芽菜算是蔬菜吗?如果豆芽菜也是蔬菜,那所有蔬菜都可以用来当作小吃店菜单上的烫青菜吗?以上这个问题将会影响你接下来的判断原则。一位小吃店女老板在网上发帖抱怨,称有客……心肠三部曲,您是这样吗?孩童时代的心肠一般是比较柔软广阔的。他也是。他是那么富有爱心,一棵小草,一只小虫,他都会忘我地去爱,摸摸,揉揉,吻吻,甚至与之同吃同睡,没有一丁点儿隔阂。随着岁月的推移,……
为什么日本那边这么发达,却很少有黑人?反而喜欢扎堆在广州?大家好,欢迎观看本期的科普大格局。随着现在各国往来越发密切,我国境内也多了许多的外国人,无论是白种人还是黑种人,我们都能够在现实中遇见,尤其是在广州地区,更是随处都能见到……孕期怕血糖升高,水果和碳水都不敢吃了?不吃也不行,方法要掌握文福林妈咪以前也没怎么听说过妊娠期糖尿病,但近几年来就经常听到。所以很少听老一辈的说怀孕的时候注意自己的血糖,反倒现在的准妈妈们,格外注意自己的血糖,就怕出现妊娠期……救助父母孩子爱乱发脾气,父母怎么教育?有用有效的方法来了文笑雪育儿思考孩子爱乱发脾气,是很多家长都头疼的一件事。今天我在商场还遇到了这样一幕:一个五岁左右的男孩蹲在地上哭闹,旁边爸爸和妈妈在劝慰,依稀听到是孩子想要买玩具……两性交往,这3个地方出了问题,很容易纠缠不清在男女交往中,一见如故容易,难得的是来日方长的陪伴。然而陪伴也是要看是否合得来,貌合神离的两性关系不能长久。纠缠不清的恋情更是让人烦心,出问题的感情想必双方都烦恼,还是要……上海8区在发放入园录取通知!你家收到了吗?附园所统筹去向导语截至今日,8区幼儿园录取结果开始公布了!验证结束之后,家长们一定很关心什么时候能收到录取通知,今天托幼君汇总了截至目前的动向,家里有孩子的家长们,快来看看吧!徐……男孩不男缺阳刚之气,该普及阳刚教育吗?刻板印象不是好事文福林妈咪一份防止男生女性化的提案,让大家展开激烈辩论。其实,对于男孩不男这一现象,早在十年前的时候就已经有学者和专家广泛讨论过了。当时这种现象被称为男孩危机。……两性交往技巧,女人恋爱要修炼的三样东西在男女两性交往中,对于女人来说,恋爱虽是美好,可同样充满着各种纠结。害怕到头来不过是竹篮打水一场空,自己落得个为情所伤,万千迷茫。其实,完全大可不必。女人只要在恋爱前做一……实测10款常见火腿肠,看看到底有多少肉火腿肠作为随处可见的方便食品,一直以来凭借便宜又好吃的优势,笼络了大批吃货的心。但周围总有声音告诉你:火腿肠里的防腐剂会致癌!火腿肠哪有肉?全是淀粉和添加剂!谁知道都是什……我再也不扔这种塑料瓶了,切2刀放在卫生间,超实用,不花冤枉钱摘要:塑料瓶家家户户都有,别再当垃圾扔掉了,像这种塑料瓶我从来不扔,只需要简单改造一下,切两刀放在卫生间里超实用,家家户户都用得到,不要再花冤枉钱了。今天就教大家一个塑料……上海这个05后高中生,在街头上演教科书般的神操作今年10月下旬,63岁的王老伯参加高中同学聚会后太开心倒在了饭店大门外的路上老同学们手足无措时一个高大的男孩挤进了人群在等救护车的五六分钟时间里……扎克伯格天天念叨的元宇宙,到底是什么?艺术家VincentDiFate创作的超元域主题作品《哈利波特》因构建魔法世界的手游而热度重现,爆款影视如《失控玩家》《西部世界》层出不穷,Facebook、字节跳动和腾……一样的父母,一样的家庭环境,为何大宝与二宝性格迥异?表哥家有两个男孩,大的温文乖巧,特别懂事。小的调皮捣蛋,自我为中心。每次小的犯错基本都是大的抗雷,但大宝还是处处的让着弟弟。一样的父母,一样的家庭环境,为什么两个孩……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网