读取队列任务的优先级要比写入队列任务的优先级要低,这点非常重要。不然,一旦向队列中添加一个元素之后,高优先级的任务将会马上占有CPU并处理该元素。设备驱动任务将不能够做其它更多的操作直到高优先级任务因为再次等待队列而阻塞。因此,如果一个应用任务拥有比写入队列任务更高的优先级,它的控制流就如同在设备驱动任务以事件为参数调用了应用任务函数。如果应用任务花费的时间长于轮询周期,则会有事件丢失的可能。
下面的例子可以让我们开发出用于简单应用的事件队列结构。一台VCR有一些控制按钮,如时间设置频道设置等等。它也有三个一般的按钮A B C 。在屏幕上会根据上下文给出这三个按钮的具体操作用处。例如,在按下时间设置按钮后:菜单会把A B C 解释为:
A = 小时
B = 分钟
C = 设置结束时间
在按下频道按钮后,菜单显示:
A = 下一个
B = 搜索
C = 存储
每个按键如何执行它的工作并不重要。我们关心的是在指定上下文中如何对每个按键的意义进行解析。很多产品在屏幕底部放置没有标识的按钮,然后在屏幕下方相应于按钮的位置显示标识说明。从这一点来看,按钮位于显示屏幕上面的标识说明,可以通过软件来更改。许多自动柜员机(ATM)就是采用的这种技术。
VCR拥有两个带箭头显示的按钮用于增加或减小选定的值。录像带驱动器中使用限位开关来对录像带插入还是移除状态进行检测,当没有录像带插入时候,需要对用户的某些选择操作进行阻止。我们称该开关为录像带检测器。当用户插入或者移除录像带时候,我们可以把它当成一个用户事件。
这里有两种类型的事件:按键事件和录像带检测事件。按键可以按如下方式定义:
typedef enum
{
KEY_CHANNEL,
KEY_SET_TIMER,
KEY_A,
KEY_B,
KEY_C,
KEY_UP_ARROW,
KEY_DOWN_ARROW
}KeyValue;
typedef enum
{
KEY_PRESS,
KEY_RELEASE,
}KeyAction;
typedef struct
{
KeyValue value;
KeyAction action;
}KeyEvent;
录像带检测器事件描述如下:
typedef enum
{
TAPE_INSERTED,
TAPE_REMOVED
}TapeAction;
typedef struct
{
TapeAction action;
}TapeEvent;
一个只有一个条目的结构体可以当成一个单一元素,但是为了保持一致性,我们还是将其放入一个结构体中。
如果在30秒的时间内用户没有任何操作,则会产生一个超时事件。VCR显示屏将会恢复到一个显示系统时间的默认状态。这个事件不需要任何附加的数据。
为了将这些事件中的任何一个放入到队列中,需要创建一个可以区分的包含两个结构体的联合体类型。区分标志是在联合体之外的一个数据域,通过它可以区分联合体中的结构体哪一个是有效的。具体如下所示:
typedef enum
{
KEY_EVENT,
TAPE_EVENT,
TIMEOUT_EVENT
}EventTag;
typedef struct
{
EventTag event;
union
{
KeyEvent keyData;
TapeEvent tapeData;
}eventData;
}EventQueueEntry;
现在我们已经有了一个可以插入到队列中的结构体,它已经包含了足够的信息以供队列读取任务对事件进行解析。