 最近有人在Github上开源了鸿蒙OS(https://www.github.com/Awesome-HarmonyOS)并且累计获得了一万多颗Star。从华为的官方宣传中就提到了“安卓总代码超过一亿行,其中内核代码超过2000万行,实际用到的不过8%,如此庞大和冗余的这种设计,实际上很难保证流畅度,使用效率很低。” 而笔者之前介绍过的TDengine(https://github.com/taosdata/TDengine)做为一个数据库项目更是仅用1.5M安装包就能搞定,代码效率高的惊人。所以从这方面我们也能看出优秀的项目对于速度的要求都是极致的。不过这两个项目开源后都引发了一些争议,比如鸿蒙开源当天就有人发微博说华为只是做了个安卓的定制版,质量甚至还不如MIUI,笔者的这位创造Github冠军项目的老男人,堪称10倍程序员本尊发布后,也有人在评论说TDengine的consumer-productor实现无法通过code review。但是仔细阅读这些评论可以发现,这些批评其实都不是基于代码的。笔者做为一名程序员奉行“Talk is cheap,show me the code'的理念,所以我利用周末时间阅读了这两个项目的代码,发现了很多值得学习的设计亮点。尤其是鸿蒙OS做为操作系统项目而Tdengine做为数据库项目,比较他们两者在同一模块上的设计异同,非常有收获,下面给各位读者分享一下,如有意见欢迎留言。与一般操作系统一样,鸿蒙也将任务状态通常分为以下三种:就绪(Ready): 该任务在就绪列表中,只等待CPU。 其代码位置在los_task.c,以任务恢复函数LOS_TaskResume为例,其代码如下:LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskResume(UINT32 uwTaskID) { UINTPTR uvIntSave; LOS_TASK_CB *pstTaskCB; UINT16 usTempStatus; UINT32 uwErrRet = OS_ERROR;
if (uwTaskID > LOSCFG_BASE_CORE_TSK_LIMIT) { return LOS_ERRNO_TSK_ID_INVALID; }
pstTaskCB = OS_TCB_FROM_TID(uwTaskID); uvIntSave = LOS_IntLock(); usTempStatus = pstTaskCB->usTaskStatus;
if (OS_TASK_STATUS_UNUSED & usTempStatus) { uwErrRet = LOS_ERRNO_TSK_NOT_CREATED; OS_GOTO_ERREND(); } else if (!(OS_TASK_STATUS_SUSPEND & usTempStatus)) { uwErrRet = LOS_ERRNO_TSK_NOT_SUSPENDED; OS_GOTO_ERREND(); } //以上为任务状态检查 pstTaskCB->usTaskStatus &= (~OS_TASK_STATUS_SUSPEND);//清除任务的suspend标志位置 if (!(OS_CHECK_TASK_BLOCK & pstTaskCB->usTaskStatus) )//若任务的还自在阻塞状态则变为就绪状态 ,并调用 LOS_Schedule()进行调度 { pstTaskCB->usTaskStatus |= OS_TASK_STATUS_READY; LOS_PriqueueEnqueue(&pstTaskCB->stPendList, pstTaskCB->usPriority); if (g_bTaskScheduled) { (VOID)LOS_IntRestore(uvIntSave); LOS_Schedule(); return LOS_OK; } g_stLosTask.pstNewTask = LOS_DL_LIST_ENTRY(LOS_PriqueueTop(), LOS_TASK_CB, stPendList); /*lint !e413*/ }
(VOID)LOS_IntRestore(uvIntSave); return LOS_OK;
LOS_ERREND: (VOID)LOS_IntRestore(uvIntSave); return uwErrRet; }
我们知道完整的LINUX内核是支持将任务指定在某个CPU上运行的,不过鸿蒙OS做为一个微内核的移动操作系统没有继承这些复杂的功能,直接做了减法,实现一个最简模型。而对比TDengine的调度模块tsched.c,可以看到TDengine更是放弃了任务优先级调度功能,因为做为时序数据库其数据全是按照生成时间排序处理入库的,所以他的只将任务调度模块,仅实现了以下四个功能从其循环处理任务的函数(taosProcessSchedQueue),可以看出它只是队尾不断取出任务进行循环处理,而没有优化级调整排序的过程。void *taosProcessSchedQueue(void *param) { SSchedMsg msg; SSchedQueue *pSched = (SSchedQueue *)param;
while (1) { if (sem_wait(&pSched->fullSem) != 0) { pError('wait %s fullSem failed, errno:%d, reason:%s', pSched->label, errno, strerror(errno)); if (errno == EINTR) { /* sem_wait is interrupted by interrupt, ignore and continue */ continue; } }
if (pthread_mutex_lock(&pSched->queueMutex) != 0) pError('lock %s queueMutex failed, reason:%s', pSched->label, strerror(errno));
msg = pSched->queue[pSched->fullSlot]; memset(pSched->queue pSched->fullSlot, 0, sizeof(SSchedMsg)); pSched->fullSlot = (pSched->fullSlot 1) % pSched->queueSize;//从队尾取出消息不断处理
if (pthread_mutex_unlock(&pSched->queueMutex) != 0) pError('unlock %s queueMutex failed, reason:%s ', pSched->label, strerror(errno));
if (sem_post(&pSched->emptySem) != 0) pError('post %s emptySem failed, reason:%s ', pSched->label, strerror(errno));
if (msg.fp) (*(msg.fp))(&msg); else if (msg.tfp) (*(msg.tfp))(msg.ahandle, msg.thandle); } }
int taosScheduleTask(void *qhandle, SSchedMsg *pMsg) { SSchedQueue *pSched = (SSchedQueue *)qhandle; if (pSched == NULL) { pError('sched is not ready, msg:%p is dropped', pMsg); return 0; }
if (sem_wait(&pSched->emptySem) != 0) pError('wait %s emptySem failed, reason:%s', pSched->label, strerror(errno));
if (pthread_mutex_lock(&pSched->queueMutex) != 0) pError('lock %s queueMutex failed, reason:%s', pSched->label, strerror(errno));
pSched->queue[pSched->emptySlot] = *pMsg; pSched->emptySlot = (pSched->emptySlot 1) % pSched->queueSize;
if (pthread_mutex_unlock(&pSched->queueMutex) != 0) pError('unlock %s queueMutex failed, reason:%s', pSched->label, strerror(errno));
if (sem_post(&pSched->fullSem) != 0) pError('post %s fullSem failed, reason:%s', pSched->label, strerror(errno));
return 0; }
软件定时器,是基于系统Tick时钟中断且由软件来模拟的定时器,当经过设定的Tick时钟计数值后会触发用户定义的回调函数。定时精度与系统Tick时钟的周期有关。 硬件定时器受硬件的限制,数量上不足以满足用户的实际需求,因此为了满足用户需求,提供更多的定时器,Huawei LiteOS操作系统提供软件定时器功能。软件定时器扩展了定时器的数量,允许创建更多的定时业务。如果官方文档的说明没看懂,可以直接查阅其源代码,具体位置在los_swtmr.cLITE_OS_SEC_TEXT VOID osSwTmrTask(VOID) { SWTMR_HANDLER_ITEM_P pstSwtmrHandle = (SWTMR_HANDLER_ITEM_P)NULL; SWTMR_HANDLER_ITEM_S stSwtmrHandle; UINT32 uwRet;
for ( ; ; ) { uwRet = LOS_QueueRead(m_uwSwTmrHandlerQueue, &pstSwtmrHandle, sizeof(SWTMR_HANDLER_ITEM_P), LOS_WAIT_FOREVER); if (uwRet == LOS_OK) { if (pstSwtmrHandle != NULL) { stSwtmrHandle.pfnHandler = pstSwtmrHandle->pfnHandler; stSwtmrHandle.uwArg = pstSwtmrHandle->uwArg; (VOID)LOS_MemboxFree(m_aucSwTmrHandlerPool, pstSwtmrHandle); if (stSwtmrHandle.pfnHandler != NULL) { stSwtmrHandle.pfnHandler(stSwtmrHandle.uwArg); } } } }//end of for }
以上函数的运行原理动画解析如下: 其实Tdengine的timer我之前已经做过解读了,200行代码为大家解读这个Github冠军项目背后的定时器。就不加赘述了,这里把鸿蒙和Tdengine的timer做一下简单的对比:节约关键资源:由于每个操作系统的timer都需要一个线程进行回调处理,这对于Tdengine这种数据库动辙几万个timer的应用来说是不可接受的,所以为了节省线程资源,Td要用自己实现的timer之所以要实现自己的定时器是为了节省线程资源。而鸿蒙实现timer则是为了节约硬件定时器资源。最简化设计:两个timer都没有优先级的设定。其中鸿蒙OS做为移动操作系统直接将timer的精度值也舍弃了。使用双链表提高效率:两个timer都使用双链表来存储同一时刻到期的定时器,这样能节省遍历和移动的时间,大大提高效率。从上面这两个简单的模块中我们也看到这些优秀的项目都使用最精简的设计,紧贴需求、甩掉包袱、轻装上阵才能回归本质取得成功。无论是TdEngine取消任务调度的优先级排序,还是鸿蒙放弃对定时器精度的支持,都是看来出乎意料,实则颇具内涵的减法操作。真正优秀的项目都是敢于做减法的,只有减掉那些看似高大上的设计,才能向着有取有舍,大道至简的境界迈进。
|