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

3.2 Shell的消息处理过程

默认情况下,一个线程在创建的同时会创建一个消息队列,该消息队列与线程的控制结构(核心线程对象)进行绑定。其他的线程可以调用SendMessage函数向该线程发送消息,发送的消息类型是__KERNEL_THREAD_MESSAGE。线程可以调用GetMessage函数从自己的消息队列中获取消息,该函数以KernelThreadMessage的地址(指针)为参数。若该函数成功地从消息队列中获取了一个消息,则返回TRUE,KernelThreadMessage结构体里面存储了所获得的消息的内容;若当前线程消息队列为空,则该函数会阻塞当前线程。线程之间的消息有许多种类型,比如键盘消息、鼠标消息,以及用户自己定义的消息。在EntryPoint的实现中,首先打印出用户提示标识符,然后进入一个循环,循环调用GetMessage函数,从自己的线程队列中获得消息,若调用成功,则判断消息的类型是否是线程终止消息(一个线程可以给另外一个线程发送终止消息KTMSG_THREAD_TERMINAL,来终止此线程的运行)。若是,则跳出循环,从而导致线程运行结束,否则,调用DispatchMessage函数来分发消息(处理消息)。

GetMessage函数和SendMessage函数的实现在另外的章节中会有描述。Dispatch Message函数完成消息分发的功能。当前情况下,该函数的实现十分简单,只是把KernelThreadMessage中的参数分离出来,然后调用消息处理函数EventHandler。其中,EventHandler函数作为一个指针,传递给DispatchMessage函数,该函数的实现代码如下。

BOOL EventHandler(WORD wCommand,WORD wParam,DWORD dwParam)
{
    WORD wr=0x0700;
    BYTE bt=0x00;
    BYTE Buffer[12];
    switch(wCommand)
    {
    case MSG_KEY_DOWN:
        bt=LOBYTE(LOWORD(dwParam));
        if(VK_RETURN==bt)
        {
            if(BufferPtr)
                DoCommand();
            PrintPrompt();
            break;
        }
        if(VK_BACKSPACE==bt)
        {
            if(0 !=BufferPtr)
            {
                GotoPrev();
                BufferPtr--;
            }
            break;
        }
        else
        {
            if(MAX_BUFFER_LEN-1 > BufferPtr)
            {
                CmdBuffer[BufferPtr]=bt;
                BufferPtr++;
                wr+=LOBYTE(LOWORD(dwParam));
                PrintCh(wr);
            }
        }
        break;
    default:
        break;
    }
    return 0L;
  }

在当前的实现中,EventHandler函数只处理键盘消息。其中,KernelThreadMessage的wCommand变量存储了具体的消息类型。目前情况下,定义了两种键盘消息:MSG_KEY_DOWN和MSG_KEY_UP,分别在用户按下键盘和放开按键的时候由键盘驱动程序发送。其中,EventHandler函数只处理键盘按下消息MSG_KEY_DOWN。WCommand变量标明了消息类型,而dwParam变量(KernelThreadMessage的另一个成员)则包含了对应于特定消息的相关参数。比如,在按键消息中,dwParam的最低一个字节存放了键盘被按下的ASCII码,这样EventHandler函数就可以根据dwParam的最低一个字节确定按下哪个键。根据所按下键的不同,分三种情况进行处理。

(1)若按下的键是回车键(VK_RETURN),则EventHandler函数判断当前键盘缓冲队列(CmdBuffer是一个键盘缓冲队列,BufferPtr则指明了当前队列中元素的数量)是否为空,若为空(BufferPtr为零),则只会换一行,重新打印出提示字符,然后返回。若不为空,则说明用户已经在提示符下输入了命令。这时候EventHandler函数会调用DoCommand函数来处理用户输入的命令。在目前的实现中,CmdBuffer是一个全局变量数组,因此DoCommand函数可直接访问该数组,不需要任何参数。DoCommand函数的实现在后面进行详细介绍。

(2)若按下的键是一个退格键(BACKSPACE),则判断当前命令缓冲区(CmdBuffer)是否为空,若为空,则不做任何处理;若不为空,则删除缓冲区的最后一个元素,并把显示器上的光标后移一格,最终的表现就是用户按BACKSPACE键,删除了输入的一个字符。

(3)若按下的键不是上述两者之一,则EventHandler函数会判断当前命令缓冲区是否满(长度是否达到了MAX_BUFFER_LEN,目前定义为512)。若已经满了,则不做任何处理,直接返回,否则,会把用户按下的键的ASCII字符存到CmdBuffer当中,并更新BufferPtr。

与DOS命令提示符类似,EventHandler函数实际上是实现了一个简单的行编辑器,用户可以输入字符,通过BACKSPACE键删除字符,并通过回车键确认输入的命令引发操作系统的执行。

上面讲到,若用户按下的键是回车键,且当前命令缓冲区不为空,则EventHandler函数会调用DoCommand函数,处理用户输入的命令。DoCommand函数会对CmdBuffer (全局的命令缓冲区)进行分析,然后根据命令的不同,调用合适的处理函数。该函数的实现代码如下。

VOID  DoCommand()
{
    DWORD wIndex=0x0000;
    BOOL bResult=FALSE;     //If find the correct command object,then
                            //This flag set to TRUE.
    BYTE tmpBuffer[36];
    CmdBuffer[BufferPtr]=0x00; //Prepare the command string.
    BufferPtr=0;
    while((' ' !=CmdBuffer[wIndex]) && CmdBuffer[wIndex] && (wIndex < 32))
    {
        tmpBuffer[wIndex]=CmdBuffer[wIndex];
        wIndex++;
    }
    tmpBuffer[wIndex]=0;
    for(DWORD dwIndex=0;dwIndex < CMD_OBJ_NUM;dwIndex++)
    {
        if(StrCmp(&tmpBuffer[0],CmdObj[dwIndex].CmdStr))
        {
            CmdObj[dwIndex].CmdHandler(&CmdBuffer[wIndex]);
                                //Call the command handler.
            bResult=TRUE;     //Set the flag.
            break;
        }
    }
    if(!bResult)
    {
        bResult=DoExternalCmd(&CmdBuffer[0]);
                                //Call the external command parser.
      if(!bResult)
      {
              DefaultHandler(NULL);
                                //Call default event handler.
      }
    }
    return;
}

DoCommand函数是整个Shell命令处理的入口点。在目前的实现中,对于系统命令,分成两类。

(1)内部命令。操作系统自带的一些功能命令,比如设置时间、设置日期等。这些命令在运行的时候,直接在Shell线程的上下文中运行,不需要额外创建核心线程。这类命令的处理过程往往很短,且不会引起阻塞。

(2)外部命令。这类命令实际上是一些应用程序,由用户编写(也有几个是操作系统自带的),一般实现一些特定的功能。与内部命令不同的是,所有外部命令都需要额外创建一个线程,即外部命令在执行的时候,都有自己的上下文空间,且可以被阻塞。