讀了鴻蒙 OS 的代碼後,我發現優秀項目都有這個共性!

640?wx_fmt=gif

640?wx_fmt=jpeg

做者 | 馬超git

責編 | 胡巍巍程序員

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

最近有人在Github上開源了鴻蒙OS(https://www.github.com/Awesome-HarmonyOS)而且累計得到了一萬多顆Star。數據庫

從華爲的官方宣傳中就提到了「安卓總代碼超過一億行,其中內核代碼超過2000萬行,實際用到的不過8%,如此龐大和冗餘的這種設計,實際上很難保證流暢度,使用效率很低。」 app

而筆者以前介紹過的TDengine(https://github.com/taosdata/TDengine)作爲一個數據庫項目更是僅用1.5M安裝包就能搞定,代碼效率高的驚人。函數

因此從這方面咱們也能看出優秀的項目對於速度的要求都是極致的。post

不過這兩個項目開源後都引起了一些爭議,好比鴻蒙開源當天就有人發微博說華爲只是作了個安卓的定製版,質量甚至還不如MIUI,筆者的這位創造Github冠軍項目的老男人,堪稱10倍程序員本尊發佈後,也有人在評論說TDengine的consumer-productor實現沒法經過code review。學習

可是仔細閱讀這些評論能夠發現,這些批評其實都不是基於代碼的。筆者作爲一名程序員奉行「Talk is cheap,show me the code"的理念,因此我利用週末時間閱讀了這兩個項目的代碼,發現了不少值得學習的設計亮點。優化

尤爲是鴻蒙OS作爲操做系統項目而Tdengine作爲數據庫項目,比較他們二者在同一模塊上的設計異同,很是有收穫,下面給各位讀者分享一下,若有意見歡迎留言。動畫


640?wx_fmt=png

兩個項目對於任務調度模塊的實現對比


1.鴻蒙OS的調度模塊

與通常操做系統同樣,鴻蒙也將任務狀態一般分爲如下三種:

  • 就緒(Ready):

    該任務在就緒列表中,只等待CPU。

  • 運行(Running):

    該任務正在執行。

  • 阻塞(Blocked):

    該任務不在就緒列表中。

    包含任務被掛起、任務被延時、任務正在等待信號量、讀寫隊列或者等待讀寫事件等。


640?wx_fmt=png


任務狀態遷移圖

其代碼位置在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;
}


640?wx_fmt=png  

  兩個項目對於定時器(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都使用雙鏈表來存儲同一時刻到期的定時器,這樣能節省遍歷和移動的時間,大大提升效率。


640?wx_fmt=png

結語


從上面這兩個簡單的模塊中咱們也看到這些優秀的項目都使用最精簡的設計,緊貼需求、甩掉包袱、輕裝上陣才能迴歸本質取得成功。

不管是TdEngine取消任務調度的優先級排序,仍是鴻蒙放棄對定時器精度的支持,都是看來出乎意料,實則頗具內涵的減法操做。真正優秀的項目都是勇於作減法的,只有減掉那些看似高大上的設計,才能向着有取有舍,大道至簡的境界邁進。

原文:https://blog.csdn.net/BEYONDMA/article/details/100049796?utm_source=app

640?wx_fmt=png

640?wx_fmt=jpeg

 熱 文 推 薦 

☞重磅!全球首個可視化聯邦學習產品與聯邦pipeline生產服務上線

☞ 10 步教你接手同事的代碼!

☞ 90 後程序員健康現狀:掉頭髮、油膩、腰椎間盤突出……| 程序員有話說

☞ 漫畫:進阿里第一年,第二年,第三年……

☞ 程序員破解推薦系統瓶頸,帶來超百億收入增量!

語音識別技術簡史

意大利黑手黨四你們族作了條"犯罪鏈", 把家族的權利被分的明明白白的……

Istio 庖丁解牛六:多集羣網格應用場景

☞寫出讓同事沒法維護的代碼?

 
 

640?wx_fmt=gif點擊閱讀原文,輸入關鍵詞,便可搜索您想要的程序人生文章。

640?wx_fmt=png

你點的每一個「在看」,我都認真當成了喜歡