Linux设备驱动开发-2简单字符模块

上传人:飞****9 文档编号:61677664 上传时间:2022-03-12 格式:DOCX 页数:16 大小:60.85KB
收藏 版权申诉 举报 下载
Linux设备驱动开发-2简单字符模块_第1页
第1页 / 共16页
Linux设备驱动开发-2简单字符模块_第2页
第2页 / 共16页
Linux设备驱动开发-2简单字符模块_第3页
第3页 / 共16页
资源描述:

《Linux设备驱动开发-2简单字符模块》由会员分享,可在线阅读,更多相关《Linux设备驱动开发-2简单字符模块(16页珍藏版)》请在装配图网上搜索。

1、Linux设备驱动程序-简单字符模块这一章主要通过介绍字符设备的驱动程序编写,来学习Linux设备驱动的基本知识。Globalmem可以为真正的设备驱动程序提供样板。一、主设备号和次设备号主设备号表示设备对应的驱动程序;次设备号由内核使用,用于正确确定设备文件所指的设备。主设备号相同的设备使用相同的驱动程序,次设备号用于区分具体设备的实例。比如PC机中的IDE设备,一般主设备号使用3,WINDOWS进行的分区,一般将主分区的次设备号为1,扩展分区的次设备号为2、3、4,逻辑分区使用5、6.。内核用dev_t类型()来保存设备编号,dev_t是一个32位的数,12位表示主设备号,20位表示次设备

2、号。在实际使用中,是通过中定义的宏来转换格式。(dev_t)-主设备号、次设备号MAJOR(dev_tdev)MINOR(dev_tdev)主设备号、次设备号-(dev_t)MKDEV(intmajor,intminor)对于查看/dev目录下的设备的主次设备号可以使用如下命令:/mnt/yaffsls/dev-lcrw1rootroot5,1Jan100:00consolecrw1rootroot5,64Jan100:00cua0crw1rootroot5,65Jan100:00cua1crw-rw-rw-1rootroot1,7Jan100:00fulldrwxr-xr-x1rootroo

3、t0Jan100:00keyboardcrw-r1rootroot1,2Jan100:00kmemcrw-r1rootroot1,1Jan100:00memdrwxr-xr-x1rootroot0Jan100:00mtddrwxr-xr-x1rootroot0Jan100:00mtdblockcrw-rw-rw-1rootroot1,3Jan100:00nullcrw-r1rootroot1,4Jan100:00portcrw1rootroot108,0Jan100:00pppcrw-rw-rw-1rootroot5,2Jan100:00ptmxcrw-r-r-1rootroot1,8Jan1

4、00:00randomlr-xr-xr-x1rootroot4Jan100:00root-rd/0crw-rw-rw-1rootroot5,0Jan100:00ttycrw1rootroot4,64Jan100:11ttyS0crw1rootroot4,65Jan100:00ttyS1crw-r-r-1rootroot1,9Jan100:00urandomcrw-rw-rw-1 rootroot1,5 Jan 1 00:00 zero建立一个字符设备之前,驱动程序首先要做的事情就是获得设备编号。其这主要函数在中声明:intregister_chrdev_region(dev_tfirst,un

5、signedintcount,char*name);/静态指定设备编号intalloc_chrdev_region(dev_t*dev,unsignedintfirstminor,unsignedintcount,char*name);/动态生成设备编号voidunregister_chrdev_region(dev_tfirst,unsignedintcount);/释放设备编号分配之设备号的最佳方式是:默认采用动态分配,同时保留在加载甚至是编译时指定主设备号的余地。以下是在globalmeml.c中用来获取主设备好的代码:dev_tdevno=MKDEV(globalmem_major,0

6、);/*申请设备号*/if(globalmem_major)result=register_chrdev_region(devno,1,globalmem);else/*动态申请设备号*/result=alloc_chrdev_region(&devno,0,1,globalmem);globalmem_major=MAJOR(devno);if(result0)returnresult;name是和该编号范围file_operations在这部分中,比较重要的是在用函数获取设备编号后,其中的参数中。关联的设备名称,它将出现在/proc/devices二、一些重要的数据结构大部分基本的驱动程序

7、操作涉及及到三个重要的内核数据结构,分别是file和inode,它们的定义都在。三、字符设备的注册内核内部使用structcdev结构来表示字符设备。在内核调用设备的操作之前,必须分配并注册一个或多个structcdev。代码应包含,它定义了structcdev以及与其相关的一些辅助函数。我们一般将cdev结构嵌入到自己的设备特定结构中去如:structglobalmem_devstructcdevcdev;/*cdev结构体*/unsignedcharmemGLOBALMEM_SIZE;/*全局内存*/;注册一个独立的cdev设备的基本过程如下:1、为自己的设备结构体分配空间structgl

8、obalmem_dev*globalmem_devp;/*设备结构体指针*/globalmem_devp=kmalloc(sizeof(structglobalmem_dev),GFP_KERNEL);if(!globalmem_devp)/*申请失败*/result =-ENOMEM;gotofail_malloc;memset(globalmem_devp,0,sizeof(structglobalmem_dev);2、初始化structcdevvoidcdev_init(structcdev*cdev,conststructfile_operations*fops)cdev.owner=

9、THIS_MODULE;4、cdev设置完成,通知内核structcdev的信息(在执行这步之前必须确定你对structcdev的以上设置已经完成!)intcdev_add(structcdev*p,dev_tdev,unsignedcount)5、从系统中移除一个字符设备:voidcdev_del(structcdev*p)以下globalmem的初始化代码/*初始化并注册cdev*/staticvoidglobalmem_setup_cdev(structglobalmem_dev*dev,intindex)interr,devno=MKDEV(globalmem_major,index)

10、;cdev_init(&dev-cdev,&globalmem_fops);dev-cdev.owner=THIS_MODULE;dev-cdev.ops=&globalmem_fops;err=cdev_add(&dev-cdev,devno,1);if(err)printk(KERN_NOTICEError%daddingglobalmem%d,err,index);/*设备驱动模块加载函数*/intglobalmem_init(void)intresult;dev_tdevno=MKDEV(globalmem_major,0);/*申请设备号*/if(globalmem_major)re

11、sult=register_chrdev_region(devno,1,globalmem);else/*动态申请设备号*/result=alloc_chrdev_region(&devno,0,1,globalmem);globalmem_major=MAJOR(devno);if(result0)returnresult;/*动态申请设备结构体的内存*/globalmem_devp=kmalloc(sizeof(structglobalmem_dev),GFP_KERNEL);if(!globalmem_devp)/*申请失败*/result=-ENOMEM;gotofail_malloc

12、;memset(globalmem_devp,0,sizeof(structglobalmem_dev);globalmem_setup_cdev(globalmem_devp,0);return0;fail_malloc:unregister_chrdev_region(devno,1);returnresult;四、内存申请函数Globalmem驱动程序引入了两个Linux内核中用于内存管理的核心函数,它们的定义都在:void*kmalloc(size_tsize,intflags);voidkfree(void*ptr);例子:#includechar*buff;buff=kmalloc

13、(1024,GFP_KERNEL);if(buff!=NULL)kfree(buff);elseprintk(kmallocerrorn);flags的参数GFP_KERNEL请求动态内存总是分配成功,如无则等待。故不能用在中断中。GFP_ATOMIC无条件分配内存,没有立即释放,进程不睡眠。GFP_DMA用于分配连续的物理内存五、open和release、open方法提供给驱动程序以初始化的能力,为以后的操作作准备。应完成的工作如下:(1)检查设备特定的错误(如设备未就绪或硬件问题);(2)如果设备是首次打开,则对其进行初始化;(3)如有必要,更新f_op文件指针;(4)分配并填写置于fil

14、p-private_data里的数据结构。而根据globalmem的实际情况,他的open函数只要完成第四步(将初始化过的structglobalmem_devdev的指针传递到filp-private_data里,以备后用)就好了,所以open函数很简单。但是其中用到了定义在中的container_of宏,源码如下:#definecontainer_of(ptr,type,member)(consttypeof(type*)0)-member)*._mptr=(ptr);(type*)(char*)_mptr-offsetof(type,member);)其实从源码可以看出,其作用就是:通过

15、指针ptr,获得包含ptr所指向数据(是member结构体)的type结构体的指针。即是用指针得到另外一个指针。intglobalmem_open(structinode*inode,structfile*filp)/*将设备结构体指针赋值给文件私有数据指针*/structglobalmem_dev*dev;dev=container_of(inode-i_cdev,structglobalmem_dev,cdev);filp-private_data=dev;return0;第一个参数是结构体成员的指针,第二个参数为整个结构体的类型,第三个参数为传入的第一个参数即结构体成员的类型,返回值为整

16、个结构体的指针。、release方法提供释放内存,关闭设备的功能。应完成的工作如下:(1)释放由open分配的、保存在file-private_data中的所有内容;(2)在最后一次关闭操作时关闭设备。有时执行的内容和exit里面的内容重合,所以本代码有的时候什么也不做。六、read和writeread和write方法的主要作用就是实现内核与用户空间之间的数据拷贝。因为Linux的内核空间和用户空间隔离的,所以要实现数据拷贝就必须使用在中定义的:read方法完成将数据从内核拷贝到应用程序空间,write方法相反,将数据从应用程序空间拷贝到内核。对于这两个方法,参数filp是文件指针,count

17、是请求传输数据的长度,buffer是用户空间的数据缓冲区,ppos(有时候写成fpos都是一个概念)是文件中进行操作的偏移量,类型为64位数。由于用户空间和内核空间的内存映射方式完全不同,所以不能使用象memcpy之类的函数,必须使用如下函数:unsignedlongcopy_to_user(void_user*to,constvoid*from,unsignedlongcount);unsignedlongcopy_from_user(void*to,constvoid_user*from,unsignedlongcount);如果要复制的为简单类型,如char,long,int等,则可以使

18、用简单的put_user()和get_user().如下所示:intval;/内核空间整型变量get_user(val,(int*)arg);/用户空间到内核空间,arg是用户空间的地址put_user(val,(int*)arg);/内核空间到用户空间,arg是用户空间的地址至于read和write的具体函数比较简单,就在实验中验证好了。?read的返回值1 .返回值等于传递给read系统调用的count参数,表明请求的数据传输成功。2 .返回值大于0,但小于传递给read系统调用的count参数,表明部分数据传输成功,根据设备的不同,导致这个问题的原因也不同,一般采取再次读取的方法。3 .

19、返回值=0,表示到达文件的末尾。4 .返回值为负数,表示出现错误,并且指明是何种错误。5 .在阻塞型io中,read调用会出现阻塞。?Write的返回值1 .返回值等于传递给write系统调用的count参数,表明请求的数据传输成功。2 .返回值大于0,但小于传递给write系统调用的count参数,表明部分数据传输成功,根据设备的不同,导致这个问题的原因也不同,一般采取再次读取的方法。3 .返回值=0,表示没有写入任何数据。标准库在调用write时,出现这种情况会重复调用write。4 .返回值为负数,表示出现错误,并且指明是何种错误。错误号的定义参见5 .在阻塞型io中,write调用会出

20、现阻塞。七、模块实验测试结果:rootNEU/#insmod/lib/modules/rootNEU/#Ismodglobalmem29200-Live0xbf000000rootNEU/#cat/proc/devicesCharacterdevices:1 mem2 pty3 ttyp4 /dev/vc/05 tty6 /dev/tty7 /dev/console8 /dev/ptmx9 vcs10 misc13 input14 sound21sg29fb81video4linux90mtd128ptm136pts180usb189usb_device204s3c2410_serial253

21、globalmem254devfsBlockdevices:1ramdisk31mtdblock180ubrootNEU/#rootNEU/#mknod/dev/globalmemc2530rootNEU/#ls/dev/globalmem/dev/globalmemrootNEU/#echohello/dev/globalmemwritten6bytes(s)from0rootNEU/#cat/dev/globalmemread4096bytes(s)from0hellocat:readerror:NosuchdeviceoraddressrootNEU/#八、附录:file_operati

22、ons结构 :structmodule*owner第一个file_operations成员根本不是一个操作;它是一个指向拥有这个结构的模块的指针.这个成员用来在它的操作还在被使用时阻止模块被卸载.几乎所有时间中,它被简单初始化为THIS_MODULE,一个在中定义的宏.loff_t(*llseek)(structfile*,loff_t,int);llseek方法用作改变文件中的当前读/写位置,并且新位置作为(正的)返回值.loff_t参数是一个longoffset,并且就算在32位平台上也至少64位宽.错误由一个负返回值指示.如果这个函数指针是NULL,seek调用会以潜在地无法预知的方式修

23、改file结构中的位置计数器(在file结构一节中描述).ssize_t(*read)(structfile*,char_user*,size_t,loff_t*);用来从设备中获取数据.在这个位置的一个空指针导致read系统调用以-EINVAL(Invalidargument)失败.一个非负返回值代表了成功读取的字节数(返回值是一个signedsize类型,常常是目标平台本地的整数类型).ssize_t(*aio_read)(structkiocb*,char_user*,size_t,loff_t);初始化一个异步读-可能在函数返回前不结束的读操作.如果这个方法是NULL,所有的操作会由r

24、ead代替进行(同步地).ssize_t(*write)(structfile*,constchar_user*,size_t,loff_t*);发送数据给设备.如果NULL,-EINVAL返回给调用write系统调用的程序.如果非负,返回值代表成功写的字节数.ssize_t(*aio_write)(structkiocb*,constchar_user*,size_t,loff_t*);初始化设备上的一个异步写.int(*readdir)(structfile*,void*,filldir_t);对于设备文件这个成员应当为NULL;它用来读取目录,并且仅对文件系统有用.unsignedint

25、(*poll)(structfile*,structpoll_table_struct*);poll方法是3个系统调用的后端:poll,epoll,和select,都用作查询对一个或多个文件描述符的读或写是否会阻塞.poll方法应当返回一个位掩码指示是否非阻塞的读或写是可能的,并且,可能地,提供给内核信息用来使调用进程睡眠直到I/O变为可能.如果一个驱动的poll方法为NULL,设备假定为不阻塞地可读可写.int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);ioctl系统调用提供了发出设备特定命令的方法(例如格式化软盘的

26、一个磁道,这不是读也不是写).另外,几个ioctl命令被内核识别而不必引用fops表.如果设备不提供ioctl方法,对于任何未事先定义的请求(-ENOTTY,设备无这样的ioctl),系统调用返回一个错误.int(*mmap)(structfile*,structvm_area_struct*);mmap用来请求将设备内存映射到进程的地址空间.如果这个方法是NULL,mmap系统调用返回-ENODEV.int(*open)(structinode*,structfile*);尽管这常常是对设备文件进行的第一个操作,不要求驱动声明一个对应的方法.如果这个项是NULL,设备打开一直成功,但是你的驱

27、动不会得到通知.int(*flush)(structfile*);flush操作在进程关闭它的设备文件描述符的拷贝时调用;它应当执行(并且等待)设备的任何未完成的操作.这个必须不要和用户查询请求的fsync操作混淆了.当前,flush在很少驱动中使用;SCSI磁带驱动使用它,例如,为确保所有写的数据在设备关闭前写到磁带上.如果flush为NULL,内核简单地忽略用户应用程序的请求.int(*release)(structinode*,structfile*);在文件结构被释放时引用这个操作.如同open,release可以为NULL.int(*fsync)(structfile*,struct

28、dentry*,int);这个方法是fsync系统调用的后端,用户调用来刷新任何挂着的数据.如果这个指针是NULL,系统调用返回-EINVAL.int(*aio_fsync)(structkiocb*,int);这是fsync方法的异步版本.int(*fasync)(int,structfile*,int);这个操作用来通知设备它的FASYNC标志的改变.异步通知是一个高级的主题,在第6章中描述.这个成员可以是NULL如果驱动不支持异步通知.int(*lock)(structfile*,int,structfile_lock*);lock方法用来实现文件加锁;加锁对常规文件是必不可少的特性,但

29、是设备驱动几乎从不实现它.ssize_t(*readv)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*writev)(structfile*,conststructiovec*,unsignedlong,loff_t*);这些方法实现发散/汇聚读和写操作.应用程序偶尔需要做一个包含多个内存区的单个读或写操作;这些系统调用允许它们这样做而不必对数据进行额外拷贝.如果这些函数指针为NULL,read和write方法被调用(可能多于一次).ssize_t(*sendfile)(structfile*,loff_t*,siz

30、e_t,read_actor_t,void*);这个方法实现sendfile系统调用的读,使用最少的拷贝从一个文件描述符搬移数据到另一个.例如,它被一个需要发送文件内容到一个网络连接的web服务器使用.设备驱动常常使sendfile为NULL.ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);sendpage是sendfile的另一半;它由内核调用来发送数据,一次一页,到对应的文件.设备驱动实际上不实现sendpage.unsignedlong(*get_unmapped_area)(structfile*,u

31、nsignedlong,unsignedlong,unsignedlong,unsignedlong);这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段中.这个任务通常由内存管理代码进行;这个方法存在为了使驱动能强制特殊设备可能有的任何的对齐请求.大部分驱动可以置这个方法为NULL.10int(*check_flags)(int)这个方法允许模块检查传递给fnctl(F_SETFL)调用的标志.int(*dir_notify)(structfile*,unsignedlong);这个方法在应用程序使用fcntl来请求目录改变通知时调用.只对文件系统有用;驱动不需要实现

32、dir_notifyfile_operations结构是整个Linux内核的重要数据结构,它也是file、inode结构的重要成员,表中分别说明结构中主要的成员:表file_operations结构Ownermodule的拥有者。Llseek重新定位读写位置。Read从设备中读取数据。Write向字符设备中写入数据。Readdir只用于文件系统,对设备无用。Ioctl控制设备,除读写操作外的其他控制命令。Mmap将设备内存映射到进程地址空间,通常只用于块设备。Open打开设备并初始化设备。Flush清除内容,一般只用于网络文件系统中。Release关闭设备并释放资源。Fsync实现内存与设备的

33、同步,如将内存数据写入硬盘。Fasync实现内存与设备之间的异步通讯。Lock文件锁定,用于文件共享时的互斥访问。Readv在进行读操作前要验证地址是否可读。Writev在进行写操作前要验证地址是否可写。在嵌入式系统的开发中,我们一般仅仅实现其中几个接口函数:read、write、ioctl、open、release,就可以完成应用系统需要的功能。structfile_operationsglobalmem_fops=.owner=THIS_MODULE,.llseek=scull_llseek,.read=scull_read,.write=scull_write,.ioctl=scull_

34、ioctl,.open=scull_open,.release=scull_release,;staticstructfile_operationsglobalmem_fops=完成了将驱动函数映射为标准接口,上面的这种特殊表示方法不是标准C的语法,这是GNU译器的一种特殊扩展,它使用名字对进行结构字段的初始化,它的好处体现在结构清晰,易于理解,并且避免了结构发生变化带来的许多问题。structfile结构定义于,是设备驱动中第二个最重要的数据结构.系统中每个打开的文件有一个关联的structfile在内核空间).它由内核在open时创建,并传递给在文件上操作的任何函数,直到最后的关闭.在文件

35、的所有实例都关闭后,内核释放这个数据结构.在内核源码中,structfile的指针常常称为file或者filp(filepointer).我们将一直称这个指针为filp以避免和结构自身混淆.因此,file指的是结构,而filp是结构指针.mode_tf_mode;文件模式确定文件是可读的或者是可写的(或者都是),通过位FMODE_READ和FMODE_WRITE.你可能想在你的open或者ioctl函数中检查这个成员的读写许可,但是你不需要检查读写许可,因为内核在调用你的方法之前检查.当文件还没有为那种存取而打开时读或写的企图被拒绝,驱动甚至不知道这个情况.loff_tf_pos;当前读写位置

36、.loff_t在所有平台都是64位(在gcc术语里是longlong).驱动可以读这个值,如果它需要知道文件中的当前位置,但是正常地不应该改变它读和写应当使用它们作为最后参数而收到的指针来更新一个位置,代替直接作用于filp-f_pos.这个规则的一个例外是在llseek方法中,它的目的就是改变文件位置.另一种解释:此变量显示出当前读写位置,而由read,write,llseek等可修改读写位置的函数改变,用在管理文件指针的设备驱动程序上。unsignedintf_flags;这些是文件标志,例如O_RDONLY,O_NONBLOCK,和O_SYNC.驱动应当检查O_NONBLOCK标志来看是

37、否是请求非阻塞操作(我们在第一章的阻塞和非阻塞操作一节中讨论非阻塞I/O);其他标志很少使用.特别地,应当检查读/写许可,使用f_mode而不是f_flags.所有的标志在头文件中定义.structfile_operations*f_op;和文件关联的操作.内核安排指针作为它的open实现的一部分,接着读取它当它需要分派任何的操作时.filp-f_op中的值从不由内核保存为后面的引用;这意味着你可改变你的文件关联的文件操作,在你返回调用者之后新方法会起作用.例如,关联到主编号1(/dev/null,/dev/zero,等等)的open代码根据打开的次编号来替代filp-f_op中的操作.这个做

38、法允许实现几种行为,在同一个主编号下而不必在每个系统调用中引入开销.替换文件操作的能力是面向对象编程的方法重载的内核对等体.void*private_data;open系统调用设置这个指针为NULL,在为驱动调用open方法之前.你可自由使用这个成员或者忽略它;你可以使用这个成员来指向分配的数据,但是接着你必须记住在内核销毁文件结构之前,在release方法中释放那个内存.private_data是一个有用的资源,在系统调用间保留状态信息,我们大部分例子模块都使用它.structdentry*f_dentry;关联到文件的目录入口(dentry)结构.设备驱动编写者正常地不需要关心dentry

39、结构,除了作为filp-f_dentry-d_inode存取inode结构.真实结构有多几个成员,但是它们对设备驱动没有用处.我们可以安全地忽略这些成员,因为驱动从不创建文件结构;它们真实存取别处创建的结构。inode结构inode结构由内核在内部用来表示文件.因此,它和代表打开文件描述符的文件结构是不同的.可能有代表单个文件的多个打开描述符的许多文件结构,但是它们都指向一个单个inode结构.inode结构包含大量关于文件的信息.作为一个通用的规则,这个结构只有2个成员对于编写驱动代码有用:dev_ti_rdev;对于代表设备文件的节点,这个成员包含实际的设备编号.structcdev*i_

40、cdev;structcdev是内核的内部结构,代表字符设备;这个成员包含一个指针,指向这个结构当节点指的是一个字符设备文件时关于驱动程序中staticssize_tglobalmem_write(structfile*filp,constchar_user*buf,size_tsize,loff_t*ppos)staticssize_tglobalmem_read(structfile*filp,char_user*buf,size_tsize,loff_t*ppos)的进一步解释:ppos表示的是文件的读写位置,打开一个设备文件后filp为该文件建立了一个file类型的结构体,其中一个成员为loff_t类型的,表示文件的读写位置,该值和filp紧密联系,如果将设备驱动程序创建为具有管理文件读写位置的功能,就需要处理ppos变量,比如我们的驱动程序中设备驱动程序实现内存的读写功能,那么ppos就是指向内存的地址,当你写了一些数据后,再写的话,这时的ppos就和上一次写操作完成后的位置了,读的时候也应该注意这个问题,如果我们只是简单的测试下write和read的功能的话,那么就不用处理ppos。一般情况下,多数驱动程序不管理文件指针。

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

copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!