一、前言
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的差异性。
总是有很多小伙伴反馈说:OpenHarmony开发不知道学习哪些技术?不知道需要重点掌握哪些OpenHarmony开发知识点? 为了解决大家这些学习烦恼。在这准备了一份很实用的鸿蒙全栈开发学习路线与学习文档给大家用来跟着学习。
针对一些列因素,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植……等)技术知识点。
OpenHarmony 开发环境搭建:https://gitcode.com/HarmonyOS_MN/733GH/overview
《OpenHarmony源码解析》
搭建开发环境
系统架构分析
- 构建子系统
- 启动流程
- 子系统
- 分布式任务调度子系统
- 分布式通信子系统
- 驱动子系统
- ……