0%

linux笔记—共享内存

共享内存

  • 即多进程之间直接对读写同一段内存.相比较管道及消息队列,显而易见的好处是速度快,所有的IPC方法中效率最高.但共享内存并未提供同步机制,需要自行实现.

原理

  • 顾名思义,共享内存就是说两个不同的进程A、B可以共同享有一块内存区域
    20150124140604078.png

  • 整个处理流程是

    • 进程A第一次访问该页中的数据时, 生成一个缺页中断. 内核读入此页到内存并更新页表使之指向此页.
    • 进程B访问同一页面而出现缺页中断,内核只将进程B的页表登记项指向次页即可.
    • 进程A.B 即可访问同一段内存.

使用

  • shmget函数
    shmget被用来开辟/初始化一段共享内存.其他进程使用相同的key 通过 shgat 获取同一段共享内存.,只有shmget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符.

    函数原型

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

    key : 与信号量的semget函数一样,使用共享内存key(非0整数)shmget函数成功时返回一个与key相关的共享内存标识符(非负整数),用于多进程的共享.调用失败返回-1.
    size : 开辟的共享内存大小(字节).
    shmflg : 权限控制,与IPC_CREAT做或操作,控制其他进程对共享内存权限. 0644即代表其他进程只有读的权限.

  • shmat函数
    用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间

    函数原型

    1
    void *shmat(int shm_id, const void *shm_addr, int shmflg);

    shm_id: 由shmget函数返回的共享内存标识.
    shm_addr: 指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址.
    shm_flg: 标志位,通常为0.

  • shmdt函数
    将共享内存从当前进程中分离,使该共享内存对当前进程不再可用.

    函数原型:

    1
    int shmdt(const void *shmaddr);

    shmaddr: shmat函数返回的地址指针,调用成功时返回0,失败时返回-1.

  • shmctl函数
    控制共享内存.

    函数原型

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

    shm_id: shmget函数返回的共享内存标识符

    command: 要采取的操作,以下三个值 :

    • IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值.
    • IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
    • IPC_RMID:删除共享内存段

    buf: 结构指针,指向共享内存模式和访问权限的结构.

    1
    2
    3
    4
    5
    6
    7
    shmid_ds结构至少包括以下成员:
    struct shmid_ds
    {
    uid_t shm_perm.uid;
    uid_t shm_perm.gid;
    mode_t shm_perm.mode;
    };

系统限制及规避

大小

  • linux默认限制共享内存总大小由 SHMMAX 值确定.默认值未32MB

  • 读取

    1
    cat /proc/sys/kernel/shmmax
  • 当超过系统限制时 提示 unable to attach to shared memory.

  • 规避

    • 直接修改/proc.无需重启

      1
      echo "2147483648" > /proc/sys/kernel/shmmax

      可以将命令写入启动脚本 /etc/rc.local中.保证重启生效

    • 使用 sysctl 命令修改

      1
      sysctl -w kernel.shmmax=2147483648

      可以将此参数插入到 /etc/sysctl.conf 启动文件中

      1
      echo "kernel.shmmax=2147483648" >> /etc/sysctl.conf

      永久生效.

数量

  • 与大小类似的,共享内存创建的总数量由 SHMMNI 参数确定.

  • 读取

    1
    cat /proc/sys/kernel/shmmni

    默认情况下是 4096.

  • 一般不需要修改.

同一进程多次shmat

  • shmat即挂载共享内存到进程的进程空间.

  • 当同一进程多次调用shmat 挂载同一共享内存时,shamat每次返回的地址都不同,相当于在进程的线性空间中存在多个实际指向同一块共享内存.直到最后进程线性空间消耗殆尽.

  • 解决:
    需要在挂载共享内存前,判断申请的共享内存指针是否为空,为 NULL ,则第一次加载此共享内存.否则不再重复加载.

    1
    2
    3
    4
    5
    6
    void* p = NULL;
    /*其他操作*/
    if (NULL == p)
    {
    p = shmat(shmid,p,0666);
    }

多个进程相同key多次创建共享内存

  • 共享内存创建有大小之分.key相同情况下,容量小的共享内存会获得之前创建的大的共享内存的内容.有可能导致之前创建共享内存的进程崩溃.

  • 解决

    • 在shmage使用IPC_EXCL标记.现行判断共享内存是否已创建.如果已创建则挂载,没有创建返回失败后,再创建.

      1
      2
      3
      4
      5
      6
      Shmid = Shmget(key, size,IPC_CREATE|IPC_EXCL);
      if (-1 != shmid)
      {
      /*错误处理*/
      Shmid = Shmget(key, size,IPC_CREATE);
      }
    • 不通过key 标记同一块共享内存.
      shmget使用 kry = IPC_PRIVATE ,linux会忽略key值,直接新建一块共享内存.返回标识,通过管道/文件方式共享给其他进程使用.

共享内存删除

  • 调用shmctl 删除共享内存后,共享内存并不会立刻被系统清理.

  • 首先共享内存的 shmid_ds结构中的 shm_nattch 减一.该共享内存从调用shmctl的进程剥离.但 shm_nattch 不为 0 的情况下,即仍然有别进程连接的情况下.共享内存并不会立刻清除. 只有在 shm_nattch 为 0 ,没有任何进程连接的情况下,系统才会清理 这段共享内存.

  • 该段共享内存被任何连接的进程执行 shmctl 删除操作后, 新的进程将无法连接到该段共享内存.

  • 与之对应的 Shmdt ,只是将共享内存由 调用的进程空间剥离.不会影响其他进程连接到该段共享内存.

应用

  • 多进程共享数据.以链表为例.

  • 有两种方式:

    • 开辟一段共享内存,之后存入整个链表.将共享内存连接到各个进程.
      • 优点:
        • 对原有代码改动少.
      • 缺点:
        • 封装链表操作时,各个进程头节点偏移量不同,不能简单的通过 *p->next 访问.
        • 有共享内存大小限制.
    • 每一个链表节点对应一个共享内存.开辟同等数量的共享内存.
      • 优点
        • 可以随用随创,相对节省内存.
        • 链表节点next 存储相对链表头节点偏移即可,访问操作相对单一共享内存容易.
      • 缺点
        • 受限于 linux 默认 4096的总数量限制.且总数不易更改.
        • 开辟新链表节点相对复杂.
  • 实际使用中第一种较为常见.

同步

  • 与共享内存类似的信号量,也是跨进程的.信号量是比较一种比较方便配合共享内存同步的方式.