管道通信(PIPE)
管道通信方式的中间介质是文件,通常称这种文件为管道文件。两个进程利用管道文件进行通信时,一个
进程为写进程,另一个进程为读进程。写进程通过写端(发送端)往管道文件中写入信息;读进程通过读
端(接收端)从管道文件中读取信息。两个进程协调不断地进行写、读,便会构成双方通过管道传递信息
的流水线。
利用系统调用PIPE()创建一个无名管道文件,通常称为无名管道或PIPE;利用系统调用MKNOD()创建
一个有名管道文件,通常称为有名管道或FIFO。
PIPE是一种非永久性的管道通信机构,当它访问的进程全部终止时,它也将随之被撤消;它也不能用于不
同族系的进程之间的通信。而FIFO是一种永久的管道通信机构,它可以弥补PIPE的不足。
管道文件被创建后,便可对它进行读写操作,通过系统调用WRITE()和READ()来实现。通信完毕后,
可将管道文件关闭,用CLOSE()来实现。
消息通信(MESSAGE)
消息通信方式以消息缓冲区为中间介质,通信双方的发送和接收操作均以消息为单位。在存储器中,消息
缓冲区被组织成队列,通常称之为消息队列。
创建消息队列用系统调用MSGGET()来实现,这一步工作也被称为消息队列的初始化。在进行通信时,消
息队列的发送和接收分别用系统调用MSGSND()和MSGRCV()来实现。在需要改变队列的使用权限及其它
一些特性时,用MSGCTL()来实现。
管道一般用于父子进程间通信(有名管道除外,有名管道不限于父子进程通信)。而消息队列可用于你机器上的任何进程间通信(只要进程有权操作消息队列)。
请问消息队列比运用管道有什么优势?
管道一般用于父子进程间通信(有名管道除外,有名管道不限于父子进程通信)。而消息队列可用于你机
器上的任何进程间通信(只要进程有权操作消息队列)。
如果要大规模的复制数据,最快的方法莫过于共享内存。管道只是所有的UNIX都支持,性能上肯定不如共
享内存。
至于消息队列,有些UNIX系统肯定是不支持的。具体的你可以看看APUE第二版的15.1节
转载自:http://www.ibm.com/developerworks/cn/linux/l-ipc/part3/
消息队列与管道以及有名管道相比,具有更大的灵活性,首先,它提供有格式字节流,有利于减少开发人
员的工作量;其次,消息具有类型,在实际应用中,可作为优先级使用。这两点是管道以及有名管道所不
能比的。同样,消息队列可以在几个进程间复用,而不管这几个进程是否具有亲缘关系,这一点与有名管
道很相似;但消息队列是随内核持续的,与有名管道(随进程持续)相比,生命力更强,应用空间更大。
转载自:http://blog.sina.com.cn/s/blog_4a0e545d01000c7l.html
vxworks消息队列与其他方式的一些比较:
1、信号量使用方便,可以解决很多任务间的协调问题,但是信号量所传递的信息有限,而内存共享虽然
传递信息可以大些,但是不标准。消息队列作为一种折忠方式用于线程之间的信息交换。
2、消息队列允许许多的消息排队,而每个信息可以有不同长度,而传统管道中的数据仅仅是一个数据流
,没有边界。Vxworks中的管道数据有消息组成。
vxworks消息队列
消息队列允许一定数量不同长度的消息进行排列。任何任务或中断服务程序(ISR)能够发送消息给消息队
列。任何任务可以从消息队列接受消息。多任务可以从同意消息队列发送和接受消息。两个任务之间的全
双工(Full-duplex)通信需要针对不同方向的两个消息队列。
一个任务或中断服务程序(ISR)用函数msgQSend( )发送一个消息到消息队列。如果没有任务等待消息队列
的消息,这个消息被添加消息缓存的队列里。如果某些任务已经在等待消息队列中的消息,消息立刻被传
递给第一个等待的消息的任务。
一个任务用函数msgQReceive( )从消息队列得到一个消息。如果消息队列缓存中有消息存在,第一个消息
立刻出列并回到调用处(caller).如果没有消息存在,则任务(calling task)停止(blocks)并被添加到
等待消息的任务队列中。这个等待的任务队列按照优先级或先进先出(FIFO)规则排列,这个规则有消息
队列创建时所指定。
管道(Pipes)
管道对消息队列提供了一个可供选择的接口,VxWorks的I/O系统。管道是虚拟的I/O设备,由驱动pipeDrv
管理。函数pipeDevCreate()创建一个管道设备,这个调用指定管道的名字,能被排列的最多的消息数,
和每个消息允许的长度。
status = pipeDevCreate ("/pipe/name", max_msgs, max_length);
被创建的管道是一个通常命名(named)的I/O设备,任务能用标准的I/O函数打开,读,写管道,并能调用
ioctl例程。当任务试图从一个空的管道中读取数据,或向一个满的管道中写入数据时,任务被阻塞。和
消息队列一样,ISR可以向管道写入,但不能从管道读取。
vxworks消息队列和管道
消息队列是VxWorks提供的单个CPU中的任务之间通信的主要机制之一。消息队列允许基于FIFO或基于任务
优先级方式排队消息,一个消息队列的消息数目和消息长度可以由开发者在创建消息队列时指定。在理论
上,VxWorks允许多个任务向同一个消息队列发送消息,或者从同一个消息队列接收消息;而在实际应用
中,一般来说只有一个任务从消息队列接收消息,有一个或多个任务发送消息,即这个消息队列有多个生
产者,而只有一个消费者。消息队列时单向的,对于需要进行双向通信的两个任务,必须使用两个消息队
列。消息队列非常适合于Client-Server结构的任务之间的通信.
在VxWorks中,消息队列是一种代价比较高的一种通信机制,因此在使用时应该使消息的长度尽量短,而
且应避免在需要十分频繁通信的场合使用消息队列。另外消息队列中的消息是排队的,即使是完全相同的
消息,后面的消息也不会覆盖前面的消息。
管道(pipe)
在VxWorks中,管道是一种通过虚拟的I/O设备来实现的消息队列通信机制。使用函数pipeDevCreate()和
pipeDevDelete()来生成和删除管道,管道一经生成后,任务之间就可以使用标准I/O操作主要是read()和
write()进行通信。管道的优点在于它是一个I/O设备,与标准的VxWorks
I/O一样,可以使用select机制,而有了select机制,一个任务很方便地使用多个异步I/O设备,如任务要
处理同时从串口、管道、socket接收到的数据,就可以使用select。
Linux环境进程间通信(三)
消息队列(也叫做报文队列)能够克服早期unix通信机制的一些缺点。作为早期unix通信机制之一的信号能够传送的信息量有限,后来虽然POSIX 1003.1b在信号的实时性方面作了拓广,使得信号在传递信息量方面有了相当程度的改进,但是信号这种通信方式更像"即时"的通信方式,它要求接受信号的进程在某个时间范围内对信号做出反应,因此该信号最多在接受信号进程的生命周期内才有意义,信号所传递的信息是接近于随进程持续的概念(process-persistent),见 附录 1;管道及有名管道及有名管道则是典型的随进程持续IPC,并且,只能传送无格式的字节流无疑会给应用程序开发带来不便,另外,它的缓冲区大小也受到限制。
消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息。消息队列是随内核持续的(参见 附录 1)。
目前主要有两种类型的消息队列:POSIX消息队列以及系统V消息队列,系统V消息队列目前被大量使用。考虑到程序的可移植性,新开发的应用程序应尽量使用POSIX消息队列。
在本系列专题的序(深刻理解Linux进程间通信(IPC))中,提到对于消息队列、信号灯、以及共享内存区来说,有两个实现版本:POSIX的以及系统V的。Linux内核(内核2.4.18)支持POSIX信号灯、POSIX共享内存区以及POSIX消息队列,但对于主流Linux发行版本之一redhad8.0(内核2.4.18),还没有提供对POSIX进程间通信API的支持,不过应该只是时间上的事。
因此,本文将主要介绍系统V消息队列及其相应API。 在没有声明的情况下,以下讨论中指的都是系统V消息队列。
一、消息队列基本概念
- 系统V消息队列是随内核持续的,只有在内核重起或者显示删除一个消息队列时,该消息队列才会真正被删除。因此系统中记录消息队列的数据结构(struct ipc_ids msg_ids)位于内核中,系统中的所有消息队列都可以在结构msg_ids中找到访问入口。
- 消息队列就是一个消息的链表。每个消息队列都有一个队列头,用结构struct msg_queue来描述(参见 附录 2)。队列头中包含了该消息队列的大量信息,包括消息队列键值、用户ID、组ID、消息队列中消息数目等等,甚至记录了最近对消息队列读写进程的ID。读者可以访问这些信息,也可以设置其中的某些信息。
- 下图说明了内核与消息队列是怎样建立起联系的:
其中:struct ipc_ids msg_ids是内核中记录消息队列的全局数据结构;struct msg_queue是每个消息队列的队列头。
从上图可以看出,全局数据结构 struct ipc_ids msg_ids 可以访问到每个消息队列头的第一个成员:struct kern_ipc_perm;而每个struct kern_ipc_perm能够与具体的消息队列对应起来是因为在该结构中,有一个key_t类型成员key,而key则唯一确定一个消息队列。kern_ipc_perm结构如下:
struct kern_ipc_perm{ //内核中记录消息队列的全局数据结构msg_ids能够访问到该结构; key_t key; //该键值则唯一对应一个消息队列 uid_t uid; gid_t gid;uid_t cuid;gid_t cgid;mode_t mode;unsigned long seq;}二、操作消息队列
对消息队列的操作无非有下面三种类型:
1、 打开或创建消息队列
消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述字,只需提供该消息队列的键值即可;
注:消息队列描述字是由在系统范围内唯一的键值生成的,而键值可以看作对应系统内的一条路经。
2、 读写操作
消息读写操作非常简单,对开发人员来说,每个消息都类似如下的数据结构:
struct msgbuf{long mtype;char mtext[1];};mtype成员代表消息类型,从消息队列中读取消息的一个重要依据就是消息的类型;mtext是消息内容,当然长度不一定为1。因此,对于发送消息来说,首先预置一个msgbuf缓冲区并写入消息类型和内容,调用相应的发送函数即可;对读取消息来说,首先分配这样一个msgbuf缓冲区,然后把消息读入该缓冲区即可。
3、 获得或设置消息队列属性:
消息队列的信息基本上都保存在消息队列头中,因此,可以分配一个类似于消息队列头的结构(struct msqid_ds,见 附录 2),来返回消息队列的属性;同样可以设置该数据结构。
消息队列API
1、文件名到键值
#include <sys/types.h>#include <sys/ipc.h>key_t ftok (char*pathname, char proj);它返回与路径pathname相对应的一个键值。该函数不直接对消息队列操作,但在调用ipc(MSGGET,…)或msgget()来获得消息队列描述字前,往往要调用该函数。典型的调用代码是:
key=ftok(path_ptr, ‘a‘); ipc_id=ipc(MSGGET, (int)key, flags,0,NULL,0); …2、linux为操作系统V进程间通信的三种方式(消息队列、信号灯、共享内存区)提供了一个统一的用户界面:int ipc(unsigned int
call, int
first, int
second, int
third, void *
ptr, long
fifth);
第一个参数指明对IPC对象的操作方式,对消息队列而言共有四种操作:MSGSND、MSGRCV、MSGGET以及MSGCTL,分别代表向消息队列发送消息、从消息队列读取消息、打开或创建消息队列、控制消息队列;first参数代表唯一的IPC对象;下面将介绍四种操作。
- int ipc(
MSGGET, intfirst,
intsecond,
intthird,
void*ptr,
longfifth);
与该操作对应的系统V调用为:int msgget( (key_t)first,second)。 - int ipc(
MSGCTL, intfirst,
intsecond,
intthird,
void*ptr,
longfifth)
与该操作对应的系统V调用为:int msgctl( first,second, (struct msqid_ds*) ptr)。 - int ipc(
MSGSND, intfirst,
intsecond,
intthird,
void*ptr,
longfifth);
与该操作对应的系统V调用为:int msgsnd( first, (struct msgbuf*)ptr, second, third)。 - int ipc(
MSGRCV, intfirst,
intsecond,
intthird,
void*ptr,
longfifth);
与该操作对应的系统V调用为:int msgrcv( first,(struct msgbuf*)ptr, second, fifth,third),
注:本人不主张采用系统调用ipc(),而更倾向于采用系统V或者POSIX进程间通信API。原因如下:
- 虽然该系统调用提供了统一的用户界面,但正是由于这个特性,它的参数几乎不能给出特定的实际意义(如以first、second来命名参数),在一定程度上造成开发不便。
- 正如ipc手册所说的:ipc()是linux所特有的,编写程序时应注意程序的移植性问题;
- 该系统调用的实现不过是把系统V IPC函数进行了封装,没有任何效率上的优势;
- 系统V在IPC方面的API数量不多,形式也较简洁。
3.系统V消息队列API
系统V消息队列API共有四个,使用时需要包括几个头文件:
#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>1)int msgget(key_t key, int msgflg)
参数key是一个键值,由ftok获得;msgflg参数是一些标志位。该调用返回与健值key相对应的消息队列描述字。
在以下两种情况下,该调用将创建一个新的消息队列:
- 如果没有消息队列与健值key相对应,并且msgflg中包含了IPC_CREAT标志位;
- key参数为IPC_PRIVATE;
参数msgflg可以为以下:IPC_CREAT、IPC_EXCL、IPC_NOWAIT或三者的或结果。
调用返回:成功返回消息队列描述字,否则返回-1。
注:参数key设置成常数IPC_PRIVATE并不意味着其他进程不能访问该消息队列,只意味着即将创建新的消息队列。
2)int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg);
该系统调用从msgid代表的消息队列中读取一个消息,并把消息存储在msgp指向的msgbuf结构中。
msqid为消息队列描述字;消息返回后存储在msgp指向的地址,msgsz指定msgbuf的mtext成员的长度(即消息内容的长度),msgtyp为请求读取的消息类型;读消息标志msgflg可以为以下几个常值的或:
- IPC_NOWAIT 如果没有满足条件的消息,调用立即返回,此时,errno=ENOMSG
- IPC_EXCEPT 与msgtyp>0配合使用,返回队列中第一个类型不为msgtyp的消息
- IPC_NOERROR 如果队列中满足条件的消息内容大于所请求的msgsz字节,则把该消息截断,截断部分将丢失。
msgrcv手册中详细给出了消息类型取不同值时(>0; <0; =0),调用将返回消息队列中的哪个消息。
msgrcv()解除阻塞的条件有三个:
- 消息队列中有了满足条件的消息;
- msqid代表的消息队列被删除;
- 调用msgrcv()的进程被信号中断;
调用返回:成功返回读出消息的实际字节数,否则返回-1。
3)int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg);
向msgid代表的消息队列发送一个消息,即将发送的消息存储在msgp指向的msgbuf结构中,消息的大小由msgze指定。
对发送消息来说,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待。造成msgsnd()等待的条件有两种:
- 当前消息的大小与当前消息队列中的字节数之和超过了消息队列的总容量;
- 当前消息队列的消息数(单位"个")不小于消息队列的总容量(单位"字节数"),此时,虽然消息队列中的消息数目很多,但基本上都只有一个字节。
msgsnd()解除阻塞的条件有三个:
- 不满足上述两个条件,即消息队列中有容纳该消息的空间;
- msqid代表的消息队列被删除;
- 调用msgsnd()的进程被信号中断;
调用返回:成功返回0,否则返回-1。
4)int msgctl(int msqid, int cmd, struct msqid_ds *buf);
该系统调用对由msqid标识的消息队列执行cmd操作,共有三种cmd操作:IPC_STAT、IPC_SET 、IPC_RMID。
- IPC_STAT:该命令用来获取消息队列信息,返回的信息存贮在buf指向的msqid结构中;
- IPC_SET:该命令用来设置消息队列的属性,要设置的属性存储在buf指向的msqid结构中;可设置属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes,同时,也影响msg_ctime成员。
- IPC_RMID:删除msqid标识的消息队列;
调用返回:成功返回0,否则返回-1。
三、消息队列的限制
每个消息队列的容量(所能容纳的字节数)都有限制,该值因系统不同而不同。在后面的应用实例中,输出了redhat 8.0的限制,结果参见 附录 3。
另一个限制是每个消息队列所能容纳的最大消息数:在redhad 8.0中,该限制是受消息队列容量制约的:消息个数要小于消息队列的容量(字节数)。
注:上述两个限制是针对每个消息队列而言的,系统对消息队列的限制还有系统范围内的最大消息队列个数,以及整个系统范围内的最大消息数。一般来说,实际开发过程中不会超过这个限制。
四、消息队列应用实例
消息队列应用相对较简单,下面实例基本上覆盖了对消息队列的所有操作,同时,程序输出结果有助于加深对前面所讲的某些规则及消息队列限制的理解。
#include <sys/types.h>#include <sys/msg.h>#include <unistd.h>void msg_stat(int,struct msqid_ds );main(){int gflags,sflags,rflags;key_t key;int msgid;int reval;struct msgsbuf{ int mtype; char mtext[1]; }msg_sbuf;struct msgmbuf { int mtype; char mtext[10]; }msg_rbuf;struct msqid_ds msg_ginfo,msg_sinfo;char* msgpath="/unix/msgqueue";key=ftok(msgpath,‘a‘);gflags=IPC_CREAT|IPC_EXCL;msgid=msgget(key,gflags|00666);if(msgid==-1){ printf("msg create error\n"); return;}//创建一个消息队列后,输出消息队列缺省属性msg_stat(msgid,msg_ginfo);sflags=IPC_NOWAIT;msg_sbuf.mtype=10;msg_sbuf.mtext[0]=‘a‘;reval=msgsnd(msgid,&msg_sbuf,sizeof(msg_sbuf.mtext),sflags);if(reval==-1){ printf("message send error\n");}//发送一个消息后,输出消息队列属性msg_stat(msgid,msg_ginfo);rflags=IPC_NOWAIT|MSG_NOERROR;reval=msgrcv(msgid,&msg_rbuf,4,10,rflags);if(reval==-1) printf("read msg error\n");else printf("read from msg queue %d bytes\n",reval);//从消息队列中读出消息后,输出消息队列属性msg_stat(msgid,msg_ginfo);msg_sinfo.msg_perm.uid=8;//just a trymsg_sinfo.msg_perm.gid=8;//msg_sinfo.msg_qbytes=16388;//此处验证超级用户可以更改消息队列的缺省msg_qbytes//注意这里设置的值大于缺省值reval=msgctl(msgid,IPC_SET,&msg_sinfo);if(reval==-1){ printf("msg set info error\n"); return;}msg_stat(msgid,msg_ginfo);//验证设置消息队列属性reval=msgctl(msgid,IPC_RMID,NULL);//删除消息队列if(reval==-1){ printf("unlink msg queue error\n"); return;}}void msg_stat(int msgid,struct msqid_ds msg_info){int reval;sleep(1);//只是为了后面输出时间的方便reval=msgctl(msgid,IPC_STAT,&msg_info);if(reval==-1){ printf("get msg info error\n"); return;}printf("\n");printf("current number of bytes on queue is %d\n",msg_info.msg_cbytes);printf("number of messages in queue is %d\n",msg_info.msg_qnum);printf("max number of bytes on queue is %d\n",msg_info.msg_qbytes);//每个消息队列的容量(字节数)都有限制MSGMNB,值的大小因系统而异。在创建新的消息队列时,//msg_qbytes的缺省值就是MSGMNBprintf("pid of last msgsnd is %d\n",msg_info.msg_lspid);printf("pid of last msgrcv is %d\n",msg_info.msg_lrpid);printf("last msgsnd time is %s", ctime(&(msg_info.msg_stime)));printf("last msgrcv time is %s", ctime(&(msg_info.msg_rtime)));printf("last change time is %s", ctime(&(msg_info.msg_ctime)));printf("msg uid is %d\n",msg_info.msg_perm.uid);printf("msg gid is %d\n",msg_info.msg_perm.gid);}程序输出结果见 附录 3。
小结:
消息队列与管道以及有名管道相比,具有更大的灵活性,首先,它提供有格式字节流,有利于减少开发人员的工作量;其次,消息具有类型,在实际应用中,可作为优先级使用。这两点是管道以及有名管道所不能比的。同样,消息队列可以在几个进程间复用,而不管这几个进程是否具有亲缘关系,这一点与有名管道很相似;但消息队列是随内核持续的,与有名管道(随进程持续)相比,生命力更强,应用空间更大。
附录 1: 在参考文献[1]中,给出了IPC随进程持续、随内核持续以及随文件系统持续的定义:
- 随进程持续:IPC一直存在到打开IPC对象的最后一个进程关闭该对象为止。如管道和有名管道;
- 随内核持续:IPC一直持续到内核重新自举或者显示删除该对象为止。如消息队列、信号灯以及共享内存等;
- 随文件系统持续:IPC一直持续到显示删除该对象为止。
附录 2:
结构msg_queue用来描述消息队列头,存在于系统空间:
struct msg_queue { struct kern_ipc_perm q_perm; time_t q_stime; /* last msgsnd time */ time_t q_rtime; /* last msgrcv time */ time_t q_ctime; /* last change time */ unsigned long q_cbytes; /* current number of bytes on queue */ unsigned long q_qnum; /* number of messages in queue */ unsigned long q_qbytes; /* max number of bytes on queue */ pid_t q_lspid; /* pid of last msgsnd */ pid_t q_lrpid; /* last receive pid */ struct list_head q_messages; struct list_head q_receivers; struct list_head q_senders;};结构msqid_ds用来设置或返回消息队列的信息,存在于用户空间;
struct msqid_ds { struct ipc_perm msg_perm; struct msg *msg_first; /* first message on queue,unused */ struct msg *msg_last; /* last message in queue,unused */ __kernel_time_t msg_stime; /* last msgsnd time */ __kernel_time_t msg_rtime; /* last msgrcv time */ __kernel_time_t msg_ctime; /* last change time */ unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */ unsigned long msg_lqbytes; /* ditto */ unsigned short msg_cbytes; /* current number of bytes on queue */ unsigned short msg_qnum; /* number of messages in queue */ unsigned short msg_qbytes; /* max number of bytes on queue */ __kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */ __kernel_ipc_pid_t msg_lrpid; /* last receive pid */};//可以看出上述两个结构很相似。
附录 3: 消息队列实例输出结果:
current number of bytes on queue is 0number of messages in queue is 0max number of bytes on queue is 16384pid of last msgsnd is 0pid of last msgrcv is 0last msgsnd time is Thu Jan 1 08:00:00 1970last msgrcv time is Thu Jan 1 08:00:00 1970last change time is Sun Dec 29 18:28:20 2002msg uid is 0msg gid is 0//上面刚刚创建一个新消息队列时的输出current number of bytes on queue is 1number of messages in queue is 1max number of bytes on queue is 16384pid of last msgsnd is 2510pid of last msgrcv is 0last msgsnd time is Sun Dec 29 18:28:21 2002last msgrcv time is Thu Jan 1 08:00:00 1970last change time is Sun Dec 29 18:28:20 2002msg uid is 0msg gid is 0read from msg queue 1 bytes//实际读出的字节数current number of bytes on queue is 0number of messages in queue is 0max number of bytes on queue is 16384 //每个消息队列最大容量(字节数)pid of last msgsnd is 2510pid of last msgrcv is 2510last msgsnd time is Sun Dec 29 18:28:21 2002last msgrcv time is Sun Dec 29 18:28:22 2002last change time is Sun Dec 29 18:28:20 2002msg uid is 0msg gid is 0current number of bytes on queue is 0number of messages in queue is 0max number of bytes on queue is 16388 //可看出超级用户可修改消息队列最大容量pid of last msgsnd is 2510pid of last msgrcv is 2510 //对操作消息队列进程的跟踪last msgsnd time is Sun Dec 29 18:28:21 2002last msgrcv time is Sun Dec 29 18:28:22 2002last change time is Sun Dec 29 18:28:23 2002 //msgctl()调用对msg_ctime有影响msg uid is 8msg gid is 8相关主题
- UNIX网络编程第二卷:进程间通信,作者:W.Richard Stevens,译者:杨继张,清华大学出版社。对POSIX以及系统V消息队列都有阐述,对Linux环境下的程序开发有极大的启发意义。
- linux内核源代码情景分析(上),毛德操、胡希明著,浙江大学出版社,给出了系统V消息队列相关的源代码分析。
- http://www.fanqiang.com/a4/b2/20010508/113315.html,主要阐述linux下对文件的操作,详细介绍了对文件的存取权限位,对IPC对象的存取权限同样具有很好的借鉴意义。
- msgget、msgsnd、msgrcv、msgctl手册
Vxworks消息队列小结
▲消息队列与其他方式的一些比较:
1、
信号量使用方便,可以解决很多任务间的协调问题,但是信号量所传递的信息有限,而内存共享虽然传递信息可以大些,但是不标准。消息队列作为一种折忠方式用于线程之间的信息交换。
2、
消息队列允许许多的消息排队,而每个信息可以有不同长度,而传统管道中的数据仅仅是一个数据流,没有边界。Vxworks中的管道数据有消息组成。
▲消息队列使用特性:
在传输较小的数据块时,效率较高,但在传输大的数据时,不如共享内存高效。另外,消息队列不能指定接受者,消息队列不支持广播机制,因此一个任务所发出的信息不能被许多任务所接收。
使用消息队列作为任务传送数据量比较小的数据时的通信机制是比较理想的,一般不会产生死锁问题。
▲ 消息队列的种类:
分wind型和POSIX型。Wind型转为Vxworks设计,而POSIX型是为了方便移植到其他遵循POSIX标准的操作系统上。分别包含的文件库为:
msgQLib Wind消息队列库
msgQShow Wind消息队列查看函数库
mqPxLib POSIX消息队列库
mqPxShow POXIX消息队列查看函数库
▲原理简述:
首先是创建一个消息队列,该消息队列中指定存放最多消息数的数量和每个消息的最大长度,由此产生一个消息队列名称MsgQId。任务A通过消息发送函数
msgQSend()将一段信息发送到消息队列中存放。如果有任务B在等待消息队列中的消息,即该发送的消息马上交给任务B,其中任务B有一定的存放区,
即通过msgQReceive()将收到的信息存放在任务B中自己的存储区内。如果消息队列中没有消息,那么在等待消息的任务B将被阻塞并被添加到等待消
息的任务队列中。
▲参数说明:
myMsgQId = msgQCreate (MAX_MSGS, MAX_MSG_LEN,
MSG_Q_PRIORITY)
myMsgQId :消息队列名称 MAX_MSGS:最多消息数目
MAX_MSG_LEN:每个消息最大的长度。MSG_Q_PRIORITY:属性。这里为基于优先级的。还有一个是FIFO型
的。这个参数说明是针对需要等待消息的而且是被阻塞的任务而言的。即如果消息队列中没有消息,那么等待消息的任务被阻塞,这些阻塞的任务排列是基于优先级
的还是基于FIFO型的。
msgQSend (myMsgQId, MESSAGE, sizeof (MESSAGE),
WAIT_FOREVER,
MSG_PRI_NORMAL)
myMsgQId:消息发送到的消息队列名称。MESSAGE:消息内容 sizeof
(MESSAGE):消息大小 WAIT_FOREVER:等待方式
MSG_PRI_NORMAL:发送消息的优先级。分为一般型和紧急型,这个优先级是相对于消息在消息队列中的排列顺序而言的,如果为一般级别,则在消息
队列的尾部,如果为紧急级别,则在消息队列中的头部。
消息队列例子(很多教材上采用的,只做一修改):
#include "vxWorks.h"
#include "msgQLib.h"
#define MAX_MSGS (10)
#define MAX_MSG_LEN (100)
MSG_Q_ID myMsgQId;
task2 (void)
{
char msgBuf[MAX_MSG_LEN]; //接收消息,存放
if (msgQReceive(myMsgQId, msgBuf, MAX_MSG_LEN, WAIT_FOREVER) ==
ERROR)
return (ERROR);
printf ("Message from task 1:\n%s\n", msgBuf);
//在串口中显示消息内容
}
task3 (void)
{
char msgBuff[MAX_MSG_LEN];
if (msgQReceive(myMsgQId, msgBuff, MAX_MSG_LEN, WAIT_FOREVER) ==
ERROR)
return (ERROR);
printf ("\n Message from task 1f:\n%s\n", msgBuff);
//为了试验是否可以重复接收消息
}
#define MESSAGE "Greetings from Task 1"
task1 (void)
{
if ((myMsgQId = msgQCreate (MAX_MSGS, MAX_MSG_LEN,
MSG_Q_PRIORITY))== NULL) //消息创建
return (ERROR);
if (msgQSend (myMsgQId, MESSAGE, sizeof (MESSAGE),
WAIT_FOREVER,MSG_PRI_NORMAL) == ERROR)
return (ERROR);
}
void start(void)
{
int tGetId,tJudId,tProId;
tGetId =
taskSpawn("tPget",200,0,1000,(FUNCPTR)task1,1,0,0,0,0,0,0,0,0,0);
tJudId =
taskSpawn("tPjud",201,0,1000,(FUNCPTR)task2,3,0,0,0,0,0,0,0,0,0);
tProId =
taskSpawn("tPro",202,0,1000,(FUNCPTR)task3,3,0,0,0,0,0,0,0,0,0);
}
试验结果为:只打印出Message from task 1:
Greetings from Task 1
说明:如果消息队列中的一个消息被取走,其他任务将不能再次接收这个消息。如果有多个消息,那么按照消息创建时的属性参数进行消息
排列,一个个一次性地被读取。
其实以上例子整个过程就是任务1创建一个消息队列,并且往这个消息队列中放入消息(也就是一串数据),然后任务2通过接收消息队列中保存并排列好的消息,
这个有任务1送来的消息被一次性使用给任务2调走,存放在任务2接收函数中指定的存储区内,完成整个任务间的通信。
