內存屏障: 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);
}
}