下面把焦点转移到DIM(Device Input Manager,设备输入管理器)对象。在前面关于鼠标驱动程序的描述中,已经初步接触到DeviceInputManager对象,就是鼠标驱动程序通过综合三次连续中断,形成一个合法的用户输入消息后,调用SendDeviceMessage,把这个消息提交给操作系统内核。SendDeviceMessage就是DeviceImputManager(后文简写为DIM)提供的一个最重要的函数。对程序员来说,阅读代码或原型定义,是理解算法或对象最好的方法,下面就是DIM对象的定义,非常简单:
该对象最重要的两个变量,就是lpFocusKernelThread和lpShellKernelThread。其中第一个就是当前输入焦点线程,而lpShellKernelThread则是缺省的shell线程。道理很简单,DIM对象在接收到设备驱动程序送过来的消息后(通过SendDeviceMessage函数),会首先判断lpFocusKernelThread是否为空。如果为空,说明还未设置当前输入焦点线程,于是会把消息提交给shell线程(lpShellKernelThread)。如果不为空,则把消息提交给当前输入焦点线程。从这里可以看出,当前输入焦点线程是可有可无的,但shell线程是一定要有的。shell线程是一个兜底线程,当没有输入焦点接收消息时,DIM就会把消息送给shell线程。再强调一下,一定要把当前输入焦点线程与GUI界面下的焦点窗口所属线程区分开。焦点窗口所属线程不一定是当前输入焦点线程(在GUI模式下,当前输入焦点线程是RAWIT线程),当前输入焦点线程也不一定是焦点窗口所属线程。
另外两个函数,SetFocusThread和SetShellThread,就是用于设置这两个变量的。在Hello China初始化过程中,创建完字符shell线程之后,马上调用SetShellThread,把当前shell线程设置为字符shell。这样任何用户按键数据都可以传递到字符shell线程了,由shell线程做进一步的处理。而对于GUI模式,则稍有不同。在GUI模块初始化过程中,创建完RAWIT线程后,会调用SetFocusThread,把当前焦点线程设置为RAWIT。这样任何用户主动输入都会被DIM线程送到RAWIT进行处理,RAWIT再进一步把消息分发到合适的用户线程,进而被递送到窗口进行处理。这里之所以把RAWIT当作当前输入焦点线程,而不是作为当前shell线程进行处理,是因为GUI模式是可退出的,退出后,就会重新进入字符shell模式。这样如果把字符模式的shell替换为RAWIT线程,就不能退出到字符shell了。
下面是GUI模块初始化的部分代码,摘录在此,以便进一步加深读者印象:
这段代码的含义比较简单,主要是创建了RAWIT线程,然后把这个线程设置为当前输入焦点。从这以后,所有键盘输入、鼠标输入、触摸屏输入等主动输入消息,都将会被传递到RAWIT线程。RAWIT线程再把消息传递给合适的用户线程。
大概的过程似乎已经说清楚了,只要调用SetFocusThread,把一个线程设置为当前输入线程,硬件设备输入的消息就会被传递到该线程。但这只是比较泛泛的描述,具体是怎么传递的呢?所有细节都在SendDeviceMessage函数的实现里。仍然采用老方法,阅读代码。Linux操作系统的作者Linus Torvalds曾经说过,阅读源代码是最好的理解系统原理的方法。下面就是SendDeviceMessage的相关代码,为了节约篇幅,我们省略了许多不相关的注释和安全检查代码:
这段代码比较简单,但是分支比较多。标号(1)部分代码是个特殊处理,这里首先判断设备是不是指定了消息的目标线程。很多情况下,有些设备是与特定应用程序相关联的。比如一个定制的游戏操控设备,可能只会与一个特定的游戏线程相关联。这时候游戏操控设备的输入,就无需经过内核(DIM对象),直接传送到游戏程序即可。这种情况下,操控设备以游戏程序的线程对象为参数(lpTarget参数),调用SendDeviceMessage函数,即可直接把消息传递到游戏程序。因此(1)处的代码,就是处理这种情况的。
但大多数的设备,是不指定目标线程的(lpTarget为NULL),具体送给哪个线程,是由DIM来确定的。这就是后续代码的目的。(1)后面的代码不复杂,但是分支比较多,大致步骤如下:
(1)首先判断当前输入焦点线程是否为NULL,如果为NULL,则转至步骤(5)处理,否则转至步骤(2)处理。
(2)判断当前输入焦点线程的状态是否为终止状态。有些情况下,输入焦点线程运行结束了,但是还未被取消作为输入焦点,会出现这种情况。这种情况下,把消息送过去是无意义的,因此要特殊处理。如果不为终止状态,则转至步骤(4)处理,否则转至步骤(3)处理。
(3)如果当前输入焦点为终止状态,则首先把当前输入焦点线程设置为NULL,这样后续消息就会直接被送到shell线程处理了。然后把消息送给当前shell线程处理。如果shell线程也不存在,则失败返回。(www.xing528.com)
(4)当前输入焦点线程状态正常(不是终止状态),则直接把消息送给当前输入焦点线程,然后返回。
(5)如果当前输入焦点线程为NULL,则消息会被送到shell线程。在送到shell线程之前,也需要做一下判断,看shell是否也为NULL。如果shell也为NULL,则以失败返回,否则会把消息送给shell线程
至此,消息就会被送到当前输入焦点线程或shell线程的消息队列,等待输入焦点线程或shell线程做进一步处理。需要注意的有三个地方:
(1)SendDeviceMessage一般是在设备驱动程序内调用的,其运行上下文为中断上下文,不属于线程上下文。在中断返回的时候,运行上下文会切换为线程上下文,优先级最高且状态为READY的核心线程会被调度运行。这样就确保了系统的响应时间,这在嵌入式领域非常重要。
(2)SendDeviceMessage是调用SendMessage函数,把消息传递给特定线程的。SendMessage函数是一个重要的系统函数,该函数把消息放到线程的消息队列,然后检查线程的状态,如果线程处于等待消息的阻塞状态,则会唤醒线程。这样在下一个调度时机,线程就可能被调度执行(只要其优先级足够高),于是消息得以处理。SendMessage函数的详细机制,可参考第4章。
(3)__DEVICE_MESSAGE和__KERNEL_THREAD_MESSAGE是两个定义完全一致的结构体,用于承载消息信息。其中__DEVICE_MESSAGE是面向设备驱动程序使用的,而后者则是面向应用程序使用的,因此使用了不同的名字。为了方便读者理解,再把消息对象的定义摘录下来:
其中wCommand是消息的类型,而wParam和dwParam分别是与消息关联的两个变量,用于承载更进一步的消息信息,比如鼠标的坐标等。
至此,DIM的工作机制解释完了。至于DIM的初始化(Initialize函数)、如何设置当前输入焦点线程、当前shell线程,可参考相关代码(位于[kernel/kernel/dim.cpp]文件中),这些内容比较简单,就不单独讲述了。
到此为止,消息已经经过了设备驱动程序的处理,经过了DIM对象的处理,被传递到了当前输入焦点线程(在GUI模块中即是RAWIT线程)。后续部分将进一步讲解当前输入焦点线程是如何进一步把消息传递给应用线程的。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。