VxWorks中基于消息队列实现C/S通信

预备知识

在VxWorks中实现基于消息队列的C/S通信主要用到了Wind内核中的消息队列API,msgQLib。

这个库里面的发送消息和接受消息的API分别为:

STATUS msgQSend(MSG_Q_ID msgQId, char * buffer, UNIT nBytes, int timeout, int priority)
// 当有任务正在等待某个消息的到来,则被发送的消息立即提交给第一个等待的任务;否则,消息插入消息队列,当消息队列满时,任务被阻塞

int msgQReceive(MSG_Q_ID msgQId, char * buffer, UNIT maxNBytes, int timeout)
// 当有消息在消息队列上,第一个消息被接受;否则调用者被阻塞,进入目标队列的任务队列排队(基于优先级/FIFO)

其中两个函数的timeout参数说明:

  • 对于msgQSend,当消息队列满时,发送者被阻塞,等待时间最长为timeout个tick。
  • 对于msgQReceive,当消息队列为空时,接受者被阻塞,等待时间最长为timeout个tick。
其中timeout
  • 为NO_WAIT,则不等待,立即返回。
  • 为WAIT_FOREVER,则一直等待下去,知道队列为非满(对于发送)后非空(对于接受)
  • 为某个整形,则等待timeout个ticks,超过时限,则返回。
使用消息队列实现的C/S通信模式示意图为:
VxWorks中基于消息队列实现C/S通信_第1张图片
很直观吧。这里要明确的一点是,若从空队列读消息或者向满队列发消息的任务都会被阻塞,阻塞的时长由timeout决定。
要想通过消息队列实现两个任务的全双工通信,至少需要两个消息队列。

程序编写

程序设计很简单,但是在设计程序的时候还要明确:Wind内核默认采用基于优先级的抢占式调度(Priority-based preemptive scheduling)。

什么意思呢,系统中每个任务都拥有一个优先级,任意时刻,内核将CPU分给处于就绪态的优先级最高的任务运行,这是“基于优先级的”;一旦内核发现有一个比当前正在运行的任务优先级高的任务就绪,内核立即保存当前任务的上下文,切换到这个高优先级任务的上下文中运行,这是“抢占”,在接下去的程序修改客户端和服务端的优先级导致的运行结果不一致,可以非常直观地看出这一调度策略的运行。

但是这种任务调度策略有缺点啊。假如现在有多个相同优先级的任务要共享一台处理器时,如果某个正在执行的任务永远不被阻塞,那么这个任务将一直独占处理器(大家优先级一致,谁也无法抢占),这样其他相同优先级的任务就没有机会得到执行。为了解决这个缺陷,wind内核还采用轮转调度来配合基于抢占式的优先级调度。就是为了让优先级相同的(优先级高的仍然可以抢占轮转中的优先级低的任务)、处于就绪态的任务公平地共享CPU,VxWorks主要通过调用kernelTimeSlice()来实现轮转调度,这里不再赘述。

然后我们可以编写代码:

#include "vxWorks.h"
#include "taskLib.h"
#include "msgQLib.h"
#include "sysLib.h"
#include "stdio.h"

#define CLIENT_TASK_PRI 100
#define SERVER_TASK_PRI 101
#define TASK_STACK_SIZE 5000
#define MSG_NUM 3

LOCAL MSG_Q_ID requestQId;
LOCAL MSG_Q_ID response1QId;
LOCAL MSG_Q_ID response2QId;

LOCAL int notDone;

typedef struct msg
{
	int tid;
	int what;
}MSG;


LOCAL STATUS serverTask();
LOCAL STATUS clientTask(int cid,int value);

STATUS mMain()
{
	notDone = 1;
	/* create request Queue */
	if((requestQId = msgQCreate(MSG_NUM * 2 + 1, sizeof(MSG), MSG_Q_FIFO)) == NULL)
	{
		perror("Error on creating requestQ");
	}
	/* create response Queue */
	if((response1QId = msgQCreate(MSG_NUM + 1, sizeof(MSG), MSG_Q_FIFO)) == NULL)
	{
		perror("Error on creating responseQ1");
	}
	if ((response2QId = msgQCreate(MSG_NUM + 1, sizeof(MSG), MSG_Q_FIFO)) == NULL)
	{
		perror("Error on creating responseQ2");
	}
	
	/* spawn server task */
	if(taskSpawn("tServerTask", SERVER_TASK_PRI, 0, TASK_STACK_SIZE, (FUNCPTR)serverTask,0,0,0,0,0,0,0,0,0,0) == ERROR)
	{
		perror("Error on spawning tServerTask");
		return (ERROR);
	}
	
	/* spawn client task */
	if(taskSpawn("tClientTask_1", CLIENT_TASK_PRI, 0, TASK_STACK_SIZE, (FUNCPTR)clientTask,1,100,0,0,0,0,0,0,0,0) == ERROR)
	{
		perror("Error on spawning tClientTask_1");
		return (ERROR);
	}
	if(taskSpawn("tClientTask_2", CLIENT_TASK_PRI, 0, TASK_STACK_SIZE, (FUNCPTR)clientTask,2,100,0,0,0,0,0,0,0,0) == ERROR)
	{
		perror("Error on spawning tClientTask_2");
		return (ERROR);
	}
	
	/* wait tasks*/
	while(notDone)
	{
		taskDelay(sysClkRateGet());
	}
	if(msgQDelete(requestQId) == ERROR || msgQDelete(response1QId) == ERROR || msgQDelete(response2QId) == ERROR)
	{
		perror("Error in deleting msgQ");
		return (ERROR);
	}
	return (OK);
}

/* server Task */
STATUS serverTask()
{
	int num = 0;
	MSG msg;
	printf("\n\nI'm server task, my task id = %d.\n",taskIdSelf());
	/* read message from request Queue */
	while(num++ < MSG_NUM * 2)
	{
		printf("-->Num of request Queue:%d\n", msgQNumMsgs(requestQId));
		if((msgQReceive(requestQId, (char *)&msg, sizeof(MSG), WAIT_FOREVER)) == ERROR){
			perror("serverTask Error on receiving the msg");
			return (ERROR);
		}
		/* print and add 1 to msg.what */
		else 
		{
			printf("serverTask receive msg from %d, msg.what = %d\n", msg.tid, msg.what++);
		}
		/* response to the client */
		switch(msg.tid)
		{
			case 1:
				if((msgQSend(response1QId, (char *)&msg, sizeof(MSG), WAIT_FOREVER, MSG_PRI_NORMAL)) == ERROR)
				{
					perror("Error in sending the message to responsQ1.");
					return(ERROR);
				}
				else
				{
					printf("serverTask sending to responsQ1 with value %d.\n", msg.what);
				}
				break;
			case 2:
				if((msgQSend(response2QId, (char *)&msg, sizeof(MSG), WAIT_FOREVER, MSG_PRI_NORMAL)) == ERROR)
				{
					perror("Error in sending the message to responsQ2.");
					return(ERROR);
				}
				else
				{
					printf("serverTask sending to responsQ2 with value %d.\n", msg.what);
				}
				break;
			default:
				perror("Error when response to the msg.");
		}
	}
	return (OK);
}

/* client Task */
STATUS clientTask(int cid,int value)
{
	int num = 0;
	MSG msg;
	printf("I am client task%d, my task id = %d.\n", cid, taskIdSelf());
	/* sending message to request Queue. */
	while (num++ < MSG_NUM)
	{
		msg.tid = cid;
		msg.what = value;
		if((msgQSend(requestQId, (char *)&msg, sizeof(MSG), WAIT_FOREVER, MSG_PRI_NORMAL)) == ERROR)
		{
			perror("Error on sending the message.");
			return(ERROR);
		}
		else
		{
			printf("clientTask%d sending to requestQ with value%d.\n", msg.tid, msg.what);
		}
	}
	num = 0;
	/* receiving message from its response Queue own. */
	switch(cid)
	{
		case 1:
			while(num++ < MSG_NUM)
			{
				if((msgQReceive(response1QId,(char *)&msg, sizeof(MSG), WAIT_FOREVER)) == ERROR)
				{
					perror("Error on receiving the message from respons1Q.");
					return (ERROR);
				}
				else
				{
					printf("clientTask%d receiving msg from respons1Q with value%d.\n", msg.tid, msg.what);
				}
			}
			break;
		case 2:
			while(num++ < MSG_NUM)
			{
				if((msgQReceive(response2QId,(char *)&msg, sizeof(MSG), WAIT_FOREVER)) == ERROR)
				{
					perror("Error on receiving the message from respons2Q.");
					return (ERROR);
				}
				else
				{
					printf("clientTask%d receiving msg from respons2Q with value%d.\n", msg.tid, msg.what);
				}
			}
			break;
		default:
			perror("Error when receive message from response Queue.");
	}
	notDone = 0;
	return(OK);
}

在Tornado中打开shell,指定入口函数,运行后:我们可以在模拟器中观察到运行结果:

VxWorks中基于消息队列实现C/S通信_第2张图片

在windview的上下文切换视图中查看客户端和服务端任务的运行情况:

VxWorks中基于消息队列实现C/S通信_第3张图片

原理分析

在程序中是先将服务端的任务spawn出来,接着再去spawn两个客户端的任务。但是由于程序中设置了客户端的优先级高于服务端的优先级,服务端刚被spawn出来就被挂起了,两个客户端优先级一致,他们顺序执行,先创建出的客户端向request队列发送了三条Message后(这时虽然服务端被唤醒,但是优先级低,处于挂起状态),尝试去response1队列中读取消息,但当时该队列为空,于是这个客户端被阻塞,后创建出的客户端运行过程相同,最后也被阻塞;这时服务端得到运行,从request队列中读取消息,发现是客户端1发送的,于是服务端做了一定的处理之后放到response1队列中,接着客户端1由阻塞态被唤醒,并且它的优先级高于服务端,而Vxworks的任务调度是基于优先级的抢占式任务调度,于是它立即得到执行,从response1队列中读取一条服务端的答复消息,做出处理后,再尝试去response1队列中读取下一条信息,发现没有信息后又被阻塞,服务端继续得到执行,即去request队列中读取下一条消息,又重复了上述过程;客户端2同理。

拓展

修改客户端和服务端的优先级一致:

VxWorks中基于消息队列实现C/S通信_第4张图片

VxWorks中基于消息队列实现C/S通信_第5张图片

修改为服务端的优先级高于客户端的优先级:
VxWorks中基于消息队列实现C/S通信_第6张图片

VxWorks中基于消息队列实现C/S通信_第7张图片


明白了VxWorks的任务调度机制和消息队列API的执行过程,理解这两个结果也就不难了。


你可能感兴趣的:(通信,cs,vxworks)