




已阅读5页,还剩12页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
网络设备轮询设计与实现 -BSD操作系统核心新技术作者:xie_minix(谢小荣)1.1 引言polling(轮询)技术是一种在一定时间内不断的探测目标,以获取目标在满足条件后进行相应处理的技术。从大多数情况来讲,这种技术并没有以中断为代表的技术先进。但是在一些特定场合,如主机作为一个大型的网关,并且流量巨大,选用轮询技术还是比较合适的。本文是针对FreeBSD操作系统及其代码来进行分析其设计思路和实现过程。目前BSD类操作系统中只有FreeBSD4.10和FreeBSD5.3以上版本才加入了对轮询的支持,NetBSD和OpenBSD还未实现。作为大多数防火墙首选的操作系统OpenBSD到是应该实现轮询技术。在TCP/IP详解第二卷中,书中作者曾经提到过该技术,而且好象冯.杰克布森已经在一些实验中实现过。直到不久前我们才看到这些代码在FreeBSD中实现,要知道在核心中加入一项新的技术工作量是非常之大,轮询代码不但是要在核心中增加一些代码文件,而且把所有网卡驱动程序都要进行大量相应的改动。我们以VIA Rhine(威盛的莱因型芯片,通常的DLINK 530TX网卡使用的芯片)的驱动程序if_vr.c和他的头文件if_vrreg.h(后面具体涉及到硬件驱动程序的代码都以vr代码做实例分析)来简单讲述代码的一些改动。A XX_softc结构中的改动:XX是代表各芯片的简称,本文使用的是vr芯片代码,所以是vr_softc结构。在结构的最后部分,增加了以下代码:#ifdef DEVICE_POLLINGintrxcycles;#endif以上代码出现在源代码if_vrreg.h中的第472行中。DEVICE_POLLING是由核心配置决定。如果核心配置文件中加入了该定义,则在#ifdef 和#endif行之间的代码将编译进核心。也就是说,所有的支持POLLING的代码都应该在#ifdef DEVICE_POLLING定义之下和#endif之上。图中的rxcycles变量是用来存放在核心进入vr驱动程序时,POLLING要求驱动程序的底半部分(即读出和写入)进行多少次的循环。B 在连接该设备驱动程序到设备程序链表时的一些改动:任何以太网络设备在探测到该设备存在后(xx_probe函数,xx在这是代表芯片在系统中的简称),紧接着执行设备链入函数(xx_attach),他的工作主要是进行硬件的一些初始化(中断的分配、网卡缓存的内存映射等等)和ifnet结构的初始化。#ifdef DEVICE_POLLINGifp-if_capabilities |= IFCAP_POLLING;#endif上图是if_vr.c中的第742行中关于对POLLING支持时候的一段代码。if_capabilities成员是用来判断网卡支持性能的成员。比如对VLAN技术的支持,POLLING技术的支持等标志都是放在此成员中。1.2代码介绍所有的轮询代码主体在1个C文件和各支持轮询的网络设备驱动程序中,以及一些相关的支撑文件。图1-1中没有列出所有的网络设备驱动程序。文件说明 net/if_var.hpci/if_vrreg.h申明一些函数修改ifnet结构以支持轮询kern/kern_poll.ckern/kern_et/netisr.cpci/if_vr.c轮询的核心部分代码一段定时调用kern_poll.c中轮询代码网络软中断代码网络设备驱动程序(基于vr芯片的卡)其他的网络设备驱动程序图 1-1 本文包含的文件1.2.1 全局变量从第106行开始,都是POLLING相关的全局变量.首先是建立一SYSCTL树的节点.下图中的SYSCTL_NODE宏代表在父节点_kern下建立一个_kern_polling节点.SYSCTL_NODE(_kern, OID_AUTO, polling, CTLFLAG_RW, 0,Device polling parameters); 下图中的变量全部可用sysctl来查看,大部分都可以调整设置.这些全局变量都是使用宏SYSCTL_UINT把他们加入到_kern_polling节点下,成为该节点的叶子.之所以这样,是因为可以通过用户区来调整这些参数.static u_int32_t poll_burst = 5; - kern_poll.cstatic u_int32_t poll_each_burst = 5;static u_int32_t poll_burst_max = 150;static u_int32_t poll_in_idle_loop=0;u_int32_t poll_in_trap;static u_int32_t user_frac = 50;static u_int32_t reg_frac = 20 ;static u_int32_t short_ticks;static u_int32_t lost_polls;static u_int32_t pending_polls;static int residual_burst = 0;static u_int32_t poll_handlers;static int polling = 0;static u_int32_t phase;static u_int32_t suspect;static u_int32_t stalled;static u_int32_t idlepoll_sleeping; - kern_poll.c 图1-2-2在图1-2-2中.这些变量按功能分可分为 3 大类.A. 开关型:polling: 初始为0,即默认为不打开轮询功能.poll_in_idle_loop:该参数用于poll_idle函数,用来确定是否进入一低优先权的循环轮询中.即CPU空闲时是否来执行轮询.poll_in_trap:此参数不但是在陷入时是否执行轮询的开关,而且其值也是在陷入(trap)时执行多少次轮询B. 算法参数poll_each_burst:一个基本的轮询次数,很多其他变量都来和他进行比较(主要是空闲时执行轮询或陷入时执行轮询).该值系统默认是5,poll_burst:一个动态的轮询次数,主要用于根据核心(轮询)占用的时间片调整轮询次数,在核心(轮询)时间片小于预定的时间片时,该值加1,当核心(轮询)时间片过长,导致丢失一个或更多的时钟嘀嗒时,该值将减去该值的8分之1.这种算法是FreeBSD中轮询技术的主要算法.当然有一定的局限性.当网络分组快速增加时,此算法只是加1来增加次数再来调用软件中断,从而形成软中断暴增的做法并不好.而且在占用时间片过长时的减8分之1的做法也缺少理论依据.(而且感觉根本站不住脚).poll_burst_max:轮训的最大值,该值是用来限制poll_burst在累加过程中的最大量.当然该值可以调整.他的调整范围在后面讲的两个宏之内.user_frac:用户区占用的CPU时间片的百分比.该值用来确定核心(轮询)所占时间片是否有剩余,如果有的话就调整动态值poll_burst,使其加1.residual_burst:在正常的轮询次数中,都是以poll_each_burst为标准的,而当动态的poll_burstpoll_each_burst时候,就会产生剩余没轮询的次数,该次数就是residual_burst,当然其结果就是继续轮询完residual_burst.idlepoll_sleeping:该值的使用前提是poll_in_idle_loop变量开关已经打开,即在CPU空闲时支持轮询.系统置该值为0,可以直接进入到CPU空闲时的轮询代码中;如果poll_in_idle_loop变量开关还没开放,系统会给该值置1,也就是说.该值其实是一个CPU空闲时是否进入轮询代码的状态,reg_frac:在整个循环代码执行了该值的次数之后,就进行检查网卡的状态寄存器.看看网卡是否有什么问题.pending_polls:在进入轮询前(我们有个时钟嘀嗒钩子函数),此参数加1,再轮询后会对其减1,再次进入时钟嘀嗒后半部分会判断是否平衡,如果由于轮询时间过长,此次嘀嗒便会错过.C. 调试与参考short_ticks:每次时钟嘀嗒钩子函数所花的时间如果小于5000毫秒.那这种间隔时间太短了.这是以HZ=100来计算的.源代码作者认为.在100M卡时,HZ数调整到1000比较合适,那么我们的时钟嘀嗒钩子函数所花时间在小于500毫秒是合适的.lost_polls: 由于pending_polls的不平衡.记录一次嘀嗒时间错过,该值会不停的累加,给系统管理员提示可以调整poll_burst_max值小一些.或根据情况把user_frac(用户占用CPU时间片的百分比)的值调的更小些.poll_handlers:有多少个网络设备支持并注册了轮询.phase:指示轮询进行到了哪个阶段.轮询代码共分为6个阶段.0阶段代表是初始阶段或上次轮询的结束.1阶段时钟嘀嗒钩子函数在设置网络软中断(轮询中断)前2阶段时钟嘀嗒钩子函数在设置网络软中断(轮询中断)后.3阶段是进入软中断netisr_poll前.4阶段是从软中断netisr_poll中出来.5阶段是进入软中断netisr_pollmore前6阶段是从软中断netisr_pollmore中出来.suspect:由于最后的软中断netisr_pollmore在处理时会再完成时把阶段标志phase置为0,或出现其他未完成情况时进入阶段5和6.那么就是说.我们的时钟嘀嗒钩子在最初时小于2阶段的话,一定是在0阶段.如果出现1或2阶段的话就有问题了.此值在出现这种问题时进行记录.stalled:由于pending_polls太大(大于100),也就是说由于每次轮询时间过长,以至于轮询丢失了太多嘀嗒,当到达100次时,该值加1.(源代码的作者认为不会发生此类事情,除了在网卡激活时)1.2.2 数据结构在轮询核心代码kern_poll.c中,数据结构只有pollrec一个.该结构只有两个成员.第一个成员handler是一指针.指向网络设备驱动程序中的处理轮询的函数,另一个是ifp,是一个网络设备的ifnet结构指针.每一支持轮询的网络设备在轮询注册的过程中来初始化其pollrec结构.见图1-2-2-1struct pollrec - kern_poll.cpoll_handler_t*handler;struct ifnet*ifp; 图1-2-2-1pollrec结构在系统中实际上有个长128个成员的结构数组.static struct pollrec prPOLL_LIST_LEN; - kern_poll.c每注册一次,数组指针加1.当前位置记录在1.2.1节C类的poll_handlers全局变量中.1.2.3 几个简单的宏在轮询核心代码中的宏定义只有3个(图1-2-3).前两行是对poll_burst_max(即1-2-1节中算法参数)在用户改变时的限定最小和最大值.后一行是支持的最多轮询网络设备数.#define MIN_POLL_BURST_MAX10 - kern_poll.c#define MAX_POLL_BURST_MAX1000#define POLL_LIST_LEN 128 - kern_poll.c 图1-2-31.3 轮询的初始化系统在初始化其间将执行init_device_poll函数.宏SYSINIT是一个系统初始化被调用时使用, 使得初始化函数能够在系统开始时被执行static void - kern_poll.cinit_device_poll(void)netisr_register(NETISR_POLL, (netisr_t *)netisr_poll, NULL, 0);netisr_register(NETISR_POLLMORE, (netisr_t *)netisr_pollmore, NULL, 0);SYSINIT(device_poll, SI_SUB_CLOCKS, SI_ORDER_MIDDLE, init_device_poll, NULL) - kern_poll.c图1-31.3.1 系统中最重要的宏SYSINIT在图1-3中的SYSINIT在宏第一步展开后是:C_SYSINIT(device_poll,SI_SUB_CLOCKS,SI_ORDER_MIDDLE,(sysinit_cfunc_t)(sysinit_nfunc_t)init_device_poll,(void*)NULL)这是SYSINIT展开后的结果,宏C_SYSINIT再展开后是: static struct sysinit device_poll_sys_init = SI_SUB_CLOCKS, SI_ORDER_MIDDLE, init_device_poll, NULL ; DATA_SET(sysinit_set, device_poll_sys_init);到这里,我们知道SYSINIT实际上是建立了一个编译系统时需要的结构.那么DATA_SET是干什么的呢?先把我们的数据宏替换后展开:static void const * const _set_sysinit_set_sym_device_poll_sys_init = & device_poll_sys_init; _asm(.section .set.sysinit_set,aw); _asm(.long device_poll_sys_init); _asm(.previous)经过展开后可以看出该宏为内联式汇编语言,由gcc编辑器ld运行时把属于同一个section的数据合并到一个连续的地址块中,FreeBSD就可以把这个地址当成一个sysinit的数组, FreeBSD找出这个sysinit_set地址, 遍历这个数组并调用其中的初始化函数.对于SYSINIT的详细说明,请参考徐逸峰写的FreeBSD 内核中的SYSINIT分析一文。1.3.2 网络软中断调用在分析图1-3中的netisr_register函数前,必须清楚的了解网络软中断调用的原理.网络软中断调用及初始化的代码在netisr.h头文件和netisr.c中.网络软中断共有以下图中15种:#defineNETISR_POLL0/* 必须放置到第一位 */ -netisr.h#defineNETISR_IP2/* 在if_ethersubr.c中我们可以看到它,IP分组软中断 */#defineNETISR_ROUTE14/* routing socket */#defineNETISR_AARP15/* Appletalk ARP */#defineNETISR_ATALK216/* Appletalk phase 2 */#defineNETISR_ATALK117/* Appletalk phase 1 */#defineNETISR_ARP18/* 也是在if_ethersubr.c中. */#defineNETISR_IPX23/* same as AF_IPX */#defineNETISR_USB25/* USB soft interrupt */#defineNETISR_PPP26/* PPP soft interrupt */#defineNETISR_IPV627#defineNETISR_NATM28#defineNETISR_ATM29#defineNETISR_NETGRAPH30#defineNETISR_POLLMORE31/* 必须放到最后以测试POLL的效率 */ -netisr.h 图 1-3-2在讲述POLLING(轮询)软件中断技术之前,对以上软中断号比较熟悉的是NETISR_IP和NETISR_ARP软件中断.因为在我的通用以太网络驱动程序代码详解中已经介绍过。我们这里看到NETISR_POLL放在第一位置,NETISR_POLLMORE放在最后的位置.这样安排是为了POLLMORE可以查看POLL运行的结果,是否有剩余的residual_burst(在参数说明中的B.算法参数中).有则再进行POLL,也给中间部分的软中断(如NETISR_IP、NETISR_ROUTE)有时间执行.图1-3-2中定义的值是针对一个32位的全局变量netisr的各个bit(位).如下图所示: netisr01234.3031 NETISR_POLL NETISR_IP NETISR_NETGRAPH NETISR_POLLMORE在系统初始化其间会加入一核心线程对该变量进行每一bit(位)的判断是否置位.如果置位.则调用位序号对应的netisrs结构数组中的支撑函数.这个过程就是网络的软中断过程. 图 1-3-2-1为网络软中断守护核心线程初始化过程.static void -netisr.cstart_netisr(void *dummy)if (swi_add(NULL, net, swi_net, NULL, SWI_NET, INTR_MPSAFE, &net_ih)panic(start_netisr);SYSINIT(start_netisr, SI_SUB_SOFTINTR, SI_ORDER_FIRST, start_netisr, NULL)图 1-3-2-1SYSINIT宏使系统在初始时执行start_netisr函数.swi_add函数则把swi_net函数加入到核心线程队列中.net_ih是一intrhand结构指针.函数在成功返回后,net_ih指向的结构包含了中断句柄结构,该结构指针被用于软件中断调用函数swi_sched中.关于swi_add函数和swi_sched函数,本文不准备讨论(在kern_intr.c文件中),因为他们属于核心中断和调度部分了.swi_net函数的作用是判断全局变量netisr各个位(bit)的置位情况.进而处理相关的输入队列和调用其中断处理函数.swi_net(void *dummy) -netisr.cstruct netisr *ni;u_int bits;int i;#ifdef DEVICE_POLLINGconst int polling = 1;#elseconst int polling = 0;#endifdo bits = atomic_readandclear_int(&netisr);if (bits = 0)break;while (i = ffs(bits) != 0) isrstat.isrs_swi_count+;i-;bits &= (1 ni_handler = NULL) printf(swi_net: unregistered isr %d.n, i);continue;if (ni-ni_flags & NETISR_MPSAFE) = 0) mtx_lock(&Giant);if (ni-ni_queue = NULL)ni-ni_handler(NULL);elsenetisr_processqueue(ni);mtx_unlock(&Giant); else if (ni-ni_queue = NULL)ni-ni_handler(NULL);elsenetisr_processqueue(ni); while (polling); -netisr.c 图 1-3-2-1图1-3-2-1是整个swi_net函数的代码,它被放入两个while循环当中.最外层的循环是为了轮询而设计的.当polling没有开启时.函数只是执行内层的while循环.该循环是读netisr的每一位(从低位到高位).ffs函数是一库支撑函数.位于: liblibcstringffs.c中.用来从低位到高位读参数的每一位已经置位的序号放入到变量i中.如:当netisr为二进制的0000,0000,0000,0010时(即NETISR_IP置位),这时ffs(netisr)返回的结果为2.代表是第二位置了位.因为在C语言中,数组的成员都是从0开始.所以要引用netisrs结构数组中的成员,i的值必须减去1.这就是我们看到代码中i - -的原因.语句bits &= (1 mtx_object.lo_flags & LO_RECURSABLE) != 0, (_mtx_lock_sleep: recursed on non-recursive mutex %s %s:%dn, m-mtx_object.lo_name, file, line);m-mtx_recurse+;atomic_set_ptr(&m-mtx_lock, MTX_RECURSED); 图 1-3-2-2图 1-3-2-2中的m指向一锁结构(如Giant).该段是文件kern_mutex.c中函数_mtx_lock_sleep中的一部分(mtx_lock函数的主支撑函数). Mtx_owned函数是判断对参数m(即我们的假定的Giant锁)所前面拥有它的线程句柄和目前的线程句柄是否一致, m-mtx_recurse+则做为嵌套深度的一计数器.在解锁时会对该值进行减1操作.当为0时才真正的做解锁操作.图 1-3-2-1中最后一个支撑函数是netisr_processqueue,该函数则是调用结构数组netisrs中相应序号中结构的ni_handler成员.成员指向软中断真正的处理程序.对于该处理程序是怎么初始化的.则要回过头来讨论图1-3中的netisr_register函数.netisr_register(int num, netisr_t *handler, struct ifqueue *inq, int flags) - netisr.cKASSERT(!(num = (sizeof(netisrs)/sizeof(*netisrs), (bad isr %d, num);netisrsnum.ni_handler = handler;netisrsnum.ni_queue = inq;if (flags & NETISR_MPSAFE) & !debug_mpsafenet)flags &= NETISR_MPSAFE;netisrsnum.ni_flags = flags; - netisr.c 图 1-3-2-3KASSERT宏是一核心调试类函数.有点像if (true) panic(“”)类,在此是判断中断号是否在允许范围内.后面的几个语句都是和结构数组netisrs有关.其实该结构数组可以被认为是一个中断向量组.他被定义于文件netisr.c中:struct netisr netisr_t*ni_handler;struct ifqueue*ni_queue;intni_flags; netisrs32;此结构数组是一个长度为32个单位的数组组成.每个单位对应于全局变量netisr的每一位.结构成员ni_handler是相应中断的中断处理程序.我们轮询注册时的处理程序是netisr_poll和netisr_pollmore(图1-3中我们注册了两个,即使用了两个软中断);成员ni_queue是指向输入队列,当然在我们的轮询中断处理程序中用不上,所以该值在初始化时是NULL(有些软中断是用到了该参数,如IP,ARP等.他们在此处的值指向相应网络适配器的输入队列);成员ni_flags指明该处理程序的是否可嵌套(即是否可重入)等属性.我们现在可以总结一下网络软中断的概念了:1. 首先是在核心初始化一线程在不断的查看网络中断位(netisr的各个位)2. 如果该位置位(即为1)则找到netisrs结构数组中相应该位的中断向量.调用该向量所指到的函数并带上输入的数据包作为参数(如果有该参数的话).1.4 轮询设备的注册与POLLING代码本身作为软中断注册不同,硬件的注册(以太网设备)是在硬件驱动程序(网卡驱动程序)中来完成。一般是xx_intr(void *arg)中断函数中实现(xx代表芯片类型,在我们的例子中为vr,既威盛芯片).网卡在接收到中断后(引起网卡中断的因素很多,并不一定都是数据的到来,如果想多了解,可以看看/sys/pci/if_vrreg.h中第144行的中断状态位),xx_intr函数被调用,下图是其中前面一部分对网卡进入POLLING状态的代码。#ifdef DEVICE_POLLING - if_vr.cif (ifp-if_flags & IFF_POLLING)goto done_locked;if (ifp-if_capenable & IFCAP_POLLING) & ether_poll_register(vr_poll, ifp) /* OK, disable interrupts. */CSR_WRITE_2(sc, VR_IMR, 0x0000);vr_poll_locked(ifp, 0, 1);goto done_locked;#endif /* DEVICE_POLLING */ - if_vr.c图1.4.1代码首先对该网卡的ifnet结构中if_flags标志进行判断。是否该网卡已经在polling状态。如果是则转到出口done_locked。在前面1.1节中的B部分就已经讲过if_capenable的作用,他是用来在探测网卡时,该设备是否对POLLING技术的支持的一标志。如果设备支持polling方式,且注册网卡的polling代码成功(ether_poll_register函数用来注册具体的设备轮询代码,下面将会讲到),通过对VR_IMR寄存器置0来屏蔽掉中断信号的产生(VR_IMR寄存器顾名思义应该是interrupt mask register)。然后网卡在退出POLLING前再不会调用vr_intr()中断函数。1.4.1 轮询设备支撑函数ether_poll_register该函数是各网卡驱动程序中的中断代码注册polling方式的一支撑函数,主要是对1.2.2节中介绍的pollrec结构进行填充.int ether_poll_register(poll_handler_t *h, struct ifnet *ifp) - kern_poll.cint s;if (polling = 0) return 0;if (h = NULL | ifp = NULL)return 0;if ( !(ifp-if_flags & IFF_UP) )return 0;if (ifp-if_flags & IFF_POLLING)return 0;s = splhigh();if (poll_handlers = POLL_LIST_LEN) static int verbose = 10 ;splx(s);if (verbose 0) printf(poll handlers list full, maybe a broken driver ?n);verbose-;return 0; prpoll_handlers.handler = h;prpoll_handlers.ifp = ifp;poll_handlers+;ifp-if_flags |= IFF_POLLING;splx(s);if (idlepoll_sleeping)wakeup(&idlepoll_sleeping);return 1; - kern_poll.c 图1.4.1图1.4.1中对if_flags再一次进行了判断.虽然感觉有点重复(在调用该函数前已经进行了判断).但此处并不是真正的轮询代码.效率上没有任何损失.去掉该代码后唯一会出问题的地方是poll_handlers的数量会有些不同,而且前提条件是两个中断几乎同时到来,前一个到来时正好运行在ifp-if_flags|=IFF_POLLING之前时,后一中断同时到来才会有影响.此外,使用splhigh和splx这对函数进行锁保护越来越少了,大多使用mtx_lock方式了.写本文的时候(freebsd5.3) kern中使用splhigh只有9个地方,到6.0减少到了3个了.1.5 hardclock_device_poll函数该函数是挂接在hardclock(实时计时器)函数中.位于/sys/kern/kern_clock.c中的hardclock函数每秒中会被调用HZ(一般是100)的次数.这是polling方式两个入口之一.进入的位置是phase(在1.2.1节的C部分有介绍)在0时.hardclock_device_poll函数虽然放在kern_poll.c代码中,实际上是非常独立的一部分.该函数在每个时钟滴答时被调用,实际上他是个调度polling的程序.void hardclock_device_poll(void) - kern_poll.cstatic struct timeval prev_t, t;int delta;if (poll_handlers = 0)return;microuptime(&t);delta = (t.tv_usec - prev_t.tv_usec) +(t.tv_sec- rev_t.tv_sec)*1000000;if (delta * hz 100) stalled+;pending_polls = 0;phase = 0;if (phase = 2) if (phase != 0)suspect+;phase = 1;schednetisrbits(1 NETISR_POLL | 1 0)lost_polls+; - kern_
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年清洁能源行业全球市场分析与前景预测研究报告
- 固本延龄丸课件
- 2025年电子产品行业可穿戴设备市场前景报告
- 巴彦淖尔市2025内蒙古巴彦淖尔市统计局所属事业单位高层次急需紧缺人才引进测评笔试历年参考题库附带答案详解
- 2025年工业互联网技术在制造业中的发展前景研究报告
- 宜宾市2025上半年四川宜宾市屏山县事业单位考核招聘28人笔试历年参考题库附带答案详解
- 临夏市2025甘肃省临夏市教育系统引进人才28人笔试历年参考题库附带答案详解
- 2025福建移动春季校园招聘若干人笔试参考题库附带答案详解
- 2025江苏南通中国移动全资子公司中移铁通南通公司如东分公司招聘笔试参考题库附带答案详解
- 2025年燕舞集团有限公司公开招聘9人笔试参考题库附带答案详解
- 咖啡基础培训课件
- 人才服务合同书
- 2025年工会财务大赛理论题库(附答案)
- 2025-2026学年统编版八年级上册道德与法治教学计划含教学进度表
- 矿井顶板事故防治课件
- 2025年中国电力投资集团校园招聘笔试题型分析及备考策略
- 抗生素课件教学课件
- 销售法律知识培训
- 中国慢性胃炎诊治指南(2022年)解读
- 糖尿病低血糖症诊疗指南
- 直升机发动机油封课件
评论
0/150
提交评论