游客:  注册 | 登录 | 帮助 返回Whitecell


发表新主题   回复主题
作者
同步与IRP 上一主题 | 下一主题
qiuzhiyu


发贴 14
注册 2007-2-27
状态 离线
同步与IRP

我看到一段《漫谈兼容内核之十二:Windows的APC机制》
开头的一段描述  
"原来,文件操作有“同步”和“异步”之分。普通的写文件操作是同步写,启动这种操作的线程在内核进行写文件操作期间被“阻塞(blocked)”而进入“睡眠”,直到设备驱动完成了操作以后才又将该线程“唤醒”而从系统调用返回。"
我参阅过很多资料和书籍都是这么描述的。我觉得有些悖论。悖论的地方我最后再列出



先从IRP开始

先说说我现有的对IRP的粗淡理解,方面大伙知道我是否哪里理解有误。

IRP可以看成一个数据结构。它的功用是作为一个I/0请求包的一个容器与驱动程序相交互,沟通。

那么它生于何处? writefile的调用过程怎么产生一个IRP呢?我们调用该函数与驱动交互的时候

无非设置其相关的一些参数罢了。其中的一个参数决定交互采用的方式为同步或者是异步.

那么我是不是就可以这么猜测,当该函数调用一层一层的调用下去

到达ring0,调用到NtWriteFile。中间慢慢对我们最初提交的参数进行了封装,填充(以IRP这个数据结构为模

型)。这个就是IRP形成的本质?



接着这里我想说下对于驱动程序其派遣函数所在上下文的粗拙的认识。

一般派遣函数可能会处于以下3种环境。

1:调用者线程的上下文
2:系统线程的上下文   创建系统线程来处理IRP         \\     有理解错的请一定指出,
3:任意线程的上下文  这种情况是DPC之类的机制造成的吧.  \\   因为发在论坛上,免得误导人。



现在假设我们写了自己的一个驱动,该驱动仅仅处理了一个IRP_MJ_WRITE的派遣例程。
然后我们又写了一个用户态程序,该程序也仅仅调用WriteFile函数对该设备同步操作(注:这里调用该函数参数设置同步标志)

现在我们回头看看 上面的那段描述。

“普通的写文件操作是同步写,启动这种操作的线程在内核进行写文件操作期间被“阻塞(blocked)”而进入“睡眠” ”

恩 现在我们的假设是符合这段描述的 同步写操作。接着它说我们的线程将会堵塞。(应该是调用了WaitForSingleObject) 。其实我用过windbg 来双机调试 来给这个函数下过断点,没断下来,我也试过直接在KeWaitForSingleObject函数上下断点。也没断下来。 现在得找下这个WaitForSingleObject是在什么地方被调用的。我查了本书《windows驱动开发技术详解》在书中说 应该是在系统服务函数NtWriteFile中被调用的。再调用该函数前,IRP已经在该函数中封装完毕,可以说创建完毕了。我不清楚这个时候IRP该怎么传给派遣例程去处理?通过什么机制?  假如是通过NtWriteFile里对封装好的IRP信息的判断,再去调用到派遣函数的话。

那么好了问题来了。上面的描述中说 线程会被 阻塞(blocked)”而进入“睡眠”  那么就是说先调用WaitForSingleObject 。(这里也有个问题,这里是ring0了,应该是调用KeWaitForSingleObject吧,所以我WINDGB的时候也在这个函数上下断了,可那本windows驱动开发技术书上说调用的这个函数WaitForSingleObject) 那么这个时刻线程就pending休眠了。接着的驱动里的派遣函数如何被调用呢?
这跟我实际调试的到的是不符合的,但是所有资料都是这么描述的。

从上面 派遣函数可能会处于3种环境中看,同步方式的话往往是在主调线程上下文中被执行的。

我实际调试的到这个例子派遣函数也是在主调线程的上下文中被调用...不知道我描述清楚没? 我想我一定有很

大程度上的理解误区。

[ Last edited by qiuzhiyu on 2009-4-29 at 12:57 AM ]
2009-4-29 12:43 AM
[1 楼]  资料  邮件  搜索  消息  编辑  引用
wjjequjjw


发贴 6
注册 2009-4-22
状态 离线
API通过int 2eh或是sysenter切换到ring0,这个时候还是在调用线程上下文中,然后从句柄中取出文件对象,从文件对象中取出设备对象,再为设备分配Irp包,然后调用(这里后面说),如果是同步IO并且没有完成则会等在文件对象的一个Event上。

背后发生了什么呢,Irp被传会文件系统,文件系统查元数据得知需要读哪些磁盘簇,然后构造Irp(一个或多个)调用卷设备读卷上的簇,卷设备得知要读哪些逻辑扇区,加上分区起始偏移后分配Irp调用磁盘类驱动读绝对扇区,磁盘类驱动知道要读哪些扇区后,它会给scsiport驱动发送scsi请求(SRB,带有CDB命令数据等),scsiport做些公共的处理后再传给miniport,miniport去控制真实的硬件,启动设备进行数据传输,可能会使用dma通道等等。

然后呢,数据传送完成后设备产生一个中断,scsiport的中断服务例程响应这个中断,它调用miniport的中断回调,然后再排队一个dpc,dpc里完成前面说的scsi请求(srb),磁盘类完成卷设备发送的读请求,卷设备完成文件系统的读请求,文件系统完成调用线程发送的读请求,包括将文件对象的Event设置成信号态,然后排队一个apc,做一些IO后处理。

文件对象上Event设置成信号态的时候,会从它的waitlist上选取等待线程放到某个cpu的prcb的就绪队列中,机会合适的时候它被调度运行,然后apc被运行,再然后线程回到Wait的那个位置接着往下执行,NtReadFile返回到int 2eh中断的Handler(一段汇编代码)中,int 2eh中断返回,运行模式切换回ring3,回到应用程序。

这样说,可OK?我基本为你打通了从ReadFile到存储硬件层面的整个通路。

我知道这些是因为我比你技术好吗!显然不是,你我的区别是我读了源代码,你没有!我说过了,你需要的仅仅是一份源代码,快去下载个ReactOS吧!

http://www.reactos.org/

[ Last edited by wjjequjjw on 2009-6-20 at 01:20 AM ]
2009-6-20 12:35 AM
[2 楼]  资料  搜索  消息  编辑  引用
sinister
Administrator

发贴 302
注册 2005-11-15
状态 离线
LZ问的应该不是各 DEVICE STACK 的调用流程。相信搂主也参考过 NT/2K/ROS 的 SOURCE。
这个问题其实在其他帖子里回过。如果说读 SOURCE 区别仅是在明确调用流程的话,那意义还真不大。
2009-6-21 10:42 PM
[3 楼]  资料  邮件  搜索  消息  编辑  引用
wjjequjjw


发贴 6
注册 2009-4-22
状态 离线
我这样说是为了让人有一个大局观,但LS你我都知道了解了整个流程里的细节也就不存在他要问的这个问题了。真要细化的说,我不是还要把Cc跟Fs交互这部分再说一下呢!

就他的狭义问题是“线程阻塞了,那派遣函数是怎么运行?”,那我再来说一下吧,免得有人觉得在装B。

前面说过了,“然后从句柄取文件对象,从文件对象中取出设备对象,再为设备分配Irp包,然后调用(这里后面说),如果是同步IO并且没有完成则会等在文件对象的一个Event上。”

不知道有人看到没有,调用设备驱动(FSD)在前,等待Event在后。

在等待之前派遣函数就已经被调用(在调用者线程上下文中),然后FSD做一些处理,接着调用卷设备读数据(有时甚至会放在WorkItem里来做),然后会返回STATUS_PENDING,然后调用者发现没有完成操作,使用同步IO的时候就会等在文件对象的Event上,而这个等待动作的背后会把当前线程对象插到派遣对象的等待列表中,然后从prcb的就绪队例里选择另外一个线程运行。

中间过程不说了,因为我已经说过了,最后的唤醒部分也已经说过了,“文件对象上Event设置成信号态的时候,会从它的waitlist上选取等待线程放到某个cpu的prcb的就绪队列中,机会合适的时候它被调度运行。。。”。

我觉得区别就是因为我读了源代码,否则我肯定解释不了。要记住内核的有些东西是偏而不深,烦而不难。我们不能做tk教主说的那种恰恰相反先生,否定别人来抬高自己,不要说那些让年轻人看了就怕的大道理,会误导人家的。
2009-6-22 12:18 AM
[4 楼]  资料  搜索  消息  编辑  引用
qiuzhiyu


发贴 14
注册 2007-2-27
状态 离线
谢谢wjjequjjw 对存储栈大流程上概括性描述。使我有了新的认识
这个问题后来我已经理解了,其实本质上是我对文章语义上的理解的偏差.
当时认为writefile操作自己写的一个驱动设备(并非文件的操作),其内什么都没有做。这怎么会被堵塞。
。学习实验情况和文中描述情况不同,因此导致了歧义,是我粗心大意所致。现正有阅读ReactOS.多谢指教
2009-6-22 01:39 AM
[5 楼]  资料  邮件  搜索  消息  编辑  引用
sinister
Administrator

发贴 302
注册 2005-11-15
状态 离线
一般这种问题,我上来都会先建议去读 MS 文档与 NT/2K 的源代码。就跟我之前的回帖一样。你前一个帖子很明显给出的是一个“调用流程”,那里不涉及到具体问题,而你后面回帖中所说的“了解了整个流程里的细节”,这里的“细节”才是关键。还有我说的“调用流程”意义不大,而不是说其没有意义。谁先开始
都是从“调用流程”入手的,但你所说的区别仅仅是一份源代码,我认为不妥,我觉得这往往这才容易误导别人。让人认为读源代码很大部分受重在流程关系中。所以我的回帖中才说“如果说读 SOURCE 区别是在明确调用流程的话,那意义还真不大。”而现在对 WINDOWS 的研究绝大部
分正是陷入在这块。在我看来这就是误导导致的局限。

你后面的回帖中提到的“细节”也正是我上一个回帖中要表达的意思。那里要说的就是“细节”问题。关于这个“细节”问题,在于怎么理解。我所理解的“细节”与“整体”密不可分,而这个“整体”并不是某个 OS 中的“调用流程”,而是涉及 OS 的环境,性能,方便简洁等考虑。比如“细节”处的某一行代码为什么要出现在这里,它的用意何在?是否对对系统性能有提升?还是为了方便处理而牺牲了一定的性能?比如LZ的这个问题,其实概括起来很简单,就是“完成动作”不是在调用者上下文中完成的,而是在设备栈传递时下层新建的上下文中“完成”的,返回时调用者受信得到通知。这是在了解NT DRIVER分层时就应该熟悉的概念(我在以前的回帖中也有说明)。但系统为什么要这么做?他的目的何在?回答这个问题其实也很简单,只要想一下就能明白。但这是要去想的,不管它多么简单,也是要去想的,如果光看源代码不去思考是得不到答案的。我所要说的就是这点。在弄明白调用流程后,再去看代码时要把自己摆在一个设计者的位置,如果是自己,应该会怎样处理?为什么要这样?这样做的好处是什么?不这么做会带来什么坏处?而这些是你仅读源代码的“调用流程”读不出来的。所以我所指的读源代码的“调用流程”意义不大,是与这个进行比较。不过在我来看以上的还不是“细节”,只能算是“整体”的某“部分”。我所说的“细节”还要再细化到比如某一条语句,或某个函数的标志位,都要能与“整体”或“部分”结合起来,来回贯通几次,经的起考问。不过这还是有很大的难度的,即使有源代码能读到融会贯通的还是不多。或许是天资的问题,每当我对“整体”框架有了新的认识,原来自认为熟悉的“细节”中就有多了不少疑问。不过一些阶段的成果和收益还是有的,迄今,我觉得读源代码的最大的收益是体现在自己设计的模块中。
2009-6-22 02:44 AM
[6 楼]  资料  邮件  搜索  消息  编辑  引用
wjjequjjw


发贴 6
注册 2009-4-22
状态 离线
楼上说的有些话还是比较赞同的,只是如果细节到这种程度的话,有时候并不一定能得到性能上多少提升,比如您说的标志位这个东东,如果不知道以哪条流水线中的指令为准的话,根本是无法预测的,而且知道了标志位由谁设置以后,这背后呢!

拿现在的Intel Core CPU来说,有超过3条31阶整数型水线,有200个内部寄存器,在一条指令被解码的时候,内部会有寄存器重命名,然后以等价微指令乱序执行,还会动态执行跟踪,以及分支预测,这一切使得对一个标志位的影响,已经成为比较高层的抽像了。

然而如果考虑任何东西的时候都考虑到微指令这一层,一是会影响我们集中考虑问题本质的东西,二是现在的硬件技术发达到我们不知道的东西太多太多。三是产生心魔,思想被已有的(根据时代发展已经不一定正确的)底层知识所束缚。

所以我本人设计的东西的时候一条基本原则就是,“在合理的情况下保持尽可能的简单”。

武侠片里武功绝学通常是找一些菜鸟来学习,是因为他们没有心魔,可以集中在最关键的部分,引导正确的话就可以得出更高的成就。

没有否定任何人的意思,大家应该保持淡然,就像W.Richard Stevens,用简单的方法可以把很复杂的事情解释清楚,这是何等境界。
2009-6-22 11:20 AM
[7 楼]  资料  搜索  消息  编辑  引用
发表新主题   回复主题
可打印版本 | 订阅主题 | 收藏主题
论坛跳转:


[ Script Execution time: 0.018193 ]   [ 7 queries used ]   [ GZIP Enabled ]
 

Powered by Discuz! © 2001-05