内核中的IPC结构(消息队列、信号量、共享内存)都用一个非负整数的标识符加以引用。标识符是IPC对象的内部名,为使多个进程能够使用同一IPC对象,需要提供一个外部命名方案。为此,每个IPC对象都与一个键key相关联,将这个键作为该对象的外部名。创建IPC结构时都应指定一个键,这个键的数据类型是基本系统数据类型key_t,这个键由内核变换成标识符。
#include
#include
key_t ftok(const char *pathname, int proj_id);
// 返回值:若成功返回键 key,失败返回(key_t)-1
参数:
// 该结构规定了权限和所有者
struct ipc_perm
{
__kernel_key_t key; // key
__kernel_uid_t uid; // 所有者ID
__kernel_gid_t gid; // 所属组ID
__kernel_uid_t cuid; // 创建者进程ID
__kernel_gid_t cgid; // 创建者进程组ID
__kernel_mode_t mode; // 读写权限
unsigned short seq; // 序号
};
消息队列是消息的链表,存放在内核中,由消息队列标识符标识。
struct msqid_ds
{
struct ipc_perm msg_perm; // ipc_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; /* 重用32位垃圾字段 */
unsigned long msg_lqbytes; /* 重用32位垃圾字段 */
unsigned short msg_cbytes; /* 当前队列大小 */
unsigned short msg_qnum; /* 当前队列的消息个数 */
unsigned short msg_qbytes; /* 队列的最大字节数 */
__kernel_ipc_pid_t msg_lspid; /* 最后mesgsnd的pid*/
__kernel_ipc_pid_t msg_lrpid; /* 最后recevice的pid*/
};
消息的数据结构如下:
struct msg_msg
{
struct list_head m_list;
long m_type; // 消息的类型
size_t m_ts; // 消息的大小
struct msg_msgseg *next; // 下一个节点
void *security; // 真正的消息的位置
};
#include
#include
#include
int msgget(key_t key, int msgflg);
//返回值,若成功返回消息队列ID,失败返回-1
参数:
IPC_CREAT
:创建新的消息队列,同时需要指定对消息队列的操作权限IPC_EXCL
:检测消息队列是否存在,必须和IPC_CREAT
一起使用#include
int msgctl(int msqid,int cmd,struct msqid_ds* buf);
//返回值:成功返回0,失败返回-1
参数:
IPC_STAT
:得到当前消息队列的状态IPC_SET
:设置消息队列的状态IPC_RMID
:删除该消息队列以及仍在该队列中的所有数据,删除立即生效。仍在使用这一队列的其他进程,再次操作时,会得到EIDRM
错误IPC_STAT
,作为传出参数,会得到消息队列的相关属性信息IPC_SET
,作为传入参数,将用户的自定义属性设置到消息队列中IPC_RMID
,buf无意义,指定为NULL即可#include
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// 成功返回0, 失败返回-1
参数:
struct msgbuf
{
long mtype; // 消息的类型
char mtext[512]; // 消息内容,在使用时,自己重新定义此结构
};
IPC_NOWAIT
:类似文件I/O的非阻塞I/O标志,若消息队列已满,或者队列中的消息总数等于系统限制值,或队列中的字节总数等于系统限制值,则函数立即出错,返回EAGAIN
#include
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
// 成功返回消息数据部分的长度,出错返回-1
IPC_NOWAIT
:类似文件I/O的非阻塞I/O标志。MSG_NOERROR
:若返回的消息长度大于msgsz,则该消息会被截断,系统不会通知。若没有设置这一标志,则出错返回E2BIG
,消息仍留在队列中。#include
#include
#include
#include
#include
#include
// 自定义消息
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
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)
{
perror("msgget");
return -1;
}
}
else
{
perror("msgget");
return -1;
}
}
printf("message 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("message receive: %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;
}
#include
#include
#include
#include
#include
#include
// 自定义消息
struct msg_buf
{
long mtype; // 消息类型
char mtext[512]; // 消息缓冲区
};
int main(int argc, char *argv[])
{
key_t key;
struct msg_buf msg_buf_snd = {2, "2-hello world!"};
struct msg_buf msg_buf_recv;
memset(&msg_buf_recv, 0, sizeof(msg_buf_recv));
// 获取Key
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)
{
perror("msgget");
return -1;
}
}
else
{
perror("msgget");
return -1;
}
}
printf("message 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("message receive: %s\n", msg_buf_recv.mtext);
}
return 0;
}
共享内存允许两个或多个进程共享同一块存储区,通过地址映射将这块物理内存映射到不同进程的地址空间中,多个进程可以通过这块物理空间进行数据的交互,达到进程间通信的目的。
内核为每块共享内存维护着一个结构体,数据结构如下:
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; /* 创建者pid */
__kernel_ipc_pid_t shm_lpid; /* 最后操作的进程pid */
unsigned short shm_nattch; /* 当前连接数 */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
#include
int shmget(key_t key, size_t size, int shmflg);
// 返回值:成功返回共享内存ID,失败返回-1
参数:
IPC_CREAT
:创建新的共享内存,指定对共享内存的操作权限IPC_EXCL
:检测共享内存是否存在,必须和IPC_CREAT
一起使用#include
void *shmat(int shmid, const void *shmaddr, int shmflg);
// 返回值:连接成功,返回共享内存的起始地址,连接失败返回(void *)-1
参数:
SHM_RDONLY
:读权限#include
int shmdt(const void *shmaddr);
// 返回值:成功返回0,失败返回-1
参数:
#include
#include
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
// 返回值:函数调用成功返回0,调用失败返回-1
参数:
IPC_STAT
:得到当前共享内存的状态IPC_SET
:设置共享内存的状态IPC_RMID
:标记共享内存为删除状态IPC_STAT
,作为传出参数,会得到共享内存的相关属性信息IPC_SET
,作为传入参数,将用户的自定义属性设置到共享内存中IPC_RMID
,buf无意义,指定为NULL即可// 查看系统中共享内存的详细信息
ipcs -m
当共享内存被标记为删除状态之后,共享内存的状态也会发生变化,共享内存内部维护的键key从一个正整数变为 0,其属性从公共的变为私有的。这里的私有是指只有已经关联成功的进程才允许继续访问共享内存,不再允许新的进程和这块共享内存进行关联了。当共享内存被标记为删除状态并且这个引用计数变为0之后,共享内存才会被真正的被删除。
// 写进程
#include
#include
#include
int main()
{
// 1. 创建共享内存, 大小为4k
int shmid = shmget(1000, 4096, IPC_CREAT | 0664);
if (shmid == -1)
{
perror("shmget error");
return -1;
}
// 2. 当前进程和共享内存关联
void *ptr = shmat(shmid, NULL, 0);
if (ptr == (void *)-1)
{
perror("shmat error");
return -1;
}
// 3. 写共享内存
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
#include
#include
int main()
{
// 1. 打开已经存在的共享内存
int shmid = shmget(1000, 0, 0);
if (shmid == -1)
{
perror("shmget error");
return -1;
}
// 2. 当前进程和共享内存关联
void *ptr = shmat(shmid, NULL, 0);
if (ptr == (void *)-1)
{
perror("shmat error");
return -1;
}
// 3. 读共享内存
printf("共享内存数据: %s\n", (char *)ptr);
// 阻塞程序
printf("按任意键继续, 删除共享内存\n");
getchar();
// 进程和共享内存解除关联
shmdt(ptr);
// 删除共享内存
shmctl(shmid, IPC_RMID, NULL);
printf("共享内存已经被删除...\n");
return 0;
}
信号量与以上IPC结构不同,它是一个计数器,信号量用于进程间的互斥与同步,若要在进程间传递数据需要结合共享内存。
struct semid_ds
{
struct ipc_perm sem_perm; /* IPC权限 */
long sem_otime; /* 最后一次对信号量操作(semop)的时间 */
long sem_ctime; /* 对这个结构最后一次修改的时间 */
struct sem *sem_base; /* 在信号量数组中指向第一个信号量的指针 */
struct sem_queue *sem_pending; /* 待处理的挂起操作*/
struct sem_queue **sem_pending_last; /* 最后一个挂起操作 */
struct sem_undo *undo; /* 在这个数组上的undo请求,用于恢复初始信号量值 */
ushort sem_nsems; /* 在信号量数组上的信号量编号 信号量的数量*/
};
单个信号量的数据结构如下:
struct sem
{
unsigned short semval; /* 信号量的值 */
pid_t sempid; /* 在信号量上最后一次操作的进程号 */
unsigned short semncnt;/* 待信号量的值递增的进程数 */
unsigned short semzcnt;/* 等待信号量的值递减的进程数 */
};
#include
int semget(key_t key, int nsems, int semflg);
// 返回值:成功返回信号量ID,失败返回-1
参数:
IPC_CREAT
:创建新的信号量,指定对信号量的操作权限IPC_EXCL
:检测信号量是否存在,必须和IPC_CREAT
一起使用# include
int semctl(int semid, int semnum, int cmd, union semun arg);
// 返回值:成功返回0,失败返回-1
SETVAL
:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。IPC_RMID
:删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。union semun
{
int val; // 信号量的值
struct semid_ds* buf;
unsigned short* array;
struct seminfo* _buf;
};
# include
int semop(int semid, struct sembuf* sops, unsigned nsops);
// 返回值:成功返回0,失败返回-1
参数:
struct sembuf
{
unsigned short sem_num; // 该信号量在信号量集合中对应的序号,[0, sem_nums-1]
short sem_op; // 信号量值在一次操作中的改变量
short sem_flg; // 操作标识,IPC_NOWAIT、SEM_UNDO(当进程退出时,会将信号量置为初始值)
};
#include
#include
#include
#include
// 联合体
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;
}
// P操作
int sem_p(int sem_id)
{
struct sembuf sbuf;
// 该信号量在信号量集合中对应的序号
sbuf.sem_num = 0;
// P操作
sbuf.sem_op = -1;
sbuf.sem_flg = SEM_UNDO;
if (semop(sem_id, &sbuf, 1) == -1)
{
perror("P operation Error");
return -1;
}
return 0;
}
// V操作
int sem_v(int sem_id)
{
struct sembuf sbuf;
// 该信号量在信号量集合中对应的序号
sbuf.sem_num = 0;
// V操作
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
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;
}
}
// 初始化信号量,初始值为0
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());
// V操作,释放资源
sem_v(sem_id);
}
// 父进程
else
{
// P操作,等待资源
sem_p(sem_id);
printf("Process father: pid=%d\n", getpid());
// 删除信号量集合
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