




已阅读5页,还剩7页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Linux系统设备驱动程序概述1.1 Linux设备驱动程序分类Linux设备驱动程序在Linux的内核源代码中占有很大的比例,源代码的长度日益增加,主要是驱动程序的增加。在Linux内核的不断升级过程中,驱动程序的结构还是相对稳定。在2.0.xx到2.2.xx的变动里,驱动程序的编写做了一些改变,但是从2.0.xx的驱动到2.2.xx的移植只需做少量的工作。Linux系统的设备分为字符设备(char device),块设备(block device)和网络设备(network device)三种。字符设备是指存取时没有缓存的设备。块设备的读写都有缓存来支持,并且块设备必须能够随机存取(random access),字符设备则没有这个要求。典型的字符设备包括鼠标,键盘,串行口等。块设备主要包括硬盘软盘设备,CD-ROM等。一个文件系统要安装进入操作系统必须在块设备上。网络设备在Linux里做专门的处理。Linux的网络系统主要是基于BSD unix的socket机制。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。系统里支持对发送数据和接收数据的缓存,提供流量控制机制,提供对多协议的支持。1.2编写驱动程序的一些基本概念无论是什么操作系统的驱动程序,都有一些通用的概念。操作系统提供给驱动程序的支持也大致相同。下面简单介绍一下网络设备驱动程序的一些基本要求。1.2.1发送和接收这是一个网络设备最基本的功能。一块网卡所做的无非就是收发工作。所以驱动程序里要告诉系统你的发送函数在哪里,系统在有数据要发送时就会调用你的发送程序。还有驱动程序由于是直接操纵硬件的,所以网络硬件有数据收到最先能得到这个数据的也就是驱动程序,它负责把这些原始数据进行必要的处理然后送给系统。这里,操作系统必须要提供两个机制,一个是找到驱动程序的发送函数,一个是驱动程序把收到的数据送给系统。1.2.2中断中断在现代计算机结构中有重要的地位。操作系统必须提供驱动程序响应中断的能力。一般是把一个中断处理程序注册到系统中去。操作系统在硬件中断发生后调用驱动程序的处理程序。Linux支持中断的共享,即多个设备共享一个中断。1.2.3时钟在实现驱动程序时,很多地方会用到时钟。如某些协议里的超时处理,没有中断机制的硬件的轮询等。操作系统应为驱动程序提供定时机制。一般是在预定的时间过了以后回调注册的时钟函数。在网络驱动程序中,如果硬件没有中断功能,定时器可以提供轮询(poll)方式对硬件进行存取。或者是实现某些协议时需要的超时重传等。二.Linux系统网络设备驱动程序2.1网络驱动程序的结构所有的Linux网络驱动程序遵循通用的接口。设计时采用的是面向对象的方法。一个设备就是一个对象(device结构),它内部有自己的数据和方法。每一个设备的方法被调用时的第一个参数都是这个设备对象本身。这样这个方法就可以存取自身的数据(类似面向对象程序设计时的this引用)。一个网络设备最基本的方法有初始化、发送和接收。-|deliver packets|receive packets queue|(dev_queue_xmit()|them(netif_rx()|-|/|-|methods and variables(initialize,open,close,hard_xmit,|interrupt handler,config,resources,status.)|-|/|-|send to hardware|receivce from hardware|-|/|-|hardware media|-初始化程序完成硬件的初始化、device中变量的初始化和系统资源的申请。发送程序是在驱动程序的上层协议层有数据要发送时自动调用的。一般驱动程序中不对发送数据进行缓存,而是直接使用硬件的发送功能把数据发送出去。接收数据一般是通过硬件中断来通知的。在中断处理程序里,把硬件帧信息填入一个skbuff结构中,然后调用netif_rx()传递给上层处理。2.2网络驱动程序的基本方法网络设备做为一个对象,提供一些方法供系统访问。正是这些有统一接口的方法,掩蔽了硬件的具体细节,让系统对各种网络设备的访问都采用统一的形式,做到硬件无关性。下面解释最基本的方法。2.2.1初始化(initialize)驱动程序必须有一个初始化方法。在把驱动程序载入系统的时候会调用这个初始化程序。它做以下几方面的工作。检测设备。在初始化程序里你可以根据硬件的特征检查硬件是否存在,然后决定是否启动这个驱动程序。配置和初始化硬件。在初始化程序里你可以完成对硬件资源的配置,比如即插即用的硬件就可以在这个时候进行配置(Linux内核对PnP功能没有很好的支持,可以在驱动程序里完成这个功能)。配置或协商好硬件占用的资源以后,就可以向系统申请这些资源。有些资源是可以和别的设备共享的,如中断。有些是不能共享的,如IO、DMA。接下来你要初始化device结构中的变量。最后,你可以让硬件正式开始工作。2.2.2打开(open)open这个方法在网络设备驱动程序里是网络设备被激活的时候被调用(即设备状态由down-up)。所以实际上很多在initialize中的工作可以放到这里来做。比如资源的申请,硬件的激活。如果dev-open返回非0(error),则硬件的状态还是down。open方法另一个作用是如果驱动程序做为一个模块被装入,则要防止模块卸载时设备处于打开状态。在open方法里要调用MOD_INC_USE_COUNT宏。2.2.3关闭(stop)close方法做和open相反的工作。可以释放某些资源以减少系统负担。close是在设备状态由up转为down时被调用的。另外如果是做为模块装入的驱动程序,close里应该调用MOD_DEC_USE_COUNT,减少设备被引用的次数,以使驱动程序可以被卸载。另外close方法必须返回成功(0=success)。2.2.4发送(hard_start_xmit)所有的网络设备驱动程序都必须有这个发送方法。在系统调用驱动程序的xmit时,发送的数据放在一个sk_buff结构中。一般的驱动程序把数据传给硬件发出去。也有一些特殊的设备比如loopback把数据组成一个接收数据再回送给系统,或者dummy设备直接丢弃数据。如果发送成功,hard_start_xmit方法里释放sk_buff,返回0(发送成功)。如果设备暂时无法处理,比如硬件忙,则返回1。这时如果dev-tbusy置为非0,则系统认为硬件忙,要等到dev-tbusy置0以后才会再次发送。tbusy的置0任务一般由中断完成。硬件在发送结束后产生中断,这时可以把tbusy置0,然后用mark_bh()调用通知系统可以再次发送。在发送不成功的情况下,也可以不置dev-tbusy为非0,这样系统会不断尝试重发。如果hard_start_xmit发送不成功,则不要释放sk_buff。传送下来的sk_buff中的数据已经包含硬件需要的帧头。所以在发送方法里不需要再填充硬件帧头,数据可以直接提交给硬件发送。sk_buff是被锁住的(locked),确保其他程序不会存取它。2.2.5接收(reception)驱动程序并不存在一个接收方法。有数据收到应该是驱动程序来通知系统的。一般设备收到数据后都会产生一个中断,在中断处理程序中驱动程序申请一块sk_buff(skb),从硬件读出数据放置到申请好的缓冲区里。接下来填充sk_buff中的一些信息。skb-dev=dev,判断收到帧的协议类型,填入skb-protocol(多协议的支持)。把指针skb-mac.raw指向硬件数据然后丢弃硬件帧头(skb_pull)。还要设置skb-pkt_type,标明第二层(链路层)数据类型。可以是以下类型:PACKET_BROADCAST:链路层广播PACKET_MULTICAST:链路层组播PACKET_SELF:发给自己的帧PACKET_OTHERHOST:发给别人的帧(监听模式时会有这种帧)最后调用netif_rx()把数据传送给协议层。netif_rx()里数据放入处理队列然后返回,真正的处理是在中断返回以后,这样可以减少中断时间。调用netif_rx()以后,驱动程序就不能再存取数据缓冲区skb。2.2.6硬件帧头(hard_header)硬件一般都会在上层数据发送之前加上自己的硬件帧头,比如以太网(Ethernet)就有14字节的帧头。这个帧头是加在上层ip、ipx等数据包的前面的。驱动程序提供一个hard_header方法,协议层(ip、ipx、arp等)在发送数据之前会调用这段程序。硬件帧头的长度必须填在dev-hard_header_len,这样协议层回在数据之前保留好硬件帧头的空间。这样hard_header程序只要调用skb_push然后正确填入硬件帧头就可以了。在协议层调用hard_header时,传送的参数包括(2.0.xx):数据的sk_buff,device指针,protocol,目的地址(daddr),源地址(saddr),数据长度(len)。数据长度不要使用sk_buff中的参数,因为调用hard_header时数据可能还没完全组织好。saddr是NULL的话是使用缺省地址(default)。daddr是NULL表明协议层不知道硬件目的地址。如果hard_header完全填好了硬件帧头,则返回添加的字节数。如果硬件帧头中的信息还不完全(比如daddr为NULL,但是帧头中需要目的硬件地址。典型的情况是以太网需要地址解析(arp),则返回负字节数。hard_header返回负数的情况下,协议层会做进一步的build header的工作。目前Linux系统里就是做arp(如果hard_header返回正,dev-arp=1,表明不需要做arp,返回负,dev-arp=0,做arp)。对hard_header的调用在每个协议层的处理程序里。如ip_output。2.2.7地址解析(xarp)有些网络有硬件地址(比如Ethernet),并且在发送硬件帧时需要知道目的硬件地址。这样就需要上层协议地址(ip、ipx)和硬件地址的对应。这个对应是通过地址解析完成的。需要做arp的的设备在发送之前会调用驱动程序的rebuild_header方法。调用的主要参数包括指向硬件帧头的指针,协议层地址。如果驱动程序能够解析硬件地址,就返回1,如果不能,返回0。对rebuild_header的调用在net/core/dev.c的do_dev_queue_xmit()里。2.2.8参数设置和统计数据在驱动程序里还提供一些方法供系统对设备的参数进行设置和读取信息。一般只有超级用户(root)权限才能对设备参数进行设置。设置方法有:dev-set_mac_address()当用户调用ioctl类型为SIOCSIFHWADDR时是要设置这个设备的mac地址。一般对mac地址的设置没有太大意义的。dev-set_config()当用户调用ioctl时类型为SIOCSIFMAP时,系统会调用驱动程序的set_config方法。用户会传递一个ifmap结构包含需要的I/O、中断等参数。dev-do_ioctl()如果用户调用ioctl时类型在SIOCDEVPRIVATE和SIOCDEVPRIVATE+15之间,系统会调用驱动程序的这个方法。一般是设置设备的专用数据。读取信息也是通过ioctl调用进行。除次之外驱动程序还可以提供一个dev-get_stats方法,返回一个enet_statistics结构,包含发送接收的统计信息。ioctl的处理在net/core/dev.c的dev_ioctl()和dev_ifsioc()里。2.3网络驱动程序中用到的数据结构最重要的是网络设备的数据结构。定义在include/linux/netdevice.h里。它的注释已经足够详尽。struct device/*This is the first field of thevisiblepart of this structure*(i.e.as seen by users in theSpace.cfile).It is the name*the interface.*/char*name;/*I/O specific fields-FIXME:Merge these and struct ifmap into one*/unsigned long rmem_end;/*shmemrecvend*/unsigned long rmem_start;/*shmemrecvstart*/unsigned long mem_end;/*shared mem end*/unsigned long mem_start;/*shared mem start*/unsigned long base_addr;/*device I/O address*/unsigned char irq;/*device IRQ number*/*Low-level status flags.*/volatile unsigned char start,/*start an operation*/interrupt;/*interrupt arrived*/*在处理中断时interrupt设为1,处理完清0。*/unsigned long tbusy;/*transmitter busy must be long for bitops*/struct device*next;/*The device initialization function.Called only once.*/*指向驱动程序的初始化方法。*/int(*init)(struct device*dev);/*Some hardware also needs these fields,but they are not part of the usual set specified in Space.c.*/*一些硬件可以在一块板上支持多个接口,可能用到if_port。*/unsigned char if_port;/*Selectable AUI,TP,.*/unsigned char dma;/*DMA channel*/struct enet_statistics*(*get_stats)(struct device*dev);/*This marks the end of thevisiblepart of the structure.All*fields hereafter are internal to the system,and may change at*will(read:may be cleaned up at will).*/*These may be needed for future network-power-down code.*/*trans_start记录最后一次成功发送的时间。可以用来确定硬件是否工作正常。*/unsigned long trans_start;/*Time(in jiffies)of last Tx*/unsigned long last_rx;/*Time of last Rx*/*flags里面有很多内容,定义在include/linux/if.h里。*/unsigned short flags;/*interface flags(a la BSD)*/unsigned short family;/*address family ID(AF_INET)*/unsigned short metric;/*routing metric(not used)*/unsigned short mtu;/*interface MTU value*/*type标明物理硬件的类型。主要说明硬件是否需要arp。定义在include/linux/if_arp.h里。*/unsigned short type;/*interface hardware type*/*上层协议层根据hard_header_len在发送数据缓冲区前面预留硬件帧头空间。*/unsigned short hard_header_len;/*hardware hdr length*/*priv指向驱动程序自己定义的一些参数。*/void*priv;/*pointer to private data*/*Interface address info.*/unsigned char broadcastMAX_ADDR_LEN;/*hw bcast add*/unsigned char pad;/*make dev_addr aligned to 8bytes*/unsigned char dev_addrMAX_ADDR_LEN;/*hw address*/unsigned char addr_len;/*hardware address length*/unsigned long pa_addr;/*protocol address*/unsigned long pa_brdaddr;/*protocol broadcast addr*/unsigned long pa_dstaddr;/*protocol P-P other side addr*/unsigned long pa_mask;/*protocol netmask*/unsigned short pa_alen;/*protocol address length*/struct dev_mc_list*mc_list;/*Multicast mac addresses*/int mc_count;/*Number of installed mcasts*/struct ip_mc_list*ip_mc_list;/*IP multicast filter chain*/_u32 tx_queue_len;/*Max frames per queue allowed*/*For load balancing driver pair support*/unsigned long pkt_queue;/*Packets queued*/struct device*slave;/*Slave device*/struct net_alias_info*alias_info;/*main dev alias info*/struct net_alias*my_alias;/*alias devs*/*Pointer to the interface buffers.*/struct sk_buff_head buffsDEV_NUMBUFFS;/*Pointers to interface service routines.*/int(*open)(struct device*dev);int(*stop)(struct device*dev);int(*hard_start_xmit)(struct sk_buff*skb,struct device*dev);int(*hard_header)(struct sk_buff*skb,struct device*dev,unsigned short type,void*daddr,void*saddr,unsigned len);int(*rebuild_header)(void*eth,struct device*dev,unsigned long raddr,struct sk_buff*skb);#define HAVE_MULTICAST void(*set_multicast_list)(struct device*dev);#define HAVE_SET_MAC_ADDR int(*set_mac_address)(struct device*dev,void*addr);#define HAVE_PRIVATE_IOCTL int(*do_ioctl)(struct device*dev,struct ifreq*ifr,int cmd);#define HAVE_SET_CONFIG int(*set_config)(struct device*dev,struct ifmap*map);#define HAVE_HEADER_CACHE void(*header_cache_bind)(struct hh_cache*hhp,struct device*dev,unsigned short htype,_u32 daddr);void(*header_cache_update)(struct hh_cache*hh,struct device*dev,unsigned char*haddr);#define HAVE_CHANGE_MTU int(*change_mtu)(struct device*dev,int new_mtu);struct iw_statistics*(*get_wireless_stats)(struct device*dev);2.4常用的系统支持2.4.1内存申请和释放include/linux/kernel.h里声明了kmalloc()和kfree()。用于在内核模式下申请和释放内存。void*kmalloc(unsigned int len,int priority);void kfree(void*_ptr);与用户模式下的malloc()不同,kmalloc()申请空间有大小限制。长度是2的整次方。可以申请的最大长度也有限制。另外kmalloc()有priority参数,通常使用时可以为GFP_KERNEL,如果在中断里调用用GFP_ATOMIC参数,因为使用GFP_KERNEL则调用者可能进入sleep状态,在处理中断时是不允许的。kfree()释放的内存必须是kmalloc()申请的。如果知道内存的大小,也可以用kfree_s()释放。2.4.2 request_irq()、free_irq()这是驱动程序申请中断和释放中断的调用。在include/linux/sched.h里声明。request_irq()调用的定义:int request_irq(unsigned int irq,void(*handler)(int irq,void*dev_id,struct pt_regs*regs),unsigned long irqflags,const char*devname,void*dev_id);irq是要申请的硬件中断号。在Intel平台,范围0-15。handler是向系统登记的中断处理函数。这是一个回调函数,中断发生时,系统调用这个函数,传入的参数包括硬件中断号,device id,寄存器值。dev_id就是下面的request_irq时传递给系统的参数dev_id。irqflags是中断处理的一些属性。比较重要的有SA_INTERRUPT,标明中断处理程序是快速处理程序(设置SA_INTERRUPT)还是慢速处理程序(不设置SA_INTERRUPT)。快速处理程序被调用时屏蔽所有中断。慢速处理程序不屏蔽。还有一个SA_SHIRQ属性,设置了以后运行多个设备共享中断。dev_id在中断共享时会用到。一般设置为这个设备的device结构本身或者NULL。中断处理程序可以用dev_id找到相应的控制这个中断的设备,或者用irq2dev_map找到中断对应的设备。void free_irq(unsigned int irq,void*dev_id);2.4.3时钟时钟的处理类似中断,也是登记一个时间处理函数,在预定的时间过后,系统会调用这个函数。在include/linux/timer.h里声明。struct timer_liststruct timer_list*next;struct timer_list*prev;unsigned long expires;unsigned long data;void(*function)(unsigned long);void add_timer(struct timer_list*timer);int del_timer(struct timer_list*timer);void init_timer(struct timer_list*timer);使用时钟,先声明一个timer_list结构,调用init_timer对它进行初始化。time_list结构里expires是标明这个时钟的周期,单位采用jiffies的单位。jiffies是Linux一个全局变量,代表时间。它的单位随硬件平台的不同而不同。系统里定义了一个常数HZ,代表每秒种最小时间间隔的数目。这样jiffies的单位就是1/HZ。Intel平台jiffies的单位是1/100秒,这就是系统所能分辨的最小时间间隔了。所以expires/HZ就是以秒为单位的这个时钟的周期。function就是时间到了以后的回调函数,它的参数就是timer_list中的data。data这个参数在初始化时钟的时候赋值,一般赋给它设备的device结构指针。在预置时间到系统调用function,同时系统把这个time_list从定时队列里清除。所以如果需要一直使用定时函数,要在function里再次调用add_timer()把这个timer_list加进定时队列。2.4.4 I/O I/O端口的存取使用:inline unsigned int inb(unsigned short port);inline unsigned int inb_p(unsigned short port);inline void outb(char value,unsigned short port);inline void outb_p(char value,unsigned short port);在include/adm/io.h里定义。inb_p()、outb_p()与inb()、outb_p()的不同在于前者在存取I/O时有等待(pause)一适应慢速的I/O设备。为了防止存取I/O时发生冲突,Linux提供对端口使用情况的控制。在使用端口之前,可以检查需要的I/O是否正在被使用,如果没有,则把端口标记为正在使用,使用完后再释放。系统提供以下几个函数做这些工作。int check_region(unsigned int from,unsigned int extent);void request_region(unsigned int from,unsigned int extent,const char*name);void release_region(unsigned int from,unsigned int extent);其中的参数from表示用到的I/O端口的起始地址,extent标明从from开始的端口数目。name为设备名称。2.4.5中断打开关闭系统提供给驱动程序开放和关闭响应中断的能力。是在include/asm/system.h中的两个定义。#define cli()_asm_ _volatile_(cli:)#define sti()_asm_ _volatile_(sti:)2.4.6打印信息类似普通程序里的printf(),驱动程序要输出信息使用printk()。在include/linux/kernel.h里声明。int printk(const char*fmt,.);其中fmt是格式化字符串。.是参数。都是和printf()格式一样的。2.4.7注册驱动程序如果使用模块(module)方式加载驱动程序,需要在模块初始化时把设备注册到系统设备表里去。不再使用时,把设备从系统中卸除。定义在drivers/net/net_init.h里的两个函数完成这个工作。int register_netdev(struct device*dev);void unregister_netdev(struct device*dev);dev就是要注册进系统的设备结构指针。在register_netdev()时,dev结构一般填写前面11项,即到init,后面的暂时可以不用初始化。最重要的是name指针和init方法。name指针空(NULL)或者内容为或者name0为空格(space),则系统把你的设备做为以太网设备处理。以太网设备有统一的命名格式,ethX。对以太网这么特别对待大概和Linux的历史有关。init方法一定要提供,register_netdev()会调用这个方法让你对硬件检测和设置。register_netdev()返回0表示成功,非0不成功。2.4.8 sk_buff Linux网络各层之间的数据传送都是通过sk_buff。sk_buff提供一套管理缓冲区的方法,是Linux系统网络高效运行的关键。每个sk_buff包括一些控制方法和一块数据缓冲区。控制方法按功能分为两种类型。一种是控制整个buffer链的方法,另一种是控制数据缓冲区的方法。sk_buff组织成双向链表的形式,根据网络应用的特点,对链表的操作主要是删除链表头的元素和添加到链表尾。sk_buff的控制方法都很短小以尽量减少系统负荷。(translated from article written by Alan Cox)常用的方法包括:.alloc_skb()申请一个sk_buff并对它初始化。返回就是申请到的sk_buff。.dev_alloc_skb()类似alloc_skb,在申请好缓冲区后,保留16字节的帧头空间。主要用在Ethernet驱动程序。.kfree_skb()释放一个sk_buff。.skb_clone()复制一个sk_buff,但不复制数据部分。.skb_copy()完全复制一个sk_buff。.skb_dequeue()从一个sk_buff链表里取出第一个元素。返回取出的sk_buff,如果链表空则返回NULL。这是常用的一个操作。.skb_queue_head()在一个sk_buff链表头放入一个元素。.skb_queue_tail()在一个sk_buff链表尾放入一个元素。这也是常用的一个操作。网络数据的处理主要是对一个先进先出队列的管理,skb_queue_tail()和skb_dequeue()完成这个工作。.skb_insert()在链表的某个元素前插入一个元素。.skb_append()在链表的某个元素后插入一个元素。一些协议(如TCP)对没按顺序到达的数据进行重组时用到skb_insert()和skb_append()。.skb_reserve()在一个申请好的sk_buff的缓冲区里保留一块空间。这个空间一般是用做下一层协议的头空间的。.skb_put()在一个申请好的sk_buff的缓冲区里为数据保留一块空间。在alloc_skb以后,申请到的sk_buff的缓冲区都是处于空(free)状态,有一个tail指针指向free空间,实际上开始时tail就指向缓冲区头。skb_reserve()在free空间里申请协议头空间,skb_put()申请数据空间。见下面的图。.skb_push()把sk_buff缓冲区里数据空间往前移。即把Head room中的空间移一部分到Data area。.skb_pull()把sk_buff缓冲区里Data area中的空间移一部分到Head room中。-|Tail room(free)|-After alloc_skb()-|Head room|Tail room(free)|-After skb_reserve()-|Head room|Data area|Tail room(free)|-After skb_put()-|Head|skb_|Data|Tail room(free)|room|push|Data area|-After skb_push()-|Head|skb_|Data area|Tail room(free)|pull|Head room|-After skb_pull()三.编写Linux网络驱动程序中需要注意的问题3.1中断共享Linux系统运行几个设备共享同一个中断。需要共享的话,在申请的时候指明共享方式。系统提供的request_irq()调用的定义:int request_irq(unsigned int irq,void(*handler)(int irq,void*dev_id,struct pt_regs*regs),unsigned long irqflags,const char*devname,void*dev_id);如果共享中断,irqflags设置SA_SHIRQ属性,这样就允许别的设备申请同一个中断。需要注意所有用到这个中断的设备在调用request_irq()都必须设置这个属性。系统在回调每个中断处理程序时,可以用dev_id这个参数找到相应的设备。一般dev_id就设为device结构本身。系统处理共享中断是用各自的dev_id参数依次调用每一个中断处理程序。3.2硬件发送忙时的处理主CPU的处理能力一般比网络发送要快,所以经常会遇到系统有数据要发,但上一包数据网络设备还没发送完。因为在Linux里网络设备驱动程序一般不做数据缓存,不能发送的数据都是通知系统发送不成功,所以必须要有一个机制在硬件不忙时及时通知系统接着发送下面的数据。一般对发送忙的处理在前面设备的发送方法(hard_start_xmit)里已经描述过,即如果发送忙,置tbusy为1。处理完发送数据后,在发送结束中断里清tbusy,同时用mark_bh()调用通知系统
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 活动舞蹈老师合同范本
- 住建部 房屋合同范本
- 酿酒作坊出售合同范本
- 运送车辆服务合同范本
- 家庭教育心得体会怎么写10篇
- 植物综合题目大全及答案
- 直面困境申论题目及答案
- 零售策略市场分析研究
- 婚庆活动方案
- 环境监测行业智能化转型路径与2025年数据质量控制要点报告
- 工程施工队伍管理制度
- 2025年室内设计师劳动雇佣合同范文
- 2025睿实消防自动跟踪定位射流灭火系统说明书
- 《数字技术应用 基础模块(WPS Office 上册)》 课件全套 第1-3单元 探索数字世界 数字技术应用基础 -编程的魅力 程序设计入门
- 餐饮服务与数字化运营 习题及答案 项目二
- 鼻的症状学相关知识
- 中职生劳动教育试题答案
- 现代学徒制课题:市域产教联合体与行业产教融合共同体内开展现场工程师培养的机制创新研究(研究思路模板、技术路线图)
- 2024年《数字摄影技术》考试复习题库(含答案)
- 医疗纠纷讲座
- 一氧化碳安全培训
评论
0/150
提交评论