首页 理论教育 简述操作系统中的消息循环

简述操作系统中的消息循环

时间:2023-10-21 理论教育 版权反馈
【摘要】:图11-18 消息在核心线程内的调度过程这里最关键的是两个函数—GetMessage和SendWindowMessage。窗口函数是一个分水岭,在窗口函数之前的所有消息处理,都是操作系统的工作。这是因为在窗口处于关闭状态时,向其发送绘制消息是无意义的,这是一种高效率的处理方式。应用程序的窗口函数对消息进行处理,并把处理结果显示在屏幕上。因为本章内容聚焦于消息的传递机制,把消息传递的每个环节都解释了。第一行中,判断线程的当前消息队列是否为空。

简述操作系统中的消息循环

RAWIT线程调用SendMessage,把消息送入目标线程的消息队列后,RAWIT线程的工作就完成了,剩下的工作,就是应用程序自己的事了。下面就详细讲解一下基于消息驱动的应用程序编程模型。

对Windows程序员来说,消息驱动机制不应该是陌生的内容。在Windows程序中,首先要有一个消息循环,这个消息循环不断地从消息队列中获取消息,一旦获得消息,就调用SendMessage(注意这里是Windows的SendMessage,与Hello China的SendMessage功能不同。前者的功能是把消息送给窗口进行处理,而后者则是把消息送给另外一个线程进行处理)或DispatchMessage,对消息进行分发处理。Windows的SendMessage或Dispatch Message进一步调用窗口对象的窗口函数,窗口函数就是真正的用户功能代码所在地。

Hello China的消息驱动机制与此类似,与Windows不同的是,Hello China使用SendWindowMessage替代了Windows的SendMessage函数,这主要是因为SendMessage函数已经被内核提前“征用”了。图11-18说明了这个过程。

978-7-111-41444-5-Chapter11-83.jpg

图11-18 消息在核心线程内的调度过程

这里最关键的是两个函数—GetMessage和SendWindowMessage。把这两个函数讲清楚了,这部分的内容就讲完了。先看SendWindowMessage,这个函数的功能相对简单,只是调用了窗口消息的窗口函数。下面是其代码(为了简便,省略了参数安全检查等部分代码):

978-7-111-41444-5-Chapter11-84.jpg

最关键的部分,就是黑体标注部分。这行代码调用了窗口对象的窗口函数。应用程序的功能,就是在窗口函数内实现的,因此这实际上就是调用了应用程序的功能代码。Windows操作系统的SendMessage函数,大致实现也是如此,就是调用了目标窗口的窗口函数。需要注意的是,上述SendWindowMessage函数中的hWnd参数,与窗口消息pWndMsg中的窗口对象句柄是同一个,这个窗口对象句柄是由RAWIT确定并填写在窗口消息里面的。但为什么SendWindowMessage函数不直接利用pWndMsg里面的窗口句柄,而是额外设计第一个参数呢?当初这样设计的时候,是考虑到pWndMsg参数内的窗口句柄可以设置为NULL,但后来发现这个设计有点多余。或许在后续的版本中,会更改这个设计。

对于“窗口函数就是应用程序的功能代码”这个结论,可能会有很多人不认同,尤其是没有编写过原始Windows程序(直接调用Windows API编程)的程序员。比如MFC程序员,他们会认为OnDraw或OnPaint等虚拟函数,才是真正的用户功能代码。但MFC不过是对Windows API的一个封装。它通过充分利用C++语言的虚拟函数机制,进一步抽象了Windows的编程模型,向程序员隐藏了充满case语句的窗口函数实现过程。但两者本质是一样的,无非是通过层层封装,简化了程序员的编程工作而已。窗口函数是一个分水岭,在窗口函数之前的所有消息处理,都是操作系统的工作。窗口函数之后的处理,则完全是应用程序的事情了。当然,如果窗口函数对消息不做特殊处理,而直接调用DefWindowProc,效果是一样的。

SendWindowMessage函数的前半部分,是根据窗口的状态做了一个特殊处理。在窗口处于关闭状态(窗口已被用户关闭,不显示在屏幕上,同时窗口对象可能即将被销毁)时,对窗口绘制消息(WM_DRAW/WM_PAINT)进行了拦截,不会发送给窗口函数。这是因为在窗口处于关闭状态时,向其发送绘制消息是无意义的,这是一种高效率的处理方式。

到此为止,实际上消息传递机制已经讲完了。一条消息,从最初的硬件(鼠标、键盘等)输入,到最终的应用程序功能代码(窗口函数),经历了万水千山。下面对整个过程做一个简单总结。

(1)首先,用户按下键盘上的键,或者移动鼠标、点击鼠标,硬件驱动程序代码会被调用。驱动程序完成原始的处理后,调用SendDeviceMessage函数,把硬件消息传递到内核。

(2)内核的DIM对象把这个消息传递给当前输入焦点线程,或者在当前输入焦点线程不存在的情况下,传递给字符shell线程。对于GUI模式,当前输入焦点线程就是RAWIT。

(3)RAWIT对消息做进一步处理,包括硬件坐标和屏幕坐标的映射、找到消息落入的窗口对象等,然后通过调用SendMessage,把消息递交给应用线程。(www.xing528.com)

(4)应用线程调用GetMessage,从自身的消息队列中获得消息,然后再调用窗口函数(实际上是通过SendWindowMessage间接调用了窗口函数),对消息进行处理。

(5)应用程序的窗口函数对消息进行处理,并把处理结果显示在屏幕上。

按既定的内容范围来说,本章内容到此就结束了。因为本章内容聚焦于消息的传递机制,把消息传递的每个环节都解释了。

但我还不想就这样结束本章,我还要介绍一下GetMessage函数。这个函数与SendMessage一起,组成了消息驱动机制的核心。这部分内容相当重要,而且与消息传递机制关联紧密,放到这里讲是比较合适的。

我想强调的最核心的一点,就是GetMessage是基于阻塞操作的。应用程序在一个无限循环内调用GetMessage函数,并不会导致应用程序一直占用CPU。GetMessage函数会检查线程的消息队列,如果消息队列为空,没有任何消息,则线程会进入等待状态,这时候是不会被调度的。只有另外的线程(或设备驱动程序等)把消息发送到线程的消息队列,线程才会被重新唤醒,进而对消息进行处理。还是通过阅读代码,来分析GetMessage的实现。代码如下(做了删节):

978-7-111-41444-5-Chapter11-85.jpg

重点注意上面黑体标注的两行代码。第一行中,判断线程的当前消息队列是否为空。如果为空,则把当前线程的状态设置为BLOCKED,然后插入消息等待队列,并引发一个调度。KernelThreadManager.ScheduleFromProc函数调用后,实际上当前线程就不再运行了,ScheduleFromProc函数会选择另外一个状态是就绪的线程投入运行。

从ScheduleFromProc返回后,说明线程的消息队列里面已经有消息了。这时候线程会获取消息,然后返回即可。这里的难点在于,ScheduleFromProc函数实际上是一个“跨越时空”的函数。一旦被调用,该函数会检查操作系统的就绪线程队列,从中选择一个优先级最高的投入运行。这样调用它的当前线程,实际上就不再占用CPU了。但是ScheduleFrom Proc函数会保存当前线程的上下文,等待线程被另外的线程唤醒。另外的线程必须调用SendMessage,才能唤醒一个等待消息的线程。

一旦另外的线程(或驱动程序)调用SendMessage,在当前线程的消息队列里面放入一个消息,则当前线程同时会被唤醒。这里的唤醒,实际上就是SendMessage函数,把当前线程的状态修改为READY,然后加入就绪队列。这样下一次调度时机来临的时候,如果当前线程的优先级足够高,就会被调度执行。

SendMessage函数的代码刚好与GetMessage相反,它向一个线程的消息队列里面放入一条消息,然后检查目标线程是否处于等待消息状态。需要注意的是,一个线程的消息队列中可能有多个消息,因此如果消息队列不为空,线程是不会处于等待消息状态的。如果SendMessage函数发现目标线程不处于消息等待状态,则直接返回。否则,会试图“唤醒”目标线程。这里的唤醒,无非是修改一下目标线程的状态,然后把它重新放入就绪队列。

这样GetMessage和SendMessage两个函数你来我往,密切配合,就形成了大名鼎鼎的消息驱动机制。很多人可能认为消息驱动机制不适合实时操作系统,因为线程之间的通信,只是发送消息,不能确保消息被及时有效处理。其实,消息传递机制与其他线程同步机制是一样的,都是线程的阻塞/唤醒等轮换操作。不同的是,一般的线程同步机制,大多是通过全局变量、共享对象等传递信息,然后通过互斥体、信号量等完成同步和资源保护。但消息机制则是通过消息来实现信息传递的,这不需要全局变量或全局对象,代码逻辑反而更加清晰。在实时性方面,消息传递机制与其他线程同步机制是一样的。不论是SendMessage函数还是其他的同步函数,本质上都是修改线程状态,然后再加入就绪队列。只要线程的优先级足够高,在下一次调度时机到来时,就会被调度执行。并不会出现消息积压的情况。

如果上面的分析还是不能说服读者,没有问题,Hello China等操作系统也实现了传统的线程同步和资源保护机制,比如事件、互斥体、信号量等机制,读者完全可以用这些机制来替代消息驱动机制。但是如果读者认同消息驱动机制,那么建议还是充分使用消息驱动机制完成程序的开发。因为消息驱动机制有很强的扩展性和灵活性,能够模拟非常复杂的交互关系,且代码逻辑关系清晰,便于维护。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈