(4)linux进程通讯之共享内存

来源:互联网 发布:sql server 字段赋值 编辑:程序博客网 时间:2024/05/21 16:53

共享内存

①为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间,这块就是共享内存区域
②由于可以多个进程共享一段内存,因此也需要依靠某种同步机制(如互斥锁和信号量等)
③共享内存是一种最为高效的进程间通信方式,因为进程可以直接读写内存,而不需要任何数据的拷贝

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 1024struct 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 1024struct 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:效率最高(直接访问内存) ,需要同步、互斥机制