嵌入式操作系统
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

4.2.8 线程的消息队列

消息队列是一个由数组结构构成的循环队列,即核心线程对象(__KERNEL_THREAD_OBJECT)定义的KernelThreadMsg数组,为方便阅读,把核心线程对象定义中关于线程消息队列的部分代码列举如下。

… … …
__KERNEL_THREAD_MESSAGE
          KernelThreadMsg[MAX_KTHREAD_MSG_NUM];
UCHAR                            ucMsgQueueHeader;
UCHAR                            ucMsgQueueTail;
UCHAR                            ucCurrentMsgNum;
UCHAR                            ucAligment;
__EVENT*                         lpMsgEvent;
… … …

KernelThreadMsg数组是一个类型为__KERNEL_THREAD_MESSAGE结构的数组,根据目前的定义,该数组大小是32(MAX_KTHREAD_MSG_NUM=32)。ucAligment是为了实现数据对齐(32 bit对齐),ucQueueHeader和ucQueueTail分别指向队列的头部和尾部,其中,ucQueueHeader指向队列的第一个非空元素(若队列非空的话),而ucQueueTail指向了消息队列中第一个空元素(若队列不满的话)。ucCurrentMsgNum则指出了当前队列中消息的个数,如图4-8所示。

图4-8 线程的消息队列

系统中的核心线程可以通过SendMessage函数调用向队列中发送消息,如果队列不满,则消息被存储在ucQueueTail所指向的位置,同时ucQueueTail后移一个元素(指向下一个非空位置),ucCurrentMsgNum增加1,如图4-9所示。

图4-9 线程消息队列的添加操作

线程本身可以调用GetMessage函数,从自己的消息队列中获取消息,若当前消息队列为空,则GetMessage函数阻塞(通过等待一个EVENT核心对象),直到有其他线程向本线程的消息队列中发送消息。若消息队列非空,则GetMessage函数取走ucQueueHeader所指位置的消息,然后ucQueueHeader向后移动一个位置,ucCurrentMsgNum减1,如图4-10所示。

图4-10 线程消息队列的删除操作

队列当前的状态(空或满)可以通过判断ucCurrentMsgNum的大小获得。

lpMsgEvent是一个__EVENT内核对象,该对象用来完成消息操作的同步。在当前Hello China的实现中,GetMessage函数是按照同步操作实现的,即若队列中有消息,则该函数立即返回,并把队列中的消息返回给用户程序,若队列中没有消息,则该函数阻塞,直到有消息到达。阻塞操作就是通过等待该事件对象实现的,下面是GetMessage函数的相关代码。

  static BOOL MgrGetMessage(__COMMON_OBJECT*
  lpThread,__KERNEL_THREAD_MESSAGE* lpMsg)
  {
    __KERNEL_THREAD_OBJECT*    lpKernelThread=NULL;
    DWORD                      dwFlags      =0L;
    if((NULL==lpThread) || (NULL==lpMsg)) //Parameters check.
        return FALSE;
    lpKernelThread=(__KERNEL_THREAD_OBJECT*)lpThread;
    if(MsgQueueEmpty(lpThread))
    {
        lpKernelThread->lpMsgEvent->WaitForThisObject(
            (__COMMON_OBJECT*)(lpKernelThread->lpMsgEvent));
//Block the current thread.
    }
    … … …
    return TRUE;
}

在上述实现中,GetMessage函数首先判断线程的消息队列是否为空,若为空,则调用lpMsgEvent对象的WaitForThisObject函数等待lpMsgEvent对象。

而lpMsgEvent对象是被SendMessage函数唤醒的,SendMessage函数的相关实现代码如下。

static BOOL MgrSendMessage(__COMMON_OBJECT*
                              lpThread,__KERNEL_THREAD_MESSAGE* lpMsg)
{
    __KERNEL_THREAD_OBJECT*    lpKernelThread=NULL;
    BOOL                     bResult     =FALSE;
    DWORD                    dwFlags     =0L;
    if((NULL==lpThread) || (NULL==lpMsg)) //Parameters check.
        return bResult;
    if(MsgQueueFull(lpThread))           //If the queue is full.
        return bResult;
    lpKernelThread=(__KERNEL_THREAD_OBJECT*)lpThread;
    … … …//Put the message into kernel thread's message queue.
    //
    //Set the signal to indicate there is one message to be get at least.
    //
    lpKernelThread->lpMsgEvent->SetEvent((__COMMON_OBJECT*)(lpKerne
lThread->lpMsgEvent));
    return bResult;
}

在上述实现中,每向线程队列发送一个消息,就会调用SetEvent函数设置事件对象的状态,这样若当前线程因为调用GetMessage函数阻塞,在此时刻就会被唤醒。

对于线程的消息队列,最后需要解释的就是__KERNEL_THREAD_MESSAGE结构本身了,顾名思义,该结构用来装载具体的消息,定义如下。

  BEGIN_DEFINE_OBJECT(__KERNEL_THREAD_MESSAGE)
    WORD           wCommand;
    WORD           wParam;
    DWORD          dwParam;
    //DWORD          (*MsgAssocRoutine)(__KERNEL_THREAD_MESSAGE*);
  END_DEFINE_OBJECT()

wCommand是一个命令字,指出具体的消息类型,比如键盘按下、鼠标按下等,也可以由用户自己定义。wParam和dwParam是两个跟wCommand关联的参数,比如,跟“键盘按下”这样一个消息相关联,可以是具体被按下的键的ASCII码(可以通过wParam设置)。

消息队列机制的应用十分广泛,也十分灵活,从理论上说,任何基于多线程通信的应用模型都可以使用消息队列来实现。