交易状态:异步处理,等待发送 。 制单是什么意思...

20189人阅读
转载请标明出处:&,本文出自很多人面试肯定都被问到过,请问Android中的Looper , Handler , Message有什么关系?本篇博客目的首先为大家从源码角度介绍3者关系,然后给出一个容易记忆的结论。1、 概述Handler 、 Looper 、Message 这三者都与Android异步消息处理线程相关的概念。那么什么叫异步消息处理线程呢?异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环。若消息队列为空,线程则会阻塞等待。说了这一堆,那么和Handler 、 Looper 、Message有啥关系?其实Looper负责的就是创建一个MessageQueue,然后进入一个无限循环体不断从该MessageQueue中读取消息,而消息的创建者就是一个或多个Handler 。2、 源码解析1、Looper 对于Looper主要是prepare()和loop()两个方法。首先看prepare()方法public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException(&Only one Looper may be created per thread&);
sThreadLocal.set(new Looper(true));
sThreadLocal是一个ThreadLocal对象,可以在一个线程中存储变量。可以看到,在第5行,将一个Looper的实例放入了ThreadLocal,并且2-4行判断了sThreadLocal是否为null,否则抛出异常。这也就说明了Looper.prepare()方法不能被调用两次,同时也保证了一个线程中只有一个Looper实例~相信有些哥们一定遇到这个错误。下面看Looper的构造方法:private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
在构造方法中,创建了一个MessageQueue(消息队列)。然后我们看loop()方法:public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException(&No L Looper.prepare() wasn't called on this thread.&);
final MessageQueue queue = me.mQ
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mL
if (logging != null) {
logging.println(&&&&&& Dispatching to & + msg.target + & & +
msg.callback + &: & + msg.what);
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println(&&&&&& Finished to & + msg.target + & & + msg.callback);
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, &Thread identity changed from 0x&
+ Long.toHexString(ident) + & to 0x&
+ Long.toHexString(newIdent) + & while dispatching to &
+ msg.target.getClass().getName() + & &
+ msg.callback + & what=& + msg.what);
msg.recycle();
第2行:public static Looper myLooper() {
return sThreadLocal.get();}方法直接返回了sThreadLocal存储的Looper实例,如果me为null则抛出异常,也就是说looper方法必须在prepare方法之后运行。第6行:拿到该looper实例中的mQueue(消息队列)13到45行:就进入了我们所说的无限循环。14行:取出一条消息,如果没有消息则阻塞。27行:使用调用
msg.target.dispatchMessage(msg);把消息交给msg的target的dispatchMessage方法去处理。Msg的target是什么呢?其实就是handler对象,下面会进行分析。 44行:释放消息占据的资源。Looper主要作用:1、 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。2、 loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。好了,我们的异步消息处理线程已经有了消息队列(MessageQueue),也有了在无限循环体中取出消息的哥们,现在缺的就是发送消息的对象了,于是乎:Handler登场了。2、Handler使用Handler之前,我们都是初始化一个实例,比如用于更新UI线程,我们会在声明的时候直接初始化,或者在onCreate中初始化Handler实例。所以我们首先看Handler的构造方法,看其如何与MessageQueue联系上的,它在子线程中发送的消息(一般发送消息都在非UI线程)怎么发送到MessageQueue中的。public Handler() {
this(null, false);
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class&? extends Handler& klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, &The following Handler class should be static or leaks might occur: & +
klass.getCanonicalName());
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
&Can't create handler inside thread that has not called Looper.prepare()&);
mQueue = mLooper.mQ
mCallback =
mAsynchronous =
14行:通过Looper.myLooper()获取了当前线程保存的Looper实例,然后在19行又获取了这个Looper实例中保存的MessageQueue(消息队列),这样就保证了handler的实例与我们Looper实例中MessageQueue关联上了。然后看我们最常用的sendMessage方法
public final boolean sendMessage(Message msg)
return sendMessageDelayed(msg, 0);
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what =
return sendMessageDelayed(msg, delayMillis);
} public final boolean sendMessageDelayed(Message msg, long delayMillis)
if (delayMillis & 0) {
delayMillis = 0;
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
} public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQ
if (queue == null) {
RuntimeException e = new RuntimeException(
this + & sendMessageAtTime() called with no mQueue&);
Log.w(&Looper&, e.getMessage(), e);
return enqueueMessage(queue, msg, uptimeMillis);
}辗转反则最后调用了sendMessageAtTime,在此方法内部有直接获取MessageQueue然后调用了enqueueMessage方法,我们再来看看此方法: private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target =
if (mAsynchronous) {
msg.setAsynchronous(true);
return queue.enqueueMessage(msg, uptimeMillis);
}enqueueMessage中首先为meg.target赋值为this,【如果大家还记得Looper的loop方法会取出每个msg然后交给msg,target.dispatchMessage(msg)去处理消息】,也就是把当前的handler作为msg的target属性。最终会调用queue的enqueueMessage的方法,也就是说handler发出的消息,最终会保存到消息队列中去。现在已经很清楚了Looper会调用prepare()和loop()方法,在当前执行的线程中保存一个Looper实例,这个实例会保存一个MessageQueue对象,然后当前线程进入一个无限循环中去,不断从MessageQueue中读取Handler发来的消息。然后再回调创建这个消息的handler中的dispathMessage方法,下面我们赶快去看一看这个方法:public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
handleMessage(msg);
}可以看到,第10行,调用了handleMessage方法,下面我们去看这个方法:
* Subclasses must implement this to receive messages.
public void handleMessage(Message msg) {
可以看到这是一个空方法,为什么呢,因为消息的最终回调是由我们控制的,我们在创建handler的时候都是复写handleMessage方法,然后根据msg.what进行消息处理。例如:private Handler mHandler = new Handler()
public void handleMessage(android.os.Message msg)
switch (msg.what)
case value:
};到此,这个流程已经解释完毕,让我们首先总结一下1、首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。2、Looper.loop()会让当前线程进入一个无限循环,不端从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)方法。3、Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue想关联。4、Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中。5、在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。好了,总结完成,大家可能还会问,那么在Activity中,我们并没有显示的调用Looper.prepare()和Looper.loop()方法,为啥Handler可以成功创建呢,这是因为在Activity的启动代码中,已经在当前UI线程调用了Looper.prepare()和Looper.loop()方法。3、Handler post今天有人问我,你说Handler的post方法创建的线程和UI线程有什么关系?其实这个问题也是出现这篇博客的原因之一;这里需要说明,有时候为了方便,我们会直接写如下代码:mHandler.post(new Runnable()
public void run()
Log.e(&TAG&, Thread.currentThread().getName());
mTxt.setText(&yoxi&);
});然后run方法中可以写更新UI的代码,其实这个Runnable并没有创建什么线程,而是发送了一条消息,下面看源码: public final boolean post(Runnable r)
sendMessageDelayed(getPostMessage(r), 0);
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback =
}可以看到,在getPostMessage中,得到了一个Message对象,然后将我们创建的Runable对象作为callback属性,赋值给了此message.注:产生一个Message对象,可以new &,也可以使用Message.obtain()方法;两者都可以,但是更建议使用obtain方法,因为Message内部维护了一个Message池用于Message的复用,避免使用new 重新分配内存。 public final boolean sendMessageDelayed(Message msg, long delayMillis)
if (delayMillis & 0) {
delayMillis = 0;
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
} public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQ
if (queue == null) {
RuntimeException e = new RuntimeException(
this + & sendMessageAtTime() called with no mQueue&);
Log.w(&Looper&, e.getMessage(), e);
return enqueueMessage(queue, msg, uptimeMillis);
}最终和handler.sendMessage一样,调用了sendMessageAtTime,然后调用了enqueueMessage方法,给msg.target赋值为handler,最终加入MessagQueue.可以看到,这里msg的callback和target都有值,那么会执行哪个呢?其实上面已经贴过代码,就是dispatchMessage方法: public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
handleMessage(msg);
}第2行,如果不为null,则执行callback回调,也就是我们的Runnable对象。好了,关于Looper , Handler , Message 这三者关系上面已经叙述的非常清楚了。最后来张图解:希望图片可以更好的帮助大家的记忆~~4、后话其实Handler不仅可以更新UI,你完全可以在一个子线程中去创建一个Handler,然后使用这个handler实例在任何其他线程中发送消息,最终处理消息的代码都会在你创建Handler实例的线程中运行。new Thread()
public void run()
Looper.prepare();
handler = new Handler()
public void handleMessage(android.os.Message msg)
Log.e(&TAG&,Thread.currentThread().getName());
Looper.loop();
Android不仅给我们提供了异步消息处理机制让我们更好的完成UI的更新,其实也为我们提供了异步消息处理机制代码的参考~~不仅能够知道原理,最好还可以将此设计用到其他的非Android项目中去~~最新补充:关于后记,有兄弟联系我说,到底可以在哪使用,见博客:
版权声明:本文为博主原创文章,未经博主允许不得转载。
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:3855859次
积分:30675
积分:30675
排名:第77名
原创:171篇
评论:6674条
长期为您推荐优秀博文、开源项目、视频等,进入还有好玩的等着你,欢迎扫一扫。
请勿重复加群,Thx
文章:11篇
阅读:66489
文章:10篇
阅读:37105
文章:67篇
阅读:1718908
(3)(4)(5)(4)(4)(3)(5)(4)(6)(7)(6)(7)(11)(10)(23)(17)(18)(39)(1)(1)php 异步发送邮件的问题
在某系统里,上传完一个产品的补丁文件或发布产品的更新信息,需要给使用该产品的用户(用户数量较多)发送一封电子邮件,发送后提示发送完毕!
我们常用的PHP代码写法如下:
//查找出所有使用该产品的用户email地址,假设存放在$email数组中
for($i=0;$i&count($email);$i++){
sendemail();
function sendemail(){
//发送邮件代码
假设该次发送100封邮件。本次操作会出现什么结果呢?
用户体验:用户等待-&发送邮件完毕-&返回信息(这期间极有可能,脚本执行超时)
本次操作由于需要发送大量的邮件,导致php执行时间过长,用户烦躁的等待。当apache或者nginx等待超过允许执行时间,将返回超时错误。这个时候用户不明确本次操作到底成功与否,到底发出了几封邮件。
由此我们可以看出以上代码用户体验极差,并且不能够顺利完成任务。
那么如何解决这个用户体验较差的问题呢?
这里提到一个概念,异步执行
用户体验:用户等待-&发送完毕
朋友们就会问,怎么缺少发信环节?
OK,发信环节就在用户提交请求的时候,把发信任务转给了一个单独处理发信的php程序处理了,当用户看见“发送完毕”的时候其实信还没发送完,这个时候,发信程序正在后台努力的工作着,一封一封的向外发送。
$domain="";
$url="/sendEmail.php";
$par="email=,,&time=".time();
$header="POST $url HTTP/1.0\r\n";
$header.="Content-Type: application/x-www-form-urlencoded\r\n";
$header.="Content-Length: ".strlen($par)."\r\n\r\n";
$fp=@fsockopen ($domain,80,$errno,$errstr,30);
fputs($fp,$header.$par);
fclose($fp);
echo '发送完毕';您可能感兴趣的文章转载请注明出处:本文永久地址:文章来源:-わ千与千寻上一篇:下一篇:热门文章随机文章应用层和驱动层的同步与异步的处理逻辑及底层实现
编辑:www.fx114.net
本篇文章主要介绍了"应用层和驱动层的同步与异步的处理逻辑及底层实现",主要涉及到应用层和驱动层的同步与异步的处理逻辑及底层实现方面的内容,对于应用层和驱动层的同步与异步的处理逻辑及底层实现感兴趣的同学可以参考一下。
应用层的实现:
1&ReadFile、WriteFile、DeviceIoControl等,这些都有两种操作方式,一种是同步,一种是异步。
操作设备的Win32API主要是这3个函数ReadFile、WriteFile、DeviceIoControl
以DeviceIOControl为例,它的同步&异步操作如下:
同步操作时,它的内部会创建一个IRP_MJ_DEVICE_CONTROL类型的IRP,并将这个IRP传送到驱动程序的派遣函数中。处理该IRP需要一段时间,直到IRP处理完毕后,DeviceIoControl函数才会返回。
异步操作时,此时的DeviceIoControl函数不会等待IRP结束,而是直接返回。当IRP经过一段时间被结束时,OS会触发一个IRP相关事件,这个事件可以通知应用程序IRP请求被执行完毕
的异步操作还必须得到驱动程序的支持,如果驱动程序不支持异步操作,win32API的异步操作就会失败
2 IRP的同步完成:同步操作时,在这3个函数的内部,会调用WaitForSingleObject函数去等待一个事件。这个事件直到IRP被结束时,才会被触发。在DeviceIoControl内部,会调用WaitForSingleObject函数去等待一个事件,这个事件直到IRP被结束时才会被触发,如果通过反汇编IoCompleteRequest内核函数,就会发现在这个函数内部设置了该事件。
3 IRP的“异步完成”:指的是这3个函数会立即返回,而发起IRP的win32函数会有三种形式发起IRP请求:(异步完成:如果派遣函数不调用IoCompleteRequest函数,则需要告诉操作系统此IRP处于“悬挂”状态。这需要调用内核函数IoMarkIrpPending。同时,派遣函数应该返回STATUS_PENDING)
【建立在:IRP被“异步完成”时,返回的都是STATUS_PENDING】
第一种:用ReadFile函数进行同步读取时
(返回 STATUS_PENDING,这时IO管理器不会激活IRP的UserEvent域并挂起该IRP,线程仍然处于睡眠中)
AReadFile函数内部会创建一个事件,这个事件连同IRP一起被传递到派遣函数中(这个事件是IRP的UserEvent域)
如果在派遣函数中没有调用IoCompleteRequest函数,该事件就没有被设置,ReadFile会一直等IRP被结束。
第二种:用ReadFile函数进行异步读取时
(返回 STATUS_PENDING,并挂起该IRP,ReadFile函数立即返回,完成该IRP时IO管理器会激活事件,该事件的作用相当于告知一下应用程序该IRP已经完成处理)
&&&&& A这时ReadFile内部不会创建事件,但ReadFile函数会接收overlap参数overlap参数中会提供一个事件,这个事件被用作同步处理。IoCompleteRequest内部会设置overlap提供的事件。
在ReadFile函数退出前,它不会检测该事件是否被设置,因此可以不等待操作是否完成,与第一种的区别就是不会一直停留在ReadFile函数中,ReadFile函数会马上返回,这时调用GetLastError获得的错误码是ERROR_IO_PENDING
当IRP操作被完成后,overlap提供的事件被设置,这个事件会通知应用程序IRP请求被完成
HANDLE hDevice
=CreateFile(&test.dat&,GENERIC_READ
| GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,/*此处设置FILE_FLAG_OVERLAPPED*/,NULL
&&&&&&&&if(hDevice == INVALID_HANDLE_VALUE)
&&&&&&&&&&&&&&&&printf(&Read Error\n&);
&&&&&&&&&&&&&&&&return 1;
&&&&&&&&UCHAR buffer[BUFFER_SIZE];
&&&&&&&&DWORD dwRead;
&&&&&&&&//初始化overlap使其内部全部为零
&&&&&&&&OVERLAPPED overlap={0};
&&&&&&&&//创建overlap事件
&&&&&&&&overlap.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
&&&&&&&&//这里没有设置OVERLAP参数,因此是异步操作
&&&&&&&&ReadFile(hDevice,buffer,BUFFER_SIZE,&dwRead,&overlap);
&&&&&&&&//做一些其他操作,这些操作会与读设备并行执行,这就是异步的作用,呵呵
&&&&&&&&//等待读设备结束
&&&&&&&&WaitForSingleObject(overlap.hEvent,INFINITE);
&&&&&&&&CloseHandle(hDevice);
第三种:用ReadFileEx函数进行异步读取时
(注意这里的完成函数是针对用户层的)
A ReadFileEx函数不提供事件,但提供一个回调函数,这个回调函数的地址会作为IRP的参数传递给派遣函数。
当处理完IRP并调用IoCompleteRequest时会将这个完成函数插入APC队列。应用程序只要进入警惕模式,APC队列会自动出队列,完成函数会被执行,这相当于内核通知应用程序IRP操作已经完成
。(APC的作用是等到调用者的线程里运行完成函数,这样完成函数才能使用R3中的数据)
与第二种的相同点是都是会返回 STATUS_PENDING,并挂起该IRP,ReadFileEx函数立即返回,这时调用GetLastError获得的错误码是ERROR_IO_PENDING,不同点是没有事件了,因为当回调函数被调用时就相当于通知了应用程序该IRP已经完成了处理
重点:若调用了IoMarkIrpPending就会设置了栈顶标志,IoCompleteRequest投递APC是根据栈顶的标志位,该标志位通过IoMarkIrpPending设置并一层层传递到栈顶,APC用于设置事件&投递ACP函数
用户层的同步指的是线程会阻塞等待函数的返回,这里主要是等待驱动调用IoCompleteRequest为止。
线程阻塞:针对第一种情况的同步读取(即等待驱动调用IoCompleteRequest)
用户层的异步指的是线程会马上返回。等驱动调用IoCompleteRequest在通知用户层(内核通过事件&用户层的回调函数来通知应用层)。
线程不阻塞:针对第二&三种情况的异步读取(即驱动返回调用IoMarkIrpPending并返回STATUS_PENDING),等驱动调用IoCompleteRequest后,会通过R3传递下来的事件&回调函数来通知应用程序驱动已经完成该IRP的处理
驱动层的实现:
分层驱动是指两个或两个以上的驱动程序,它们分别创建设备对象,并且形成一个由高到低的设备对象栈。
2 IRP请求一般会被传送到设备栈的最顶层的设备对象,顶层的设备对象可以选择直接结束IRP请求,也可以选择将IRP请求向下层的设备对象转发。
如果是向下层设备对象转发IRP请求,当IRP请求结束时,IRP会顺着设备栈的方向原路返回,当得知下层驱动程序已经结束IRP请求时,本层设备对象可以选择继续将IRP向上返回,或者选择重新将IRP再次传递给底层设备驱动。
向设备栈挂接用IoAttachDeviceToDeviceStack,而从设备栈中弹出是IoDetachDevice。
在Windows分层驱动模型中,设备栈中的设备一般都是通过对上层传来的IRP做相应的处理来实现驱动的功能。
这里对常用的几种IRP传递及完成的方式进行归纳和总结:
1. 在本层驱动中完成
在本层驱动中以同步方式完成
在本层同步完成一般做完相应处理后,设置Irp-&IoStatus.Status和Irp-&rmation,调用IoCompleteRequest完成该IRP,returnIRP的完成状态即可。
在本层驱动中以异步方式完成
在本层异步完成一般是得到IRP后将其入队/起线程另行处理,同时调用IoMarkIrpPending将该IRP标记为Pending,之后即可returnSTATUS_PENDING。此时该IRP并未真正完成,等时机成熟后(如R3返回处理结果)再调用IoCompleteRequest来真正完成或调用IoCallDriver将Irp继续向下传递,由下层驱动负责完成它(如Tdi驱动的处理方式)。
在返回STATUS_PENDING前,可将该IRP存入到链表&自定义的扩展结构中等,随后在其他IRP的派遣函数&线程&定时器(需在返回PENDING的派遣函数中SET定时器)等中再处理。
【Example:本层驱动中以异步方式完成】
过滤驱动的做法是先存入链表再挂起,等进行逻辑判断(通过应用层的反馈来决定)后再决定是否将该IRP向下转发并进行处理
(应该归属于在本层驱动中的异步完成,只不过完成该IRP的IoCompleteRequest改成了IoCallDriver)
if(filterResult== tfrProxy)//返回 if(filterResult == tfrProxy)凡是代理的就先让这个IRP等待,在CompletingDelayedIrpLIst中根据用户层的回调函数的结果再完成它
FilterDebugPrint((&IoMarkIrpPending! 0x%X\n&, irps-&MinorFunction));
if((irp-&CurrentLocation&= irp-&StackCount + 1))
&&&&&&&&&&&
IoMarkIrpPending(irp);//在CompletingDelayedIrpLIst中根据用户层的回调函数的结果在完成它
。。。。。
return STATUS_PENDING;//调用IoCallDriver将Irp继续向下传递,由下层驱动负责完成它
TCP过滤设备
IoMarkIrpPending + return PENDING:说明不见得一定是最后一个设备才能返回PENDING
凡是堆栈上设置了PENDING标志位后,当调用IoCompleteRequest时,系统都会调度APC来使那个事件变成通知状态的,对于某些IRP,IoCompleteRequest会毫无条件地调度APC,而对于某些IRP,IoCompleteRequest会检查堆栈顶部IO_STACK_LOCATION中是否设置了SL_PENDING_RETURNED标志,只有这个标志被设置时才调度APC。
(超重点:凡是驱动返回了return
PENDING就代表告诉系统整个IRP是异步完成,
系统通过APC+堆栈上的PENDING标志位来实现异步完成)
2. 转发至下层驱动
本层不作处理(仅适用于第一种情况) (效果图:DriverTool- Tool - IRPTrace - 9 Skipped)
&&&&&&& 有时对于某些IRP,本层驱动不需要做任何处理。此时可调用
IoSkipCurrentIrpStackLocation
跳过当前设备栈(这个函数的主要作用是避免了向下层拷贝堆栈的操作),然后调用IoCallDriver将IRP转发至下层驱动,并将转发的结果直接返回。此种处理方式并不需要关心下层驱动处理IRP的方式究竟是同步还是异步。IRP转发至下层后就与己无关了。
有两种方式来调用下层驱动:
1.调用IoSkipCurrentIrpStackLocation和IoCallDriver。通常如果当前设备对象不需要处理irp,我们可以这么调用。
调用IoCopyCurrentIrpStackLocationToNext和IoCallDriver。通常如果当前设备也需要处理irp,那么我们可以等处理完后,将irp拷贝到下层驱动,然后再调用IoCallDriver。
同步转发方式
同步:等待处理完后再返回
【同步转发方式】
事件是实现同步转发方式的基础,因为可以通过KeWaitForSingleObject实现等待,两种等待事件的方法:(若判断ntStatus== STATUS_PENDING,则必须在完成函数中传递PEDING位)
第一种方法:适用于当前的派遣函数也是处理该IRP的其中一层,支持在完成函数中再次获得该IRP的控制权,然后需要调用IoCompleteRequest来重新完成该IRP
//初始化事件
KeInitializeEvent(&event,NotificationEvent, FALSE);
//设置完成例程
IoSetCompletionRoutine(pIrp,MyIoCompletion,&event,TRUE,TRUE,TRUE);
ntStatus= IoCallDriver(pdx-&TargetDevice, pIrp);
if(ntStatus == STATUS_PENDING)&//若底层驱动返回的是STATUS_SUCCESS,则这里不会执行,也不会永远等待
KdPrint((&IoCallDriverreturn STATUS_PENDING,Waiting ...\n&));
KeWaitForSingleObject(&event,Executive,KernelMode ,FALSE,NULL);//完成函数会激活该事件,并返回STATUS_MORE_PROCESSING_REQUIRED,使得重新获得对此IRP的控制权
ntStatus = pIrp-&IoStatus.S//得到底层的处理结果
NTSTATUSMyIoCompletion(IN PDEVICE_OBJECT&DeviceObject,IN PIRP& Irp,INPVOID& Context)
if(Irp-&PendingReturned == TRUE)
KeSetEvent((PKEVENT)Context,IO_NO_INCREMENT, FALSE);
returnSTATUS_MORE_PROCESSING_REQUIRED;//可重新获得对IRP的控制权
第二种方法:适用于在当前的派遣函数新构建IRP并发向另一个设备栈,因为IRP发向了另一个IRP堆栈,所以在回调函数中不支持返回STATUS_MORE_PROCESSING_REQUIRED,此派遣函数只能等待另一个设备堆栈的处理结果。
1、用于同步的事件通过所发向的设备栈,在设备栈对应的派遣函数中调用IoCompleteRequest来激活该事件。这时只需要等待此事件激活即可。当附加在IRP上的事件被激活后,可以通过附加在query_irp-&UserIosb上的status变量来得到处理函数对该IRP的处理结果。
2、若所发向的设备栈设置了PEDING,则事件的激活是IRP被调用IoCompleteRequest结束时,通过返回到设备栈顶的APC来激活的,若所发向的IRP的设备栈未设置PEDING(设备栈的处理函数不是异步完成),则该事件的激活不用通过APC激活,这个新的IRP也不用在完成函数中传递PEDING位,这时的完成函数只是为了获得相关的数据信息。
TdiBuildQueryInformation(query_irp, devobj,irps-&FileObject,tdi_create_addrobj_complete2, ctx,TDI_QUERY_ADDRESS_INFO,mdl);//mdl是一个缓冲区,构造一个IRP
KeInitializeEvent(&waitEvent,SynchronizationEvent, FALSE);
query_irp-&UserIosb= &ioS
query_irp-&UserEvent= &waitEvent;
status= IoCallDriver(devobj,query_irp);//这时因为在IRP_MJ_CREATE的完成函数中,所以IRP_MJ_CREATE请求已经下发完成之后,现在发送查询请求发送查询请求,得到生成的地址
if(status == STATUS_PENDING)//TDI过滤驱动的完成函数中不用传播pending位,因为它位于最后一层堆栈中。
KeWaitForSingleObject(&waitEvent, Executive, KernelMode, FALSE,NULL);//用于等待,确保查询请求完成
status = ioStatus.S//这里只需要取到IRP的处理结果并返回上层即可,如上层是应用层函数
elseif (status != STATUS_SUCCESS) {
FilterDebugPrint((&%s:IoCallDriver: 0x%x\n&, __FUNCTION__, status));
status = STATUS_SUCCESS;//只有这里会命中,在windbg中
异步转发方式
异步:就是先返回然后在处理,处理完后再通知
【异步转发方式】(适用于同步转发方式的两种方法)
1、高层不能再对此IRP进行控制,不会涉及通过事件对象实现的等待操作。但若需要知道处理结果&再做其它处理则需要在完成函数中得知并需要传播pending位。
2、异步转发也能在下层驱动完成IRP时获得处理的机会,其主要是采用了异步处理机制。首先本层dispatch调用IoCopyCurrentIrpStackLocationToNext将本层设备栈参数复制到下层,设置相应的CompletionRoutine,然后调用IoCallDriver将IRP转发至下层驱动,并将转发的结果直接return给上层调用者。
//将当前IRP堆栈拷贝底层堆栈
IoCopyCurrentIrpStackLocationToNext(pIrp);
//设置完成例程
IoSetCompletionRoutine(pIrp,MyIoCompletion,NULL/*即不向完成函数传递事件对象*/,TRUE,TRUE,TRUE);
ntStatus= IoCallDriver(pdx-&TargetDevice, pIrp);
if(ntStatus == STATUS_PENDING)
KdPrint((&STATUS_PENDING\n&));
MyIoCompletion(IN PDEVICE_OBJECT DeviceObject,IN PIRP
Irp,IN PVOID Context)
//进入此函数标志底层驱动设备将IRP完成
KdPrint((&EnterMyIoCompletion\n&));
if(Irp-&PendingReturned) 只有当底层驱动处理IRP先返回过STATUS_PENDING时
&IoMarkIrpPending( Irp);//继续传播pending位,用于系统投递APC激活事件
ntStatus = pIrp-&IoStatus.S//得到底层的处理结果
returnSTATUS_SUCCESS;//同STATUS_CONTINUE_COMPLETION
注:在完成函数中,只有当下层驱动设置了PENDING位(通过判断Irp-&PendingReturned得知)和本完成函数不返回STATUS_MORE_PROCESSING_REQUIRED时才需要调用IoMarkIrpPending设置本层的PEDING位,用于系统投递APC激活事件
还有一种情况就是,位于最后一层堆栈中的完成函数不用传播PENDING位,如TDI驱动中的情况。
【完成函数的使用归纳】
1、完成函数需要判断
if (Irp-&PendingReturned)来决定是否通过调用IoMarkIrpPending(Irp)来继续传播堆栈上的PEDING位,若完成函数要返回STATUS_MORE_PROCESSING_REQUIRED
和已知这个IRP不会异步完成(if (Irp-&PendingReturned) 返回FALSE)时则不用判断Irp-&PendingReturned。
2、完成函数可以对下层驱动处理该IRP的结果进行相应修改操作。
3、使用完成函数的一个技巧:
的起始地址是ctx-&tax,构造TDI类型的IRP时的最后一个参数正是这个mdl,这个IRP被处理后生成的地址&端口会填充到这个mdl指向的缓冲区中,即TDI_ADDRESS_INFO_MAX。
2)又因为在构造TDI时将ctx传入完成函数,这样在完成函数中就可以通过ctx-&tax取出IP地址和端口了
3)这样做的两大好处是ctx指向的结构体还可包含很多逻辑处理的成员如ctx-&fileobj,另一个好处是在回调中获得了生成的IP地址和端口,因为mdl指向的正是ctx-&tax,调用IoAllocateMdl中设定的。
同步转发方式的第一种方法:
同步转发方式(通过当前设备栈中的完成函数中激活事件并返回STATUS_MORE_PROCESSING_REQUIRED):
高层需要对此IRP进行控制,需要在完成函数中激活事件并返回
STATUS_MORE_PROCESSING_REQUIRED(这时需要调用IoCompleteRequest来重新完成该IRP),这样整个IRP不会继续向上返回,KeWaitForSingleObject处则被激活。(在完成函数里激活用于同步的事件对象)
&&&有时IRP在本层无法直接处理,需要将其转发至下层,待下层处理完后在其结果上可进行修改再将其返回。这时可以采用同步转发方式进行处理。首先在相应派遣函数中初始化一个未激发的event,调用IoCopyCurrentIrpStackLocationToNext将本层设备栈参数复制到下层。然后设置一个CompletionRoutine(因为完成例程会设置到下层的IO堆栈中,见下图),将刚才初始化过的event作为context传给它。
同步转发方式(通过当前设备栈中的完成函数中激活事件并返回STATUS_MORE_PROCESSING_REQUIRED):
高层需要对此IRP进行控制,需要在完成函数中激活事件并返回
STATUS_MORE_PROCESSING_REQUIRED(这时需要调用IoCompleteRequest来重新完成该IRP),这样整个IRP不会继续向上返回,KeWaitForSingleObject处则被激活。(在完成函数里激活用于同步的事件对象)
&&&有时IRP在本层无法直接处理,需要将其转发至下层,待下层处理完后在其结果上可进行修改再将其返回。这时可以采用同步转发方式进行处理。首先在相应派遣函数中初始化一个未激发的event,调用IoCopyCurrentIrpStackLocationToNext将本层设备栈参数复制到下层。然后设置一个CompletionRoutine(因为完成例程会设置到下层的IO堆栈中,见下图),将刚才初始化过的event作为context传给它。
之后调用IoCallDriver转发IRP至下层,并判断返回值是否为STATUS_PENDING。是则等待之前的event被激活。
&在CompletionRoutine中判断该IRP是否PendingReturned,是则说明之前IoCallDriver返回了STATUS_PENDING,于是激发event。
CompletionRoutine返回STATUS_MORE_PROCESSING_REQUIRED使我们的dispatchroutine重新取得对该IRP的控制权。本层dispatch等待结束再次获得控制权后,进行相应处理,之后需再次调用IoCompleteRequest完成该IRP。
&&&&同步转发是驱动中常用的一种IRP处理方式。一般会将本层dispatch转发IRP至下层并等待CompletionRoutine激发event的行为独立成一个ForwardIrpSynchronous的函数。几个dispatch只需一个ForwardIrpSynchronous,代码相对简单。
NTSTATUS MyIoCompletion(INPDEVICE_OBJECT& DeviceObject,IN PIRP& Irp,IN PVOID&Context)
//若底层驱动未返回STATUS_PENDING(见使用IoMarkPending的原因及原理),则不用激活事件,因为不会走进if(ntStatus == STATUS_PENDING)块中的KeWaitForSingleObject
if(Irp-&PendingReturned == TRUE)
KeSetEvent((PKEVENT)Context, IO_NO_INCREMENT, FALSE);
returnSTATUS_MORE_PROCESSING_REQUIRED;//重新获得对IRP的控制权,JK 这个可能是通知一下IO管理器吧~
#pragma PAGEDCODE
NTSTATUS HelloDDKRead(INPDEVICE_OBJECT pDevObj,IN PIRP pIrp)
KdPrint((&DriverB:EnterB HelloDDKRead\n&));
NTSTATUSntStatus = STATUS_SUCCESS;
//将自己完成IRP,改成由底层驱动负责
PDEVICE_EXTENSIONpdx = (PDEVICE_EXTENSION)pDevObj-&DeviceE
//将本层的IRP堆栈拷贝到底层堆栈
IoCopyCurrentIrpStackLocationToNext(pIrp);
//初始化事件
KeInitializeEvent(&event,NotificationEvent, FALSE);
//设置完成例程
IoSetCompletionRoutine(pIrp,MyIoCompletion,&event,TRUE,TRUE,TRUE);
//调用底层驱动
&&&&& ntStatus =IoCallDriver(pdx-&TargetDevice, pIrp);
if(ntStatus == STATUS_PENDING)&//若底层驱动返回的是STATUS_SUCCESS,则这里不会执行,也不会永远等待
KdPrint((&IoCallDriverreturn STATUS_PENDING,Waiting ...\n&));
KeWaitForSingleObject(&event,Executive,KernelMode ,FALSE,NULL);//完成函数会激活该事件,并返回STATUS_MORE_PROCESSING_REQUIRED,使得重新获得对此IRP的控制权
ntStatus = pIrp-&IoStatus.S//得到底层的处理结果
//虽然在底层驱动已经将IRP完成了,但是由于完成例程返回的是STATUS_MORE_PROCESSING_REQUIRED,因此需要再次调用IoCompleteRequest!
IoCompleteRequest (pIrp, IO_NO_INCREMENT);//由于完成函数中返回了STATUS_MORE_PROCESSING_REQUIRED,所以要再次调用IoCompleteRequest!
KdPrint((&DriverB:LeaveB HelloDDKRead\n&));
同步转发方式的第二种方法:
同步转发方式(用于同步的事件通过B设备栈的IoCompleteRequest来激活):
&&&&A层需要等待发向B设备的IRP的处理结果(A层不能对这个IRP进行控制,只能等待处理结果,因为两个设备不在同一个堆栈上),需要向发送给B设备的IRP附加一个事件,这时只需要等待此事件激活即可。当附加在IRP上的事件被激活后(即B层的处理函数调用了IoCompleteRequest),可以通过附加在query_irp-&UserIosb上的status变量来得到B层的处理函数对该IRP的处理结果。
&&&&&&&&&&
若B层所在的设备栈先返回了PEDING,则事件的激活是向B层发送的IRP被调用IoCompleteRequest结束时,通过返回到B设备栈顶的APC来激活的,若B层所在的设备栈未返回PEDING(B层的处理函数不是异步完成),则该事件的激活不用通过APC激活,这个新的IRP也不用在完成函数中传递PEDING位,这时的完成函数只是为了获得相关的数据信息。
KeInitializeEvent(&waitEvent,SynchronizationEvent, FALSE);
query_irp-&UserIosb =&ioS
query_irp-&UserEvent =&waitE
//这时因为在IRP_MJ_CREATE的完成函数中,所以IRP_MJ_CREATE请求已经下发完成之后,现在发送查询请求发送查询请求,得到生成的地址
status =IoCallDriver(devobj, query_irp);
if(status ==
STATUS_PENDING)
KeWaitForSingleObject(&waitEvent, Executive,KernelMode, FALSE, NULL);//用于等待,确保查询请求完成
status = ioStatus.S这里只需要取到IRP的处理结果并返回上层即可,如上层是应用层函数
FilterDebugPrint((&%s:IoCallDriver Async result: 0x%x\n&, __FUNCTION__, status));
}else if (status !=STATUS_SUCCESS)
FilterDebugPrint((&%s:IoCallDriver: 0x%x\n&, __FUNCTION__, status));
异步转发方式:
异步转发方式(通过当前设备栈中的完成函数实现):
高层不能再对此IRP进行控制,但若需要知道处理结果&再做其它处理则需要在完成函数中得知并需要传播pending位。
异步转发也能在下层驱动完成IRP时获得处理的机会,其主要是采用了异步处理机制。首先本层dispatch调用IoCopyCurrentIrpStackLocationToNext将本层设备栈参数复制到下层,设置相应的CompletionRoutine,然后调用IoCallDriver将IRP转发至下层驱动,并将转发的结果直接return给上层调用者。
在CompletionRoutine中再判断该IRP是否PendingReturned,是则需要调用IoMarkIrpPending。之后可对下层驱动处理该IRP的结果进行相应操作。最后返回STATUS_CONTINUE_COMPLETION(同STATUS_SUCCESS)。
异步转发在异步处理时性能最佳,但处理的逻辑放在了CompletionRoutine中,因此多个dispatch需要编写多个CompletionRoutine。
NTSTATUSHelloDDKRead(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp)
KdPrint((&DriverB:EnterB HelloDDKRead\n&));
NTSTATUSntStatus = STATUS_SUCCESS;
//将自己完成IRP,改成由底层驱动负责
PDEVICE_EXTENSIONpdx = (PDEVICE_EXTENSION)pDevObj-&DeviceE
//将当前IRP堆栈拷贝底层堆栈
IoCopyCurrentIrpStackLocationToNext(pIrp);
//设置完成例程
IoSetCompletionRoutine(pIrp,MyIoCompletion,NULL/*即不向完成函数传递事件对象*/,TRUE,TRUE,TRUE);
//调用底层驱动
&&&&& ntStatus =IoCallDriver(pdx-&TargetDevice, pIrp);
//当IoCallDriver后,并且完成例程返回的是STATUS_SUCCESS,IRP就不在属于派遣函数了,就不能对IRP进行操作了
if(ntStatus == STATUS_PENDING)
KdPrint((&STATUS_PENDING\n&));
KdPrint((&DriverB:LeaveB HelloDDKRead\n&));
&MyIoCompletion(IN PDEVICE_OBJECTDeviceObject,IN PIRP
Irp,IN PVOID Context)
//进入此函数标志底层驱动设备将IRP完成
KdPrint((&EnterMyIoCompletion\n&));
if(Irp-&PendingReturned) 只有当底层驱动处理IRP先返回过STATUS_PENDING时
&&&&&&& IoMarkIrpPending( Irp);//继续传播pending位,用于系统投递APC激活事件
returnSTATUS_SUCCESS;//同STATUS_CONTINUE_COMPLETION
本文标题:
本页链接:}

我要回帖

更多关于 会计制单 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信