标识符与键key
?内核中的IPC结构(消息队列、信号量、共享内存)都用一个非负整数的标识符加以引用。标识符是IPC对象的内部名,为使多个进程能够使用同一IPC对象,需要提供一个外部命名方案。为此,每个IPC对象都与一个键key相关联,将这个键作为该对象的外部名。创建IPC结构时都应指定一个键,这个键的数据类型是基本系统数据类型key_t,这个键由内核变换成标识符。
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
参数:
- pathname:当前操作系统中一个存在的路径,使用该文件属性的st_dev和st_ino填充键
- proj_id:产生键时,使用该参数的低8位
struct ipc_perm
{
__kernel_key_t key;
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid;
__kernel_gid_t cgid;
__kernel_mode_t mode;
unsigned short seq;
};
消息队列
消息队列是消息的链表,存放在内核中,由消息队列标识符标识。
- 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
- 消息队列独立于进程。进程终止后,消息队列及其内容并不会被删除。
- 消息队列的消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
内核为每个消息队列维护了一个结构体,数据结构如下:
struct msqid_ds {
struct ipc_perm msg_perm;
struct msg *msg_first;
struct msg *msg_last;
__kernel_time_t msg_stime;
__kernel_time_t msg_rtime;
__kernel_time_t msg_ctime;
unsigned long msg_lcbytes;
unsigned long msg_lqbytes;
unsigned short msg_cbytes;
unsigned short msg_qnum;
unsigned short msg_qbytes;
__kernel_ipc_pid_t msg_lspid;
__kernel_ipc_pid_t msg_lrpid;
};
消息的数据结构如下:
struct msg_msg
{
struct list_head m_list;
long m_type;
size_t m_ts;
struct msg_msgseg *next;
void *security;
};
创建/获取消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
参数:
- key:消息队列的键key
- msgflg:创建消息队列时指定的属性
IPC_CREAT :创建新的消息队列,同时需要指定对消息队列的操作权限IPC_EXCL :检测消息队列是否存在,必须和IPC_CREAT 一起使用
操作消息队列
#include <sys/msg.h>
int msgctl(int msqid,int cmd,struct msqid_ds* buf);
参数:
- shmid:消息队列ID
- cmd
IPC_STAT :得到当前消息队列的状态IPC_SET :设置消息队列的状态IPC_RMID :删除该消息队列以及仍在该队列中的所有数据,删除立即生效。仍在使用这一队列的其他进程,再次操作时,会得到EIDRM 错误
- buf:
cmd=IPC_STAT ,作为传出参数,会得到消息队列的相关属性信息 cmd=IPC_SET ,作为传入参数,将用户的自定义属性设置到消息队列中 cmd=IPC_RMID ,buf无意义,指定为NULL即可
发送消息
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数:
- msqid:消息队列ID
- msgp:指向自定义缓冲区
struct msgbuf {
long mtype;
char mtext[512];
};
- msgsz:待发送的消息内容的长度,即mtext的长度
- msgflg
- 0:阻塞式函数。进程解除阻塞:有空间可以容纳要发送的消息;从系统中删除此队列;捕捉到一个信号
IPC_NOWAIT :类似文件I/O的非阻塞I/O标志,若消息队列已满,或者队列中的消息总数等于系统限制值,或队列中的字节总数等于系统限制值,则函数立即出错,返回EAGAIN
接收消息
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
- msqid:消息队列ID
- msgp:与msgsnd函数一致
- msgsz:数据缓冲区的长度
- msgtype:从消息队列中取出哪一类型的消息
- type=0:返回队列中的第一个消息。
- type>0:返回队列中消息类型为type的第一个消息。
- type<0:返回队列中消息类型值小于等于type绝对值的消息,如果这种消息有若干个,则取类型值最小的消息。
- msgflg参数
- 0:阻塞式函数。
IPC_NOWAIT :类似文件I/O的非阻塞I/O标志。MSG_NOERROR :若返回的消息长度大于msgsz,则该消息会被截断,系统不会通知。若没有设置这一标志,则出错返回E2BIG ,消息仍留在队列中。
示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <string.h>
struct msg_buf
{
long mtype;
char mtext[512];
};
int main(int argc, char *argv[])
{
key_t key;
struct msg_buf msg_buf_snd = {1, "1-hello world!"};
struct msg_buf msg_buf_recv;
memset(&msg_buf_recv, 0, sizeof(msg_buf_recv));
key = ftok("./key.c", 0xFF);
if (key == -1)
{
perror("ftok");
return -1;
}
int msgid = msgget(key, 0777 | IPC_CREAT | IPC_EXCL);
if (msgid == -1)
{
if (errno = EEXIST)
{
msgid = msgget(key, 0777);
if (msgid == -1)
goto MSG_GET_ERR;
}
else
{
goto MSG_GET_ERR;
}
}
printf("msg id :%d\n", msgid);
int ret = 0;
for (int i = 0; i < 10; i++)
{
ret = msgrcv(msgid, &msg_buf_recv, sizeof(msg_buf_recv.mtext), 2, 0);
if (ret == -1)
{
perror("msgrecv");
return -1;
}
printf("msgrecv: %s\n", msg_buf_recv.mtext);
ret = msgsnd(msgid, &msg_buf_snd, strlen(msg_buf_snd.mtext), 0);
if (ret == -1)
{
perror("msgsnd");
return -1;
}
}
return 0;
MSG_GET_ERR:
perror("msgget");
return -1;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <string.h>
struct msg_buf
{
long mtype;
char mtext[512];
};
int main(int argc, char *argv[])
{
key_t key;
struct msg_buf msg_buf_recv;
struct msg_buf msg_buf_snd = {2, "2-hello world!"};
memset(&msg_buf_recv, 0, sizeof(msg_buf_recv));
key = ftok("key.c", 0xFF);
if (key == -1)
{
perror("ftok");
return -1;
}
int msgid = msgget(key, 0777 | IPC_CREAT | IPC_EXCL);
if (msgid == -1)
{
if (errno = EEXIST)
{
msgid = msgget(key, 0777);
if (msgid == -1)
goto MSG_GET_ERR;
}
else
goto MSG_GET_ERR;
}
printf("msg id :%d\n", msgid);
int ret = 0;
for (int i = 0; i < 10; i++)
{
ret = msgsnd(msgid, &msg_buf_snd, strlen(msg_buf_snd.mtext), 0);
if (ret == -1)
{
perror("msgsnd");
return -1;
}
ret = msgrcv(msgid, &msg_buf_recv, sizeof(msg_buf_recv.mtext), 1, 0);
if (ret == -1)
{
perror("msgrecv");
return -1;
}
printf("msgrecv: %s\n", msg_buf_recv.mtext);
}
return 0;
MSG_GET_ERR:
perror("msgget");
return -1;
}
共享内存
? ? ? ?共享内存允许两个或多个进程共享同一块存储区,通过地址映射将这块物理内存映射到不同进程的地址空间中,多个进程可以通过这块物理空间进行数据的交互,达到进程间通信的目的。
- 共享内存既可以实现有血缘关系的进程间通信也可以实现没有血缘关系的进程间通信。
- 共享内存不属于任何进程,并且不受进程生命周期的影响。
- 共享内存操作默认不阻塞,共享内存不保证进程间的数据同步。
- 当共享内存被标记为删除状态之后,并不会马上被删除,直到所有的进程全部与共享内存解除关联(引用计数原理),共享内存才会被删除。
- 共享内存是最快的进程间通信方式,进程间通信不涉及到内核,不用通过系统调用来传递数据。
内核为每块共享内存维护着一个结构体,数据结构如下:
struct shmid_ds {
struct ipc_perm shm_perm;
int shm_segsz;
__kernel_time_t shm_atime;
__kernel_time_t shm_dtime;
__kernel_time_t shm_ctime;
__kernel_ipc_pid_t shm_cpid;
__kernel_ipc_pid_t shm_lpid;
unsigned short shm_nattch;
unsigned short shm_unused;
void *shm_unused2;
void *shm_unused3;
};
创建/获取共享内存
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
参数:
- key:共享内存的键key
- size:创建共享内存时,指定共享内存的大小(单位:字节),而如果引用一个已存在的共享内存,则将size指定为0。
- shmflg:创建共享内存时指定的属性
IPC_CREAT :创建新的共享内存,指定对共享内存的操作权限IPC_EXCL :检测共享内存是否存在,必须和IPC_CREAT 一起使用
连接共享内存到当前进程的地址空间
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
- shmid:共享内存ID
- shmaddr:共享内存的起始地址,指定为NULL,让内核指定
- shmflg:对共享内存的操作权限
断开与共享内存的连接
#include <sys/shm.h>
int shmdt(const void *shmaddr);
参数:
- shmaddr:共享内存的起始地址
操作共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
- shmid:共享内存ID
- cmd
IPC_STAT :得到当前共享内存的状态IPC_SET :设置共享内存的状态IPC_RMID :标记共享内存为删除状态
- buf:
- cmd=
IPC_STAT ,作为传出参数,会得到共享内存的相关属性信息 - cmd=
IPC_SET ,作为传入参数,将用户的自定义属性设置到共享内存中 - cmd=
IPC_RMID ,buf无意义,指定为NULL即可
相关shell命令
// 查看系统中共享内存的详细信息
ipcs -m
? ? ? ?当共享内存被标记为删除状态之后,共享内存的状态也会发生变化,共享内存内部维护的键key从一个正整数变为 0,其属性从公共的变为私有的。这里的私有是指只有已经关联成功的进程才允许继续访问共享内存,不再允许新的进程和这块共享内存进行关联了。当共享内存被标记为删除状态并且这个引用计数变为0之后,共享内存才会被真正的被删除。
示例
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
int main()
{
int shmid = shmget(1000, 4096, IPC_CREAT | 0664);
if (shmid == -1)
{
perror("shmget error");
return -1;
}
void *ptr = shmat(shmid, NULL, 0);
if (ptr == (void *)-1)
{
perror("shmat error");
return -1;
}
const char *p = "hello world, 共享内存真香。";
memcpy(ptr, p, strlen(p) + 1);
printf("按任意键继续, 删除共享内存\n");
getchar();
shmdt(ptr);
shmctl(shmid, IPC_RMID, NULL);
printf("共享内存已经被删除...\n");
return 0;
}
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
int main()
{
int shmid = shmget(1000, 0, 0);
if (shmid == -1)
{
perror("shmget error");
return -1;
}
void *ptr = shmat(shmid, NULL, 0);
if (ptr == (void *)-1)
{
perror("shmat error");
return -1;
}
printf("共享内存数据: %s\n", (char *)ptr);
printf("按任意键继续, 删除共享内存\n");
getchar();
shmdt(ptr);
shmctl(shmid, IPC_RMID, NULL);
printf("共享内存已经被删除...\n");
return 0;
}
信号量
信号量与以上IPC结构不同,它是一个计数器,信号量用于进程间的互斥与同步,若要在进程间传递数据需要结合共享内存。
- 信号量基于操作系统的P/V操作,程序对信号量的操作都是原子操作,是在内核中实现。
- 对信号量的PV操作不仅限于对信号量值+1或-1,可以加减任意正整数。
内核为每个信号量集合维护了一个结构体,数据结构如下:
struct semid_ds
{
struct ipc_perm sem_perm;
long sem_otime;
long sem_ctime;
struct sem *sem_base;
struct sem_queue *sem_pending;
struct sem_queue **sem_pending_last;
struct sem_undo *undo;
ushort sem_nsems;
};
单个信号量的数据结构如下:
struct sem {
unsigned short semval;
pid_t sempid;
unsigned short semncnt;
unsigned short semzcnt;
};
创建/获取信号量集合
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
参数:
- key:信号量集合的键key
- nsems:如果是创建新信号量集合,那么nsems代表新信号量集合中的信号量的数目。如果是获取当前存在的信号量集合,那么此设置参数为0。
- semflg:创建信号量时指定的属性
IPC_CREAT :创建新的信号量,指定对信号量的操作权限IPC_EXCL :检测信号量是否存在,必须和IPC_CREAT 一起使用
设置信号量集合
# include<sys/sem.h>
int semctl(int semid, int semnum, int cmd, union semun arg);
- semid: 信号量集合ID
- semunm: 某一信号量在信号量集合中对应的序号
- cmd: 用来指定对信号量集合的操作
SETVAL :用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。IPC_RMID :删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。
union semun
{
int val;
struct semid_ds* buf;
unsigned short* array;
struct seminfo* _buf;
};
操作信号量集合
# include<sys/sem.h>
int semop(int semid, struct sembuf* sops, unsigned nsops);
参数:
- semid: 信号量集合ID
- sops:指针指向一个sembuf类型的结构体,指明对某信号量的操作。
struct sembuf{
unsigned short sem_num;
short sem_op;
short sem_flg;
};
- nsops:操作的信号量的个数
示例
#include <stdio.h>
#include <stdlib.h>
#include <sys/sem.h>
#include <errno.h>
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
};
int init_sem(int sem_id, int value)
{
union semun tmp;
tmp.val = value;
if (semctl(sem_id, 0, SETVAL, tmp) == -1)
{
perror("Init Semaphore Error");
return -1;
}
return 0;
}
int sem_p(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0;
sbuf.sem_op = -1;
sbuf.sem_flg = SEM_UNDO;
if (semop(sem_id, &sbuf, 1) == -1)
{
perror("P operation Error");
return -1;
}
return 0;
}
int sem_v(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0;
sbuf.sem_op = 1;
sbuf.sem_flg = SEM_UNDO;
if (semop(sem_id, &sbuf, 1) == -1)
{
perror("V operation Error");
return -1;
}
return 0;
}
int del_sem(int sem_id)
{
union semun tmp;
if (semctl(sem_id, 0, IPC_RMID, tmp) == -1)
{
perror("Delete Semaphore Error");
return -1;
}
return 0;
}
int main()
{
key_t key = ftok("./key.c", 0xFF);
if (key == -1)
{
perror("ftok");
return -1;
}
int sem_id = semget(key, 1, 0777 | IPC_CREAT | IPC_EXCL);
if (sem_id == -1)
{
if (errno == EEXIST)
{
int sem_id = semget(key, 0, 0777);
if (sem_id == -1)
{
perror("semget error");
return -1;
}
}
else
{
perror("semget error");
return -1;
}
}
init_sem(sem_id, 0);
pid_t pid = fork();
if (pid == -1)
{
perror("Fork Error");
}
else if (pid == 0)
{
sleep(2);
printf("Process child: pid=%d\n", getpid());
sem_v(sem_id);
}
else
{
sem_p(sem_id);
printf("Process father: pid=%d\n", getpid());
sem_v(sem_id);
del_sem(sem_id);
}
return 0;
}
参考:https://subingwen.cn/linux/shm/ 参考:https://www.cnblogs.com/CheeseZH/p/5264465.html 参考:https://blog.csdn.net/weixin_43937576/article/details/116599068
|