共享内存环形队列shm_ring_queue(内存屏障)

内存屏障: https://blog.csdn.net/caoshangpa/article/details/78853919

读内核代码进一步学习 Memory Barrier 的使用。
Linux 内核实现的无锁(只有一个读线程和一个写线程时)环形缓冲区 kfifo 就使用到了 Memory Barrier


文章目录

      • (1)shmfifo.h
      • (2)shmfifo.c
      • (3)get.c
      • (4)put.c


共享内存环形队列的功能介绍:
1、共享内存的操作:创建/获取、映射、反映射、删除
2、无锁环形队列,本文(支持多生产者多消费者模型)
3、若想(支持单生产者单消费者模型),可以将“信号量”、“互斥锁”的相关部分代码注释; 还要添加2个接口,判断空和判断满
4、可以在head_t中添加一个成员size,表示当前队列中块的个数,用于判断队列空和队列满

question:为什么要使用共享内存?
答案:因为要支持“进程间通信”!


实现核心点:
(1) 共享内存的第一个位置,存放环形队列的信息,用head_t结构体表示
    当进程向共享内存的环形队列中存取数据时,首先查看head_t,即找到读写位置,判断是否空满,之后再存取
(2) 共享内存head_t后面的所有内容,都是环形队列,用p_payload表示
    当向共享内存存取数据时,获取到head_t后,操作的都是p_payload指针
(3)p_payload是指向真正存取数据的缓冲区,类型可以是void*么?
答案:不行,必须设置为char,因为后面指针p_payload的增加操作来定位存取位置(需要用 fifo->p_payload+(fifo->p_head->wr_idx/wr_idx * fifo->p_head->blksz ),将类型设置为char*,增加的步长是1
(4)fifo->p_head->blksz      // 块大小的妙用
    表示环形队列中存放一个数据块ELEM的大小 ==> 采用blksz变量,可以不用预先知道环形队列中存放什么类型的数据块,只需要在使用时知道存放数据块的尺寸即可。简单的说,不用在shmfifo之前 [定义/声明] 结构体:

typedef struct{  // 提前 [定义/声明] 结构体ELEM
	... 
	...
}ELEM;

typedef struct shmfifo
{
    head_t    *p_head;
    ELEM*     p_payload; // 类型是已经知道的结构体
    int shmid;
    int sem_full;
    int sem_empty;
    int sem_mutex;
}shmfifo_t;

(1)shmfifo.h

#ifndef __SHMFIFO_H__
#define __SHMFIFO_H__

#include 
#include 
#include 
#include 
#include 
#include 
#include 
//头信息结构体
typedef struct shm_head
{
    int rd_idx;  //读位置
    int wr_idx;  //写位置
    int blocks;  //块的总数capacitu
    int blksz;   //每块的大小sizeof(ELEM)
}head_t;

//总的这块用来实现消息队列的 共享内存结构体
typedef struct shmfifo
{
    head_t *p_head;//指向头信息结构体的指针
    char*  p_payload;//装有效内容的起始地址
    int shmid;
    int sem_full;//还有多少可以装的信号量
    int sem_empty;//还有多少可以消费的信号量
    int sem_mutex;//不能2个用户同时访问一个位置的 互斥信号量
}shmfifo_t;

//初始化以上结构体的函数,
//返回类型为那块共享内存(共享内存结构体)的地址
//参数为:共享内存的key,要申请的块数,每块的大小
shmfifo_t* shmfifo_init(int key,int blocks,int blksz);

//往这块共享内存放数据
//参数为:申请的共享内存的地址,要放的数据的源地方
void shmfifo_put(shmfifo_t* fifo, const void* buf);

//从这块共享内存取数据
//参数为:要取的共享内存地址,取到的数据暂放的地方
void shmfifo_get(shmfifo_t* fifo, void* buf);

//销毁申请的这块共享内存
//参数为:要销毁的共享内存的地址
void shmfifo_destroy(shmfifo_t* fifo);

#if 0
/**
*    环形队列,判满
*@param [in]    p_fifo_idx   队列头部
*@return   满,1;不满,0
*/
int is_full(fifo_t *p_fifo_idx)
{
    if ((p_fifo_idx->rq_head.wr_index + 1) % (p_fifo_idx->rq_head.fifo_capacity) == p_fifo_idx->rq_head.rd_index)
        return 1;
    else
        return 0;
}

/**
*    环形队列,判空
*@param [in]    p_fifo_idx   队列头部
*@return   空,1;不空,0
*/
int is_empty(fifo_t *p_fifo_idx)
{
    if (p_fifo_idx->rq_head.rd_index == p_fifo_idx->rq_head.wr_index) // 空
        return 1;
    else
        return 0;
}
#endif


#endif //__SHMFIFO_H__

(2)shmfifo.c

 #include "shmfifo.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

//用来初始化信号量的初值的联合体
union semun
{
    int value;
};

//p操作
static void p(int id)
{
    struct sembuf sb[1];
    sb[0].sem_num=0;
    sb[0].sem_op=-1;
}
static void v(int id)
{
    struct sembuf sb[1];
    sb[0].sem_num=0;
    sb[0].sem_op=1;
    sb[0].sem_flg=0;
}

//初始化以上结构体的函数,
//返回类型为那块共享内存(共享内存结构体)的地址
//参数为:共享内存的key,要申请的块数,每块的大小
shmfifo_t* shmfifo_init(int key,int blocks,int blksz){
    //1.初始化
    //向内存申请一块(共享内存结构体大小)的空间,用一个这种结构体类型指针保存其地址
    shmfifo_t *p=(shmfifo_t*)malloc(sizeof(shmfifo_t));
    assert(p);

    //计算要创建的共享内存大小,记得加上头部信息结构体部分
    int len = blocks * blksz + sizeof(head_t);

    int shmid = shmget(key, len, 0);//打开len大小的共享内存key

    if(shmid == -1)//共享内存不存在,则创建
    {
        shmid = shmget(key, len, IPC_CREAT|0644);
        if(shmid == -1)//创建失败
        {
            perror("shmget failure\n");
            exit(1);
        }
        else//创建成功,则初始化共享内存结构体,p指向这个结构体
        {
            //读写位置初始化为0 
            p->p_head->rd_idx=0;
            p->p_head->wr_idx=0;
            //块数量、块大小由使用者传递过来
            p->p_head->blocks=blocks;
            p->p_head->blksz=blksz;
            //指向真正内容的起始位置,即跳过头部信息
            p->p_payload=(char*)(p->p_head+1);
            p->shmid=shmid;
            p->sem_empty=semget(key,1,IPC_CREAT|0644);
            p->sem_full=semget(key+1,1,IPC_CREAT|0644);
            p->sem_mutex=semget(key+2,1,IPC_CREAT|0644);

            //用来初始化信号量初值的联合体
            union semun su;
            semctl(p->sem_empty, 0, SETVAL, su);
            su.value=blocks;
            semctl(p->sem_full, 0, SETVAL, su);

            //将sem_mutex信号量初始值设置为1(允许一个用户进来)
            su.value=1;
            semctl(p->sem_mutex, 0, SETVAL, su);
        }
    }
    else//共享内存存在,那么只需要初始化部分数据
    {
        //存在的话,将共享内存挂载
        p->p_head=(head_t*)shmat(shmid,NULL,0);
        //打开的话不用给信号量设定初值,由于是打开,后2个参数都是0
        p->sem_empty=semget(key+2,0,0);
    }
    return p;
}

//向队列插入数据(放数据):将buf指向的内容,拷贝到wri_idx
void shmfifo_put(shmfifo_t* fifo, const void* buf)
{
    //可装的资源减1
    p(fifo->sem_full);
    //保证只有一个操作
    p(fifo->sem_mutex);
    //放数据
    memcpy(fifo->p_payload+(fifo->p_head->wr_idx*fifo->p_head->blksz), buf, fifo->p_head->blksz);
    //写下标后移
    fifo->p_head->wr_idx=(fifo->p_head->wr_idx+1)%(fifo->p_head->blocks);
    //释放资源
    v(fifo->sem_mutex);
    v(fifo->sem_empty);
}

//从队列弹出数据(取数据):将wri_idx指向的内容,拷贝到buf
void shmfifo_get(shmfifo_t* fifo, void* buf)
{
    p(fifo->sem_mutex);
    //可消费的资源减1
    p(fifo->sem_empty);
    //保证只有一个操作
    //取数据
    memcpy(buf,fifo->p_payload+(fifo->p_head->rd_idx*fifo->p_head->blksz),fifo->p_head->blksz);
    //写下标后移
    fifo->p_head->rd_idx=(fifo->p_head->rd_idx+1)%(fifo->p_head->blocks);
    //释放资源
    v(fifo->sem_full);
    v(fifo->sem_mutex);
}
//销毁共享内存
void shmfifo_destroy(shmfifo_t* fifo)
{
    //先卸载(即断开连接)
    shmdt(fifo->p_head);
    //销毁(即删除)
    shmctl(fifo->shmid, IPC_RMID, 0);
    semctl(fifo->sem_full, 0, IPC_RMID);
    semctl(fifo->sem_mutex, 0, IPC_RMID);
    semctl(fifo->sem_empty, 0, IPC_RMID);
    free(fifo);
}

(3)get.c

消费者进程

#include 
#include "shmfifo.h"

typedef struct
{
    int age;
    char name[32];
}person_t;


int main()
{
    shmfifo_t *fifo = shmfifo_init(1234,3,sizeof(person_t));
    person_t person;

    int i=0;
    for(i=0;i<10;i++)
    {
        shmfifo_get(fifo,&person);
        printf("name=%s, age=%d\n", person.name, person.age);
        sleep(1);
    }
    shmfifo_destroy(fifo);
}

(4)put.c

生产者进程

 #include "shmfifo.h"
 #include 
 
 typedef struct
 {
     int age;
     char name[32];
 }person_t;
 
 int main()
 {
 
     shmfifo_t *fifo=shmfifo_init(1234,3,sizeof(person_t));
     person_t person;
 
     int i=0;
     for(i=0;i<10;i++)
     {
         person.age=10+i;
         sprintf(person.name,"name:%d",i+1);
         shmfifo_put(fifo,&person);
         printf("put %d\n",i+1);
     }
 }

你可能感兴趣的:(Coding)