以队列的形式使用共享内存

共享内存允许多个进程使用某一段存储区,数据不需要在进程之间复制,多个进程都把该内存区域映射到自己的虚拟地址空间,直接访问该共享内存区域,从而可以通过该区域进行通信,是一种速度很快IPC。

下面是共享内存映射图

以队列的形式使用共享内存_第1张图片

一般描述使用共享内存,都是以普通缓冲区的形式访问,这里除了讲述共享内存的原理和使用方法,还会实现用队列的方式使用共享内存。

创建共享内存

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函数即可

你可能感兴趣的:(Linux编程,数据结构,IPC)