Linux 进程间通信之System V 共享内存

共享内存允许两个或多个进程共享物理内存的同一块区域(通常称之为段)。
复制代码

由于一个共享内存段会成为一个进程用户空间内存的一部分,因此这种IPC机制无需内核介入。所有需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其他所有共享同一个段的进程可用。与管道或消息队列要求发送进程将数据从用户空间的缓冲区复制进内核缓冲区和接收进程将数据从内核内存复制进用户空间的缓冲区的做法相比,这种IPC技术的速度更快。(每个进程也存在通过系统调用来执行复制操作的开销。)

另一方面,共享内存这种IPC机制不由内核控制意味着通常需要通过某些同步方法是的进程不会出现同时访问共享内存的情况。

2.创建或打开一个共享内存段

#include<sys/types.h>
#include<sys/shm.h>
int shmget(key_t key,size_t size,int shmflg);
//return shared memory segment identifier on success,or -1 on error
复制代码
  • key: IPC_PRIVATE或ftok()返回的键

  • size: 是一个正整数,它表示需分配的段的字节数。内核是以系统分页大小的整数倍来分配共享内存的,因此实际上size会被提升到最近的系统分页大小的整数倍。如果使用shmget()来获取一个既有段的标识符,那么size对段不会产生任何效果,但它必须要小于等于段的大小

  • shmflg:指定施加于新共享内存段上的权限或需检查的既有内存段的权限。标记可以零个或多个取OR来控制操作。

    • IPC_CREAT:如果不存在与指定的key对应的段,那么就创建一个新段。

    • IPC_EXCL:如果同时指定了IPC_CREAT并且与指定的key对应的段已经存在,那么返回EEXIST错误。

3.使用共享内存

#include<sys/types.h>
#include<sys/shm.h>
void *shmat(int shmid,const void *shmaddr,int shmflg);
//return address at which shared memory is attached on success, or (void*) -1 on error
复制代码
  • shmaddr:

    • 为NULL时,那么段会被附加到内核所选择的一个合适的地址处。这是附加一个段的优选方法。
    • 不为NULL并且没有设置SHM_RND(shmflg),那么段会被附加到由shmaddr指定的地址处,它必须是系统分页大小的一个倍数(否则会发生EINVAL错误)。
    • 不为NULL并设置了SHM_RND(shmflg),那么段会被映射到的地址为在shmaddr中提供的地址被舍入到最近的常量SHMLBA(shared memory low boundary address)的倍数。这个常量等于系统分页大小的某个倍数。

    将一个段附加到值为SHMLBA的倍数的地址处在一些架构上是有必要的,因为这样才能够提升CPU的快速缓冲性能和防止出现同一个段的不同附加操作在CPU快速缓冲性能和防止同一个段的不同附加操作在CPU快速缓冲中存在不一致的视图的情况。

  • shmflg: 是一个位掩码值

如果没有指定SHM_RDONLY,那么就即可以读取内存又可以修改内存。
复制代码

shmat()的函数结果是返回附加共享内存段的地址。开发人员可以像对待普通的C指针那样对待这个值,段与进程的虚拟内存的其他部分看起来毫无差异。通常会将shmat()的返回值赋给一个指向某个由程序员定义的结构的指针以便在该段上设定该结构。

在一个进程中可以多次附加同一个共享内存段,即使一个附加操作是只读的而另一个是读写的也没有关系。

4. 共享内存控制操作

#include<sys/types.h>
#include<sys/shm.h>
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
//return 0 on success,or -1 on error

struct shmid_ds
  {
    struct ipc_perm shm_perm;		/* operation permission struct */
    size_t shm_segsz;			/* size of segment in bytes */
    __time_t shm_atime;			/* time of last shmat() */
#ifndef __x86_64__
    unsigned long int __glibc_reserved1;
#endif
    __time_t shm_dtime;			/* time of last shmdt() */
#ifndef __x86_64__
    unsigned long int __glibc_reserved2;
#endif
    __time_t shm_ctime;			/* time of last change by shmctl() */
#ifndef __x86_64__
    unsigned long int __glibc_reserved3;
#endif
    __pid_t shm_cpid;			/* pid of creator */
    __pid_t shm_lpid;			/* pid of last shmop */
    shmatt_t shm_nattch;		/* number of current attaches */
    __syscall_ulong_t __glibc_reserved4;
    __syscall_ulong_t __glibc_reserved5;
  };

复制代码
  • cmd:参数规定了待执行的控制操作。

    常规控制操作
    复制代码
    • IPC_RMID: 标记这个共享内存段及其关联shmid_ds数据结构以便删除。如果当前没有进程附加该段,那么就会执行删除操作,否则就在所有进程都已经与该段分离(即当shmid_ds数据结构中shm_nattch字段的值为0时)之后再执行删除操作。

    • IPC_STAT:将与这个共享内存段关联的shmid_ds数据结构的一个副本防止到buf指向的缓冲区中。

    • IPC_SET:使用buf指向的缓冲区中的值来更新与这个共享内存段相关联的shmid_ds数据结构中被选中的字段。

      加锁与解锁共享内存
      复制代码

      一个共享内存段可以被锁进RAM中,这样它就永远不会被交换出去了。这种做法能够带来性能上的提升,因为一旦段中的所有分页都驻留在内存中,就能够确保一个应用程序在访问分页时永远不会因为分页故障而被延迟。通过shmctl()可以完成两种锁操作。

    • SHM_LOCK:操作将一个共享内存段锁进内存。

    • SHM_UNLOCK:操作为共享内存段解锁以允许它被交换出去。

5.获取共享内存的限制

struct shminfo buf;
shmctl(0,IPC_INFO,(struct shmid_ds *)&buf);

struct	shminfo
  {
 
    __syscall_ulong_t shmmax; //这是一个共享内存段的最大大小(字节数)。(shmget(),EINVAL)
    
    __syscall_ulong_t shmmin; //这是一个共享内存段的最小大小(字节数)。这个限制的值被定义成了1(无法修改),实际的限制是系统分页的大小(shmget(),EINVAL)
   
    __syscall_ulong_t shmmni;  //系统级别限制,限制了所能创建的共享内存标识符的数量(shmget(),ENOSPC) 
  
    __syscall_ulong_t shmseg;   //进程级别限制,限制了所能附加的共享内存段数量
   
    __syscall_ulong_t shmall;    //系统级别限制,限制了共享内存中的分页总数。(shmget(),ENOSPC)
    __syscall_ulong_t __glibc_reserved1;
    __syscall_ulong_t __glibc_reserved2;
    __syscall_ulong_t __glibc_reserved3;
    __syscall_ulong_t __glibc_reserved4;
  };

复制代码
我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章