UNIX(编程-进程间通信):09---XSI IPC之共享存储(struct shmid_ds)

一、概念与特点

  • 共享存储允许两个或多个进程共享一个给定的存储区
  • 共享存储不属于某一特定进程,申请时是由系统提供的,大家都可以使用
  • 因为数据不需要在进程之间复制,所以这是最快的一种IPC结构
  • 重点:父进程fork子进程或者exec执行一个新的程序。在子进程和新程序里面不会继承父进程之前使用的共享存储

二、共享存储的互斥问题

  • 因为共享存储大家都可以使用,所以当有多个进程对共享存储进行操作时,需要控制好读写操作(也就是互斥操作)
  • 通常,信号量用于同步共享存储的访问(不过前一篇文章最后提到的记录锁或互斥量也可以使用)

三、共享存储与内存映射的不同

  • 共享存储在多个进程将同一个文件映射到它们的地址空间的时候,XSI共享存储和内存映射的文件的不同之处在于,共享存储没有相关的文件
  • XSI共享存储段是内存的匿名段

四、共享存储结构体(struct  shmid_ds)

  • 内核为每个共享存储维护着一个结构,该结构只要要为每个共享存储段包含以下成员:
struct shmid_ds {
    struct ipc_perm shm_perm;    /* Ownership and permissions */
    size_t          shm_segsz;   /* Size of segment (bytes) */
    time_t          shm_atime;   /* Last attach time */
    time_t          shm_dtime;   /* Last detach time */
    time_t          shm_ctime;   /* Last change time */
    pid_t           shm_cpid;    /* PID of creator */
    pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
    shmatt_t        shm_nattch;  /* No. of current attaches */
    ...
};
  • shm_perm成员:见==>https://blog.csdn.net/qq_41453285/article/details/90552522
  • shmatt_t数据类型:无符号整型,至少与unsigned short一样大
  • shm_nattch:表示有多少个进程正在使用这个共享存储

五、共享存储的系统限制

UNIX(编程-进程间通信):09---XSI IPC之共享存储(struct shmid_ds)_第1张图片

六、共享存储的创建/引用(shmget函数)

#include 
#include 
int shmget(key_t key, size_t size, int flag);

//返回值:成功返回共享存储ID;出错返回-1
  • 功能:用来创建或者引用一个共享存储
  • 共享存储ID:每个共享存储对应一个ID,此函数的返回值就是共享存储ID

参数:

  • key、flag:如何设置见文章==>https://blog.csdn.net/qq_41453285/article/details/90552522
  • size:如果此函数用来创建一个共享存储,那么此参数指定共享存储的长度,单位为“字节”,段内的内容全部初始化为0。如果此函数用来引用一个已存在的共享存储,那么此参数指定为0

size参数的注意事项:

  • size通常将其向上取为系统页长的整倍数。如果指定的size值并非系统页长的整数倍,那么最后一页的余下部分是不可使用的

如果此函数用来创建一个新的共享存储,那么内核会自动初始化共享存储struct shmid_ds结构体,初始化结果如下:

UNIX(编程-进程间通信):09---XSI IPC之共享存储(struct shmid_ds)_第2张图片

七、共享存储操作函数(shmctl函数)

#include 
#include 
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

//返回值:成功返回0;失败返回-1
  • 功能:此函数可以对参数1指定的共享存储进行多种操作(删除、取信息、加锁、解锁等)

cmd参数:

  • 此参数对应如下,此参数对shmid指向的共享存储进行多种不同操作

UNIX(编程-进程间通信):09---XSI IPC之共享存储(struct shmid_ds)_第3张图片

八、共享存储的连接使用(shmat函数)

#include 
#include 
void *shmat(int shmid, const void *addr, int flag);

//返回值:成功,返回指向共享存储段的指针;出错返回(void*)-1
  • 功能:一旦创建/引用了一个共享存储段,那么进程就可调用shmat函数将其连接到它的地址空间中
  • 返回值:成功返回的是该段所连接的十几地址
  • 如果shmat成功执行,那么内核将使与该共享存储相关的shmid_ds结构中的shm_nattch计数器值加1

共享存储段连接到调用进程的哪个地址上与addr参数以及flag中是否指定SHM_RND有关:

addr参数:

  • 如果addr为0:则此函数连接到由内核选择的共享存储段的第一个可用地址上。这是推荐的使用方法
  • 如果addr非0:并且没有指定SHM_RND,则此段连接到参数addr所指定的地址上
  • 如果addr非0:并且指定了SHM_RND,则此段连接到(addr-(addr mod SHMLBA))所表示的地址上,该算式是将地址向下去最近1个SHMLBA的倍数

flag参数:

  • SHM_RND:意思是“取整”
  • SHMLBA:意思是“地边界地址倍数”,它总是2的乘方
  • SHM_RDONLY:则以只读方式连接此段,否则以读写方式连接此段

九、 共享存储的分离(shmdt函数)

#include 
#include 
int shmdt(const void *shmaddr);

//返回值:成功,返回0;出错返回-1
  • 功能:当对共享存储的操作已经结束时,则调用shmdt与该存储段分离
  • 参数:shmaddr是上一次调用shmat函数的返回值
  • 如果shmat成功执行,那么内核将使与该共享存储相关的shmid_ds结构中的shm_nattch计数器值减1
  • 注意:这并不从系统中删除共享存储的标识符以及其相关的数据结构。共享存储的仍然存在,直至某个进程带IPC_RMID命令的调用shmctl特地删除共享存储为止

十、演示案例

  • 查看程序的内存模型,以及查看共享存储的内存处于哪一位置
#include 
#include 
#include 
#include 
#include 

#define ARRAY_SIZE  40000
#define MALLOC_SIZE 100000
#define SHM_SIZE    100000
#define SHM_MODE    0600

char array[ARRAY_SIZE];
int main()
{
    int shmid;
    char *ptr,*shmptr;
    
    if((ptr=malloc(MALLOC_SIZE))==NULL){
        perror("malloc");
        exit(EXIT_FAILURE);
    }
    if((shmid=shmget(IPC_PRIVATE,SHM_SIZE,SHM_MODE))==-1){
        perror("shmget");
        exit(EXIT_FAILURE);
    }
    if((shmptr=shmat(shmid,0,0))==(void*)-1){
        perror("shmat");
        exit(EXIT_FAILURE);
    }
    
    printf("arrar[] from %p to %p\n",(void*)&array[0],(void*)&array[ARRAY_SIZE]);
    printf("stack around %p\n",(void*)&shmid);
    printf("malloc from %p to %p\n",(void*)ptr,(void*)(ptr+MALLOC_SIZE));
    printf("share memory attached from %p to %p\n",(void*)shmptr,(void*)(shmptr+SHM_SIZE));

    if(shmctl(shmid,IPC_RMID,0)==-1){
        perror("shmctl");
        exit(EXIT_FAILURE);
    }
    
    exit(0);
}

UNIX(编程-进程间通信):09---XSI IPC之共享存储(struct shmid_ds)_第4张图片

UNIX(编程-进程间通信):09---XSI IPC之共享存储(struct shmid_ds)_第5张图片

十一、演示案例

  • A_3_shm_A.c创建一个共享存储,然后A_3_shm_B.c引用A_3_shm_A.c创建的共享存储并写入一些内容,写入内容之后A_3_shm_A.c去读取。因为共享存储的操作需要解决互斥的问题,因此程序中用到了信号量的操作解决互斥的问题
  • A_3_shm_A.c运行之后阻塞等待信号量变为0,变为0之后读取共享存储的内容。A_3_shm_B.c向共享存储写入内容,之后将信号量变为0。信号量变为0之后A_3_shm_A.c阻塞消失,继续运行,读取A_3_shm_B.c向共享存储读取的内容
//A_3_shm_A.c

#include 
#include 
#include 
#include 
#include 


#define SHM_SIZE 512
#define FLAG     IPC_CREAT|0777
#define SHMID    100
#define SEMID    100

#define SHMFILENAME "./shm.txt"
#define SEMFILENAME "./sem.txt"

key_t createKey(char *name,int id);

void create_sem_and_wait();

union semun{
    int              val;
    struct semid_ds *buf;
    unsigned short  *array;
    struct seminfo  *__buf;
};

int main()
{
    int shmId;
    char *shmptr;
    key_t key;

    key=createKey(SHMFILENAME,SHMID);

    //创建共享存储
    if((shmId=shmget(key,SHM_SIZE,FLAG))==-1){
        printf("A shmget error\n");
        exit(EXIT_FAILURE);
    }

    //以只读的方式连接到共享存储,并且shmptr引用到共享存储的地址上
    if((shmptr=shmat(shmId,0,SHM_RDONLY))==(void*)-1){
        printf("A shmat error\n");
        exit(EXIT_FAILURE);
    }

    printf("wait...\n");
    //创建信号量并且等待信号量的值变为0
    create_sem_and_wait();

    printf("The content of the Shared memory is:%s\n",shmptr);

    //分离共享存储
    if(shmdt(shmptr)==-1){
        printf("A shmdt error\n");
        exit(EXIT_FAILURE);
    }

    //消除共享内存
    if(shmctl(shmId,IPC_RMID,NULL)==-1){
        printf("A shmctl error\n");
        exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}

key_t createKey(char *name,int id)
{
    key_t key;
    if((key=ftok(name,id))==-1){
        printf("A ftok error\n");
        exit(EXIT_FAILURE);
    }
    return key;
}

void create_sem_and_wait()
{
    key_t key;
    int semId;
    key=createKey(SEMFILENAME,SEMID);

    //创建信号量集合,集合中只有1个信号量(ID为0)
    if((semId=semget(key,1,IPC_CREAT|0777))==-1){
        printf("A semget error\n");
        exit(EXIT_FAILURE);
    }

    //设置信号量集合中ID为0的信号量的值为1
    union semun arg;
    arg.val=1;
    if(semctl(semId,0,SETVAL,arg)==-1){
        printf("A semctl error\n");
        exit(EXIT_FAILURE);
    }

    //获得当前信号量集合中ID为0的信号量值
    /*int semval;
    if((semval=semctl(semId,0,GETVAL,NULL))==-1){
        printf("A semctl error\n");
        exit(EXIT_FAILURE);
    }
    printf("current semval:%d\n",semval);*/
    
    //等待信号量集合中ID为0的信号量值变为0
    struct sembuf buff;
    buff.sem_num=0;
    buff.sem_op=0;
    buff.sem_flg=0;
    if(semop(semId,&buff,1)==-1){
        printf("A semop error\n");
        exit(EXIT_FAILURE);
    }

    //销毁信号量集合
    if(semctl(semId,0,IPC_RMID,NULL)==-1){
        printf("A semctl error\n");
        exit(EXIT_FAILURE);
    }
}
////A_3_shm_B.c

#include 
#include 
#include 
#include 
#include 
#include 

#define SHM_SIZE 512
#define FLAG     0666
#define SHMFILENAME "./shm.txt"
#define SEMFILENAME "./sem.txt"

#define SHMID    100
#define SEMID    100


key_t createKey(char* filename,int id);

void signal_sem();

int main()
{
    int shmId;
    char *shmptr;
    char buff[]="B writes something to the shared memory";
    key_t key;

    key=createKey(SHMFILENAME,SHMID);

    //引用共享存储
    if((shmId=shmget(key,0,FLAG))==-1){
        printf("B shmget error\n");
        exit(EXIT_FAILURE);
    }

    //获取共享存储首地址
    if((shmptr=shmat(shmId,(void*)0,SHM_RND))==(void*)-1){
        printf("B shmat error\n");
        exit(EXIT_FAILURE);
    }

    //向共享存储写入内容
    bcopy(buff,shmptr,sizeof(buff));

    //分离共享内存
    if(shmdt(shmptr)==-1){
        printf("B shmdt error\n");
        exit(EXIT_FAILURE);
    }

    signal_sem();
    
    exit(EXIT_SUCCESS);
}

key_t createKey(char* filename,int id)
{
    key_t key;
    if((key=ftok(filename,id))==-1){
        printf("B ftok error\n");
        exit(EXIT_FAILURE);
    }
    return key;
}

void signal_sem()
{
    int semId;
    key_t key;
    key=createKey(SEMFILENAME,SEMID);

    //引用信号量集合
    if((semId=semget(key,0,0777))==-1){
        printf("B semget error\n");
        exit(EXIT_FAILURE);
    }

    //获得当前信号量集合中ID为0的信号量值
    /*int semval;
    if((semval=semctl(semId,0,GETVAL,NULL))==-1){
        printf("A semctl error\n");
        exit(EXIT_FAILURE);
    }
    printf("current semval:%d\n",semval);*/

    
    //将信号量集合中信号量ID为0的信号量值变为0
    struct sembuf buff;
    buff.sem_num=0;
    buff.sem_op=-1;
    buff.sem_flg=0;
    if(semop(semId,&buff,1)==-1){
        printf("B semop error\n");
        exit(EXIT_FAILURE);
    }
}

你可能感兴趣的:(UNIX(编程-进程间通信))