文章目录
- 一、IPC(Inter-Process Communication)进程间通信相关命令 :
- (一)ipcs --- 查看IPC对象
- (二)获取IPC键值
- (三)删除IPC对象的命令
- (四)获取IPC键值的函数
- 1. 函数定义
- 2. 使用示例
- 二、消息队列
- (一) 特点
- (二) 相关API
- 1. 创建或获取一个消息队列
- 2. 向消息队列中写消息
- 3. 在消息队列中读取一条消息
- 4. 控制消息队列
- (三) 不关注消息类型
- (四)关注消息类型
- (五)消息队列属性结构体
- 三、共享内存 shared memory(shm)
- (一)特点
- (二) 相关API
- 1. 创建共享内存
- 2. 映射共享内存到当前的进程空间
- 3. 取消地址映射
- 4. 共享内存控制
- (三)使用示例
- (四) 属性
- 四、信号灯集---控制进程间同步
- (一)特点
- (二) 相关API
- (三) 原生函数使用示例
- (四)封装函数
一、IPC(Inter-Process Communication)进程间通信相关命令 :
(一)ipcs — 查看IPC对象
(二)获取IPC键值
ipcs -q
查看消息队列
ipcs -m
查看共享内存
ipcs -s
查看信号灯集
(三)删除IPC对象的命令
ipcrm -q id
删除消息队列
ipcrm -m id
删除共享内存
ipcrm -s id
删除信号灯集
(四)获取IPC键值的函数
1. 函数定义
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:
使用给定的文件和字符来生成一个IPC通信的key值
参数:
pathname:路径和文件名(必须是已存在的可访问的)
proj_id:[0-255]的值 一般我们传一个字符即可 如 'A' 'm'
返回值:
成功 key值
失败 -1 重置错误码
- 注:
typedef int key_t
key_t 即 int 类型- pathname 要求必须是一个已存在的文件,因为key值的是由proj_id的后八位,设备号的后8位以及inode号的后16位组成。因此这种机制并不保证key值一定不重复
- proj_id 只是用了int的一个字节,即取值范围是[0-255],一般使用时传一个字符,字符ASCII码是0-127。
2. 使用示例
生成并打印出key,分析key值的由来
验证代码:
#include <my_head.h>
int main(int argc, char const *argv[])
{
key_t key=0;
key = ftok("/home/linux/05work",'A');
printf("my_key = %#x\n",key);
struct stat file_stat= {0};
stat("/home/linux/05work",&file_stat);
printf("proj_id=%#x dev=%#lx inode=%#lx\n",'A', file_stat.st_dev, file_stat.st_ino);
return 0;
}
输出结果:
二、消息队列
(一) 特点
- 消息队列也是基于内核实现的,存放在内存上(而非硬盘上)。
- 消息队列的大小,默认是 16K。
- 消息队列中的消息有类型和正文。
A进程将消息写入消息队列;
B进程可以根据消息的类型从消息队列中将对应类型的消息取走。 - 半双工通信
(二) 相关API
1. 创建或获取一个消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
功能:
创建或者获取一个消息队列
参数:
key:键值
key 通过ftok获取的
IPC_PRIVATE 表示只有亲缘进程间能用
msgflg:消息队列的标志位
IPC_CREAT|0666 消息队列不存在则创建,权限0666
或者
IPC_CREAT|IPC_EXCL|0666 消息不存在则创建,存在则但会-1,置错误码为EEXIST
返回值:
成功 消息队列的id
失败 -1 重置错误码
2. 向消息队列中写消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息队列中写入一条消息
参数:
msqid:消息队列的id
msgp: 要写入的数据的首地址
struct msgbuf {
long mtype; /* 消息的类型 必须大于 0 */
char mtext[1]; /* 消息正文 可以自定义 */
};
msgsz:消息正文的大小
msgflg:标志位 0 阻塞发送 IPC_NOWAIT 非阻塞发送
返回值:
成功 0
失败 -1 重置错误码
- 注:
- 关于void *msgp参数,第一个long类型大小的空间必须用来存放消息的类型,消息的正文可以自定义
- size_t msgsz参数,只包含正文数据的大小(sizeof(struct msgbuf)-sizeof(mtype))
- 阻塞发送的情况下,如果消息队列满了,A进程还想向消息队列中写入消息,此时A进程将会阻塞。
3. 在消息队列中读取一条消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz,
long msgtyp, int msgflg);
功能:在消息队列中读取一条消息
参数:
msqid:消息队列的id
msgp: 用来保存接收的数据的缓冲区的首地址
struct msgbuf {
long mtype; /* 消息的类型 必须大于 0 */
char mtext[1]; /* 消息正文 可以自定义 */
};
msgsz:消息正文的大小
msgtyp:要接受的消息的类型
0 :接收消息队列中第一条消息
>0 : 接收指定类型的第一条消息
<0 :一般不使用,了解即可,表示接收消息队列中第一条类型最小的小于msgtyp的绝对值的消息
3-2-5-500-200-8
读取时,类型传 -200
读取的顺序 2-3-5
msgflg:标志位 0 阻塞接收 IPC_NOWAIT 非阻塞接收
返回值:
成功 实际读到的正文的字节数
失败 -1 重置错误码
- 注:读消息队列和写消息队列中的void *msgp结构体的内部成员要尽量对应。
4. 控制消息队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:控制消息队列
参数:
msqid:消息队列id
cmd:指令
IPC_STAT:获取消息队列的属性
IPC_SET:设置消息队列的属性
IPC_RMID:立即删除消息队列
只有消息队列的创建者和所有者以及root用户可以删除消息队列
msgctl函数的第三个参数被忽略
buff:属性结构体的地址
返回值:
成功 0
失败 -1 重置错误码
(三) 不关注消息类型
此时进程间通信不关心消息类型,按顺序接收第一条消息。
注意,当其中一个进程关闭消息队列后,另一个进程再试图关闭,就会报错,错误码EINVAL
read.c
#include <my_head.h>
typedef struct msgbuf{
long type;
char name[20];
}msg_t;
int main(int argc, char const *argv[])
{
//获取key值
key_t key=ftok("/home/linux/05work",'A');
//创建消息队列
int msgid = msgget(key,IPC_CREAT|0666);
if(-1 == msgid)
ERR_LOG("msgget error");
//定义消息结构体
msg_t msg={0};
int type=0;
while(1){
if(-1 == msgrcv(msgid,&msg,sizeof(msg_t)-sizeof(msg.type),type,0)){
ERR_LOG("msgrcv error");
}
if(!strcmp(msg.name,"quit")){
break;
}
printf("%ld:%s\n",msg.type,msg.name);
}
if(-1 == msgctl(msgid,IPC_RMID,NULL)){
if(EINVAL == errno){
return 0;
}
ERR_LOG("msgctl error");
}
return 0;
}
write.c
#include <my_head.h>
typedef struct msgbuf{
long type;
char name[20];
}msg_t;
int main(int argc, char const *argv[])
{
//获取key值
key_t key=ftok("/home/linux/05work",'A');
//创建消息队列
int msgid = msgget(key,IPC_CREAT|0666);
if(-1 == msgid)
ERR_LOG("msgget error");
//定义消息结构体
msg_t msg={0};
while(1){
printf("请输入消息:<类型> <正文>:");
scanf("%ld %s",&msg.type,msg.name);
msgsnd(msgid,&msg,sizeof(msg_t)-sizeof(msg.type),0);
if(!strcmp(msg.name,"quit")){
break;
}
}
//销毁消息队列
if(-1 == msgctl(msgid,IPC_RMID,NULL)){
if(EINVAL == errno){
return 0;
}
ERR_LOG("msgctl error");
}
return 0;
}
(四)关注消息类型
此时进程间通信关心消息类型,按顺序接收第一条符合类型的消息,如果消息队列中没有同类型消息,会阻塞等待。
read.c
#include <my_head.h>
typedef struct msgbuf{
long type;
char name[20];
}msg_t;
int main(int argc, char const *argv[])
{
//获取key值
key_t key=ftok("/home/linux/05work",'A');
//创建消息队列
int msgid = msgget(key,IPC_CREAT|0666);
if(-1 == msgid)
ERR_LOG("msgget error");
//定义消息结构体
msg_t msg={0};
int type=0;
while(1){
printf("请输入想要接收的消息类型:");
scanf("%d",&type);
if(-1 == msgrcv(msgid,&msg,sizeof(msg_t)-sizeof(msg.type),type,0)){
ERR_LOG("msgrcv error");
}
if(!strcmp(msg.name,"quit")){
break;
}
printf("%ld:%s\n",msg.type,msg.name);
}
if(-1 == msgctl(msgid,IPC_RMID,NULL)){
if(EINVAL == errno){
return 0;
}
ERR_LOG("msgctl error");
}
return 0;
}
write.c
#include <my_head.h>
typedef struct msgbuf{
long type;
char name[20];
}msg_t;
int main(int argc, char const *argv[])
{
//获取key值
key_t key=ftok("/home/linux/05work",'A');
//创建消息队列
int msgid = msgget(key,IPC_CREAT|0666);
if(-1 == msgid)
ERR_LOG("msgget error");
//定义消息结构体
msg_t msg={0};
while(1){
printf("请输入消息:<类型> <正文>:");
scanf("%ld %s",&msg.type,msg.name);
msgsnd(msgid,&msg,sizeof(msg_t)-sizeof(msg.type),0);
if(!strcmp(msg.name,"quit")){
break;
}
}
//销毁消息队列
if(-1 == msgctl(msgid,IPC_RMID,NULL)){
if(EINVAL == errno){
return 0;
}
ERR_LOG("msgctl error");
}
return 0;
}
(五)消息队列属性结构体
struct msqid_ds {
struct ipc_perm msg_perm; /* IPC权限结构体 */
time_t msg_stime; /* 最后一次执行msgsnd的时间 */
time_t msg_rtime; /* 最后一次执行msgrcv的时间 */
time_t msg_ctime; /* 最后一次被修改的时间 */
unsigned long __msg_cbytes; /* 当前消息队列中的字节数 */
msgqnum_t msg_qnum; /* 当前消息队列中的消息数 */
msglen_t msg_qbytes; /* 允许的最大字节数 */
pid_t msg_lspid; /* 最后一次执行msgsnd的进程的PID */
pid_t msg_lrpid; /* 最后一次执行msgrcv的进程的PID */
};
struct ipc_perm {
key_t __key; /* 键值 */
uid_t uid; /* 所属用户的id */
gid_t gid; /* 所属用户的组id */
uid_t cuid; /* 创建者的id */
gid_t cgid; /* 创建者的组id */
unsigned short mode; /* 权限 */
};
- 注:
qbytes可以改小,改大的话需要sudo权限
三、共享内存 shared memory(shm)
(一)特点
在内核中创建共享内存,让进程A和进程B都能够访问到,通过这段内存进行数据的传递。
共享内存是所有进程间通信方式中效率最高的(不需要来回进行数据的拷贝)
(二) 相关API
1. 创建共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
功能:
创建共享内存
参数:
key:键值
key 通过ftok获取
IPC_PRIVATE:只能用于亲缘进程间的通信
size:共享内存的大小 PAGE_SIZE(4k)的整数倍
shmflg:共享的标志位
IPC_CREAT|0666
或
IPC_CREAT|IPC_EXCL|0666
返回值:
成功 共享内存编号
失败 -1 重置错误码
- 注:
- 共享内存大小必须要4k的整数倍,因为一页是4k。如果申请时不要求4
的整数倍,分配时也是分配4k的整数倍。 - 内核空间越界会直接段错误
- 同一个key值可以同时用于消息队列,共享内存,信号灯集
2. 映射共享内存到当前的进程空间
#include <sys/ipc.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:
映射共享内存到当前的进程空间
参数:
shmid:共享内存编号
shmaddr:指定共享内存出现在进程内存地址的什么位置,直接指定为NULL,让系统自动分配
shmflg:共享内存操作方式
0 读写
SHM_RDONLY 只读
返回值:
成功 指向共享内存的地址
失败 (void *)-1 重置错误码
3. 取消地址映射
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
功能:
取消地址映射
参数:
shmaddr:指向共享内存的指针
返回值:
成功 0
失败 -1 重置错误码
4. 共享内存控制
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:
共享内存控制的函数
参数:
shmid:共享内存编号
cmd:操作的命令码
IPC_STAT:获取
IPC_SET:设置
IPC_RMID:删除共享内存
标记要销毁的段。实际上,只有在最后一个进程将其分离之后
(也就是说,关联结构shmid_ds的shm_nattch成员为零时),
段才会被销毁。
调用者必须是段的所有者或创建者,或具有特权。buf参数被忽略。
buf:共享内存属性结构体指针
返回值:
成功 0
失败 -1 重置错误码
(三)使用示例
read.c
#include <my_head.h>
#define SHM_PAGE 1024*4
int main(int argc, char const *argv[])
{
//获取键值
key_t key = ftok("/home/linux/05work",'A');
if(-1 == key)
ERR_LOG("ftok error");
//创建共享内存
int shmid = shmget(key, SHM_PAGE,IPC_CREAT|0666);
if(-1 == shmid)
ERR_LOG("shmget error");
//映射共享内存
char *addr = (char *)shmat(shmid,NULL,SHM_RDONLY);
while(1){
getchar();
printf("%s",addr);
}
return 0;
}
write.c
#include <my_head.h>
#define SHM_PAGE 1024*4
int main(int argc, char const *argv[])
{
//获取键值
key_t key = ftok("/home/linux/05work",'A');
if(-1 == key)
ERR_LOG("ftok error");
//创建共享内存
int shmid = shmget(key, SHM_PAGE,IPC_CREAT|0666);
if(-1 == shmid)
ERR_LOG("shmget error");
//映射共享内存
char *addr = (char *)shmat(shmid,NULL,0);
while(1){
printf("请输入要发送的内容:");
scanf("%s",addr);
}
return 0;
}
(四) 属性
struct shmid_ds{
struct ipc_perm shm_perm; //权限结构体
size_t shm_segsz; //共享内存大小,单位是字节
__time_t shm_atime; //最后一次映射的时间
__pid_t shm_cpid; //创建共享内存进程的pid
__pid_t shm_lpid; //最后一次操作共享内存进程的pid
shmatt_t shm_nattch; //共享内存映射的次数
};
struct ipc_perm{
__key_t __key; //ftok获取的key
__uid_t uid; //用户的ID
__gid_t gid; //组ID
__uid_t cuid; //创建共享内存的用户的ID
__gid_t cgid; //创建共享内存的组的ID
unsigned short int mode; //消息队列的权限
};
四、信号灯集—控制进程间同步
(一)特点
进程间同步的机制
(二) 相关API
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
功能:
创建一个信号灯集
参数:
key:键值
IPC_PRIVATE key
nsems:信号灯集合中信号灯的个数
semflag:创建的标志位
IPC_CREAT|0666 或 IPC_CREAT|IPC_EXCL|0666
返回值:
成功 semid
失败 -1 重置错误码
int semctl(int semid, int semnum, int cmd, ...);
功能:信号灯集的控制函数
参数:
semid信号灯集的ID
senum:信号灯的编号 从0开始
cmd:命令码
SETVAL:设置信号灯的值 --->第四个参数val选项
GETVAL:获取信号灯的值 --->不需要第四个参数
IPC_STAT:获取信号灯集的属性--->第二参数被忽略,第四个参数buf选项
IPC_SET :设置信号灯集的属性--->第二参数被忽略,第四个参数buf选项
IPC_RMID:删除信号灯集 第二参数被忽略,第4个参数不用填写
@...:可变参
union semun{
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
};
返回值:
成功:
GETVAL:成功返回信号灯的值
其余的命令码成功返回0
失败 -1 重置错误码
int semop(int semid, struct sembuf *sops, size_t nsops);
功能:
信号灯集中信号灯的操作函数
参数:
semid:信号灯集的编号
sops:操作方式
struct sembuf{
unsigned short sem_num; //信号灯的编号
short sem_op; //操作方式(PV)-1:P操作,申请资源 1:V操作,释放资源
short sem_flg; //操作的标志位 0:阻塞 IPC_NOWAIT:非阻塞方式操作
}
nsops:本次操作信号灯的个数
返回值:
成功 0
失败 -1 重置错误码
- 注:
- 初始化操作必须只能一次,如果多次可能会将其他进程的值初始化
- 同时对多个信号灯进行操作时,可以定义一个结构体数组
(三) 原生函数使用示例
(四)封装函数
原生函数直接使用会比较繁琐,因此会做二次封装。
初始化(解决可能重复初始化的问题):
P操作
V操作
销毁