OpenHarmony POSIX和CMSIS接口适配层解读(1):queue_adapter

100 篇文章 1 订阅
100 篇文章 0 订阅

往期知识点记录:

一、前言

POSIX和CMSIS接口适配层,用于协调M核和A核API之间的差异。本文针对queue_adapter部分进行解读。

二、头文件分析

在samgr\adapter\queue_adapter.h中对队列的操作进行声明。

MQueueId QUEUE_Create(const char *name, int size, int count);//创建队列
int QUEUE_Put(MQueueId queueId, const void *element, uint8 pri, int timeout);//入队列
int QUEUE_Pop(MQueueId queueId, void *element, uint8 *pri, int timeout);//出队列
int QUEUE_Destroy(MQueueId queueId);//销毁队列

在samgr\adapter\posix\lock_free_queue.h中对无锁队列的操作进行声明。

typedef struct LockFreeQueue LockFreeQueue;
struct LockFreeQueue {
    uint32 write;//入队消息的位置
    uint32 read;//出队消息的位置
    uint32 itemSize;//元素大小
    uint32 totalSize;//总字节大小
    uint8 buffer[0];
};
LockFreeQueue *LFQUE_Create(int size, int count);//创建队列
BOOL LFQUE_IsEmpty(LockFreeQueue *queue);//判断队列中消息是否为空
BOOL LFQUE_IsFull(LockFreeQueue *queue);//判断队列是否已满
int LFQUE_Push(LockFreeQueue *queue, const void *element, uint8 pri);//入队操作
int LFQUE_Pop(LockFreeQueue *queue, void *element, uint8 *pri);//出队操作

三、函数实现分析

- CMSIS的队列操作

在samgr\adapter\cmsis\queue_adapter.c中实现CMSIS的队列操作。

队列的创建操作

MQueueId QUEUE_Create(const char *name, int size, int count)
{
    osMessageQueueAttr_t queueAttr = {name, 0, NULL, 0, NULL, 0};//配置队列属性
    /*
        函数功能:创建队列
        函数参数:@param1:队列中消息的最大数量
                @param2:元素所占最大字节数
                @param3:队列属性
        函数返回:成功返回对象指针,失败返回NULL   
    */
    return (MQueueId)osMessageQueueNew(count, size, &queueAttr);
}

队列的入队操作

int QUEUE_Put(MQueueId queueId, const void *element, uint8 pri, int timeout)
{
    //当超时设置为 0 时,函数立即返回
    uint32_t waitTime = (timeout <= 0) ? 0 : (uint32_t)timeout;
    /*
        函数功能:入队操作
        函数参数:@param1:队列ID
                @param2:指向消息缓冲区的指针
                @param3:消息优先级
                @param4:超时值
        函数描述:将 element 指向的消息放入由参数 queueId 指定的消息队列中。
                参数 pri 用于根据插入的优先级对消息进行排序(较高的数字表示较高的优先级)
                waitTime指定系统等待将消息放入队列的时间。
    */
    osStatus_t ret = osMessageQueuePut(queueId, element, pri, waitTime);
    if (ret != osOK)
    {
        return EC_BUSBUSY;
    }
    return EC_SUCCESS;
}

队列的出队操作

int QUEUE_Pop(MQueueId queueId, void *element, uint8 *pri, int timeout)
{
    //当超时设置为 osWaitForever 时,该函数将等待无限时间,直到检索到消息
    uint32_t waitTime = (timeout <= 0) ? osWaitForever : (uint32_t)timeout;
    /*
        函数功能:出队操作
        函数参数:@param1:队列ID
                @param2:指向接收缓冲区的指针
                @param3:消息优先级
                @param4:超时值
        函数描述:从 queueId 指定的消息队列中检索消息,并将其保存到 element 所指向的缓冲区中。
                消息优先级存储到参数 pri 
                waitTime指定系统等待从队列中检索消息的时间。
    */
    osStatus_t evt = osMessageQueueGet(queueId, element, pri, waitTime);
    if (evt != osOK)
    {
        return EC_BUSBUSY;
    }
    return EC_SUCCESS;
}

队列的销毁操作

int QUEUE_Destroy(MQueueId queueId)
{
    //函数删除由参数 queueId 指定的消息队列对象,释放内存。
    osStatus_t evt = osMessageQueueDelete(queueId);
    if (evt != osOK)
    {
        return EC_FAILURE;
    }
    return EC_SUCCESS;
}

- POSIX的队列操作

在samgr\adapter\posix\queue_adapter.c中实现POSIX的队列操作。

队列的控制结构体

typedef struct LockFreeBlockQueue LockFreeBlockQueue;
struct LockFreeBlockQueue {
    pthread_mutex_t wMutex;//用于控制消息入队
    pthread_mutex_t rMutex;//用于控制消息出队
    pthread_cond_t cond;//条件变量
    LockFreeQueue *queue;
};

队列的创建操作

/*
    函数功能:创建队列
    函数参数:@name:队列名
             @size:元素所占最大字节数
             @count:队列中消息的最大数量
    函数返回:队列id,本质上是队列在内存中的首地址
    函数描述:申请队列内存并初始化相关的锁
*/
MQueueId QUEUE_Create(const char *name, int size, int count)
{
    //通过malloc申请一个无锁队列块,保障队列的线程安全。内部包含一个指向队列的指针
    LockFreeBlockQueue *queue = (LockFreeBlockQueue *)SAMGR_Malloc(sizeof(LockFreeBlockQueue));
    if (queue == NULL) {
        return NULL;
    }
    //创建队列
    queue->queue = LFQUE_Create(size, count);
    if (queue->queue == NULL) {
        SAMGR_Free(queue);
        return NULL;
    }
    //创建写锁
    pthread_mutex_init(&queue->wMutex, NULL);
    //创建读锁
    pthread_mutex_init(&queue->rMutex, NULL);
    //创建条件变量
    pthread_cond_init(&queue->cond, NULL);
    return (MQueueId)queue;
}

队列的入队操作

/*
    函数功能:入队操作
    函数参数:@queueId:队列ID
            @element:指向消息缓冲区
            @pri:消息优先级
            @timeout:超时值
    函数返回:EC_SUCCESS or EC_BUSBUSY or EC_INVALID
    函数描述:在把消息入队前加锁,保证互斥
            消息入队后wMutex解锁,再通过rMutex加锁后,发送消息入队信号
*/
int QUEUE_Put(MQueueId queueId, const void *element, uint8 pri, int timeout)
{
    //判断参数是否有效
    if (queueId == NULL || element == NULL || timeout > 0) {
        return EC_INVALID;
    }
    LockFreeBlockQueue *queue = (LockFreeBlockQueue *)queueId;
    //写入前先加锁
    pthread_mutex_lock(&queue->wMutex);
    //向队列写入消息
    int ret = LFQUE_Push(queue->queue, element, pri);
    //解锁
    pthread_mutex_unlock(&queue->wMutex);
    //抢占读锁
    pthread_mutex_lock(&queue->rMutex);
    //发送队列有新元素加入的信号
    pthread_cond_signal(&queue->cond);
    pthread_mutex_unlock(&queue->rMutex);
    return ret;
}

队列的出队操作

/*
    函数功能:出队操作
    函数参数:@queueId:队列ID
            @element:指向接收消息的缓冲区
            @pri:用于保存消息优先级信息
            @timeout:超时值
    函数返回:EC_SUCCESS or EC_INVALID
    函数描述:从队列中读取消息,若读取失败则条件变量阻塞,等待入队信号通知
*/
int QUEUE_Pop(MQueueId queueId, void *element, uint8 *pri, int timeout)
{
    //判断参数是否有效
    if (queueId == NULL || element == NULL || timeout > 0) {
        return EC_INVALID;
    }
    LockFreeBlockQueue *queue = (LockFreeBlockQueue *)queueId;
    //从队列中读取消息
    if (LFQUE_Pop(queue->queue, element, pri) == EC_SUCCESS) {
        return EC_SUCCESS;
    }
    //EC_FAILURE,读取失败,阻塞 等待消息入队信号通知
    pthread_mutex_lock(&queue->rMutex);
    while (LFQUE_Pop(queue->queue, element, pri) != EC_SUCCESS) {
        pthread_cond_wait(&queue->cond, &queue->rMutex);//阻塞
    }
    pthread_mutex_unlock(&queue->rMutex);
    return EC_SUCCESS;
}

队列的销毁操作

/*
    函数功能:释放队列资源
    函数参数:@queueId:队列ID
    函数返回:EC_SUCCESS or EC_INVALID
*/
int QUEUE_Destroy(MQueueId queueId)
{
    if (queueId == NULL) {
        return EC_INVALID;
    }
    //获取队列
    LockFreeBlockQueue *queue = (LockFreeBlockQueue *)queueId;
    //释放写锁
    pthread_mutex_destroy(&queue->wMutex);
    //释放读锁
    pthread_mutex_destroy(&queue->rMutex);
    //释放条件变量
    pthread_cond_destroy(&queue->cond);
    //释放队列占用的内存
    SAMGR_Free(queue->queue);
    SAMGR_Free(queue);
    return EC_SUCCESS;
}

在samgr\adapter\posix\lock_free_queue.c中实现POSIX的无锁队列操作。

无锁队列的创建

/*
    函数功能:创建队列
    函数参数:@size:元素所占最大字节数
            @count:队列中消息的最大数量
    函数返回:NULL or LockFreeQueue指针
    函数描述:申请队列,队列由队头LockFreeQueue + 数据段组成
            队头记录队列读位、写位、元素大小、总字节
            total = size * count + 1
    ________________________________________________________
    |write|read|itemsize|totalsize|buffer[0]|     buffer    |
    |  4  |  4 |   4    |   4     |   1     |  size*count+1 |
    —————————————————————————————————————————————————————————
*/
LockFreeQueue *LFQUE_Create(int size, int count)
{
    //参数检查
    if (size <= 0 || count <= 0) {
        return NULL;
    }
    //缓冲区字节数
    int total = size * count + 1;
    if (total <= 0) {
        return NULL;
    }
    //malloc申请大小为sizeof(LockFreeQueue) + total的空间,转换为LockFreeQueue类型
    register LockFreeQueue *queue = (LockFreeQueue *)SAMGR_Malloc(sizeof(LockFreeQueue) + total);
    if (queue == NULL) {
        return NULL;
    }
    queue->write = 0;//初始化写位
    queue->read = 0;//初始化读位
    queue->itemSize = size;//单个消息占用的空间大小
    queue->totalSize = total;//总字节数
    return queue;
}

无锁队列的判满操作

/*
    函数功能:判断队列是否已满
    函数参数:@queue:队列指针
    函数返回:true or false
    函数描述:判断写位+1是否等于读位。相等返回true,否则返回false
*/
BOOL LFQUE_IsFull(LockFreeQueue *queue)
{
    //获取新消息写入的位置
    uint32 nextWrite = queue->write + 1;
    if (nextWrite >= queue->totalSize) {
        //若达到队列尾部,则跳转到队列头部
        nextWrite = 0;
    }
    //判断写入的位置与读位是否冲突
    return (nextWrite == queue->read);
}

无锁队列的判空操作

/*
    函数功能:判断队列中消息是否为空
    函数参数:@queue:队列指针
    函数返回:true or false
    详细描述:根据写位是否等于读位来判断队列是否已满。相等返回true,否则返回false
*/
BOOL LFQUE_IsEmpty(LockFreeQueue *queue)
{
    //队列的写位与读位的比较
    return (queue->write == queue->read);
}

无锁队列的入队操作

/*
    函数功能:入队操作
    函数参数:@queue:队列指针
            @element:指向入队消息缓冲区
            @pri:消息优先级
    函数返回:EC_INVALID or EC_BUSBUSY or EC_SUCCESS
    函数描述:
            1.判断参数是否有效,若队列未满,则计算队列尾部到写位的空闲空间。
            2.若空闲空间大于最大元素大小,则直接写入;
            3.若空闲空间小于最大元素大小,则分开写入,前部分写入队尾,后部分从队起始开始写。
            4.更新写位
*/
int LFQUE_Push(LockFreeQueue *queue, const void *element, uint8 pri)
{
    (void)pri;
    //判断参数是否有效
    if (queue == NULL || element == NULL) {
        return EC_INVALID;
    }
    //判断队列是否已满
    if (LFQUE_IsFull(queue)) {
        return EC_BUSBUSY;
    }
    //计算队列尾部与写位的差值,若差值小于元素大小,则分段写入,一部分写在队尾,一部分写到队首。若差值大于元素大小,则直接写入。
    uint32 copyLen = (queue->totalSize - queue->write < queue->itemSize) ?
                  (queue->totalSize - queue->write) : queue->itemSize;
    //拷贝copylen个字节的数据到队列中
    if (memcpy_s(&queue->buffer[queue->write], copyLen, element, copyLen) != EOK) {
        return EC_INVALID;
    }
    //元素的前copyLen个字节的数据已入队,所以将元素指针滑动copyLen个字节,指向待入队的数据段
    element += copyLen;
    //计算待入队的剩余字节数目
    copyLen = queue->itemSize - copyLen;
    //若写入的位置已到队列尾部,则从起始位继续写
    if (copyLen > 0) {
        //拷贝入队
        if (memcpy_s(queue->buffer, copyLen, element, copyLen) != EOK) {
            return EC_INVALID;
        }
    }
    //元素入队后,更新写位
    uint32 write = queue->write + queue->itemSize;
    if (write >= queue->totalSize) {
        write = write - queue->totalSize;
    }
    queue->write = write;
    return EC_SUCCESS;
}

无锁队列的出队操作

/*
    函数功能:出队操作
    函数参数:@queue:队列指针
            @element:指向消息出队的缓冲区
            @pri:记录消息优先级
    函数返回:EC_INVALID or EC_FAILURE or EC_SUCCESS
    函数描述:
            1.判断参数是否有效,若队列中消息不为空,则计算队列尾部到读位的空间。
            2.若空间大于最大元素大小,则直接读取;
            3.若空间小于最大元素大小,则分开读取,前部分从读位读到队尾,后部分从队起始继续读。
            4.更新读位   
*/
int LFQUE_Pop(LockFreeQueue *queue, void *element, uint8 *pri)
{
    (void)pri;
    //判断参数是否有效
    if (queue == NULL || element == NULL) {
        return EC_INVALID;
    }
    //判断队列中消息是否为空
    if (LFQUE_IsEmpty(queue)) {
        return EC_FAILURE;
    }
    //计算队尾与读位的差值,获取拷贝的字节长度
    uint32 copyLen = (queue->totalSize - queue->read < queue->itemSize) ?
                  (queue->totalSize - queue->read) : queue->itemSize;
    //拷贝
    if (memcpy_s(element, copyLen, &queue->buffer[queue->read], copyLen) != EOK) {
        return EC_FAILURE;
    }
    //若读取的位置已到队列尾部,则从起始位继续读
    element += copyLen;
    copyLen = queue->itemSize - copyLen;
    if (copyLen > 0) {
        if (memcpy_s(element, copyLen, queue->buffer, copyLen) != EOK) {
            return EC_FAILURE;
        }
    }
    //更新读位
    uint32 read = queue->read + queue->itemSize;
    if (read >= queue->totalSize) {
        read = read - queue->totalSize;
    }
    queue->read = read;
    return EC_SUCCESS;
}

四、总结

统一队列操作的接口,如QUEUE_Create、QUEUE_Put、QUEUE_Pop、QUEUE_Destroy,屏蔽了底层POSIX和CMSIS的差异性。

写在最后

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙

  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请看下图提示:
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值