本系列文章中的前两部分,我们探讨道?font color="#003399">信号两种通信机制Q本文将深入W三部分Q介l系l?V 消息队列及其相应 API?/blockquote>消息队列Q也叫做报文队列Q能够克服早期unix通信机制的一些缺炏V作为早期unix通信机制之一的信可够传送的信息量有限,后来虽然 POSIX 1003.1b在信L实时性方面作了拓q,使得信号在传递信息量斚w有了相当E度的改q,但是信号q种通信方式更像"x"的通信方式Q它要求接受信号 的进E在某个旉范围内对信号做出反应Q因此该信号最多在接受信号q程的生命周期内才有意义Q信h传递的信息是接q于随进E持l的概念 Qprocess-persistentQ,?font color="#003399">附录 1Q管道及有名道及有名管道则是典型的随进E持lIPCQƈ且,只能传送无格式的字节流无疑会给应用E序开发带来不便,另外Q它的缓冲区大小也受到限制?/p>
消息队列是一个消息的链表。可以把消息看作一个记录,h特定的格式以及特定的优先U。对消息队列有写权限的进E可以向中按照一定的规则d新消息;Ҏ息队列有L限的q程则可以从消息队列中读走消息。消息队列是随内核持l的Q参?font color="#003399">附录 1Q?/p>
目前主要有两U类型的消息队列QPOSIX消息队列以及pȝV消息队列Q系lV消息队列目前被大量用。考虑到程序的可移植性,新开发的应用E序应尽量用POSIX消息队列?/p>
在本pd专题的序Q深ȝ解Linuxq程间通信QIPCQ)中,提到对于消息队列、信L、以及共享内存区来说Q有两个实现版本QPOSIX的以 及系lV的。Linux内核Q内?.4.18Q支持POSIX信号灯、POSIX׃n内存Z及POSIX消息队列Q但对于LLinux发行版本之一 redhad8.0Q内?.4.18Q,q没有提供对POSIXq程间通信API的支持,不过应该只是旉上的事?/p>
因此Q本文将主要介绍pȝV消息队列及其相应API?strong>在没有声明的情况下,以下讨论中指的都是系lV消息队列?/strong>
一、消息队列基本概?/font>
- pȝV消息队列是随内核持箋的,只有在内栔Rh者显C删除一个消息队列时Q该消息队列才会真正被删除。因此系l中记录消息队列的数据结构(struct ipc_ids msg_idsQ位于内怸Q系l中的所有消息队列都可以在结构msg_ids中找到访问入口?
- 消息队列是一个消息的链表。每个消息队列都有一个队列头Q用l构struct msg_queue来描qͼ参见附录 2Q。队列头中包含了该消息队列的大量信息Q包括消息队列键倹{用户ID、组ID、消息队列中消息数目{等Q甚臌录了最q对消息队列dq程的ID。读者可以访问这些信息,也可以设|其中的某些信息?
- 下图说明了内怸消息队列是怎样建立赯pȝQ?br>其中Qstruct ipc_ids msg_ids是内怸记录消息队列的全局数据l构Qstruct msg_queue是每个消息队列的队列头?
从上囑֏以看出,全局数据l构 struct ipc_ids msg_ids 可以讉K到每个消息队列头的第一个成员:struct kern_ipc_permQ而每个struct kern_ipc_perm能够与具体的消息队列对应h是因为在该结构中Q有一个key_tcd成员keyQ而key则唯一定一个消息队列? kern_ipc_perml构如下Q?/p>
struct kern_ipc_perm{ //内核中记录消息队列的全局数据l构msg_ids能够讉K到该l构Q?br> key_t key; //该键值则唯一对应一个消息队?br> uid_t uid;
gid_t gid;
uid_t cuid;
gid_t cgid;
mode_t mode;
unsigned long seq;
}
二、操作消息队?/font>
Ҏ息队列的操作无非有下面三U类型:
1?打开或创建消息队?br>消息队列的内核持l性要求每个消息队列都在系l范围内对应唯一的键|所以,要获得一个消息队列的描述字,只需提供该消息队列的键值即可;
注:消息队列描述字是由在pȝ范围内唯一的键值生成的Q而键值可以看作对应系l内的一条\l?/p>
2?d操作
消息d操作非常单,对开发h员来_每个消息都类似如下的数据l构Q?/p>
struct msgbuf{
long mtype;
char mtext[1];
};
mtype成员代表消息cdQ从消息队列中读取消息的一个重要依据就是消息的cdQmtext是消息内容,当然长度不一定ؓ1。因此,对于发送消? 来说Q首先预|一个msgbuf~冲区ƈ写入消息cd和内容,调用相应的发送函数即可;对读取消息来_首先分配q样一个msgbuf~冲区,然后把消? d该缓冲区卛_?/p>
3?获得或设|消息队列属性:
消息队列的信息基本上都保存在消息队列头中Q因此,可以分配一个类g消息队列头的l构(struct msqid_dsQ见附录 2)Q来q回消息队列的属性;同样可以讄该数据结构?/p>
消息队列API
1、文件名到键?/strong>
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok (char*pathname, char proj)Q?br>
它返回与路径pathname相对应的一个键倹{该函数不直接对消息队列操作Q但在调用ipc(MSGGET,…)或msgget()来获得消息队列描q字前,往往要调用该函数。典型的调用代码是:
key=ftok(path_ptr, 'a');
ipc_id=ipc(MSGGET, (int)key, flags,0,NULL,0);
…
2、linux为操作系lVq程间通信的三U方式(消息队列、信L、共享内存区Q提供了一个统一的用L面:
int ipc(MSGGET, int first, int second, int third, void *ptr, long fifth);
与该操作对应的系lV调用为:int msgget( (key_t)firstQsecond)?int ipc(MSGCTL, int first, int second, int third, void *ptr, long fifth)
与该操作对应的系lV调用为:int msgctl( firstQsecond, (struct msqid_ds*) ptr)?int ipc(MSGSND, int first, int second, int third, void *ptr, long fifth);
与该操作对应的系lV调用为:int msgsnd( first, (struct msgbuf*)ptr, second, third)?int ipc(MSGRCV, int first, int second, int third, void *ptr, long fifth);
与该操作对应的系lV调用为:int msgrcv( firstQ?struct msgbuf*)ptr, second, fifth,third)Q?
注:本h不主张采用系l调用ipc()Q而更們于采用系lV或者POSIXq程间通信API。原因如下:
- 虽然该系l调用提供了l一的用L面,但正是由于这个特性,它的参数几乎不能l出特定的实际意义(如以first、second来命名参敎ͼQ在一定程度上造成开发不ѝ?
- 正如ipc手册所说的Qipc()是linux所Ҏ的,~写E序时应注意E序的移植性问题;
- 该系l调用的实现不过是把pȝV IPC函数q行了封装,没有M效率上的优势Q?
- pȝV在IPC斚w的API数量不多QŞ式也较简z?
3.pȝV消息队列API
pȝV消息队列API共有四个Q用时需要包括几个头文gQ?/p>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
1Qint msgget(key_t key, int msgflg)
参数key是一个键|由ftok获得Qmsgflg参数是一些标志位。该调用q回与健值key相对应的消息队列描述字?/p>
在以下两U情况下Q该调用创Z个新的消息队列:
- 如果没有消息队列与健值key相对应,q且msgflg中包含了IPC_CREAT标志位;
- key参数为IPC_PRIVATEQ?
参数msgflg可以Z下:IPC_CREAT、IPC_EXCL、IPC_NOWAIT或三者的或结果?/p>
调用q回Q?/strong>成功q回消息队列描述字,否则q回-1?/p>
?br>注:参数key讄成常数IPC_PRIVATEq不意味着其他q程不能讉K该消息队列,只意味着卛_创徏新的消息队列?/p>
2Qint msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg);
该系l调用从msgid代表的消息队列中d一个消息,q把消息存储在msgp指向的msgbufl构中?/p>msqid为消息队列描q字Q消息返回后存储在msgp指向的地址Qmsgsz指定msgbuf的mtext成员的长度(x息内容的长度Q,msgtyp求读取的消息cdQ读消息标志msgflg可以Z下几个常值的或:
- IPC_NOWAIT 如果没有满条g的消息,调用立即q回Q此Ӟerrno=ENOMSG
- IPC_EXCEPT 与msgtyp>0配合使用Q返回队列中W一个类型不为msgtyp的消?
- IPC_NOERROR 如果队列中满x件的消息内容大于所h的msgsz字节Q则把该消息截断Q截断部分将丢失?
msgrcv手册中详l给Z消息cd取不同值时(>0; <0; =0)Q调用将q回消息队列中的哪个消息?/p>
msgrcv()解除d的条件有三个Q?/p>
- 消息队列中有了满x件的消息Q?
- msqid代表的消息队列被删除Q?
- 调用msgrcvQ)的进E被信号中断Q?
调用q回Q?/strong>成功q回d消息的实际字节数Q否则返?1?/p>
3Qint msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg);
向msgid代表的消息队列发送一个消息,卛_发送的消息存储在msgp指向的msgbufl构中,消息的大由msgze指定?/p>对发送消息来_有意义的msgflg标志为IPC_NOWAITQ指明在消息队列没有_I间容纳要发送的消息Ӟmsgsnd是否{待。造成msgsnd(){待的条件有两种Q?/p>
- 当前消息的大与当前消息队列中的字节C和超q了消息队列的d量;
- 当前消息队列的消息数Q单??Q不于消息队列的d量(单位"字节?Q,此时Q虽然消息队列中的消息数目很多,但基本上都只有一个字节?
msgsnd()解除d的条件有三个Q?
- 不满上qC个条Ӟx息队列中有容U消息的空_
- msqid代表的消息队列被删除Q?
- 调用msgsndQ)的进E被信号中断Q?
调用q回Q?/strong>成功q回0Q否则返?1?/p>
4Qint msgctl(int msqid, int cmd, struct msqid_ds *buf);
该系l调用对由msqid标识的消息队列执行cmd操作Q共有三Ucmd操作QIPC_STAT、IPC_SET 、IPC_RMID?/p>
- IPC_STATQ该命o用来获取消息队列信息Q返回的信息存贮在buf指向的msqidl构中;
- IPC_SETQ该命o用来讄消息队列的属性,要设|的属性存储在buf指向的msqidl构中;可设|属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytesQ同Ӟ也媄响msg_ctime成员?
- IPC_RMIDQ删除msqid标识的消息队列;
调用q回Q?/strong>成功q回0Q否则返?1?/p>
三、消息队列的限制
每个消息队列的容量(所能容U的字节敎ͼ都有限制Q该值因pȝ不同而不同。在后面的应用实例中Q输Zredhat 8.0的限Ӟl果参见附录 3?/p>另一个限制是每个消息队列所能容U的最大消息数Q在redhad 8.0中,该限制是受消息队列容量制U的Q消息个数要于消息队列的容量(字节敎ͼ?/p>
注:上述两个限制是针Ҏ个消息队列而言的,pȝҎ息队列的限制q有pȝ范围内的最大消息队列个敎ͼ以及整个pȝ范围内的最大消息数。一般来_实际开发过E中不会过q个限制?/p>
四、消息队列应用实?/font>
消息队列应用相对较简单,下面实例基本上覆盖了Ҏ息队列的所有操作,同时Q程序输出结果有助于加深对前面所讲的某些规则及消息队列限制的理解?/p>E序输出l果?font color="#003399">附录 3
#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;
}
//创徏一个消息队列后Q输出消息队列缺省属?br>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");
}
//发送一个消息后Q输出消息队列属?br>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);
//从消息队列中d消息后,输出消息队列属?br>msg_stat(msgid,msg_ginfo);
msg_sinfo.msg_perm.uid=8;//just a try
msg_sinfo.msg_perm.gid=8;//
msg_sinfo.msg_qbytes=16388;
//此处验证用户可以更改消息队列的缺省msg_qbytes
//注意q里讄的值大于缺省?br>reval=msgctl(msgid,IPC_SET,&msg_sinfo);
if(reval==-1)
{
printf("msg set info error\n");
return;
}
msg_stat(msgid,msg_ginfo);
//验证讄消息队列属?br>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);//只是Z后面输出旉的方?br>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);
//每个消息队列的容量(字节敎ͼ都有限制MSGMNBQ值的大小因系l而异。在创徏新的消息队列Ӟ//msg_qbytes的缺省值就是MSGMNB
printf("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);
}结Q?/font>
消息队列 与管道以及有名管道相比,h更大的灵zL,首先Q它提供有格式字节流Q有利于减少开发h员的工作量;其次Q消息具有类型,在实际应用中Q可作ؓ优先U 用。这两点是管道以及有名管道所不能比的。同P消息队列可以在几个进E间复用Q而不这几个q程是否h亲缘关系Q这一点与有名道很相|但消息队? 是随内核持箋的,与有名管道(随进E持l)相比Q生命力更强Q应用空间更大?/p>附录 1Q?/strong>在参考文献[1]中,l出了IPC随进E持l、随内核持箋以及随文件系l持l的定义Q?/p>
- 随进E持l:IPC一直存在到打开IPC对象的最后一个进E关闭该对象为止。如道和有名管道;
- 随内核持l:IPC一直持l到内核重新自D或者显C删除该对象为止。如消息队列、信L以及׃n内存{;
- 随文件系l持l:IPC一直持l到昄删除该对象ؓ止?
附录 2Q?/strong>
l构msg_queue用来描述消息队列_存在于系l空_
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;
};
l构msqid_ds用来讄或返回消息队列的信息Q存在于用户I间Q?/p>
//可以看出上述两个l构很相伹{?br>
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 */
};附录 3Q?/strong>消息队列实例输出l果Q?/p>
current number of bytes on queue is 0
number of messages in queue is 0
max number of bytes on queue is 16384
pid of last msgsnd is 0
pid of last msgrcv is 0
last msgsnd time is Thu Jan 1 08:00:00 1970
last msgrcv time is Thu Jan 1 08:00:00 1970
last change time is Sun Dec 29 18:28:20 2002
msg uid is 0
msg gid is 0
//上面刚刚创徏一个新消息队列时的输出
current number of bytes on queue is 1
number of messages in queue is 1
max number of bytes on queue is 16384
pid of last msgsnd is 2510
pid of last msgrcv is 0
last msgsnd time is Sun Dec 29 18:28:21 2002
last msgrcv time is Thu Jan 1 08:00:00 1970
last change time is Sun Dec 29 18:28:20 2002
msg uid is 0
msg gid is 0
read from msg queue 1 bytes
//实际d的字节数
current number of bytes on queue is 0
number of messages in queue is 0
max number of bytes on queue is 16384 //每个消息队列最大容量(字节敎ͼ
pid of last msgsnd is 2510
pid of last msgrcv is 2510
last msgsnd time is Sun Dec 29 18:28:21 2002
last msgrcv time is Sun Dec 29 18:28:22 2002
last change time is Sun Dec 29 18:28:20 2002
msg uid is 0
msg gid is 0
current number of bytes on queue is 0
number of messages in queue is 0
max number of bytes on queue is 16388 //可看U用户可修改消息队列最大容?br>pid of last msgsnd is 2510
pid of last msgrcv is 2510 //Ҏ作消息队列进E的跟踪
last msgsnd time is Sun Dec 29 18:28:21 2002
last msgrcv time is Sun Dec 29 18:28:22 2002
last change time is Sun Dec 29 18:28:23 2002 //msgctl()调用对msg_ctime有媄?br>msg uid is 8
msg gid is 8
参考文献:
- UNIX|络~程W二Pq程间通信Q作者:W.Richard StevensQ译者:杨张,清华大学出版C。对POSIX以及pȝV消息队列都有阐述Q对Linux环境下的E序开发有极大的启发意义?
- linux内核源代码情景分析(上)Q毛h、胡希明著,江大学出版C,l出了系lV消息队列相关的源代码分析?
- http://www.fanqiang.com/a4/b2/20010508/113315.htmlQ主要阐qlinux下对文g的操作,详细介绍了对文g的存取权限位Q对IPC对象的存取权限同样具有很好的借鉴意义?
- msgget、msgsnd、msgrcv、msgctl手册
]]>
2) l构体每个成员相对于l构体首地址的偏U量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节Q例如上面第二个l构体变量的地址I间?/p> 3) l构体的dؓl构体最宽基本类型成员大的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。例如上面第一个结构体变量?br>
C计算Z内存I间都是按照byte划分的,从理Z讲似乎对Mcd的变量的讉K可以从Q何地址开始,但实际情冉|在访问特定类型变量的时候经常在? 定的内存地址讉KQ这需要各U类型数据按照一定的规则在空间上排列Q而不是顺序的一个接一个的排放Q这是寚w?br> 寚w的作用和原因Q各个硬件^台对存储I间的处理上有很大的不同。一些^台对某些特定cd的数据只能从某些特定地址开始存取。比如有些架构的CPU在访? 一个没有进行对齐的变量的时候会发生错误,那么在这U架构下~程必须保证字节寚w.其他q_可能没有q种情况Q但是最常见的是如果不按照适合其^台要求对 数据存放q行寚wQ会在存取效率上带来损失。比如有些^台每ơ读都是从偶地址开始,如果一个int型(假设?2位系l)如果存放在偶地址开始的地方Q那 么一个读周期可以读32bitQ而如果存攑֜奇地址开始的地方Q就需?个读周期Qƈ对两ơ读出的l果的高低字节进行拼凑才能得到该32bit? 据。显然在d效率上下降很多?/p>
先让我们看几个例子吧(32bit,x86环境,gcc~译?:
讄构体如下定义Q?br>struct A
{
int a;
char b;
short c;
};
struct B
{
char b;
int a;
short c;
};
现在已知32位机器上各种数据cd的长度如?
char:1(有符hW号?
short:2(有符hW号?
int:4(有符hW号?
long:4(有符hW号?
float:4 double:8
那么上面两个l构大小如何?
l果?
sizeof(strcut A)gؓ8
sizeof(struct B)的值却?2
l构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一?B也一?按理说A,B大小应该都是7字节?br>之所以出C面的l果是因为编译器要对数据成员在空间上q行寚w。上面是按照~译器的默认讄q行寚w的结?那么我们是不是可以改变编译器的这U默认对齐设|呢,当然可以.例如:
#pragma pack (2) /*指定?字节寚w*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定寚wQ恢复缺省对?/
sizeof(struct C)值是8?br>修改寚wgؓ1Q?br>#pragma pack (1) /*指定?字节寚w*/
struct D
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定寚wQ恢复缺省对?/
sizeof(struct D)gؓ7?br>后面我们再讲?pragma pack()的作?
先让我们看四个重要的基本概念Q?br>1.数据cd自n的对齐|
对于char型数据,其自w对齐gؓ1Q对于short型ؓ2Q对于int,float,doublecdQ其自n寚wgؓ4Q单位字节?br>2.l构体或者类的自w对齐|其成员中自n寚w值最大的那个倹{?br>3.指定寚w?/font>Q?pragma pack (value)时的指定寚w值value?br>4.数据成员、结构体和类的有效对齐|自n寚w值和指定寚wg的那个倹{?br>?
了这些|我们可以很方便的来讨论具体数据l构的成员和其自w的寚w方式。有效对齐值N是最l用来决定数据存攑֜址方式的|最重要。有效对齐NQ就?
表示“寚w在N?#8221;Q也是说该数据?存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址是?
据结构的起始地址。结构体的成员变量要寚w排放Q结构体本n也要Ҏ自n的有效对齐值圆?是l构体成员变量占用总长度需要是对结构体有效寚w值的整数
倍,l合下面例子理解)。这样就不能理解上面的几个例子的g?br>例子分析Q?br>分析例子BQ?br>struct B
{
char b;
int a;
short c;
};
?
设B从地址I间0x0000开始排放。该例子中没有定义指定对齐|在笔者环境下Q该值默认ؓ4。第一个成员变量b的自w对齐值是1Q比指定或者默认指?
寚w?,所以其有效寚wgؓ1Q所以其存放地址0x0000W合0x0000%1=0.W二个成员变量aQ其自n寚wgؓ4Q所以有效对齐g?Q?
所以只能存攑֜起始地址?x0004?x0007q四个连l的字节I间中,复核0x0004%4=0,且紧靠第一个变量。第三个变量c,自n寚wgؓ
2Q所以有效对齐g?Q可以存攑֜0x0008?x0009q两个字节空间中Q符?x0008%2=0。所以从0x0000?x0009存放?
都是B内容。再看数据结构B的自w对齐gؓ其变量中最大对齐?q里是bQ所以就?Q所以结构体的有效对齐g?。根据结构体圆整的要求,
0x0009?x0000=10字节Q(10Q?Q%4Q?。所?x0000A?x000B也ؓl构体B所占用。故B?x0000?x000B
共有12个字?sizeof(struct B)=12;其实如果p一个就来说它已满_节对齐了,
因ؓ它的起始地址?,因此肯定是对齐的,之所以在后面补充2个字?是因为编译器Z实现l构数组的存取效?试想如果我们定义了一个结构B的数l??
么第一个结构v始地址?没有问题,但是W二个结构呢?按照数组的定?数组中所有元素都是紧挨着?如果我们不把l构的大补充ؓ4的整数?那么下一
个结构的起始地址是0x0000A,q显然不能满结构的地址寚w?因此我们要把l构补充成有效对齐大的整数?其实诸如:对于char型数据,?
自n寚wgؓ1Q对于short型ؓ2Q对于int,float,doublecdQ其自n寚wgؓ4Q这些已有类型的自n寚wg是基于数l考虑??
是因些类型的长度已知?所以他们的自n寚wg已知了.
同理,分析上面例子CQ?br>#pragma pack (2) /*指定?字节寚w*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定寚wQ恢复缺省对?/
W?
一个变量b的自w对齐gؓ1Q指定对齐gؓ2Q所以,其有效对齐gؓ1Q假设C?x0000开始,那么b存放?x0000Q符?x0000%1=
0;W二个变量,自n寚wgؓ4Q指定对齐gؓ2Q所以有效对齐gؓ2Q所以顺序存攑֜0x0002?x0003?x0004?x0005四个q箋
字节中,W合0x0002%2=0。第三个变量c的自w对齐gؓ2Q所以有效对齐gؓ2Q顺序存?br>?x0006?x0007中,W合
0x0006%2=0。所以从0x0000?x00007共八字节存放的是C的变量。又C的自w对齐gؓ4Q所以C的有效对齐gؓ2。又8%2=0,C
只占?x0000?x0007的八个字节。所以sizeof(struct C)=8.
1.在VC IDE中,可以q样修改Q[Project]|[Settings],c/c++选项卡Category的Code Generation选项的Struct Member Alignment中修改,默认?字节?br>2.在编码时Q可以这样动态修改:#pragma pack .注意:是pragma而不是progma.
如果在编E的时候要考虑节约I间的话,那么我们只需要假定结构的首地址?,然后各个变量按照上面的原则进行排列即?基本的原则就是把l构中的变量按照
cd大小从小到大声明,量减少中间的填补空?q有一U就是ؓ了以I间换取旉的效?我们昄的进行填补空间进行对?比如:有一U用空间换旉?
法是昑ּ的插入reserved成员Q?br> struct A{
char a;
char reserved[3];//使用I间换时?br> int b;
}
reserved成员Ҏ们的E序没有什么意?它只是v到填补空间以辑ֈ字节寚w的目?当然即不加q个成员通常~译器也会给我们自动填补寚w,我们自己加上它只是v到显式的提醒作用.
代码中关于对齐的隐患Q很多是隐式的。比如在强制cd转换的时候。例如:
unsigned int i = 0x12345678;
unsigned char *p=NULL;
unsigned short *p1=NULL;
p=&i;
*p=0x00;
p1=(unsigned short *)(p+1);
*p1=0x0000;
最后两句代码,从奇数边界去讉Kunsignedshort型变量,昄不符合对齐的规定?br>在x86上,cM的操作只会媄响效率,但是在MIPS或者sparc上,可能是一个error,因ؓ它们要求必须字节寚w.
如果出现寚w或者赋值问题首先查?br>1. ~译器的big little端设|?br>2. 看这U体pLw是否支持非寚w讉K
3. 如果支持看设|了寚w与否,如果没有则看讉K旉要加某些Ҏ的修饰来标志其特D访问操作?/p>
?相关文章:转自http://blog.csdn.net/goodluckyxl/archive/2005/10/17/506827.aspx
ARM下的寚w处理
from DUI0067D_ADS1_2_CompLib
3.13 type qulifiers
有部分摘自ARM~译器文档对齐部?/p>
寚w的?
1.__align(num)
q个用于修改最高别对象的字节边界。在汇编中用LDRD或者STRD?br> p用到此命令__align(8)q行修饰限制。来保证数据对象是相应对齐?br> q个修饰对象的命令最大是8个字节限?可以?字节的对象进?字节
寚w,但是不能?字节的对?字节寚w?br> __align是存储类修改,他只修饰最高cd对象不能用于l构或者函数对象?br>
2.__packed
__packed是进行一字节寚w
1.不能对packed的对象进行对?br> 2.所有对象的d讉K都进行非寚w讉K
3.float及包含float的结构联合及未用__packed的对象将不能字节寚w
4.__packed对局部整形变量无影响
5.强制由unpacked对象向packed对象转化是未定义,整Ş指针可以合法?br> 义ؓpacked?br> __packed int* p; //__packed int 则没有意?br> 6.寚w或非寚wd讉K带来问题
__packed struct STRUCT_TEST
{
char a;
int b;
char c;
} ; //定义如下l构此时b的v始地址一定是不对齐的
//在栈中访问b可能有问?因ؓ栈上数据肯定是对齐访问[from CL]
//下面变量定义成全局静态不在栈?
static char* p;
static struct STRUCT_TEST a;
void Main()
{
__packed int* q; //此时定义成__packed来修饰当前q指向为非寚w的数据地址下面的访问则可以
p = (char*)&a;
q = (int*)(p+1);
*q = 0x87654321;
/*
得到赋值的汇编指o很清?br>ldr r5,0x20001590 ; = #0x12345678
[0xe1a00005] mov r0,r5
[0xeb0000b0] bl __rt_uwrite4 //在此处调用一个写4byte的操作函?
[0xe5c10000] strb r0,[r1,#0] //函数q行4ơstrb操作然后q回保证了数据正的讉K
[0xe1a02420] mov r2,r0,lsr #8
[0xe5c12001] strb r2,[r1,#1]
[0xe1a02820] mov r2,r0,lsr #16
[0xe5c12002] strb r2,[r1,#2]
[0xe1a02c20] mov r2,r0,lsr #24
[0xe5c12003] strb r2,[r1,#3]
[0xe1a0f00e] mov pc,r14
*/
/*
如果q没有加__packed修饰则汇~出来指令是q样直接会导致奇地址处访问失?br>[0xe59f2018] ldr r2,0x20001594 ; = #0x87654321
[0xe5812000] str r2,[r1,#0]
*/
在APUE 14.7节对消息队列的讲解中Q最后一D说“我们得出的结论是Q在新的应用E式中不应当再用他们?#8221;
虽然在新的应用程式中不应该再使用消息队列Q我也没有怎么使用qSystem V IPC总觉得在UNIX/Linux~程中少了什么,也许学习一下System V IPCҎ的自信心会有相当大的帮助Q从此我也敢讲我知道如何使用IPC了?br>
先把各个函数原Ş列出?br> #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflag);
int msgsnd(int msgid, struct msgbuf *msgp, size_t msgsz, int msgflag);
ssize_t msgrcv(int msgid, struct msgbuf *msgp, size_t msgsz, long msgtype, int msgflag);
int msgctl(int msgid, int cmd, struct msqid_ds *buf);
msgget()用来创徏Message QueueQ服务端Q或和一个已建立的Message
Queueq接Q客LQ。keyQ指定用来生成message
id的关键字Qmsgflag和open()的flags很相|可用IPC_CREAT, IPC_EXECL, S_IRUSR{?br>
在服务端Q可用IPC_PRIVATEQ或0Q来指定key|来生成一个新的Message Queue,或用指定的key?32位的无符hQ,或用ftok()来生成一个key?br> #include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
在客LQ能够直接用服务端生成的message
idQ通过某些途径传送,如文档,父子q程Q,也能够用msgget通过和服务端使用相同的key值来生成相同的message
idQ但不能使用IPC_PRIVATEQ或0Q,msgflag也不能用IPC_CREAT?/sys>
Return Value: Sucess return value is the message id(non-negative integer), otherwise -1 return.
msgsnd()用来发送消息?br> struct msgbuf {
long mtype;
char mtext[1];
};
msgsz的计方法: msgsz = sizeof(msgbuf) - sizeof(long);
msgflag有一个标志:IPC_NOWAIT。当消息队列已满Q可能是消息L辑ֈ了限制|也可能是队列中字节L辑ֈ了限制|Q立卛_错返回,假如没有指定Q则d?/p>
msgrcv()用来接收消息。msgtype用来指定d的消息类型。msgtype == 0, q回W一个消息; msgtype > 0, q回消息cd为msgtype的消息;msgtype < 0Q?q回队列中类型值小于msgtype的绝对值的消息集中最的消息?br> msgflag有两个|MSG_NOERROR, IPC_NOWAIT。当MSG_NOERROR被指定的时候,若消息太长就被截断,否则q回错误QIPC_NOWAIT用于需要读取的消息不存在时则阻塞?/p>
msgctl用于控制消息队列。cmd有三U|IPC_STAT,IPC_SET,IPC_RMID?br> IPC_STAT用于取出消息队列?msqid_dsl构q保存到buf中?br> IPC_SET用来把buf指向的msqid_dsQ配|成消息队列的msqid_ds。只有四个D够更改:msg_perm.uid, msg_perm.gid,msg_perm.mode, msg_qbytes?br> IPC_RMID用来删除消息队列?br>
struct msqid_ds
{
struct ipc_perm msg_perm;
ulong msg_qbytes; //max of bytes of queue
...
};
struct ipc_perm
{
uid_t uid; //owner's effective user id
gid_t gid; //owner's effective group id
uid_t cuid; //creator's effective user id
gid_t cgid; //creator's effective group id
mode_t mode; //access modes
ulong seq; //slot usage sequence number
key_t key;
};