(四)进程间通讯方式-----共享内存

共享内存

在linux进程间通信的方式中,共享内存是一种最快的IPC方式。共享内存允许两个或多个进程共享一个给定的存储区,这一段存储区通常安排为同一段物理内存,进程可以将这一段共享内存映射至自身的地址空间中,一个进程写入共享内存的信息,可以被其他使用这个共享内存的进程,通过一个简单的内存读取错做读出,从而实现了进程间的通信。某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

注意:共享内存并未提供同步机制,在第一个进程结束对共享内存的写操作之前,并没有自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问。通常,信号量被用来实现对共享内存存储访问的同步。

内核为每个共享内存段设置了一个shmid_ds结构

struct  shmid_ds
{
    struct ipc_perm   shm_perm;          
    int              shm_segsz;           //设置请求的长度      
    time_t           shm_atime;           0
    time_t           shm_dtime;           0
    time_t           shm_ctime;         
    pid_t            shm_cpid;           
    pid_t            shm_lpid;            0
    shmatt_t         shm_nattch;          0
    ....
};


struct ipc_perm
{
    key_t           key;          
    gid_t           gid;        
    uid_t           cuid;       
    gid_t           cgid;        
    mode_t          mode;    
};

共享内存的操作 

1:获取一块共享内存标识符

#include<sys/shm.h>
int shmget(key_t key,size_t size,int flag);
                    //成功返回共享内存ID,失败返回-1

key:程序必须提供一个以键来命名的共享内存。不同进程可以通过键值,来获取同一个共享内存,相当于文件名。key的取值也可以为IPC_PRIVATE,则函数shmget()将创建一块新的共享内存;如果key的取值为0,而参数shmflg中设置了IPC_CREATE这个标志,则同样创建一块新的共享内存。但是通过这种方式分配的共享内存,一般用来亲缘关系的进程间通信。

size是要建立共享内存的长度。所有的内存分配操作都是以页为单位的。所以如果一个进程只申请一块只有一个字节的内存,内存也会分配整整一页(在i386机器中一页的缺省大小PACE_SIZE = 4096字节)。

flag是一个权限标志,与文件的访问权限一样,IPC_CREAT可与flag做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。

IPC_CREAT      如果不存在,则创建一个,否则直接打开已存在的

IPC_EXCL       只有在不存在的时候,新的才建立,否则就产生错误

ps:是内核对象来创建这个共享内存,并不是用户,因为一个进程是不能直接访问物理内存的。

2:共享内存的映射链接

#include<sys/shm.h>
void *shmat(int shmid,const void *addr,int flag);
                    //成功返回指向共享内存的指针,失败返回-1

shmid : 要映射的共享内存区标识符

addr : 将共享内存映射到指定地址(若为NULL,则表示由系统自动完成映射)

flag : SHM_RDONLY  共享内存只读(默认0:共享内存可读写)。

返回值 :调用成功返回的是该段所链接的实际地址 ,出错返回(void *)-1;

如果shmat执行成功,内核会使该共享内存段shmid_ds中的shm_nattch计数器值加1

3:取消共享内存与用户进程之间的映射

#include<sys/shm.h>
int shmdt(const void *addr);
                //成功返回0,出错返回-1

参数addr是shmat映射成功返回的地址。

注意:当一个进程不再需要共享内存段时,它将调用shmdt()系统调用取消这个段,但是,内核并不会真证的在内存中删除这个段及其标识符(即内核对象),而是把相关shmid_ds结构的shm_nattch域的值减1,当这个值为0时,内核才从物理上删除这个共享段及标识符(相当于一个引用计数)。或者某个进程调用shmctl(带命令IPC_RMID)特地删除它。

4、控制共享内存

#include<sys/shm.h>
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
                 //成功返回0,失败返回-1

参数说明:

shmid:共享内存标识ID

参数cmd:

PC_STAT:取此段shmid_ds结构,并把它存放在buf指向的结构中。

IPC_SET:按照buf指向的结构中的值,设置与此段相关结构中的下列三个字段:shm_perm.uid、shn_perm.gid和shm_perm.mode。此命令只能有下列两种进程执行。一种是其有效用户ID等于shm_perm.uid或shm_perm.cuid;另一种是具有超级用户特权的进程。

IPC_RMID:从系统中删除该共享存储段及其标识符(删除内核对象)。因为每个共享存储段有一个连接计算(shmid_ds结构中的shm_nattch字段),所以除非使用该段的最后一个进程终止或与该段脱节,否则不会实现实际上删除该存储段。不管此段是否还在使用,该段标识符立即被删除,所以不能再用shmat与此段连接。一种是其有效用户ID等于shm_perm.uid或shm_perm.cuid;另一种是具有超级用户特权的进程。

为什么共享内存是最快的一种IPC机制?

我们使用管道(FIFO/消息队列)从一个文件传输信息到另外一个文件需要复制2次。一是,用户空间将信息复制到管道(FIFO/消息队列,其实是内核中);二是,另一个进程的用户空间将信息从管道(FIFO/消息队列,内核)复制到自己的空间中。但是,共享内存并没有进行信息的拷贝,用户空间直接在自己的空间操作(返回的地址),实际操作的是物理内存,不需要拷贝到自己的用户空间。读也是直接从自己的空间读(实际上从内存中读取),无须拷贝。这就是共享内存高效的原因。

使用共享内存的优缺点

1、优点:我们可以看到使用共享内存进行进程间的通信真的是非常方便,而且函数的接口也简单,数据的共享还使进程间的数据不用传送,而是直接访问内存,也加快了程序的效率。同时,它也不像匿名管道那样要求通信的进程有一定的父子关系。

2、缺点:共享内存没有提供同步的机制,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。


 

猜你喜欢

转载自blog.csdn.net/Eunice_fan1207/article/details/83153609