關於System V共享內存和信號量的知識已經學過了,這次主要是綜合這兩者來解決一個問題達到靈活運用的目的,下面開始:
生產者消費者問題:
關於什麼是“生產者消費者”,應該都比較清楚,這裏還是先貼上百度百科對它的描述:
下面用圖來說明一下該問題:
以上就是生產者消費者從邏輯上的一個解決方案,從中可以看到這是互斥跟同步相結合的例子,下面則用所畫的這些模型來實現一下shmfifo
實現shmfifo:
爲什麼要實現共享內存的先進先出的緩衝區(shmfifo)呢?實際上要實現進程間通信可以直接用消息隊列來實現先進先出的隊列,但是,由於消息隊列還實現了其它的功能,如果僅僅只是想要先進先出這樣的一個功能的話,能使用共享內存來實現的話,效率會更高,因爲對共享內存的訪問不涉及到對內核的操作,這個之前也有講過,因此就有必要實現一個shmfifo。
要實現這樣的一個緩衝區,我們可以做一些假定,假定放到緩衝區當中的數據塊是定長的,並且可以有多個進程往緩衝區中寫入數據,也有多個進程往緩衝區中讀取數據,所以這是典型的生產者消費者問題,這塊緩衝區剛纔說過可以用共享內存的方式來實現,但是有一個問題需要思考:生產者進程當前應該在什麼位置添加產品,消費者進程又從什麼位置消費產品呢?所以說還需要維護這些狀態,所以很自然地就能想到將這些狀態保存在共享內存當中,如下:
由於多個生產者都能往裏面添加產品,多個消費者也能夠從裏面消費產品,那生產者在生產產品的時候應該放在什麼位置呢?消費者又該從哪裏消費產品呢?下面來說明下:
而這時再次生產就會是在0的位置上開始了:
可見這是一個環形緩衝區,可以重複利用的,基於這些分析下面來看一下所定義出來的數據結構:
有了這些數據結構實際上就能夠實現了shmfifo了,下面實現一下:
由於用到了信號量,所以將之前的信號量相關的函數及定義放到一個單獨的文件當中,裏面代碼都是之前學過的,就不多解釋
ipc.h:
#ifndef _IPC_H_
#define _IPC_H_
#include <sys/types.h> #include <unistd.h> #include <sys/ipc.h> #include <sys/sem.h> #include <sys/shm.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h>
#define ERR_EXIT(m) \
do\
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
union semun {
int val; /*value for SETVAL */
struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */ unsigned short *array; /*array for GETALL, SETALL */
/* Linux specific part: */
struct seminfo *__buf; /*buffer for IPC_INFO */ };
int sem_create(key_t key);
int sem_open(key_t key);
int sem_p(int semid);
int sem_v(int semid);
int sem_d(int semid);
int sem_setval(int semid, int val);
int sem_getval(int semid);
int sem_getmode(int semid);
int sem_setmode(int semid,char* mode);
#endif /* _IPC_H_ */
以上文件是爲了實現shmfifo提供輔助功能的,下面則開始實現它,分頭文件及具體實現:
shmfifo.h:
#ifndef _SHM_FIFO_H_
#define _SHM_FIFO_H_
#include "ipc.h"
typedef structshmfifo shmfifo_t;
typedef structshmhead shmhead_t;
structshmhead
{
unsigned int blksize; //塊大小
unsigned int blocks; // 總塊數
unsigned int rd_index; //讀索引
unsigned int wr_index; //寫索引
};
structshmfifo
{
shmhead_t *p_shm; // 共享內存頭部指針
char *p_payload; //有效負載的起始地址
int shmid; // 共享內存ID
int sem_mutex; // 用來互斥用的信號量
int sem_full; //用來控制共享內存是否滿的信號量
int sem_empty; //用來控制共享內存是否空的信號量
};
shmfifo_t* shmfifo_init(int key, int blksize, int blocks);//初始化
void shmfifo_put(shmfifo_t *fifo, const void *buf);//添加數據到環形緩衝區
void shmfifo_get(shmfifo_t *fifo, void *buf);//從緩衝區中取數據
void shmfifo_destroy(shmfifo_t *fifo);//釋放共享內存的環形緩衝區
#endif /* _SHM_FIFO_H_ */
下面來具體解釋一下:
下面來具體實現一下些這函數:
這個方法既可以創建共享內存信號量,也可以打開共享內存信號量,所以下面可以做一個判斷:
接下來還得初始化共享內存中的其它字段:
接下來對其信號量集中的信號進行初始化:
shmfifo的初始化函數就已經寫完了,接下來來實現第二個函數:shmfifo_put(生產產品),對於生產者的過程,上面也說明過,則嚴格按照該步驟來進行實現:
下面則開始實現,首先先按照流程把代碼框架寫出來:
那如何生產產品呢?先來看下圖:
首先進行數據偏移:
在生產一個產品之後,下一次要生產的位置則要發生改變,所以:
這樣生產產品的函數實現就如上,類似的,消費產品實現就容易了,依照這個流程:
具體實現如下:
接下來實現最後一個函數,就是資源釋放:
好了,shmfifo.c的所有函數已經實現,先貼出代碼:
shmfifo.c:
#include "shmfifo.h" #include <assert.h>
shmfifo_t* shmfifo_init(int key, int blksize, intblocks)
{
//分配內存空間
shmfifo_t *fifo = (shmfifo_t *)malloc(sizeof(shmfifo_t));
assert(fifo != NULL);
memset(fifo, 0, sizeof(shmfifo_t));
intshmid;
shmid = shmget(key, 0, 0);
int size = sizeof(shmhead_t) + blksize*blocks;
if (shmid == -1)
{//創建共享內存
fifo->shmid = shmget(key, size, IPC_CREAT | 0666);
if (fifo->shmid == -1)
ERR_EXIT("shmget");
fifo->p_shm = (shmhead_t*)shmat(fifo->shmid, NULL, 0);
if (fifo->p_shm == (shmhead_t*)-1)
ERR_EXIT("shmat");
fifo->p_payload = (char*)(fifo->p_shm + 1);
fifo->p_sem->blksize = blksize;
fifo->p_shm->blocks = blocks;
fifo->p_shm->rd_index = 0;
fifo->p_shm->wr_index = 0;
fifo->sem_mutex = sem_create(key);
fifo->sem_full = sem_create(key+1);
fifo->sem_empty = sem_create(key+2);
sem_setval(fifo->sem_mutex, 1);
sem_setval(fifo->sem_full, blocks);
sem_setval(fifo->sem_empty, 0);
}
else {//打開共享內存
fifo->shmid = shmid;
fifo->p_shm = (shmhead_t*)shmat(fifo->shmid, NULL, 0);
if (fifo->p_shm == (shmhead_t*)-1)
ERR_EXIT("shmat");
fifo->p_payload = (char*)(fifo->p_shm + 1);
fifo->sem_mutex = sem_open(key);
fifo->sem_full = sem_open(key+1);
fifo->sem_empty = sem_open(key+2);
}
returnfifo;
}
void shmfifo_put(shmfifo_t *fifo, const void *buf)
{
sem_p(fifo->sem_full);
sem_p(fifo->sem_mutex);
//生產產品
memcpy(fifo->p_payload+fifo->p_shm->blksize*fifo->p_shm->wr_index,
buf, fifo->p_shm->blksize);
fifo->p_shm->wr_index = (fifo->p_shm->wr_index + 1) % fifo->p_shm->blocks;
sem_v(fifo->sem_mutex);
sem_v(fifo->sem_empty);
}
void shmfifo_get(shmfifo_t *fifo, void *buf)
{
sem_p(fifo->sem_empty);
sem_p(fifo->sem_mutex);
memcpy(buf, fifo->p_payload+fifo->p_shm->blksize*fifo->p_shm->rd_index,
fifo->p_shm->blksize);
fifo->p_shm->rd_index = (fifo->p_shm->rd_index + 1) % fifo->p_shm->blocks;
sem_v(fifo->sem_mutex);
sem_v(fifo->sem_full);
}
void shmfifo_destroy(shmfifo_t *fifo)
{
//刪除創建的信息量集
sem_d(fifo->sem_mutex);
sem_d(fifo->sem_full);
sem_d(fifo->sem_empty);
//刪除共享內存
shmdt(fifo->p_shm);//刪除共享內存頭部
shmctl(fifo->shmid, IPC_RMID, 0);//刪除整個共享內存
//釋放fifo的內存
free(fifo);
}
下面則寫兩個測試程序,分別用來生產、消費產品: