通过前面介绍的包装函数的开发,您现在有了一种简单、有点优雅的方法来在您的应用程序中创建和使用消息队列。现在,我们将讨论转向直接操作与给定消息队列相关的内部结构。
要对消息队列执行控制操作,您可以使用msgctl()系统调用。
SYSTEM CALL: msgctl(); PROTOTYPE: int msgctl ( int msgqid, int cmd, struct msqid_ds *buf ); RETURNS: 0 on success -1 on error: errno = EACCES (No read permission and cmd is IPC_STAT) EFAULT (Address pointed to by buf is invalid with IPC_SET and IPC_STAT commands) EIDRM (Queue was removed during retrieval) EINVAL (msgqid invalid, or msgsz less than 0) EPERM (IPC_SET or IPC_RMID command was issued, but calling process does not have write (alter) access to the queue) NOTES:
检索队列的 msqid_ds 结构,并将其存储在 buf 参数的地址中。
设置队列的 msqid_ds 结构的 ipc_perm 成员的值。从 buf 参数获取值。
从内核中移除队列。
回顾我们关于消息队列的内部数据结构 (msqid_ds) 的讨论。内核为系统中存在的每个队列维护此结构的一个实例。通过使用 IPC_STAT 命令,我们可以检索此结构的副本以供检查。让我们看看一个快速的包装函数,它将检索内部结构并将其复制到传递的地址
int get_queue_ds( int qid, struct msgqid_ds *qbuf ) { if( msgctl( qid, IPC_STAT, qbuf) == -1) { return(-1); } return(0); }
表示的消息队列的内部数据结构的副本。现在我们有了队列的内部数据结构的副本,哪些属性可以被操作,以及我们如何修改它们?数据结构中唯一可修改的项目是ipc_perm成员。这包含队列的权限,以及关于所有者和创建者的信息。但是,ipc_perm结构中可修改的成员是mode, uid,和gid。您可以更改所有者的用户 ID、所有者的组 ID 以及队列的访问权限。
让我们创建一个旨在更改队列模式的包装函数。模式必须作为字符数组传递(即 ``660'')。
int change_queue_mode( int qid, char *mode ) { struct msqid_ds tmpbuf; /* Retrieve a current copy of the internal data structure */ get_queue_ds( qid, &tmpbuf); /* Change the permissions using an old trick */ sscanf(mode, "%ho", &tmpbuf.msg_perm.mode); /* Update the internal data structure */ if( msgctl( qid, IPC_SET, &tmpbuf) == -1) { return(-1); } return(0); }
请务必小心! 更改队列的权限是可能的,这样做可能会不小心将自己锁定在外!记住,这些 IPC 对象不会消失,除非它们被正确删除,或者系统重新启动。因此,即使您无法使用ipcs看到队列,并不意味着它不存在。
为了说明这一点,似乎有必要讲一个有点幽默的轶事。在南佛罗里达大学教授 UNIX 内部原理课程时,我遇到了一个相当尴尬的障碍。前一天晚上我拨号连接到他们的实验室服务器,以便编译和测试为期一周的课程中要使用的实验作业。在我的测试过程中,我意识到我在用于更改消息队列权限的代码中犯了一个错字。我创建了一个简单的消息队列,并测试了发送和接收功能,没有发生任何问题。但是,当我尝试将队列的模式从“660”更改为“600”时,结果操作是我被锁定在自己的队列之外!结果,我无法在源代码目录的同一区域测试消息队列实验作业。由于我使用 ftok() 函数创建 IPC 密钥,因此我试图访问一个我没有适当权限的队列。最后我在上课当天早上联系了当地的系统管理员,花了整整一个小时向他解释什么是消息队列,以及为什么我需要他为我运行 ipcrm 命令。grrrr。
在成功从队列中检索消息后,消息将被删除。但是,如前所述,IPC 对象仍然存在于系统中,除非显式删除或系统重新启动。为了完成消息队列的生命周期,应该通过调用msgctl(),并使用 IPC_RMID 命令来删除
int remove_queue( int qid ) { if( msgctl( qid, IPC_RMID, 0) == -1) { return(-1); } return(0); }