目录
[1.知识回顾: 进程间通信的前提](#1.知识回顾: 进程间通信的前提)
4.shmid_ds、msqid_ds、semqid_ds共同点
[如何管理struct ipc_perm?](#如何管理struct ipc_perm?)
1.知识回顾: 进程间通信的前提
之前在前面的几篇文章都反复强调了进程间通信的前提: 必须先让不同进程间看到同一份资源
,同一份资源可以是文件缓冲区也可以是内存块也可以是:对于System V消息队列,即必须先让不同进程间看到同一个队列,如下图:

2.消息队列的原理
进程间以数据块的形式发送数据,这些数据块需要排队形成队列
这个数据块是带类型的数据块

假设进程A和进程B使用消息队列进行通信,那么进程A和要区分是自己发送的数据块还是进程B发给它的
其次,该队列肯定由操作系统提供,不可能是进程提供的,因为进程间具有独立性
3.消息队列的系统调用
类比OS52.【Linux】System V 共享内存(1)文章讲的:shmget、shmat、shmdt、shmctl系统调用
msgget
作用: 获取或创建消息队列

和之前一样, key仍然用ftok算
若要创建全新的消息队列,则msgflg为IPC_CREAT | IPC_EXCL
返回值也和shmget类似:

注意: 消息队列的msgget和共享内存的shmget有区别, 消息队列不用提供大小
msgctl

若要关闭消息队列,buf传空指针即可
msgsnd
msgsnd的snd是send的缩写,作用: 发送消息,换句话说,是发送数据块

msqid、msgflg分别类似shmid、shmflg
msgp指向要发送的数据块,msgsz为要发送数据块的大小
注意到: msgp的类型是const void*,用户可以自己定义类型进行传递
手册指出"caller-defined structure": 大致定义为struct msgbuf

查看消息队列的命令
bash
ipcs -q
删除消息队列的命令
bash
ipcrm -M key
#或
ipcrm -m shmid
4.shmid_ds、msqid_ds、semqid_ds共同点
shmid_ds负责描述共享内存:
cpp
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Creation time/time of last
modification via shmctl() */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and
SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
};
msqid_ds负责描述消息队列:
cpp
struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of creation or last
modification by msgctl() */
unsigned long msg_cbytes; /* # of bytes in queue */
msgqnum_t msg_qnum; /* # number of messages in queue */
msglen_t msg_qbytes; /* Maximum # of bytes in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
struct ipc_perm {
key_t __key; /* Key supplied to msgget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
进程间通信其实还可以用信号量,semqid_ds负责描述信号量:
cpp
struct semid_ds {
struct ipc_perm sem_perm; /* Ownership and permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Creation time/time of last
modification via semctl() */
unsigned long sem_nsems; /* No. of semaphores in set */
};
struct ipc_perm {
key_t __key; /* Key supplied to semget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
有统一的结构
System V的进程间通信被精心设计过,三个结构体的第一个字段类型都是struct ipc_perm,尽管shm_perm msg_perm sem_perm的名字不一样
cpp
struct xxxid_ds {
struct ipc_perm xxx_perm; /* Ownership and permissions */
//......
};
struct ipc_perm {
key_t __key;
uid_t uid;
gid_t gid;
uid_t cuid;
gid_t cgid;
unsigned short mode;
unsigned short __seq;
};
这里面有面向对象的思想,struct xxxid_ds含有struct ipc_perm xxx_perm,显然后者是基类,前者是子类
如何管理struct ipc_perm?
答:先描述再组织
先描述: ipc权限使用struct ipc_perm
再组织: 使用数据结构将struct ipc_perm串起来
++描述ipc权限有两种,struct ipc_perm是用户层的描述,struct kern_ipc_perm是内核层的描述++
例如linux 2.6.18内核的/include/linux/ipc.h定义了struct kern_ipc_perm:
cpp
/* used by in-kernel data structures */
struct kern_ipc_perm
{
spinlock_t lock;
int deleted;
key_t key;
uid_t uid;
gid_t gid;
uid_t cuid;
gid_t cgid;
mode_t mode;
unsigned long seq;
void *security;
};
在/ipc/util.h中定义了struct ipc_id_ary ,其使用变长数组织ipc权限:
cpp
struct ipc_id_ary {
int size;
struct kern_ipc_perm *p[0]; //写在结构体末尾,是变长数组
};
将shm_perm、msg_perm、sem_perm的地址写入struct kern_ipc_perm *p[0],那么就能很方便地使用箭头**->和强制类型转换**访问struct shmid_ds msqid_ds semid_ds的每一个字段:
因为三个结构体的第一个字段类型都是struct ipc_perm,即struct ipc_perm字段的地址等同于struct xxxid_ds的地址
cpp
//addr1、addr2、addr3的类型都为struct ipc_perm*
((struct shmid_ds*)addr1)->???
((struct msqid_ds*)addr2)->???
((struct semqid_ds*)addr3)->???
管理ipc权限就相当于对struct kern_ipc_perm *p[0]进行增删改查
++描述xxid有两种,struct xxxid_ds是用户层的描述,struct xxxid_kernel是内核层的描述++
例如Linux 6.18.6中的/ipc/shm.c:
cpp
struct shmid_kernel /* private to the kernel */
{
struct kern_ipc_perm shm_perm;
struct file *shm_file;
unsigned long shm_nattch;
unsigned long shm_segsz;
time64_t shm_atim;
time64_t shm_dtim;
time64_t shm_ctim;
struct pid *shm_cprid;
struct pid *shm_lprid;
struct ucounts *mlock_ucounts;
/*
* The task created the shm object, for
* task_lock(shp->shm_creator)
*/
struct task_struct *shm_creator;
/*
* List by creator. task_lock(->shm_creator) required for read/write.
* If list_empty(), then the creator is dead already.
*/
struct list_head shm_clist;
struct ipc_namespace *ns;
} __randomize_layout;
补: shmid msqid semid是数组的下标,是线性递增的