Linux进程间通信:共享内存

一、什么是共享内存

共享内存就是在物理内存中开辟一片空间,让不同进程通过某种方式能够直接访问该内存块,实现进程间的数据通信

1.1 共享内存原理

谈到共享内存空间,那我们就简述一下进程虚拟地址空间与物理地址空间的关系。

我们都知道进程的虚拟地址空间共有4G大小,那么这4G是不是真实存在于物理地址空间的呢,显然不是的,虚拟地址空间是操作系统为进程抽象出的一个地址空间,虚拟地址并不是一个真实的物理地址。那么又是怎么通过使用虚拟地址来使用物理地址的呢,那就是通过页表映射。

(非原创图片)

 共享内存作为存取最快的一种IPC通信方式,原因就是通过映射,进程可以直接访问共享内存,进行数据的读写,不需要在内核空间中转。

让我们来分析一下IPC的几种通信方式:

管道、FIFO或消息队列:完成数据的读写共需要四步

对于写进程:

(1)进程buf -> 内核空间(也就是调用read/write写入缓存区)

(2)内核空间 -> 管道、FIFO或消息队列

对于读进程:

(1)管道、FIFO或消息队列 -> 内核空间

(2)内核空间 -> 进程buf

共享内存:完成数据的读写仅需要两步

对于写进程:

(1)进程 -> 共享内存buf(进程直接对共享内存中的数据进行读写,而不需要在进程间复制)

对于读进程:

(2)共享内存buf -> 进程

总结:

因为共享内存不需要执行read/write等函数进行内核系统调用,减少了数据的拷贝次数,节省了数据传输时间

 二、相关函数

2.1 共享内存创建

int shmget(key_t key,size_t size,int shmflg);

通过调用shmget函数在内存中创建一个共享内存块,并生成一个标识符指向这个内存块

(1)第一个参数key:用于标识共享内存的键值,一般使用ftok函数生成。不同进程间使用相同键值来关联同一个共享内存空间。使用特殊常量IPC_PRIVATE作为键值可以保证系统建立一个全新的共享内存块。

(2)第二个参数size:创建共享内存块的大小,因为共享内存是用以页(4kb)为单位,所以实际创建的内存大小会扩大调整为页的整数倍,我们在设置的时候就应该设为页的整数倍;若只获取已存在的共享内存设为0

(3)第三个参数shmflg:权限标志位;设置为IPC_CREAT时,不存在则创建一个共享内存,存在则返回该共享内存标识符;设置为IPC_CREAT | IPC_EXCL时,不存在则创建一个新的共享内存,存在则报错;设置为0,不存在则报错,存在则返回该共享内存标识符

(4)返回值:成功返回一个标识符(为正);失败返回 -1 ,出错结果保存在error中

2.2 绑定共享内存

void *shmat(int shmid, const void *shmaddr, int shmflg);

绑定共享内存,获得共享内存的首地址

(1)第一个参数:shmget返回的共享内存标识符

(2)第二个参数:指定共享内存存放在进程的哪一地址;指定为NULL,则由内核在进程地址中找一合适位置

(3)第三个参数:对共享内存的操作权限位;SHM_RDONLY,是只读权限;0,表示为可读可写

返回值:成功返回共享内存空间的首地址,用指针指向;失败返回 -1 ,出错结果保存在error中

2.3 解除与共享内存的关联

void *shmdt( const void *shmaddr);

 结束进程与共享内存的关联,如果没有调用该函数,进程退出时会自动断开与共享内存的关联,此时共享内存依然存在于内存空间中。

(1)参数:共享内存的起始地址

返回值:成功返回 0;失败返回-1,出错结果保存在error中

2.4 控制共享内存操作

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

(1)第一个参数:共享内存的标识符

(2)第二个参数:对共享内存要采取的操作;IPC_SET,设置共享内存shmid_ds结构体数据;IPC_STAT,获取共享内存shmid_ds结构体数据;IPC_RMID,删除共享内存块

(3)第三个参数:共享内存shmid_ds结构体

三、代码示例

3.1 共享内存的创建

int shmaphore_init(char **shmptr)  //这里我将共享内存的创建抽象成一个函数
{

        int    shmid;
        key_t  key;

        key=ftok(PATH_NAME,PROJ_ID);
        if(key < 0)
        {    
                printf("ftok failure:%s\n",strerror(errno));
                return -1; 
        }   
        printf("key:%d\n",key);
        shmid=shmget(key,4096,IPC_CREAT|0660);  //如果不存在,则创建一个4kb的共享内存块;如果已存在,则关联
        if(shmid < 0)
        {   
                printf("shmget failure:%s\n",strerror(errno));
                return -2; 
        }   
        printf("shmid:%d\n",shmid);

        *shmptr=(char *)shmat(shmid,NULL,0);  //不指定共享内存地址,由内核分配,返回共享内存的起始地址
        if(*shmptr == (char *)-1)
        {   
                printf("shmat failure:%s\n",strerror(errno));
                return -3; 
        }
        memset(*shmptr,0,4096);   
        return shmid;
}

(1)不同进程通过相同的key值可以关联同一个共享内存,在不同进程间调用ftok函数,只要给定的路径跟ID相同就可以生成相同的key值

(2)注意:对于自定义的shmaphore_init函数,传入的参数为一个初级指针的地址,这里使用到二级指针,注意不要出错

3.2 共享内存解除关联与删除

void shmaphore_exit(int shmid,char *shmptr)
{

        shmdt(shmptr);  //进程解除对共享内存的关联
        if(shmctl(shmid,IPC_RMID,NULL) < 0)  //删除共享内存块,释放内存空间
        {
                printf("shmctl failure:%s\n",strerror(errno));
        }
}

注意:进程不再使用共享内存后,虽然进程退出会自动解除与共享内存间的关联,但为了代码的严谨性,我们应主动断开与共享内存的关联。同时,最后一个与之相关联的进程在退出前,一定要将共享内存删除。

四、小结

 1. 共享内存是最快的进程间通信的方式

 2. 共享内存本身并不具备同步机制,一般在使用共享内存时,我们会使用信号量、互斥量或记录锁来补充这一功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值