共享内存
①为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间,这块就是共享内存区域
②由于可以多个进程共享一段内存,因此也需要依靠某种同步机制(如互斥锁和信号量等)
③共享内存是一种最为高效的进程间通信方式,因为进程可以直接读写内存,而不需要任何数据的拷贝
Shell的ipcs命令可以查看共享内存情况
主要步骤
①创建/打开共享内存
②映射共享内存(即把指定的共享内存映射到进程的地址空间用于访问)
③撤销共享内存映射
④删除共享内存对象
1、创建/打开共享内存
int shmget(key_t key, int size, int shmflg);
参数
key:IPC_PRIVATE 或 一个key_t值(如(key_t)0001),一般使用ftok()
size:共享内存区大小
shmflg:同open函数的权限位,也可以用8进制表示法
返回值
成功:共享内存段标识符
出错:-1
注意
①key是系统建立IPC通讯(消息队列、信号量和共享内存)时必须指定一个ID值。因为一定要唯一!!(要不然IPC通讯就乱套了),我们就想起来了文件或目录的节点号(因为他们是唯一的),那么这个节点号具体怎么用呢,这时候ftok()就出场了。ftok()是将文件的索引节点号取出,前面加上子序号得到key_t的返回值,完全符合我们对键值获取!
②如果key的值指定为IPC_PRIVATE,表明由系统为进程创建一个新的共享内存对象
③shmflg如包含IPC_CREAT,表明如果指定的共享内存不存在,则新建一个对象
ftok()原型如下:
key_t ftok( char * fname, int id )
fname就时你指定的文件名,该文件必须是存在而且可以访问的,若该文件不存在返回0xffffffff
id是子序号,虽然为int,但是只有8个比特被使用(0-255)。
当成功执行的时候,一个key_t键值将会被返回
shmget例子:
int shmid;
key_t key;
key= ftok("./tmp.txt",0);
shmid = shmget(key,1024,0666|IPC_CREAT);
将目录下tmp.txt的索引节点号作为键值,打开共享内存,大小为1024,权限0666,如果共享内存不存在,就创建。
2、映射共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid:要映射的共享内存区标识符(shmget()返回值)
shmaddr:将共享内存映射到指定地址(若为NULL,则表示由系统自动完成映射)
shmflg :SHM_RDONLY:共享内存只读,默认0:共享内存可读写
返回值
成功:映射后共享内存的地址
出错:-1
//创立一个共享内存结构体
struct share_mm
{
int w;
char buf[BUFFER_SIZE];
}* shmaddr;
//映射,并获得映射地址,shmid为创建/打开共享内存的标识符
shmaddr=shmat(shmid,0,0);
if(shmaddr==(void *)-1)
{
printf("shmat error\n");
exit(1);
}
//shmaddr是共享内存的地址,可以对此地址进行读写
3、撤销共享内存映射
int shmdt(const void *shmaddr);
参数
shmaddr:共享内存映射后的地址
返回值
成功:0
出错:-1
例子
if((shmdt(shmaddr))<0)
{
printf("shmdt error\n");
exit(1);
}
4、删除共享内存对象
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:要操作的共享内存标识符
cmd : IPC_STAT (获取对象属性)
IPC_SET (设置对象属性)
IPC_RMID (删除对象)
buf : 指定IPC_STAT/IPC_SET时用以保存/设置属性
返回值
成功:0
出错:-1
例子
if((shmctl(shmid,IPC_RMID,NULL))<0)//删除内核中的共享内存
{
printf("shmctl error\n");
exit(1);
}
最后一个例子
struct share_mm//共享内存结构体
{
int pid_r;
int pid_w;
char buf[BUFFER_SIZE];
}*shmaddr;
共享结构体中,pid_r和pid_w保存读写进程的进程号,然后共享内存的read和write通过信号实现同步。
即writer进程通过发送信号告诉reader进程共享内存我已写入新的数据,你可以读;读完后,reader进程通过发送信号告诉writer进程共享内存我已读走新的数据,你可以写;
read.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define BUFFER_SIZE 1024
struct share_mm//共享内存结构体
{
int pid_r;
int pid_w;
char buf[BUFFER_SIZE];
}*shmaddr;
void read_message(int signum)
{
printf("From father message:%s\n",shmaddr->buf);
}
int main()
{
int shmid;
pid_t pid;
key_t key;
//struct share_mm * shmaddr;//保存映射地址
//创建共享内存
key= ftok("./",0);
shmid = shmget(key,sizeof(struct share_mm),0666|IPC_CREAT);
if(shmid==-1)
{
printf("shmget error\n");
exit(1);
}
else
{
printf("Shmid is %d\n",shmid);
system("ipcs -m");
}
//映射,并获得映射地址
shmaddr=shmat(shmid,0,0);
if(shmaddr==(void *)-1)
{
printf("shmat error\n");
exit(1);
}
else
{
printf("Chile attach shm is %p\n",shmaddr);
system("ipcs -m");
}
//初始化共享内存
shmaddr->pid_r=getpid();
shmaddr->pid_w=0;
memset((void *)shmaddr->buf,0,BUFFER_SIZE);
while(shmaddr->pid_w==0);//等待写进程准备就绪
signal(SIGUSR1,read_message);
do
{
pause();//等带write进程的SIGUSR1信号,到来就读
kill(shmaddr->pid_w,SIGUSR2);
}while(strncmp(shmaddr->buf,"quit",4));
printf("Writer byebye\n");
if((shmdt(shmaddr))<0)
{
printf("shmdt error\n");
exit(1);
}
exit(0);
return 0;
}
write.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<signal.h>
#define BUFFER_SIZE 1024
struct share_mm//共享内存结构体
{
int pid_r;
int pid_w;
char buf[BUFFER_SIZE];
}* shmaddr;
void write_message(int signum)
{
memset((void *)shmaddr->buf,0,BUFFER_SIZE);
printf("In father process:\nPlease write message\n");
gets(shmaddr->buf);//从键盘写入数据到共享内存
}
int main()
{
int shmid;
pid_t pid;
key_t key;
//struct share_mm * shmaddr;//保存映射地址
//创建共享内存
key= ftok("./",0);
shmid = shmget(key,sizeof(struct share_mm),0666);
if(shmid==-1)
{
printf("shmget error\n");
exit(1);
}
else
{
printf("Shmid is %d\n",shmid);
system("ipcs -m");
}
//映射,并获得映射地址
shmaddr=shmat(shmid,0,0);
if(shmaddr==(void *)-1)
{
printf("shmat error\n");
exit(1);
}
else
{
printf("Chile attach shm is %p\n",shmaddr);
system("ipcs -m");
}
//初始化共享内存
shmaddr->pid_w=getpid();
signal(SIGUSR2,write_message);
printf("In father process:\nPlease write message\n");
gets(shmaddr->buf);//从键盘写入数据到共享内存
do
{
kill(shmaddr->pid_r,SIGUSR1);
pause();
}while(strncmp(shmaddr->buf,"quit",4));
kill(shmaddr->pid_r,SIGUSR1);//通知reader进程推出
waitpid(shmaddr->pid_r,NULL,0);//等待读进程先退出
printf("Reader byebye\n");
if((shmdt(shmaddr))<0)
{
printf("shmdt error\n");
exit(1);
}
if((shmctl(shmid,IPC_RMID,NULL))<0)//删除内核中的共享内存
{
printf("shmctl error\n");
exit(1);
}
exit(0);
return 0;
}
linux进程通讯的总结:
pipe: 具有亲缘关系的进程间,半双工,数据在内存中
fifo: 可用于任意进程间,双工,有文件名,数据在内存
signal: 唯一的异步通信方式
shm:效率最高(直接访问内存) ,需要同步、互斥机制