一.内存共享
1.1.什么是共享内存
允许两个或多个进程共享一个给定的存储区,是进程间通信最快的一种方式,不要同时对共享文件储存空间进行写操作,通常信号量用于同步共享储存访问;
1.2.共享内存使用流程
1.2.1:ftok( )函数生成键值,
所需头文件:#include<sys/ipc.h>
函数原型: key_t ftok(const char *path ,int id);
path是一个yi存在的路劲名,id为0~255之间的一个数值,代表项目id,自己去
返回值:成功返回键值,失败返回-1;
例如:key_t key = ftok( “/tmp”, 66);
1.2.2.shmget函数创建共享存储空间并返回一个共享存储标识符
头文件:#include<sys/shm.h>
函数原型: int shmget(key_t key, size_t size,int flag);
key为ftok生成的键值
size:为共享内存长度,以字节为单位;
flag:为所需的操作和权限,可以用来创建一个共享存储空间并返回一个标识符或者获得一个共享标识符。
flag的值为IPC_CREAT:如果不存在key值的共享存储空间,且权限不为0,则创建共享存储空间,并返回一个共享存储标识符。如果存在,则直接返回共享存储标识符。
flag的值为 IPC_CREAT | IPC_EXCL:如果不存在key值的共享存储空间,且权限不为0,则创建共享存储空间,并返回一个共享存储标识符。如果存在,则产生错误。
返回值:成功返回id,失败返回-1;
例如:
:int id = shmget(key,4096,IPC_CREAT|IPC_EXCL|0666);创建一个大小为4096个字节的权限为0666(所有用户可读可写,具体查询linux权限相关内容)的共享存储空间,并返回一个整形共享存储标识符,如果key值已经存在有共享存储空间了,则出错返回-1。
1.2.3.shmat函数获取第一个可用共享内存空间的地址
将开辟的物理空间映射到当前进程的虚拟内存
头文件:#include<sys/shm.h>
函数原型: void *shmat(int shmid, const void *addr, int flag);
shmid: shmget生成的共享储存标识符,
addr:可指定共享内存出现在进程内存地址的神么位置;可以直接指定为NULL让内核自己决定一个合适的地址位置
flag:对数据的操作,如果指定为SHM_RDONLY则以只读方式连接此段,其他值为读写方式连接此段。
返回值:成功返回指向共享文件的指针,错误返回-1;
l例如:
char *addr = shmat(id, NULL, 0);就会返回第一个可用的共享内存地址的指针的值给addr
1.2.4.shmdt函数进行分离
当不需要对此共享内存进行操作时候,调用shmdt函数进行分离,不是删除此共享存储空间哟。
所需头文件:#include<sys/shm.h>
函数原型: int shmdt(const void *addr);
addr为shmat函数返回的地址指针
返回值:成功返回0;错误返回-1
例如:int ret = shmdt(addr);
1.2.5.shmctl函数对共享内存进行控制
简单的操作就是删除共享存储空间了,也可以获取和改变共享内存的状态
所需头文件:#include<sys/shm.h>
函数原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid就是shmget函数返回的共享存储标识符
cmd有三个:
常用删除共享内存的为IPC_RMID;
IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中;
IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内。(内核为每个共享存储段维护着一个结构,结构名为shmid_ds,这里就不讲啦,里面存放着共享内存的大小,pid,存放时间等一些参数)
buf就是结构体shmid_ds
返回值: 成功返回0;错误返回-1
例如: nt ret = shmctl(id, IPC_RMID,NULL);删除id号的共享存储空间
代码示例:写一个共享内存,实现消息发送并读取
消息发送到共享空间:
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <unistd.h>
#define SIZE 1024
int main()
{
key_t key= ftok(".",10);//创建KEY
int shmID= shmget(1,SIZE,IPC_CREAT|0666);//开辟物理空间用于共享内存
if(shmID==-1)
{
perror("共享内存创建错误");
return -1;
}
char *mes=shmat(shmID,NULL,0);//将开辟的物理空间映射到当前进程的虚拟内存
for(int i=0;i<20;i++)
{
bzero(mes,SIZE);
sprintf(mes,"Hello![%d]我是:[%d]\n",i,getpid());
printf("写入消息:%s\n",mes);
sleep(1);
}
sprintf(mes,"exit");
if(shmdt(mes)!=-1)//分离共享内存
{
puts("已分离");
}
}
从共享空间读取:
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <unistd.h>
#define SIZE 1024
int main()
{
key_t key= ftok(".",10);//创建KEY
int shmID= shmget(1,SIZE,IPC_CREAT|0666);//开辟物理空间用于共享内存
char *data=shmat(shmID,NULL,0);//将开辟的物理空间映射到当前进程的虚拟内存
if(shmID==-1)
{
perror("共享内存创建错误");
return -1;
}
while(1)
{
if(strlen(data)>0)
{
printf("{%d}读取到数据:%s\n",getpid(),data);
if(strcmp(data,"exit")==0)
{
shmdt(data);//分离共享内存
int res=shmctl(shmID,IPC_RMID,NULL);
if(res!=-1)
{
puts("已删除");
exit(0);
}
}
bzero(data,SIZE);
}
usleep(500000);
}
}
运行结果:
发送:
接受:
二. 消息队列:
2.1.定义
内核提供的一个链表,内核基于这个链表实现一个数据结构,通过维护这个数据结构来维护消息列队,向消息中写数据实际就是向这个数据结构中插入一个新节点,从消息队列汇总读数据,实际上是从这个数据结构中删除一个结点
2.2.创建消息列队:
消息队列需要用msgget()函数创建,
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
原型函数:
int msgget(key_t key, int msgflg);
key:相当于文件系统的文件名,msgflg:操作指令如:IPC_CREAT | 0644前者为创建指令,后者为权限
2.3.发送和接受:
接受消息队列还是在发送消息队列时,都需要用户自己定义一个结构体,用来存储数据,并作为函数参数传入
struct msgbuf
{
long mtype;
char mtext[1];
}
2.3.1发送
使用msgsand()函数
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msqid:消息队列的id,由msgget()得到
msgp:定义的结构体,需要将地址穿过去
msgsz:结构体中mtext的大小,不包含类型
msgflg:直接填.0即可
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/msg.h>
typedef struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
}message;
int main()
{
key_t key = ftok(".", 120); // 创建KEY
int msgID = msgget(key, IPC_CREAT | 0666);
message msg;
for(int i=0;i<10;i++)
{
bzero(&msg,sizeof(msg));
msg.mtype=100+i;
sprintf(msg.mtext,"Hello![%ld]=>[%d]\n",msg.mtype,getpid());
// msgsnd(msgID,(void*)(msg.mtext),strlen(msg.mtext),0);
msgsnd(msgID,&msg ,strlen(msg.mtext),0);
printf("已发送:%ld => %s\n",msg.mtype,msg.mtext);
sleep(1);
}
exit(0);
}
2.3.2.接收:
msgrcv()函数
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
sgtype:消息类型
msgtyp==0时即按照先进先出的顺序读取当前消息队列中第一个消息。
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/msg.h>
typedef struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
} message;
int main()
{
key_t key = ftok(".", 120); // 创建KEY
int msgID = msgget(key, IPC_CREAT | 0666);
message buf;
for(int i=0;i<10;i++)
{
usleep(100000);
// int size = msgrcv(msgID, &buf, 128, i, 0);
msgrcv(msgID, &buf, 128, 100+i, 0);
printf("收到消息:%s\n", buf.mtext);
}
// msgrcv(msgID, &buf, sizeof(buf.mtext), 100, 0);
// printf("收到消息:%s\n", buf.mtext);
// msgrcv(msgID, &buf, 128, 101, 0);
// printf("收到消息:%s\n", buf.mtext);
// msgrcv(msgID, &buf ,128, 102, 0);
// printf("收到消息:%s\n", buf.mtext);
// msgrcv(msgID, &buf ,128, 103, 0);
// printf("收到消息:%s\n", buf.mtext);
// msgrcv(msgID, &buf, 128, 104, 0);
// printf("收到消息:%s\n", buf.mtext);
}
运行结果:
2.3.3.注意:
msgsnd函数的返回值:成功返回0,失败返回-1;
msgrv 函数的返回值:成功返回读取的数据大小,失败返回-1
数据队列有三个上限:每个数据最大长度上限(MSGMAX),每个数据队列的总字节数上限(MSGMNB),系统上消息队列的总数的上限(MSGMNI)。
每个数据最大长度上限(MSGMAX) /proc/sys/kernel/msgmax
每个数据队列的总字节数上限(MSGMNB) /proc/sy/kernel/msgmnb
系统上消息队列的总数的上限(MSGMNI) /proc/sys/kernel/msgmni
2.4.销毁
通过调用msgctl()函数可以,销毁消息队列。函数原型:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msqid; 打开或者创建消息队列得到的id,
cmd: 操作数,销毁的操作数为IPC_RMID,
buf : 操作数我们不关心一般填0。
2.5.查看消息队列
我们可以通过ipcs -q 命令查看已经创建的消息队列,包括他的key值信息,id信息,拥有者信息,文件权限信息,已使用的字节数,和消息条数。
最后我们也可以使用ipcrm -Q加消息队列的key值,来删除一个消息队列。