读了鸿蒙 OS 的代码后,我发现优秀项目都有这个共性!

2019 年 8 月 25 日 程序人生

作者 | 马超

责编 | 胡巍巍

出品 | 程序人生(ID:coder_life)

最近有人在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做为数据库项目,比较他们两者在同一模块上的设计异同,非常有收获,下面给各位读者分享一下,如有意见欢迎留言。

两个项目对于任务调度模块的实现对比

1.鸿蒙OS的调度模块
与一般操作系统一样,鸿蒙也将任务状态通常分为以下三种:
  • 就绪(Ready):

    该任务在就绪列表中,只等待CPU。

  • 运行(Running):

    该任务正在执行。

  • 阻塞(Blocked):

    该任务不在就绪列表中。

    包含任务被挂起、任务被延时、任务正在等待信号量、读写队列或者等待读写事件等。



任务状态迁移图
其代码位置在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;
}
 我们看到这个函数的处理过程基本分为三步:
  • 任务合法性(TaskId)及任务状态校验:判断任务序号以及任务当前状态是否确实为挂起。

  • 改变任务状态:将任务的suspend状态位清掉

  • 起用任务调度:如果任务被阻塞,则调起LOS_Schedule进行调度。

我们知道完整的LINUX内核是支持将任务指定在某个CPU上运行的,不过鸿蒙OS做为一个微内核的移动操作系统没有继承这些复杂的功能,直接做了减法,实现一个最简模型。
2.TdEngine的任务调度模块     
而对比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\n", pSched->label, strerror(errno));

     if (sem_post(&pSched->emptySem) !=  0)
      pError( "post %s emptySem failed, reason:%s\n", 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;
}


  
  两个项目对于定时器(timer)的实现对比

1.鸿蒙的timer
在鸿蒙的官方文档中是这么介绍定时器的:
软件定时器,是基于系统Tick时钟中断且由软件来模拟的定时器,当经过设定的Tick时钟计数值后会触发用户定义的回调函数。 定时精度与系统Tick时钟的周期有关。  
硬件定时器受硬件的限制,数量上不足以满足用户的实际需求,因此为了满足用户需求,提供更多的定时器,Huawei LiteOS操作系统提供软件定时器功能。 软件定时器扩展了定时器的数量,允许创建更多的定时业务。
2.运作机制
  • 软件定时器是系统资源,在模块初始化的时候已经分配了一块连续的内存,系统支持的最大定时器个数可以在los_config.h文件中配置。

  • 软件定时器使用了系统的一个队列和任务资源,软件定时器的触发遵循队列规则,先进先出。

    定时时间短的定时器总是比定时时间长的靠近队列头,满足优先被触发的准则。

  • 软件定时器以Tick为基本计时单位,当用户创建并启动一个软件定时器时,Huawei LiteOS会根据当前系统Tick时间及用户设置的定时间隔确定该定时器的到期Tick时间,并将该定时器控制结构挂入计时全局链表。

  • 当Tick中断到来时,在Tick中断处理函数中扫描软件定时器的计时全局链表,看是否有定时器超时,若有则将超时的定时器记录下来。

  • Tick处理结束后,软件定时器任务(优先级为最高)被唤醒,在该任务中调用之前记录下来的超时定时器的处理函数。

3.代码解读
如果官方文档的说明没看懂,可以直接查阅其源代码,具体位置在los_swtmr.c
下面笔者来简述一下鸿蒙定时器的工作原理。
  • 首先明确鸿蒙的定时器是为了节省硬件定时器资源而设计的。

    由于硬件定时器往往数量有限而系统实际运行中,对于定时器的需求往往高于硬件定时器的数量,所以操作系统都会实现软件定时器以满足用户需求。

  • 先启动硬件定时器,注册硬件定时器的tick事件,也就是硬件定时器到时发生tick时会调用软件定时器的处理函数。

  • 将在同一时刻到期的timer放在同一链表中。

  • 在硬件产生tick事件时,取出当时到期的定时器列表,并顺序调起链表内所有到时定时器的处理函数。

   
   
     
LITE_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
}

以上函数的运行原理动画解析如下:

4.timer之间的对比
其实Tdengine的timer我之前已经做过解读了,200行代码为大家解读这个Github冠军项目背后的定时器。 就不加赘述了,这里把鸿蒙和Tdengine的timer做一下简单的对比:
节约关键资源:由于每个操作系统的timer都需要一个线程进行回调处理,这对于Tdengine这种数据库动辙几万个timer的应用来说是不可接受的,所以为了节省线程资源,Td要用自己实现的timer之所以要实现自己的定时器是为了节省线程资源。而鸿蒙实现timer则是为了节约硬件定时器资源。
最简化设计: 两个timer都没有优先级的设定。 其中鸿蒙OS做为移动操作系统直接将timer的精度值也舍弃了。
使用双链表提高效率: 两个timer都使用双链表来存储同一时刻到期的定时器,这样能节省遍历和移动的时间,大大提高效率。

结语

从上面这两个简单的模块中我们也看到这些优秀的项目都使用最精简的设计,紧贴需求、甩掉包袱、轻装上阵才能回归本质取得成功。
无论是TdEngine取消任务调度的优先级排序,还是鸿蒙放弃对定时器精度的支持,都是看来出乎意料,实则颇具内涵的减法操作。 真正优秀的项目都是敢于做减法的,只有减掉那些看似高大上的设计,才能向着有取有舍,大道至简的境界迈进。
原文:https://blog.csdn.net/BEYONDMA/article/details/100049796?utm_source=app

 热 文 推 荐 
☞重磅!全球首个可视化联邦学习产品与联邦pipeline生产服务上线
☞  10 步教你接手同事的代码!
☞ 90  后程序员健康 现状:掉头发、油腻、腰椎间盘突出……| 程序员有话说
☞  漫画:进阿里第一年,第二年,第三年……
☞  程序员破解推荐系统瓶颈,带来超百亿收入增量!
深度 | 语音识别技术简史:从不温不火到炙手可热
意大利黑手党四大家族做了条"犯罪链", 把家族的权利被分的明明白白的……
Istio 庖丁解牛六:多集群网格应用场景
☞写出让同事无法维护的代码?
       
       
         
点击阅读原文,输入关键词,即可搜索您想要的程序人生文章。
你点的每个“在看”,我都认真当成了喜欢
登录查看更多
0

相关内容

SEM 是 Search Engine Marketing 的缩写,中文意思是搜索引擎营销。SEM 是一种新的网络营销形式。SEM 所做的就是全面而有效的利用搜索引擎来进行网络营销和推广。SEM 追求最高的性价比,以最小的投入,获最大的来自搜索引擎的访问量,并产生商业价值。
Python分布式计算,171页pdf,Distributed Computing with Python
专知会员服务
107+阅读 · 2020年5月3日
【2020新书】如何认真写好的代码和软件,318页pdf
专知会员服务
63+阅读 · 2020年3月26日
专知会员服务
109+阅读 · 2020年3月12日
抢鲜看!13篇CVPR2020论文链接/开源代码/解读
专知会员服务
49+阅读 · 2020年2月26日
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
95+阅读 · 2019年12月4日
【书籍】深度学习框架:PyTorch入门与实践(附代码)
专知会员服务
163+阅读 · 2019年10月28日
[综述]深度学习下的场景文本检测与识别
专知会员服务
77+阅读 · 2019年10月10日
计算机视觉最佳实践、代码示例和相关文档
专知会员服务
17+阅读 · 2019年10月9日
34个最优秀好用的Python开源框架
专知
9+阅读 · 2019年3月1日
年度大盘点:机器学习开源项目及框架
云栖社区
3+阅读 · 2018年12月17日
Python 杠上 Java、C/C++,赢面有几成?
CSDN
6+阅读 · 2018年4月12日
Python为啥这么牛?
Python程序员
3+阅读 · 2018年3月30日
代码这样写不止于优雅(Python版)
数说工作室
4+阅读 · 2017年7月17日
开源巨献:阿里巴巴最热门29款开源项目
算法与数据结构
5+阅读 · 2017年7月14日
Arxiv
26+阅读 · 2020年2月21日
Object Detection in 20 Years: A Survey
Arxiv
48+阅读 · 2019年5月13日
Deep Learning for Generic Object Detection: A Survey
Arxiv
13+阅读 · 2018年9月6日
Arxiv
8+阅读 · 2018年5月21日
VIP会员
相关VIP内容
Python分布式计算,171页pdf,Distributed Computing with Python
专知会员服务
107+阅读 · 2020年5月3日
【2020新书】如何认真写好的代码和软件,318页pdf
专知会员服务
63+阅读 · 2020年3月26日
专知会员服务
109+阅读 · 2020年3月12日
抢鲜看!13篇CVPR2020论文链接/开源代码/解读
专知会员服务
49+阅读 · 2020年2月26日
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
95+阅读 · 2019年12月4日
【书籍】深度学习框架:PyTorch入门与实践(附代码)
专知会员服务
163+阅读 · 2019年10月28日
[综述]深度学习下的场景文本检测与识别
专知会员服务
77+阅读 · 2019年10月10日
计算机视觉最佳实践、代码示例和相关文档
专知会员服务
17+阅读 · 2019年10月9日
相关资讯
34个最优秀好用的Python开源框架
专知
9+阅读 · 2019年3月1日
年度大盘点:机器学习开源项目及框架
云栖社区
3+阅读 · 2018年12月17日
Python 杠上 Java、C/C++,赢面有几成?
CSDN
6+阅读 · 2018年4月12日
Python为啥这么牛?
Python程序员
3+阅读 · 2018年3月30日
代码这样写不止于优雅(Python版)
数说工作室
4+阅读 · 2017年7月17日
开源巨献:阿里巴巴最热门29款开源项目
算法与数据结构
5+阅读 · 2017年7月14日
Top
微信扫码咨询专知VIP会员