Gem5模拟器代码阅读要点总结_第1页
Gem5模拟器代码阅读要点总结_第2页
Gem5模拟器代码阅读要点总结_第3页
Gem5模拟器代码阅读要点总结_第4页
Gem5模拟器代码阅读要点总结_第5页
已阅读5页,还剩5页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

一些编程要点:重要!!要添加一个新的网络,找到$GEM5/configs/ruby/Ruby.py的第84行,添加一种新的网络名称,并把几个py模块给pass了。Mapping或线程迁移的实现,把原网卡类作为虚拟网卡类,要和协议收发message时,使用虚拟网卡,而在进行网络的动作时使用实际网卡,两者之间建立一张mapping表。具体实现:在GarnetNetwork_d类中,建立外部网卡和内部网卡,将networkInterface_d类拆分成两部分,一部分用于和协议通信的外部网卡,另一部分用于和网络通信的内部网卡,两者同时传入网络指针作为参数,即可相互调用。当有message要发送时,即被inNode_ptr唤醒外部网卡后,根据map找到自己对应的内部网卡src_internal_NI_id,以及消息目的地对应的内部网卡dest_internal_NI_id,将消息中增添一个属性。并调用自己的内部网卡来发送消息给目的地对应的内部网卡节点,所有路由器内部的组件,都只传入路由器指针自身,而需要调用路由器其他组件完成的事情(即流水级要前进),则函数都在路由器中实现,目的是让本组件方便调用,接着让路由器调度它的另一组件去行动。所有具有wakeup方法的类,都是consumer的子类,见各个.hh文件。因为wakeup是在consumer父类里作为虚函数出现的。当scheduleevent之后就会在规定时间调用wakeup绑定consumer、sourcequeue的目的是某些对象的consumer并不事先知道,如各种link的consumer和sourcequeue需要在构建网络时才知道,所以在那时需要设置consumer和sourcequeue。当然有些对象的consumer是固定的,比如VA器的consumer一定是SA器,所以在scheduleevent函数里的第一个参数直接就写SA器了。各种东西的表示方法:1.Flit转换为message指针:MsgPtrmsg_ptr=t_flit->get_msg_ptr();NetworkMessage*net_msg_ptr=safe_cast<NetworkMessage*>(msg_ptr.get());各种拓扑・py:位于ruby/network/topologies中,以Mesh.py为例每个都继承于Topology类,都定义了MakeTopology方法,传入的参数为:nodes:所有控制器的指针数组,模拟器先构建各种controller,然后将各个controller作为参数传进来构建拓扑options:配置,基本就是命令行敲入的num_cpus、mesh_rows等等IntLink,ExtLink,Router:输入、输出link以及路由器的构造函数指针函数返回一个mesh对象,其属性包括routers以及extlink和inlink,都是对象数组此MakeTopology函数的主要任务是构建路由器以及各种节点与路由器之间、路由器与路由器之间的连线Topology.cc/hh:传入的参数为paramp,用到mesh.py生成的拓扑结构。其param()就是mesh对象,里面含exlink/inlink等对象。从param()中ext_link对象数组,将每个都转换成ext_link(由于之前在mesh.py中就用Extlink构造函数调用的,所以构造出来的肯定是exlink类型的)。同时得到controller和router对象应用,前者是在mesh.py中的nodes参数传进去的(之前将所有controller都构建好了,作为nodes参数传进去的),后者是用Router的构造函数构造的。接着遍历每个在Python中生成的外部、内部连线,得到两端点的指针(路由器或是controller),把外部节点即controller的src,编号为0~外部节点数-1,而其dest编号为外部节点数~2倍外部节点数-1,而内部节点编号为2倍的外部节点数~2倍的外部节点数+内部节点(即路由器)数-1。则在C++中建立具体的连线,需要建立两个方向的连线。起始就是在m_link_map字典中添加<source,dest>的pair到link_entry(link指针和方向)的map映射。这里的source和dest就用刚才的方法编号了。其create_link方法:首先找到最大的switch_id,注意这里的switch不仅是路由器,还包括controller,所以最大的switch」d+1就是所有的节点加路由器。比如network_test,如果是4*4的Mesh,每个节点有一个L1和direcotry,则0-31是controller的src,32-63是controller的dest,64-79为路由器,所以总共是80个switch0权重矩阵、latency矩阵、interswitch矩阵的大小都设为switch数目*switch数目。初始化时,遍历每个m_link_map(注意是双向的),把其中的源和目的的权重找到,赋给相应的weight[src][dest]。权重矩阵的对角线设为0(自己到自己的花销是0),其余暂时设为无限。接下来调用shortest_path函数,寻找任意两个节点(包括路由和controller)之间的最短路径。即根据权重矩阵,更新延时矩阵和中间路由矩阵,返回最短距离矩阵。权重矩阵不变接着遍历权重矩阵,找到所有权重矩阵中非无限的元素,即有链路,则得到以链路起点为起点,终点为下一跳的初始一Hop,所能达到的目的地的集合。形成了一种类似反向的路由表。最后正式建立起链路,Makelink方法,把路由表的入口(即这个链路能够到达的目的地的集合)也传进链路的属性中。这里Makelink中调用的MakeExLink、MakeInLink和MakeInternalLink三个函数,由具体网络实现,Garnet中由GarnetNetwork_d实现,因为不同的网络有不同的Link的样子,如Garnet需要数据link和creditlink两条。BasicLink.cc/hh/py里面构建了3个类,分别是BasicLink、BasicIntLink和BasicExLink。后两个继承第一个。Cc/hh从py文件中获取参数,BasicLink中的参数为latency、bandwidthfactor和权重;BasicIntLink为内部节点和外部节点的指针,而BasicExLink为节点a和b的指针latency和权重为默认值1,在构建拓扑时调用构造会改掉的。而带宽系数为16。外部节点为Param.RubyController,内部节点、节点a和b均为Param.BasicRouter。这些均为属性。BasicRouter.cc/hh/py什么也没有,只有paramp传入的id,而在py中,该id也没有默认值,需要在构建拓扑时调用构造函数时指定。Network.cc/hh/py构造中,从Python中传入的参数有虚网数、拓扑类型的指针、控制消息的大小,并获得总共的controller个数。最后使得每个终端节点(controller的网络指针属性指向自身)。此外提供一个将MessageSizeType转换为控制消息type和数据消息type的方法。其中控制消息大小是由python指定的,通常是8个字节,而数据一般是一个cacheline,为64字节再加上控制的8字节,为72字节。接下来看Garnet文件夹内的文件NetworkHeader.hh规定了在Garnet中才使用的一些宏和枚举,包括虚网类型枚举(控制虚网、数据虚网默认虚网)、flit类型(头、身子、尾、又是头又是尾)、VC状态(空闲、正在VA、占用)、以及flitstage(刚生成、VA、SA、ST、LT)。BaseGarnetNetwork.cc/hh/py构造中从python中传入网卡使用的flit大小,python中默认为16字节,以及每个虚网的VC数目为4。要求每条链路的带宽因数bandwidthfactor要和ni的flit大小相同,都为16字节。生成m_toNetQueues以及m_fromNetQueues两个属性,均为长度为controller个数的messagebuffer的二维向量,第二维是它的虚网数。即对于每个controller的每个虚网,都建立一个进一个出的messagebuffer,并且实例化了。以上为Garnet(即较为精确的片上网)通用的文件*********************接下来看Garnet-fixed-pipeline文件夹内的文件,也是最内层的文件GarnetNetwork_d.cc/hh/pyPython中传入参数为每个数据、控制的VC的buffer,含多少个flit,数据默认为4。用爷爷类Network类中得到的拓扑指针,访问路由器,加入到本类的路由器向量属性中,方便调用。初始化时,使每个路由器的网络指针指向自身,接下来建立网卡,每个终端节点(controller)都建立一个,接下来根据拓扑,调用拓扑的create_link方法建立矩阵,以及每条路径的反向路由表。并根据Garnet的特征,建立的链路有credit和数据link两种。建立的所有link都加入到m_link_ptr_vector中,最后遍历之,将其网络指针指向自身。至此,终端节点、路由器以及所有Link的网络指针,全指向了自己,即GarnetNetwork。而在MakeInLink健立网卡到网络,即路由器的连线)中,BasicLink的指针作为参数传递进去的,将其强制转换为GarnetLink后,就有了netlink和creditlink两条,分别加入m_link_ptr_vector和m_creditlink_ptr_vector后,让路由器建立输入端口,网卡建立输出端口,都将数据Link和creditlink传入。MakeOutLink和MakeInternalLink也类似。注意,路由器在建立输出端口的时候,需要传递进路由表路由以及链路的权重。此外提供了父类属性m_toNetQueues以及m_fromNetQueues的getter方法,以及getRubyStartTime方法GarnetLink_d.cc/hh/py有python传入的参数有link_id、link_latency、每个虚网的vc数,虚网数以及通道宽度(必须等于bandwidthfactor)0注意这些的类为NetworkLink_d,下面的文件用到的。此外在GarnetlntLink_d以及GarnetExtLink_d类中,还要调用NetworkLink_d和CreditLink_d两个构造函数各两次,因为是双向的,构建两条链路,并append到一个向量中,作为向量的参数传给cc文件。分别为credit_links和network_links。.cc中直接取network_links[0]为入,network_links[1]为出,creditlink也一样。NetworkLink_d.cc/hhCreditLink_d.hh从python中传入参数为延时和带宽以及线路id,并创建linkbuffer,以及一些统计,如每个VC的负载,以及link的使用次数提供的函数包括设置link的consumer以及sourceQueue。NetworkLink可被其sourceQueue唤醒,唤醒后从sourceQueue中取到topFlit,将其pop出sourceQueue后放到自己的linkbuffer中,然后在link_lalency周期后调度其consumer。最后更新该Link的两个统计信息。而creditLink完全继承于NetworkLink,基本没什么区另U。因为两者运行原理一样,传的credit也是作为flit的一种,也有consumer和sourceQueue,所以基本一样。Router_d.cc/hhGarnetRouter_d.py继承于父类BasicRouter。从python中传入的参数有虚网数目、每个虚网的VC数,两者相乘得到总VC数。构造函数中新建了路由计算RC单元、VA单元、SA单元和switch单元,并都传递自己的指针this作为它们的路由器指针,并将其初始化。注意,输入单元和输出单元并不在路由器的构造函数中实例化,而在网络Makelink的时候,调用路由器的添加端口函数,同时添加了输入和输出单元。路由器的每一个端口都有一个输入和一个输出单元。为什么要这样?是因为路由器的端口数目并不固定,而是完全由路由器周围的连线所决定的,如mesh的顶点和中间显然端口不同,所以无法用统一的实例化函数来构造这些数目不同的输入、输出单元。增加输入、输出端口函数,也需要指定其creditlink和netlink的consumer、sourcequeue等。将这些链路与输入输出单元绑定。之前已述,输出单元的构造中需要传入路由表入口和权重。此外,还有路由请求、VA请求、SA请求、switch请求等函数,基本都是调用相应的模块。其中路由请求是这个周期就进行路由计算,并触发VA请求,而后两个都需要1周期后调度。最后还有更新统计的函数。RoutingUnit_d.cc/hh传入参数为路由器指针。两个关键的属性为m_routing_table以及m_weight_table。前者是路由表入口(Netdest)类型的向量,每个元素代表一个Link,后者是对应link的权重即代价,如果代价太大,即使能够在路由表中找到dest,也未必会选择这条路,因为总是找代价最小的路。类中提供往这两个向量中pushback元素的函数,在外部被调用的,即是在GarnetNetwork类中遍历每个路由器,让路由器调用addOutLink函数时,会再让路由单元将路由表和权重添加进来。RC_stage函数,进行RC过程,传入flit,inputunit对象和VC号,主要是进行RC计算,计算完后更新该VC的输出端,VC的状态由IDLE前进到VC_AB,flit流水级前进到VA。最后调度路由器进行VA过程。而RC计算函数是重点:首先根据头flit得到message指针,并转换为networkmessage,并得到该message的NetDest属性,接下来遍历该路由器的所有输出link,也就是其整张路由表的每一项,看看其netDest是否与该message的NetDest有交集,如果有的话要求权重是最小的,找到最小的权重的link作为路由计算的结果,返回该output_link。InputUnit_d.cc/hh输入单元类,构造中传入参数为路由器指针及id。得到VC数和每个虚网的VC数。建立creditqueue,用于向与之相连的creditlink发creditflit。此时建立每个VC对象实例。放进m_vcs属性中。其继承了consumer,所以又wakeup函数,wakeup函数中,观察与之相连的输入link是否有数据且数据已经到了(isReady),有的话consumer一个flit,得到该flit的VC(上一跳VA阶段分配的),如果是头flit,则显然assert该VC状态是idle的,则发出route_req,调用路由器的相应函数,再由路由器通知路由单元立刻开始路由计算。之后设置该VC的入队时间。否则如果不是头flit,则直接让flit进入SA阶段,并发出SA请求(需等一周期才调度SA单元)。此时往buffer中(也即m_vc[flit的vc])写入该flit。这就实现了BW和RC的同时进行。PS.从这里可以看出,路由器的buffer是隐含在输入单元的m_vc属性中的。最后统计该虚网的buffer读写次数等信息。OutputUnit_d.cc/hhOutVcState.cc/hh同输入单元一样,输出单元的构造中传入id和路由器指针,得到总VC数量。接着建立输出的flitBuffer,并将每一个输出VC(与之相连的路由器的输入VC的情况)建立outVcState,放入m_outvcstate属性中。提供了decrement_credit方法。当该路由器每发送一个flit时就需要调用之,使该outputUnit的outputstate[分配的VC]自减。路由器更新credit状态,这个意思是,路由器的输入单元里的VC即buffer,也必须能够看到outVcState中credit的变化,为了能够进行VA和SA。所以每个输入单元的VC的credit指的也是下一跳,每个VC都有一个creditcount,这个creditcount指的是该输入VC已经经过了VA阶段,和某一个输出VC绑定,creditcount指的是该输出VC,也就是下一跳的输入VC的credit数目。Wakeup函数里(一定是由creditlink所唤醒),检查creditlink,如果有credit,就得到其outvc,increment相应outvc的credit,并更新相应的输入VC的credit数。另外如果该credit附带了释放VC(因为下一个路由器有尾flit走了),则将该outvc状态释放为idle。而OutVcState里维护了inport、invc属性(经过VA后,与该outVC对应的输入端口和VC)以及creditcount,后者也会在调用路由器的updateCredit函数更新到输入VC中。其构造函数即初始化时,根据虚网类型,是数据还是控制,得到buffer大小所具有的初始flit的个数,作为其初始的credit数目。默认数据是4,控制是1。Flit_d.cc/hhFlitBuffer_d.cc/hhflitBuffer类中最主要的属性也就是m_buffer,即一个flit指针数组。里面提供插入flit方法、得到时间最早的flit方法,弹出队首flit方法等。用于判断状态的方法有isReady:先找到时间最早的队首flit,看看其时间是否小于等于当前模拟时间,如果是说明该flit已经可用。isReadyForNext,和isReady类似,不同的是判断flit时间是否小于等于当前时间加1,即下一周期该flit是否可用。另外还有isEmpty,即m_buffer是否为空。isFull,即m_buffer是否等于max_size。Flit类提供两个构造,一种构造为数据flit,主要提供属性为id、size(注意这里的size指的是message包含的flit个数)、vc号、虚网号以及对应消息的指针、stage。构造函数即初始化中,flit的时间定为构造时,即刚生成flit的时间。Stage属性为<状态stage、时间>的pair,前者初始化为I,后者初始化为flit时间。接着根据size和id决定其种类是头flit、尾flit、身子flit还是又是头又是尾(size==1)另一种构造为creditflit,其属性仅有vc,id—定为0,另外有个isfreesignal,即该creditflit是否附带释放VC的功能(由尾数据flit离开而产生)。NetworkInterface_d.cc/hh很重要的网卡类,将messagebuffer和整个网络联系在一起。构造函数中传入节点id,虚网总数以及网络指针。并由网络指针中获得每个虚网的VC数目,乘一下就是总VC数。另外一些属性:m_ni_buffer属性,即网卡的flit_buffer向量,每个VC对应一个。之后实例化每个flit_bufferm_ni_queue_time属性,和上面的buffer对应,即每个VC的入队到ni的时间。之后初始化均为无效值inNode_ptr和outNode_ptr:每个虚网对应一个输入messagebuffer和一个输出messagebuffercreditQueue:用来在eject掉flit后生成credit,往路由器发每个虚网必须要进行VA过程,功能和路由器的VA器类似,但只需维护一个RR的数组,表示每个虚网RR到哪里。用来获取与该NI相连的路由器内的VC。同时也需要OutVcState对象,因为要和路由器的输出单元结构类似。后者初始化为idle添加输入端口函数:传入参数有输入数据link以及creditlink,后者是由NIeject掉flit后往路由器发的。设置inLink的consumer是网卡自身,设置creditLink的sourceQueue就是自己的creditQueue。添加输出端口函数:传入参数有输出数据link以及creditlink,后者是由路由器发给NI告知是否能够继续注入的。设置outLink的sourceQueue是outSrcQueue(由m_ni_buffer往里面填的),而creditLink的consumer即为NI自身。添加节点函数:其实就是在构造网络时,将controller的messageBuffer传给网卡的inNode_ptr以及outNode_ptr。并且设置inNode的consumer就是网卡自身,因为inNode这个Messagebuffer需要唤醒其consumer(NI)进行发送事件。所以要将两者绑定。将message转换为一个一个flit的函数:通过参数传递要发送的message,获取message的目标netDest,转换为dest各节点的数组。根据天花板除得到总共的flit个数:是由MessageSizeType_to_int得到被除数(默认为72或8字节),由m_net_ptr->getNiFlitSize(在BaseGarnetNetwork.py参数里定义,默认为16)得到除数,相除结果默认为5或1。接下来循环每个dest节点,调用VA器分配VC,如果没分配成功,则直接返回了。分配成功了,需要将消息克隆一份,因为消息是组播的,单独的一份发了就没了。如果是组播的话,需要从dest数组分离出单独的dest,再重新构造回NetDest结构,作为该克隆后的message的internalDest,并将克隆前的原始组播message的internaldest以及dest数组两个结构中都消除该dest。在得到分离出来的单播message后,需要开始发送,遍历刚计算出来的flit数,网络统计注入数目自增。接着创建flit对象,将刚才VA过程得到的vc传给它,设置其src_delay为产生到发出经过的时间差,最后将flit移入m_ni_buffer[vc]中,更新入队时间m_ni_queue_time,以及outVcState为active。计算VC的函数:并不需要真正的VA器对象,只需一个int数组即可,用于RR。根据虚网号,遍历该虚网对应的所有VC,得到RR数组的当前虚网的元素并前进一个,从该VC开始察看outVcState如果是idle,则直接返回VC号Wakeup()函数:首先,NI作为三个对象的consumer,可能被这三个对象唤醒,分别是inNode_ptr(告知NI有消息要发送)、creditLink(由路由器injectcredit)和输入的数据link(告知NI有数据进来)每周期,每个虚网都可以找一个消息发送。第一步:如果有准备好发送的消息,则将其分成flit后给outputlink传送,但必须VA成功才行,最后inNode_ptr中pop出,message的一组flit都被放进m_ni_buffer中,接着调用scheduleOutputLink通知outputLink以RR方式进行传送,并检查是否还有flit,来判断下周期是否还需要schedule自身。第二步:如果被输入数据link所唤醒,则检查输入数据Link,consumer出flit,如果是尾flit,说明已经接收到了一个完整的message,则将outNode_ptr[vnet]中enqueue进该尾flit的msg_ptr消息指针。这里由于假设NIbuffer无限,所以ejectcredit一直在发送。所以直接发送credit,如果是尾flit则可以释放VC(freesignal=true)。这个过程是在creditQueue中插入新生成的creditflit,并在一周期后schedulecreditlink。之后更新flit的统计信息,因为flit已经完成了其生命周期,最后删除Flit。第三步:检测NI的creditlink,如果有credit,贝I」consume,outVcState[vc]调用incrementCredit,如果是freesignali则将VC释放(状态置为IDLE)。scheduleOutputLink函数:作用为当m_ni_buffer中有数据要发送,则调度其输出link进行发送。维护一个m_RR_vc指向所有VC,m_RR先自增,到头了归零。从RR开始遍历所有VC,首先该VC有数据要发送,其次其outVcState要有credit,满足这两个条件后,看如果要求网络有序,则该虚网中如果有比当前flit来的早的,则不设为候选,continue。得到候选VC后,将该VC的m_out_vc_state的credit自减。从m_ni_buffer[vc冲Pop出flit,设置flit时间为当前时间加1将flit加入到outSrcQueue,一周期后调度outnetLink。如果是尾flit,则说明该message已经完全注入,将该VC的入队时间恢复到无效值。VirturalChannel_d.cc/hhVC类:构造中传入id,每个VC其实就是一组buffer,所以需要创建flitbuffer的实例input_buffer,初始化状态first置为IDLE,second为当前时间,入队时间为无效值。route属性:储存路由计算RC的结果,一个端口号的int。output_vc属性:表示在VA完成后,将下一跳VC号传入。m_credit_count属性:当VA完成后,同步更新已分配的下一跳VC的credit数目。提供的方法有:Grant_vc:当VA成功后更新output_vc,状态置为active,从inputbuffer中获得队首flit,将其stage前进到SA。need_stage(VC_state_typestate,flit_stagestage):判断某个VC本周期是否需要进行某一stage,传入stage和期望的flitstage。即要求满足以下条件:VC状态和state参数相同,当前时间已经到了或超过了VC的时间,VC队首flit的stage和stage参数相同。need_stage_nextcycle:和上面一样,只不过时间的条件改为当前时间加1是否大于等于VC时间。VCallocator_d.cc/hhVA器类:作为路由器的组件,构造传入参数仅为路由器指针。得到虚网数、每个虚网的VC数,并建立两步VA过程的activity统计信息。初始化函数中,根据路由器指针得到输入、输出单元引用,得到输入输出端口数目(不同位置路由器的端口数目不同)。新建m_round_robin_invc和m_round_robin_outvc。用于RR的优先级指针。两者都为矩阵,长度为端口号,宽度为VC号。m_outvc_req是四维布尔向量,四个维度分别是[outport][outvc][inport][invc],为true表示第一个VA阶段中有相应的请求。m_outvc_is_req为二维布尔向量,两个维度为[outport][outvc],为true表示在第一个VA阶段,该输出VC曾被请求过。Round-robin的优先指针全初始化为0,而布尔向量全初始化为false。其唤醒函数Wakeup主要完成四步:第一步:VA第一阶段,根据RR优先级,遍历所有的输入端口及输入VC,如果是候选VC,则根据其输出端口中,RR寻找一个空闲的输出VC。将对应的m_outvc_req[outport][outvc][inport][invc]置为true,m_outvc_is_req[outport][outvc]也为true,表示该端口该VC曾被至少一次请求过。第二步:VA第二阶段,RR遍历每个输出VC,如果其m_outvc_is_req[outport][outvc]是true,贝I」RR遍历所有输入VC,第一个找到的输入VC如果正好是请求者,即m_outvc_req[outport][outvc][inport][invc]是true,则就将该VC分配给它。VA成功者,输入VC需要grantVC(见上),输出单元更新结果(状态为ACTIVE,并被分配该输出VC的输入端口和VC号),最后使路由器调度1周期后进入SA阶段。第三步:清除VA的痕迹,布尔矩阵归false,但RR结果不变,结果下次会用到,这样才算是round-robin。第四步:看看下一周期该VA器是不是需要再次VA,即遍历每个输入VC,如果下周期仍然需要VA(用VC类中的need_stage_nextcycle来判断,详见上条),则1周期后调度this。SWallocator_d.cc/hhSA器类:作为路由器的组件,构造传入参数仅为路由器指针,得到虚网数、每个虚网的VC

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论