进程间通信——IPC之共享内存

时间:2023-03-09 04:12:12
进程间通信——IPC之共享内存
共享内存是三个IPC机制中的一个。它允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在进行的进程之间传递数据的一种非常有效的方式。
大多数的共享内存的实现,都把由不同进程之间共享的内存安排为同一段物理内存.
首先我们都知道我们执行的每一个程序,它看到的内存其实都是虚拟内存,虚拟内存需要进行页表的映射将进程地址映射到物理内存,具体处理大致如下面的图
  进程间通信——IPC之共享内存
共享内存特点和优势
当*享内存的大致原理相信我们可以看明白了,就是让两个进程地址通过页表映射到同一片物理地址以便于通信,
你可以给一个区域里面写入数据,理所当然你就可以从中拿取数据,这也就构成了进程间的双向通信,而且共享内存
是IPC通信当中传输速度最快的通信方式没有之一,理由很简单,客户进程和服务进程传递的数据直接从内存里存取、放入,
数据不需要在两进程间复制,没有什么操作比这简单了。再者用共享内存进行数据通信,它对数据也没啥限制。
最后就是共享内存的生命周期随内核。
即所有访问共享内存区域对象的进程都已经正常结束,共享内存区域对象仍然在内核中存在(除非显式删除共享内存
区域对象),在内核重新引导之前,对该共享内存区域对象的任何改写操作都将一直保留;简单地说,共享内存区域对象
的生命周期跟系统内核的生命周期是一致的,而且共享内存区域对象的作用域范围就是在整个系统内核的生命周期之内。
缺陷
但是,共享内存也并不完美,共享内存并未提供同步机制,也就是说,在一个服务进程结束对共享内存的写操作
之前,并没有自动机制可以阻止另一个进程(客户进程)开始对它进行读取。这明显还达不到我们想要的,我们不单
是在两进程间交互数据,还想实现多个进程对共享内存的同步访问,这也正是使用共享内存的窍门所在。基于此,
我们通常会用平时常谈到和用到 信号量来实现对共享内存同步访问控制。

相关函数


1.创建共享内存shmget

  

原型:int shmget(key_t key, size_t size, int shmflg)

返回值: 创建成功,则返回一个非负整数,即共享内存标识;

       如果失败,则返回-.

参数:

  key:    //程序需要提供一个参数key,它为共享内存段提供一个外部名。(每个IPC对象都与一个键 即key相关联,然后此键再由内核变换为标识符)。还有一个特殊的键值IPC_PRIVATE, 它用于创建一个只属于该创建进程的新共享内存,通常不会用到;

  size:   //以字节为单位指定需要共享的内存容量。
  shmflag: //包含9个比特的权限标志,它们的作用与创建文件时使用的mode标志是一样。由IPC_CREAT定义的一个特殊比特位,同时必须和权限标志按位或才能创建一个新的共享内存段。
(注意:若想创建的新IPC结构没有引用具有同一标识符的现有的IPC结构,就要同时指定IPC_CREAT 和 IPC_EXCL;共享内存属IPC中一种,它同样如此)
注:
  权限标志对共享内存非常有用,因为它允许一个进程创建的共享内存可以被共享内存的创建者所拥有的进程写入,同时其它用户创建的进程只能读取共享内存。我们可以利用这个功能来提供一种有效的对数据进行只读访问的方法,通过将数据放共享内存并设置它的权限,就可以避免数据被其他用户修改。
 2.将共享内存端挂载到自己地址空间shmat
第一次创建共享内存段时,它不能被任何进程访问。要想启动对该内存的访问,必须将其连接到一个进程的地址空间

  

该函数原型:void *shmat(int shmid, const void *shmaddr, int shmflg) 

  返回值:调用成功返回挂载的虚拟地址空间起始地址,失败返回NULL

参数:

  int shmid            //是由shmget函数返回的共享内存标识。

  const void *shmaddr  //指定共享内存连接到当前进程中的地址位置,通常为0,表示让系统来选择          共享内存的地址。

  int shmflg     //是一组标志位,通常为0。它还可取:SHM_RND,用以决定是否将当前共享内存段连接到指定的shmaddr上。该参数和shm_addr联合使用,用来控制共享内存连接的地址,除非只计划在一种硬件上运行应用程序,否则不要这样指定。填0让操作系统自己选择是更好的方式。

  SHM_RDONLY单独使用则是指让它使连接的内存段只读,否则以读写方式连接此内存段

3. 与共享内存段分离 shmdt 

原型:int shmdt(const void *shmaddr)

参数:

  shm_addr: shmat返回的地址指针。
  成功时,返回0,
  失败时,返回-1.
NOTE:
  仅仅是共享内存分离但并未删除它,其标识符及其相关数据结构都在;直到某个进程的IPC_RMID命令的调用shmctl特地删除它为止  

  只是使得该共享内存对当前进程不再可用。

4. shmctl 共享内存控制函数

    #include <sys/ipc.h>
#include <sys/shm.h> 原型: int shmctl(int shmid, int cmd, struct shmid_ds *buf)

参数: 

  shm_id : 是shmget返回的共享内存标识符。
  cmd: 它可以取3个值:
    IPC_STAT  把shmid_ds结构中的数据设置为共享内存的当前关联值
    IPC_SET   如果进程有足够的权限就把共享内存的当前关联值设置为shmid_ds结构中给出的值
    IPC_RMID  删除共享内存段
  buf:是一个指针,包含共享内存模式和访问权限的结构。
  buf指向的shmid_ds结构体 一定要包含下列一些参数:
    struct shmid_ds {
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
}

简单使用


简单用共享内存来再两进程间交换数据,比如交换一个结构体

代码如下:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
typedef struct Stu
{
int age;
char name[];
}Stu; int main( void)
{
Stu s;
strcpy(s.name, "jack");
//创建共享内存段
int id = shmget(, , IPC_CREAT|);
if( id == -)perror( " shmget"),exit( );
//挂载到进程的地址空间
Stu* p = ( Stu*)shmat( id, NULL, ); int i =;
while( )
{
s.age = i++;
memcpy(p, &s, sizeof(Stu)); //写到共享段中
sleep( );
}
return ;
}

write.c

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
typedef struct Stu
{
int age;
char name[];
}Stu;
int main( void)
{
int id = shmget(, , );
if( id == -)perror( " shmget"),exit( ); Stu* p = ( Stu*)shmat( id, NULL, );
while( )
{
printf(" age= %d, name= %s\n", p->age, p->name);
sleep();
}
return ;
}

read.c

执行结果如下: 

进程间通信——IPC之共享内存

小结

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

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

上面只是共享内存的一些简单的应用,当多个进程对共享内存进行访问时,并没有保证同步,所以我们还需要用其它的机制来实现它的同步机制,要解决此,通常会用到信号量(PV操作)来实现。但要基于此的实现,前提还需要熟悉信号量的操作以及这里的共享内存使用。要用共享内存模拟做出一个带同步机制"先进先出"的消息通道,对我等萌新并不太容易,所以还得放到以后再实现了。。