jununfly
驱动牛犊
驱动牛犊
  • 注册日期2008-10-17
  • 最后登录2010-06-01
  • 粉丝0
  • 关注0
  • 积分86分
  • 威望560点
  • 贡献值2点
  • 好评度0点
  • 原创分0分
  • 专家分0分
阅读:2694回复:6

适合新手 翻译后的Rolling your owns

楼主#
更多 发布于:2008-11-19 15:08
 我也是新手,这是看文章的时候顺手翻译的一篇,因为是新手的翻译,所以难免在理解有偏差,不过应该比较适合新手比照着老牛们的翻译和理解或者原版英文看
大部分内容是直译加中文的写作思维。
贴文的目的是感谢坛子及群上各位对我的帮助,也希望能为需要的人提供参考。

滚动你自己的IRPs
翻译: Victor Zhang
Content provided by OSR Open Systems Resources, Inc.
版本: 200811071139
本文将从如何创建"你自己的"IRPs开始,然后我们将讲述一些I/O管理器提供的例程来进一步改善IRPs的分配和管理
工作.
常常有NT驱动程序程序员提到这样一个问题:如何执行来自他们的驱动程序内部的I/O操作.这个问题表现为两种情
形: 当只能使用FILE_OBJECT而Zw系列例程都依赖于文件句柄时,如何执行I/O;为什么从ZwCreateFile返回句柄后,
这个句柄不能为他们的驱动程序所用.问题的本质在于如何执行来自他们的驱动程序的I/O,尤其是在多线程环境中.
当然,存在一些解决这个问题的方法,但只有一个关键的方法就是你的驱动程序真实地创建了它自己的IRPs.IRP包含
了一个I/O操作在任意线程上下文中被完成的所有必需的信息.IRPs依赖FILE_OBJECTs和DEVICE_OBJECTs,它们在
任何一个上下文中都是合法的,而文件句柄仅在特定进程的上下文中是合法的.这篇文章讲述和演示了各种你可以达
成这个目的的方法.所有我们讲述的东西都基于DDK中的材料和信息,并且你不需要使用任何未公开的内核APIs.
你的驱动程序需要创建自己的IRPs有很多原因.这包括:与文件系统通信来在文件上执行I/O,或者要利用NT FS支持
的仅内核才有的特性.可能你的驱动程序正在提高一个现有设备的性能,比如通过NT容错驱动程序(FTDISK)来完成.
可能你有两个协同运作的驱动程序,而你需要在它们之间调用.或者,可能你正在NT FS上实现一个物理FS,并且需要
与一个媒体驱动程序或者传输驱动程序通信.不管什么原因,在NT中创建你自己的IRPs并传递它们是实现这个任务
的最佳途径.
分配
有两种方法能分配IRPs.最简单的方法是调用IoAllocateIrp(...).I/O管理器将会分配一个IRP附带一个适当的I/O栈位
置编号(你在这个调用中指定的). 更常见的方法是分配I/O请求.警告:不管怎样,如果你已经调用了IoAllocateIrp(...),
不要再调用IoInitializeIrp(...).DDK中的说明已经使很多无辜的受害者迷失在这点上.在NT 3.51中,I/O管理器已经清
除了IRP的一个重要域(Zoned标记),这导致后来对IoFreeIrp(...)的调用会在这个IRP上调用ExFreePool(...).如果这
个IRP是来自"zone"而不是池,则将腐化非分页的内存池.
I/O管理器输出IoInitializeIrp(...)有一个原因.那就是你的驱动程序可能希望创建自己的IRPs.一种方法是使用
ExAllocatePool(...)从非分页的内存池中分配内存.你调用IoInitializeIrp(...)来用适当的IRP格式初始化这个新分配
的内存池.你的驱动程序可以使用IoSizeOfIrp(...)来计算分配给IRP自身的内存池的正确大小.一旦你有从非分页的
内存池中分配来的IRP,你就可以接着调用IoInitializeIrp在IRP自身内部建立域.在图1中演示了这个过程的一个例子:
PIRP MyAllocateIrp(CCHAR NumberOfStackLocations) {
 // 要分配的IRP的大小
 USHORT IrpSize = IoSizeOfIrp(NumberOfStackLocations);
 // 从非分页内存池中分配IRP
 Irp = ExAllocatePool(NonPagedPool, IrpSize);
 if (!Irp) {
  return 0; // 失败
 }
 
 // 初始化IRP
 IoInitializeIrp(Irp, IrpSize, NumberOfStackLocations);
 // 返回新分配的IRP
 return Irp;
}
通常,驱动程序依靠I/O管理器来进行IRPs的分配和管理.有这样的例子,然而,当你的驱动程序分配和管理它自己的
IRPs时这个工作会变得更加高效.
当NT首先启动时,I/O管理器会创建两个旁观列表:一个给IRPs附带一个单一的I/O栈位置,一个附带四个I/O栈位置.
当你以更大的栈大小分配IRPs或当旁观列表为空时,I/O管理器就会从非分页的内存池中分配新的IRPs.如果你已经
知道你将要使用的IRPs的需要大于四个I/O栈位置的大小时,你可以通过用你自己的free列表(在NT3.51中这可能是
一个列表或者一个zone,在NT 4.0中是一个非分页的旁观列表)保存IRPs的方式来稍微提高下性能.换句话说,当你的
驱动程序首先已经启动时它可以创建一个IRPs池并在一个私有的列表中保存IRPs.当你的驱动程序需要一个IRP时
它可以从这个列表中分配一个,然后在I/O操作完成时把它返回给这个列表,这样就消除了分配和释放池内存的消耗.
OSR网页上的代码例子在文件"roll.c"中展示了一个这样做的简单框架.
如何让I/O管理器把IRP送回给你的驱动程序,从而使这个IRP能被返回给你的旁观列表呢?只需使用一个I/O完成例
程.在了解了I/O完成例程如何真正的工作之后,你就会知道即使你的驱动程序在IRPs中没有一个由它创建的I/O栈位
置,它也能注册一个I/O完成例程.
这是为什么?如果你回想起完成例程是如何被使用的,你就应该认识到最后被调用的(最底层的)驱动程序不总是需要
一个I/O完成例程的(毕竟,是驱动完成I/O请求的).因此,最后一个I/O栈位置能被用来存储下一个直到最后的驱动程
序的完成例程.向上继续这个过程直到驱动程序调用链的顶部,我们将会得到一个我们可以处理的额外的完成例程,且
它对于IRP最初的创建者来说是可用的.
对于IRP的创建者,设立这个I/O完成例程和中间的驱动程序如何设立它们的I/O完成例程是相同的(对
IoSetCompletionRoutine(...)的一个简单的调用).我们将在后面的部分讲述如何创建你的完成例程.
创建
一旦你已经分配了你的IRP,无论是通过使用IoAllocateIrp,还是来自你自己的旁观列表,你都必须初始化I/O请求来
指出你正在请求的下级驱动程序的操作是什么.无论如何,你的驱动程序必须在这里做这个工作,这和当调用一个下级
驱动程序时你将要实现它是一样的.其他的工作就是你的驱动程序现在负责初始化你的IRP中的其他域(表1).
MdlAddress   这个域将指向包含数据(如果有的话)的MDL
Flags    任何适当的标志(ntddk.h中定义的IRP_ flags)
AssociatedIrp.SystemBuffer  这个I/O请求的任何数据Buffer
RequestorMode    用户模式或者内核模式.有代表性地,如果被传递的参数应该被确
    认则为用户模式,否则为内核模式
UserBuffer    这个I/O请求的任何数据Buffer
Tail.Overlay.Thread   给最初的请求者的PETHREAD
表1

当然,在这些域中,有一些是你的特定的I/O操作所不需要的(例如MdlAddress,AssociatedIrp.SystemBuffer和
UserBuffer,只有其中的一个可能被你的驱动程序使用).当然,指定要使用的那个域将依赖于正在执行的准确的I/O操
作而改变.
Tail.Overlay.Thread数据结构只对某些类型的设备来说是重要的,比如可移动的媒体驱动程序,以便系统知道如何处
理"弹出的错误"比如当媒体没有被加载进它自己的驱动时出现的终止/重试/取消对话框.
存在一些不同意义的IRP标记,用于控制底层的驱动程序(特别是FS)将会如何解释它自己的I/O请求的内容(列表1).
-IRP_NOCACHE    – I/O请求的数据应该从真实的援助媒体中读出而不是从存储器中.
-IRP_PAGING_IO    – 正被提及的I/O操作正在执行内存分页的I/O .这个位被内存管理器
       使用.
-IRP_MOUNT_COMPLETION   – 正被提及的I/O操作正在执行一个绑定操作.
-IRP_SYNCHRONOUS_API   – 正被提及的API期望同步的行为. 这个位被设置时,同步行为被建议,但
       不是必需的.
-IRP_ASSOCIATED_IRP   – 正被提及的IRP关联更多的I/O操作.
-IRP_BUFFERED_IO    – AssociatedIrp.SystemBuffer域有效.
-IRP_DEALLOCATE_BUFFER   – 系统buffer从池中被分配而不应该被I/O管理器分配.
-IRP_INPUT_OPERATION   – I/O操作用于输入.内存管理器用它来表示一个正被操作的页.
-IRP_SYNCHRONOUS_PAGING_IO  – 内存分页的操作应该同步地完成.这个位被内存管理器使用.
-IRP_CREATE_OPERATION   – IRP表示一个FS创建操作.
-IRP_READ_OPERATION   – IRP表示一个读操作.
-IRP_WRITE_OPERATION   – IRP表示一个写操作.
-IRP_CLOSE_OPERATION   – IRP表示一个关闭操作.
-IRP_DEFER_IO_COMPLETION   – IRP应该被异步地处理.虽然当这个位被设置时,异步行为被建议,但不
       被要求.
列表1

不管什么时候都要谨慎地设置IRP中的任何一个位,因为它们将对底层的驱动程序(特别是FS)对待I/O操作的方式产
生根本的影响.
因为被记录的早一些,你的驱动程序也负责设立"下一个"I/O栈位置.既然如此这种情况只发生在首个I/O栈位置上.调
用IoGetNextIrpStackLocation(...)来重获一个指向首个I/O栈位置的指针.这为下一个将被调用的驱动程序(在这里
是首个)返回一个指向其I/O栈位置的指针.你的驱动程序负责初始化表2中的域.
MajorFunction  I/O要执行的主功能代码.
MinorFunction  I/O的一个辅助功能代码.如果没有这个域应该是0.
Flags   任何需要修改I/O操作行为的标记(ntddk.h中定义的SL_* flags).
DeviceObject  表示设备,你的驱动程序将把IRP传给它.
FileObject   文件对象表示这个I/O操作对应的文件.注意这仅用于把IRPs发送给一个FS时.
表2

当处理各种I/O请求时,标记域用来修改底层的驱动程序的行为.标记的意义,它们修改的I/O操作,和I/O操作的意图都
被描述在表3中.
 标记   关联的I/O操作  意图
SL_FORCE_ACCESS_CHECK  CREATE            强制进行安全检查即便这个调用发生在
                内核模式下.  
SL_OPEN_PAGING_FILE  CREATE            正被打开的文件是一个分页的文件.
SL_OPEN_TARGET_ DIRECTORY CREATE            如果目录存在则正被打开的文件不需要
                存在.用于为一个后来的重命名操作创建
                一个文件对象.
SL_CASE_SENSITIVE   CREATE            文件名应该区分大小写.
SL_KEY_SPECIFIED   READ/WRITE           关键参数有效.
SL_OVERRIDE_VERIFY_VOLUME   READ/WRITE/DEVICE CONTROL   对于可移动的媒体,即使在驱动程序的设
                备对象中设置了DO_VERIFY_VOLUME
                位,这个I/O也会被执行.
SL_WRITE_THROUGH   READ/WRITE            应该通过存储器写这个数据
SL_FT_SEQUENTIAL_WRITE  READ/WRITE           ???(FT Disk)
SL_FAIL_IMMEDIATELY  LOCK            当这个锁不能马上被承认时这个操作应
                该失败.
SL_EXCLUSIVE_LOCK  LOCK            这个锁用于独占访问指定的范围.
SL_RESTART_SCAN      DIRECTORY_CONTROL_QUERY_EA这个目录的枚举或者EA列表应该从这个
                列表的起始位置开始.
SL_RETURN_SINGLE_ENTRY     DIRECTORY_CONTROL_QUERY_EA至多一个入口应该作为查询目录或者EA
                内容的结果被返回给调用者.
SL_INDEX_SPECIFIED     DIRECTORY_CONTROL_QUERY_EA在目录列表或者EAs中的当前位置应该基
                于指定的索引的值被设置.
SL_WATCH_TREE      DIRECTORY_CONTROL           对于一个目录改变通知,指定的请求涉及
                整个目录树.
SL_ALLOW_RAW_MOUNT     FILE_SYSTEM_CONTROL           当处理一个绑定操作时,如果没有其他的
                文件系统绑定这个驱动器,RAW文件系统
                应该绑定它.
表3
最后,对于特定的I/O操作,你的驱动程序也必须初始化I/O指定的参数.就读或写来说,这将包含偏移量,长度和I/O操
作关键的值.

完成
有时,当你正在创建自己的IRPs时,你将会提供一个I/O完成例程.这个指定的规则在这里没有明确地被要求遵守,但如
同NT中的大多数东西一样,如果你做错了系统将会在将来的某些时候崩溃.
提供一个完成例程的一个最重要的原因是你可以因此重用一个I/O操作.退一步说,除了DDK文件中提到的一个原因,
你可以释放一个I/O操作.这使得I/O管理器不需要在这个操作上执行I/O完成处理.不论二者中的哪一种,你通过从你
的完成例程中返回STATUS_MORE_PROCESSING_REQUIRED来通知I/O管理器停止I/O的完成处理.
那么什么时候不应该使用一个完成例程呢?当你不关心这个I/O操作的真实的完成状态,或者当你不能从你的I/O完
成例程中释放IRP时.随后的情况在DDK文件中没有描述,但对于纠正系统行为来说非常重要.有代表性的,当I/O管理
器为一个线程创建一个I/O操作时,关联这个I/O的IRP被存放到一个脱离线程的连接表上(IRP中"ThreadListEntry"
域).这允许NT在线程退出时做I/O清理.如果你的驱动程序有一个返回STATUS_MORE_PROCESSING_REQUIRED
并调用IoFreeIrp(...)的完成例程,IRP可能仍然会保留在这个线程的I/O列表上,这为将来的某些时候会发生重大问题
提供了保证.其结果是一些用于IRP创建的I/O管理器函数把这个IRP添加到线程的列表上,尽管另一些不会.因此,当构
建你的完成例程时,检查和确保你的IRP没有在线程的I/O列表上可能会是个好主意.
一旦你的完成例程返回STATUS_MORE_PROCESSING_REQUIRED,I/O管理器停止任何额外的I/O处理.因此,从你
的完成例程内部,你可以不受约束地做几乎任何你想做的事情.如你所想,总有附加说明.首先,从你的完成例程内部,你
不能假设你在最初启动这个I/O操作的线程的上下文中,因此,对象句柄和用户地址不一定有效;第二,你不能假设你的
完成例程已经在PASSIVE_LEVEL级被调用过.取而代之的是,它可能在DISPATCH_LEVEL级被调用,或许是因为你调
用的驱动程序从它的DPC例程内部中完成了I/O请求.紧记当你设计你自己的I/O完成例程时,就像你需要做一切复杂
的完成处理一样,你可能需要在一个工作者例程中做这个工作仅仅是为了确保它是安全的.

重用
我们已讲述了你的驱动程序如何能把这些IRPs保存到一个旁观列表中.当你不再需要IRP时你的驱动程序能在你的
完成例程中把它放回到这个旁观列表上.然而,你可能不得不在准备好重用这个IRP之前做一些额外的处理.
例如,你进入一个FSD中调用并为这个请求(通过设置Irp->UserBuffer)指定一个用户Buffer,则FSD可能会创建一个
MDL来描述这个Buffer.此时,从你必须执行IRP的清理开始,你负责unmapping,unlocking,和freeing关联这个IRP
的MDL.通过调用MmGetSystemAddressForMdl(...)获得MDL的系统地址并把返回的地址传给
MmUnmapLockedPages(...)能简单地完成这些事情.一旦完成你就调用MmUnlockPages(...).
如果你确实看过ntddk.h中的MDL操纵例程,你可能已经注意到,使用MDL中用于决定这个MDL是否已经被映射(或
者被锁定)的域能从根本上优化这个程序.不过,要这样做你必须查看MDL的内部.如果没有,就不需要调用
MmUnmapLockedPages.因为DDK中说得非常清楚:MDL本身是不透明的,写像这样有风险的代码会打断未来NT
中的一些释放工作.对于你的工程,你可能会认定为了短期的性能提高这个潜在的风险值得尝试.

捷径
到这里,我们已经讲述了如何创建你自己的IRPs,现在说说I/O管理器提供的至少三个你可以用来缓解辛苦的"捷径"
例程.虽然这些I/O管理器函数没有创建你自己的IRPs灵活,但是它们能被用来快速地创建大多数IRP并且你能在你的
驱动程序中完成初始化.这些例程是:
- IoBuildAsynchronousFsdRequest(...)
- IoBuildSynchronousFsdRequest(...)
- IoBuildDeviceIoControlRequest(...)
这三个调用都没有初始化IRP中的FileObject参数,因此如果你正在一个FS中调用它们,你的驱动程序将不得不设置
这个域.知道如何在驱动程序中创建自己的IRP后,你可以扩充IRPs来适应你自己的需求.
像之前说的,凭借这些I/O管理器帮助函数来使用一个完成例程可能有点复杂.你不能在你的完成例程中用
IoBuildSynchronousFsdRequest(...)和IoBuildDeviceIoControlRequest(...)来释放IRPs,但是你能在
IoBuildAsynchronousFsdRequest(...)中释放IRP.这是因为前两个例程把IRP添加到线程的IRP列表上了.从没有I/O
管理器调用把IRP从这个列表中移除开始,唯一的选择是允许完成这个请求.
使用这些函数大大地简化了IRPs的创建,但也限制了你的驱动程序只能进行这些帮助例程支持的操作.
对于IoBuildDeviceIoControlRequest,仅IRP_MJ_DEVICE_CONTROL和IRP_MJ_INTERNAL_DEVICE_CONTROL
这两个操作.对于 IoBuildSynchronousFsdRequest(...)和IoBuildAsynchronousFsdRequest(...)它们可用的仅有
IRP_MJ_READ,IRP_MJ_WRITE,IRP_MJ_FLUSH_BUFFERS和IRP_MJ_SHUTDOWN.伴随这篇文章的例子(OSR网
页上)演示了这三个函数各自的用法.
当然,如果你需要你的驱动程序执行额外的一些不得不执行的操作,你还是得创建自己的IRPs.
文件sync.c中,有一个关于使用IoBuildSynchronousFsdRequest的例子作为这篇文章的补充例子,你可以在OSR网
页上获得.

你可能会问那些FSFD提供的"高级"特性是什么?
我们已经在以前的文章中提到了一些.其中两个对NT 4.0来说是新的.
基于DPC(延后的程序调用)的I/O
对于某些类型的驱动程序,具备在DPC级进行I/O的能力能极大地改进整体性能.例如,假设你正在实现一个进行数据
收集的驱动程序.直接来自你的DPC通过队列化FS的I/O操作,你能不依赖一个用户模式下的中间的"数据收集"程序,
用很短的命令把数据带给磁盘.
你要怎么做?简单.从你的DPC例程开始你可以:创建一个IRP;绑定一个MDL,它描述了你刚从你的数据获取设备中读
来的数据;指出这个I/O请求来自一个DPC例程(IRP_MN_DPC);发送I/O操作给FS.FS将会返回STATUS_PENDING(
毕竟,它不能真正地在DISPATCH_LEVEL级进行I/O).通过这个技术你可以显著地优化你的驱动程序的性能,超过了
创建一个应用程序来与你的驱动程序交流并接着把数据写到磁盘上的"传统"方法.
小心的使用这个!实际上不是所有的FS都支持在DISPATCH_LEVEL级进行I/O.
基于MDL的I/O
这是另外的一个有趣的技巧,当创建你自己的IRPs时你能在你的驱动程序中利用它.当你正对FS进行I/O时,它能提供
一个MDL给你.这个MDL直接映射到VM存储器中.通过使用这个方法,有可能避免两次复制数据.实际上这个改变的
完成依赖于I/O操作.注意这个接口不是所有的NT文件系统都支持.
凭借一切基于MDL的I/O例程,执行这个操作你要做出两个调用,不是一个.从FS把MDL提供给你开始,一旦你使用完
MDL为了释放它你必须回调到FS中.

除了你第一次创建一个没有数据缓存参数的IRP这没什么复杂的.通过指定IRP_MN_MDL辅助功能代码,你将告诉FS
你想让它提供MDL给你.除了这,你仅仅像创建一个普通的读操作那样创建一个IRP.
当返回时,你的驱动程序能使用FSD提供的MDL(比如执行对它的设备I/O).当你使用完MDL时你回调到FSD中,指定
一个读操作,但此时辅助功能是IRP_MN_MDL_COMPLETE.这会把MDL释放回给底层的FS.

写的操作与读非常类似,除了FSD返回的MDL中可能没有任何数据.当你为一个写操作从文件系统中请求一个MDL
时,你将写全部的内存,FSD被允许利用这个事实.因此它可能不用读取所有来自将被覆写的磁盘的内存页.避免这样
的不必要的I/O会是一个巨大的性能提升.因此,你的驱动应该期望覆写IRP中指定文件的全部.
一旦MDL已经被FS返回,你的驱动程序填充被MDL描述的缓存.当它准备把这个MDL释放回给底层的FS时,你设置
IRP_MN_MDL_COMPLETE辅助功能代码并把写IRP发送回给FS.
压缩
另外的一个选择是现在出现在NT 4.0中的,一个内核模式驱动程序能重获来自FS的压缩格式的数据.为使用这个选择
一组新的值被添加到读和写的辅助功能的值中:IRP_MN_COMPRESSED选项.这些"位值" 能与现有的MDL操作结
合来重获和存放来自FS的压缩格式的数据.这被SRV用来允许NT 4.0系统之间的压缩格式的数据传输.
结尾
创建你自己的IRPs不过是另一个你能添加到你的技巧库中的工具,它可以在创建真正的NT设备驱动程序时使用.虽
然我们鼓励你使用这些技术,但你应该只在必要时这样做.我们的经验指出在强大的同时,这些技术会带来不必要的复
杂并增加了你调试你的工程的时间.如果你真的需要这些特性那很好.
就像我们在这篇文章的前面提到的,一整套代码例子已经做好,在OSR网页上可获得(ryo.zip,如下表4).这些例子有如
何创建你自己的IRPs,和它必须怎样被完成(没有最终定稿).你可以把它们作为开发你自己的用于发出来自你的驱动
程序的I/O请求的例程的基础.另外,一个完整的驱动程序例子(内核文件拷贝驱动程序,或者简称为"kfc")演示了一个
简单的内核驱动程序,它有两个文件且能用IRPs把这两个文件从内核模式中完整地拷贝出来.

文件   描述
 
async.c   演示使用IoBuildAsynchronousFsdRequest
devctrl.c  演示使用IoBuildDeviceIoControlRequest
roll.c  如何"让你自己的来自不可分页内存池的IRPs动起来"
sync.c  演示使用IoBuildSynchronousFsdRequest
表4
 创建Irps.rar
cheng_5103
驱动牛犊
驱动牛犊
  • 注册日期2003-10-06
  • 最后登录2012-03-21
  • 粉丝0
  • 关注0
  • 积分23分
  • 威望228点
  • 贡献值0点
  • 好评度45点
  • 原创分0分
  • 专家分0分
沙发#
发布于:2008-12-03 10:28
哪几个文件的源代码.能否发我一份啊.先谢过.
我的邮箱:cheng_5103@126.com
cheng
cheng_5103
驱动牛犊
驱动牛犊
  • 注册日期2003-10-06
  • 最后登录2012-03-21
  • 粉丝0
  • 关注0
  • 积分23分
  • 威望228点
  • 贡献值0点
  • 好评度45点
  • 原创分0分
  • 专家分0分
板凳#
发布于:2008-12-03 10:30
能否把哪几个源代码的文件发我一份啊.先谢过大侠了.
cheng_5103@126.com
cheng
Stephenlsd
驱动牛犊
驱动牛犊
  • 注册日期2010-05-25
  • 最后登录2010-11-07
  • 粉丝0
  • 关注0
  • 积分3分
  • 威望31点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
地板#
发布于:2010-11-07 10:08
我下不了,可以发一份到我邮箱stephenlsd@gmail.com么,
谢谢。
wangyangkkx
驱动牛犊
驱动牛犊
  • 注册日期2009-12-30
  • 最后登录2011-07-08
  • 粉丝0
  • 关注0
  • 积分59分
  • 威望431点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
地下室#
发布于:2010-11-23 10:07
附件貌似有问题啊 下不了
liuchangdong007
驱动牛犊
驱动牛犊
  • 注册日期2010-04-15
  • 最后登录2011-07-12
  • 粉丝0
  • 关注0
  • 积分55分
  • 威望511点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
5楼#
发布于:2010-11-23 10:50
是的,下载不了,哪位大侠发一份给我吧,liuchangdong007@163.com
非常感谢!
liuchangdong007
驱动牛犊
驱动牛犊
  • 注册日期2010-04-15
  • 最后登录2011-07-12
  • 粉丝0
  • 关注0
  • 积分55分
  • 威望511点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
6楼#
发布于:2010-11-23 10:54
I download the file from Internet
附件名称/大小 下载次数 最后更新
ryo.zip (20KB)  4 2010-11-23 10:54
游客

返回顶部