共享内存允许多个进程使用某一段存储区,数据不需要在进程之间复制,多个进程都把该内存区域映射到自己的虚拟地址空间,直接访问该共享内存区域,从而可以通过该区域进行通信,是一种速度很快IPC。
下面是共享内存映射图
一般描述使用共享内存,都是以普通缓冲区的形式访问,这里除了讲述共享内存的原理和使用方法,还会实现用队列的方式使用共享内存。
int shmget(key_t key, size_t size, int shmflg)
利用shmget函数创建共享内存
第一个参数key用于表示共享内存的标识符,函数会利用这个标识符生成一个ID,表示创建的共享内存,通常用ftok函数来产生这个key:
key_t ftok( const char * fname, int id )
fname是一个现存的文件,id是子序号(只取低8位,不能为0),ftok方法会通过fname文件所在文件系统的信息,索引节点号,以及id组合成一个ID并且返回
第二个参数表示要创建的共享内存大小
第三个参数是权限标志,一般用IPC_CREAT,若共享内存不存在则创建,如果想要其他选项,可以使用或操作加上
成功调用后shmget会返回表示共享内存的ID,接下来利用这个ID把共享内存映射到进程的地址空间
void *shmat(int shm_id, const void *shm_addr, int shmflg)
第一个参数,shm_id是由shmget函数返回的共享内存标识。
第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为0,表示让系统来选择共享内存的地址。
第三个参数,shm_flg是一组标志位,通常为0
调用成功时返回一个指向共享内存第一个字节的指针
当使用完共享内存,要进行销毁操作
int shmdt(const void *shmaddr)
这个函数只是把共享内存到本进程地址空间的映射解除,参数就是调用shmat时的返回值(指向共享内存的指针)
接着才是真正删除共享内存
int shmctl(int shm_id, int command, struct shmid_ds *buf)
第一个参数是共享内存的标识符(即shmget的返回值)
第二个参数是命令,如果要删除共享内存,一般使用IPC_RMID
第三个参数是共享内存管理结构体,删除时一般为0
实际在项目里使用共享内存,简单的缓冲区形式往往无法满足需求。
比如:要求一个进程不断往共享内存写入数据,而另一个进程从共享内存读出数据,如果一定要读进程读取数据后,写进程才能写入新的数据,在性能上无疑是一个缺陷,如果写进程不理会数据是否被读进程获取而不断地写入新数据,那么原来的数据将被覆盖。
我们很容易想到的是利用队列实现上述要求,下面将讲述如何用队列的方式使用共享内存
首先,我们要定义数据结构:
#define MAX_NODE_DATA_SIZE 65535
#define MAX_QUEUE_NODE_COUNT 127
typedef struct buffer_node_{
uint16_t dataLen;
uint8_t data[MAX_NODE_DATA_SIZE];
} buffer_node_t;
typedef struct buffer_queue_{
buffer_node_t queue[MAX_QUEUE_NODE_COUNT];
int front;
int rear;
bool Init(){
front = rear = 0;
memset(queue, sizeof(buffer_node_t)*MAX_QUEUE_NODE_COUNT, 0);
}
bool isEmpty(){
return (rear == front);
}
bool isFull(){
return ((rear+1)%MAX_QUEUE_NODE_COUNT == front);
}
bool Enqueue(buffer_node_t *node){
if(isFull()){
return false;
}
memcpy(&queue[rear], node, sizeof(buffer_node_t));
rear = (rear+1)%MAX_QUEUE_NODE_COUNT;
return true;
}
buffer_node_t* Dequeue(){
if(isEmpty()){
printf("%d\n",__LINE__);
return NULL;
}
buffer_node_t* node = &queue[front];
front = (front+1)%MAX_QUEUE_NODE_COUNT;
return node;
}
}buffer_queue_t;
接下来创建我们需要的共享内存:
buffer_manage_t *bufferManage = NULL;
int bufferID = 0;
key_t k;
bufferID = shmget(k = ftok("./tsm3", 1), sizeof(buffer_manage_t), IPC_CREAT);
bufferManage = (buffer_manage_t*)shmat(bufferID, 0, 0);
如果上面创建共享内存这一步成功,基本也就没有大问题了,其实所谓用队列的方式使用共享内存,说白了就是把共享内存转换为我们自己定义的数据结构,这个和malloc其实很像,当利用malloc申请内存时,返回的是void*类型指针,程序员根据需要可以把指针转换成自己需要使用的类型。
我们把申请的共享内存首地址赋值给bufferManage 后,就可以利用结构体里的方法操作队列,这就是把队列的操作方法定义在结构体里的原因了
理解了这点之后,就可以很容易地用队列的方式使用共享内存
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define MAX_NODE_DATA_SIZE 65535
#define MAX_QUEUE_NODE_COUNT 32
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef struct buffer_node_{
uint16_t dataLen;
uint8_t data[MAX_NODE_DATA_SIZE];
} buffer_node_t;
typedef struct buffer_queue_{
buffer_node_t queue[MAX_QUEUE_NODE_COUNT];
int front;
int rear;
bool Init(){
front = rear = 0;
memset(queue, sizeof(buffer_node_t)*MAX_QUEUE_NODE_COUNT, 0);
}
bool isEmpty(){
return (rear == front);
}
bool isFull(){
return ((rear+1)%MAX_QUEUE_NODE_COUNT == front);
}
bool Enqueue(buffer_node_t *node){
if(isFull()){
return false;
}
memcpy(&queue[rear], node, sizeof(buffer_node_t));
rear = (rear+1)%MAX_QUEUE_NODE_COUNT;
return true;
}
buffer_node_t* Dequeue(){
if(isEmpty()){
return NULL;
}
buffer_node_t* node = &queue[front];
front = (front+1)%MAX_QUEUE_NODE_COUNT;
return node;
}
}buffer_queue_t;
typedef struct buffer_manage_{
buffer_queue_t sendQueue;
buffer_queue_t receiveQueue;
void buffer_init_(){
sendQueue.Init();
receiveQueue.Init();
}
} buffer_manage_t;
buffer_manage_t *bufferManage = NULL;
int bufferID = 0;
bool Create_ShareMem(){
key_t k;
printf("size is %d\n",sizeof(buffer_manage_t));
bufferID = shmget(k = ftok("./tsm3", 1), sizeof(buffer_manage_t), IPC_CREAT);
printf("k is %d\n",k);
if(bufferID == -1){
printf("Create_ShareMem shmget failed errno = %d, error msg is %s\n", errno,strerror(errno));
}
printf("Create_ShareMem shmget bufferID = %d, error = %d\n", bufferID, errno);
bufferManage = (buffer_manage_t*)shmat(bufferID, 0, 0);
printf("Create_ShareMem shmget bufferManage = %p, error = %d\n", bufferManage, errno);
if(bufferManage == (void *) -1 || bufferManage == NULL){
bufferManage = NULL;
printf("Create_ShareMem shmat failed errno = %d", errno);
return false;
}
bufferManage->buffer_init_();
return true;
}
bool Delete_ShareMem(){
if((shmdt(bufferManage) != 0)){
printf("Delete_ShareMem shmdt failed errno = %d", errno);
}
if(shmctl(bufferID, IPC_RMID, 0) != 0){
printf("Delete_ShareMem shmctl failed errno = %d", errno);
return false;
}
return true;
}
bool ShareMem_AddData(uint8_t* data, uint16_t dataLen)
{
bool ret = false;
buffer_node_t* node;
node = (buffer_node_t*)malloc(sizeof(buffer_node_t));
memset(node, 0, sizeof(buffer_node_t));
node->dataLen = dataLen;
if(node->dataLen > MAX_NODE_DATA_SIZE)
node->dataLen = MAX_NODE_DATA_SIZE;
if(data != NULL){
memcpy(node->data, data, node->dataLen);
}
if(bufferManage != NULL){
bufferManage->receiveQueue.Enqueue(node);
ret = true;
}
return ret;
}
bool ShareMem_RemoveData(uint8_t** data, uint16_t* dataLen)
{
bool ret = false;
if(bufferManage != NULL){
buffer_node_t* node = NULL;
//bufferManage->sendQueueProtected.Lock();
node = bufferManage->sendQueue.Dequeue();
if(node != NULL){
*data = (uint8_t*)malloc(node->dataLen);
if(*data != NULL){
memcpy(*data, node->data, node->dataLen);
*dataLen = node->dataLen;
ret = true;
}
}
//bufferManage->sendQueueProtected.Unlock();
}
return ret;
}
void printData()
{
for(int i = bufferManage->receiveQueue.front; i < bufferManage->receiveQueue.rear; i++)
{
printf("idx is %d,data is %s\n",i,bufferManage->receiveQueue.queue[i].data);
}
}
int main()
{
Create_ShareMem();
while(1){
sleep(2);
uint8_t fromT1[10] = "123456789";
ShareMem_AddData(1, fromT1, 10);
printData();
sleep(1);
//ShareMem_RemoveData();
}
Delete_ShareMem();
return 0;
}
另一个读进程的代码差不多,只要改一下main函数即可