共享內存環形隊列shm_ring_queue(內存屏障)

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

讀內核代碼進一步學習 Memory Barrier 的使用。
Linux 內核實現的無鎖(只有一個讀線程和一個寫線程時)環形緩衝區 kfifo 就使用到了 Memory Barrier



共享內存環形隊列的功能介紹:
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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <assert.h>
#include <sys/ipc.h>
#include <unistd.h>
//頭信息結構體
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 <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <string.h>
#include <assert.h>

//用來初始化信號量的初值的聯合體
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 <stdio.h>
#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 <stdio.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++)
     {
         person.age=10+i;
         sprintf(person.name,"name:%d",i+1);
         shmfifo_put(fifo,&person);
         printf("put %d\n",i+1);
     }
 }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章