一、共享内存

1.1什么是共享内存

   共享内存允许两个或者多个进程共享物理内存的同一块区域(通常被称为段)。由于一个共享内存段会称为一个进程用户空间的一部分,因此这种IPC 机制无需内核介入。所有需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其他所有共享同一个段的进程可用。与管道等要求发送进程将数据从用户空间的缓冲区复制进内核内存和接收进程将数据从内核内存复制进用户空间的缓冲区的做法相比,这种 IPC技术的速度更快。

1.2与共享内存相关系统调用和进程通信实例

/*
    #include <sys/ipc.h>
    #include <sys/shm.h>

    int shmget (key_t key, size_t size, int shmflg) ;
    -功能:创建一个新的内存共享段,或者获取一个既有的共享内存段标识
            新创建的内存段数据会被初始化为0
    -参数:
        -key:key_t类型是一个整形,通过这个找到或者创建一个共享内存,十六进制非0值
        -size:共享内存大小
        -shmflg:属性
            -访问权限
            -附加属性:创建/判断共享内存是否存在,通过位图
                      创建:IPC_CREAT
                      判断:IPC_EXCL,需要和IPC_CREAT一起使用
    -返回值:
        成功:>0返回共享内存id,后边操作共享内存都是使用此值
        失败:-1
    void *shmat(int shmid, const void *shmaddr, int shmflg) ;
    -功能:和当前的进程进行内存关联
    -参数:
        -shmid:共享内存ID,由shmget返回
        -shmaddr:申请的共享内存起始地址,指定为NULL,由内核指定
        -shmflg:对共享内存的操作
                SHM_EXEC:可执行
                SHM_RDONLY:可读,必须要有
                0:有读写权限
    -返回值:
        成功:返回共享内存首地址
        失败:(void*) -1
    int shmdt ( const void *shmaddr) ;
    -功能:解除和当前的进程进行内存关联
    -参数:共享内存首地址
    -返回值:
        成功:0
        失败:-1
    int shmctl(int shmid, int cmd,struct shmid_ds *buf) ;
    -功能:操作共享内存,创建共享内存的进程被销毁对共享内存本身没有任何影响,必须删除共享内存
    -参数:
        shmid:共享内存编号
        cmd:要做的操作
            -IPC_STAT:获取共享内存当前状态
            -IPC_SET:设置共享内存装填
            -IPC_RMID:标记共享内存被销毁
        buf:需要设置或者获取的共享内存属性信息,IPC_STAT为传出参数,IPC_SET需要初始化buf,IPC_RMID为NULL
                   struct shmid_ds {
               struct ipc_perm shm_perm;    
               size_t          shm_segsz;  
               time_t          shm_atime;   
               time_t          shm_dtime;   
               time_t          shm_ctime;   
               pid_t           shm_cpid;    
               pid_t           shm_lpid;    
               shmatt_t        shm_nattch;  
               ...
           };
    -返回值:
        成功:0
        失败:-1
    key_t ftok ( const char *pathname,int proj_id) ;
*/
//
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>

int main(){
    //创建共享内存
    int shmid = shmget(100, 4096, IPC_CREAT|0664);
    printf("shmid: %d\n", shmid);
    //和当前进程进行关联
    void * ptr = shmat(shmid, NULL, 0);

    char * str = "helloworld";
    //写数据
    memcpy(ptr, str, strlen(str)+1);
    printf("按任意键继续");
    getchar();
    //解除关联
    shmdt(ptr);

    //删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
}
//
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>

int main(){
    //获取共享内存
    int shmid = shmget(100, 4096, IPC_CREAT);
    printf("shmid: %d\n", shmid);
    //和当前进程进行关联
    void * ptr = shmat(shmid, NULL, 0);
    //读数据
    printf("%s\n", (char*)ptr);
    printf("按任意键继续");
    getchar();
    //解除关联
    shmdt(ptr);

    //删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
}

1.3 共享内存注意要点

问题1:操作系统如何知道一块共享内存被多少个进程关联?
  - 共享内存维护了一个结构体struct shmid_ds 这个结构体中有一个成员 shm_nattch
  - shm_nattach 记录了关联的进程个数

问题2:可不可以对共享内存进行多次删除 shmctl
  - 可以的
  - 因为shmctl 标记删除共享内存,不是直接删除
  - 什么时候真正删除呢?
  当和共享内存关联的进程数为0的时候,就真正被删除
  - 当共享内存的key为0的时候,表示共享内存被标记删除了
  如果一个进程和共享内存取消关联,那么这个进程就不能继续操作这个共享内存。也不能进行关联。

问题3.共享内存和内存映射的区别
  1.共享内存可以直接创建,内存映射需要磁盘文件(匿名映射除外)
  2.共享内存效果更高
  3.内存
  所有的进程操作的是同一块共享内存。
  内存映射,每个进程在自己的虚拟地址空间中有一个独立的内存。
问题4.数据安全
  - 进程突然退出
  共享内存还存在
  内存映射区消失
  - 运行进程的电脑死机,宕机了
  数据存在在共享内存中,没有了
  内存映射区的数据 ,由于磁盘文件中的数据还在,所以内存映射区的数据还存在。

问题5.生命周期
  - 内存映射区:进程退出,内存映射区销毁
  - 共享内存:进程退出,共享内存还在,标记删除(所有的关联的进程数为0),或者关机
  如果一个进程退出,会自动和共享内存进行取消关联。