圖解FreeRTOS 原理系列之任務管理器基本框架

2021-02-07 嵌入式客棧

[導讀] 學習梳理一下FreeRTOS任務管理單元實現思路,代碼分析基於V10.4.3。從本文開始計劃寫個圖解freeRTOS內核系列筆記分享給朋友們,希望大家喜歡。文章中或有錯誤,也請留言交流指正,或加本人微信進行交流~

本文主要學習梳理FreeRTOS任務管理器的基本原理,大體框架。

內核任務管理器需求

先來對比一下裸奔系統與RTOS應用系統的編程模型,看看兩種編程的不同畫風。

裸奔系統

在不用RTOS的單片機應用開發時,編程模型大概是這樣的畫風:

程序的主體是一個死循環,該應用程式由一系列協同工作的函數片段組成,相互實現邏輯配合,實現用戶業務需求。該應用程式獨佔單片機,常規的單片機系統都僅有有一個計算單元核。普通外設I/O,這裡所說I/O是指廣義的I/O,比如GPIO、PWM、ADC、DAC、LCD顯示(當然這裡並不嚴謹,比如ADC,DAC、LCD等也可以產生中斷)等。中斷函數將異步事件接收成或報文或標誌或數值,在與主循環發生邏輯關聯。中斷外設,比如UART、USB、I2C、定時器、DMA等根據應用需求而使用的中斷。這些中斷都需要相應的中斷函數進行處理異步中斷事件。對於輸出可能採樣主動輸出,一般由主循環某一個動作執行;對於輸入設備或許採用輪詢方式,在與主循環進行耦合。RTOS應用系統

在一個基於RTOS應用系統中,其編程模型大致是下面這樣一個畫風,有多個並行的任務在相對長的宏觀時間維度看起來,多個任務是並行運行的,但對於常規單片機而言(一般都是單核),任一時刻只有一個任務或中斷函數在獨佔CPU核。

常見的RTOS沒有設備驅動模型,沒有對外設設備進行抽象,中斷函數將會由用戶或調用RTOS 機制,比如event/signal等與任務進行通信任務間還有可能需要通信,或傳遞消息,或完成某項需求相互間需要同步等同樣任務需要與硬體普通IO外設進行打交道,或入或出。但有可能是這個任務實現,也有可能是哪個任務執行。完全取決於開發人員如何設計。RTOS實現任務的切入切出,切入使某任務運行;切出使某任務掛起,出讓CPU,暫停運行。RTOS充當底層支持功能,RTOS還提供豐富的時間管理,隊列、郵箱等機制供應用開發使用。

對於單片機而言,一般只有一個核,所有RTOS為了方便理解,可以看成是最最主要的目就是通過軟體方法將硬體CPU核程序運行環境抽象為每一個應用任務虛擬出一個軟核。這樣從時間維度上看起來多任務是並行的,而事實上這種並行是偽並行。

上圖僅僅為理解RTOS作用方便,這種虛擬核本質上並不存在,只是將硬體CPU核的運行時上下文(PC指針、狀態寄存器等寄存器組、任務運行時臨時變量等)通過快照保存切入切出而實現多任務的偽並行運行。

FreeRTOS任務管理器需求

從前文看出,任務管理要實現任務的切入、切出,則首先需要對任務進行抽象描述,以實現在CPU上能夠實現切換。根據閱讀代碼以及文獻加上自己的理解,將內核任務管理器的主要功能需求大致梳理成下面這樣一張用例圖Use case Diagram,僅僅為理解方便,或許並不嚴謹。

從上圖,大致可以看出FreeRTOS任務調度器需要以下一些功能需求:

任務抽象描述,一個任務一般本質上是一個死循環程序片段(當然也有任務運行著會退出被殺掉的可能)。對於任務的抽象:一般會有任務的執行主體,利用函數主體函數指針進行抽象RTOS常規都是的基於優先級搶佔調度算法,因此需要抽象出哪個任務具有更高概率能被執行,用優先級進行描述任務需要得以切換,就需要將任務在切換間的臨時狀態進行保存,棧機制就能很好的滿足這樣的需求,因此每個任務都有一個或大或小的任務棧。其本質上是一片連續的FILO(先入後出)內存。任務調度器控制接口,啟動調度器、停止調度器、掛起所有任務、恢復運行等調度器接口。任務雜項信息接口,比如獲取任務狀態、tick信息、調試、獲取任務名等API接口任務調度算法,基於調度策略對運行時的任務進行調度,或掛起、或運行、或就緒等,主要根據調度策略管理任務的切入切出。這裡主要涉及到任務間上下文切換、任務與中斷函數間的上下文切換兩種場景。抽象C運行時環境,現代RTOS應用系統一般基於C語言,抽象C運行時環境,這裡主要指棧,當然很多RTOS內核也內核堆,freeRTOS也不例外。熟悉C編程的朋友都知道,堆內存由malloc/free函數操作集提供用戶接口,既然C堆已有,為何RTOS內核重新造輪子?為啥內核額外需要實現自己的堆管理器呢?這大體是基於下面些緣由:編譯器C堆實現,在小型嵌入式系統上有時候並不能直接使用。C堆的實現可能相對較大,佔用了較大代碼空間。比較浪費有限的代碼存儲空間。C堆申請執行時間不是確定的, 執行功能所需的時間因調用而異。FreeRTOS任務描述抽象

對於其中幾項必須的關鍵數據域描述一下其抽象作用:

xStateListItem:任務狀態鍊表描述節點,用於動態將該任務添加、刪除到就緒或阻塞任務對列鍊表中xEventListItem:事件鍊表描述節點,描述本任務相關事件,用於將本任務添加到事件鍊表中。uxPriority:任務優先級,用於描述本任務的優先級。pcTaskName:任務名字符串存儲區,長度可配。默認為16位元組

其他的數據域,可裁剪實現一些更豐富的功能,比如主要用於防治優先級反轉的優先級繼承機制,trace追蹤功能等。限於篇幅,也主要梳理任務管理器的主要原理,就不展開了。

任務創建刪除管理

FreeRTOS為用戶提供一組函數集用於任務的創建、刪除等管理,先看任務的創建API:

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
      const char * const pcName,     
      const configSTACK_DEPTH_TYPE usStackDepth,
      void * const pvParameters,
      UBaseType_t uxPriority,
      TaskHandle_t * const pxCreatedTask ) PRIVILEGED_FUNCTION;

TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
        const char * const pcName,    
        const uint32_t ulStackDepth,
        void * const pvParameters,
        UBaseType_t uxPriority,
        StackType_t * const puxStackBuffer,
        StaticTask_t * const pxTaskBuffer ) PRIVILEGED_FUNCTION;

BaseType_t xTaskCreateRestricted( const TaskParameters_t * const pxTaskDefinition,
          TaskHandle_t * pxCreatedTask ) PRIVILEGED_FUNCTION;

BaseType_t xTaskCreateRestrictedStatic( const TaskParameters_t * const pxTaskDefinition,
          TaskHandle_t * pxCreatedTask ) PRIVILEGED_FUNCTION;

xTaskCreate/xTaskCreateStatic 都是用於創建任務而用,其區別在於:xTaskCreate 申請任務控制塊以及棧從內核堆申請xTaskCreateStatic 創建的任務,其任務控制塊內存以及任務棧內存由用戶傳入。或許有朋友會問StaticTask_t這不是任務控制塊嘛,仔細看看其結構定義其內存對齊及大小剛好是前面說的任務控制塊的定義。xTaskCreateRestricted() /xTaskCreateRestrictedStatic(),主要用於在有或使能MPU單元的晶片中創建任務。這裡的MPU是指Memory Protection Unit (MPU),不是微處理器的意思。這兩者的區別與上面兩個API類似,主要在於其內存分配方式不同,xTaskCreateRestricted是從內核堆動態申請,xTaskCreateRestrictedStatic用戶傳入。PRIVILEGED_FUNCTION 這個宏是用於存儲保護單元晶片的。

這幾個任務創建函數都是用於任務創建,任務一旦創建就會被插入任務就緒鍊表中,當調度器調度啟動後就按任務狀態機根據調度策略以及外部輸入事件進行調度接管。這裡以xTaskCreate繪製一下其內在幹了些啥:

再看看另外兩個函數:

void vTaskAllocateMPURegions( TaskHandle_t xTask,
        const MemoryRegion_t * const pxRegions ) PRIVILEGED_FUNCTION;
void vTaskDelete( TaskHandle_t xTaskToDelete ) PRIVILEGED_FUNCTION;

vTaskAllocateMPURegions: 定義一組內存保護單元(MPU)區域,供MPU受限任務使用.vTaskDelete: 刪除用使用xTaskCreate()或xTaskCreateStatic()創建的任務。任務控制管理接口
void vTaskDelay( const TickType_t xTicksToDelay ) PRIVILEGED_FUNCTION;
BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
                            const TickType_t xTimeIncrement ) PRIVILEGED_FUNCTION;
BaseType_t xTaskAbortDelay( TaskHandle_t xTask ) PRIVILEGED_FUNCTION;
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask ) PRIVILEGED_FUNCTION;
UBaseType_t uxTaskPriorityGetFromISR( const TaskHandle_t xTask ) PRIVILEGED_FUNCTION;
eTaskState eTaskGetState( TaskHandle_t xTask ) PRIVILEGED_FUNCTION;
void vTaskGetInfo( TaskHandle_t xTask,
                   TaskStatus_t * pxTaskStatus,
                   BaseType_t xGetFreeStackSpace,
                   eTaskState eState ) PRIVILEGED_FUNCTION;
void vTaskPrioritySet( TaskHandle_t xTask,
                       UBaseType_t uxNewPriority ) PRIVILEGED_FUNCTION;
void vTaskSuspend( TaskHandle_t xTaskToSuspend ) PRIVILEGED_FUNCTION;
void vTaskResume( TaskHandle_t xTaskToResume ) PRIVILEGED_FUNCTION;
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume ) PRIVILEGED_FUNCTION;

這一系列的API接口操作集主要用於對任務進行掛起延時、獲取優先級、自中斷函數獲取優先級、掛起、恢復運行等操作。基本從其函數名就可以看出其作用。比如:

vTaskDelay調用,會使調用該函數的任務進入阻塞狀態一段時間,時間為傳入的tick數。

這裡需要注意的是有的函數在中斷函數體裡面不可以調用,需要使用專用版本,具體可以看看手冊或注釋。

調度器控制接口
void vTaskStartScheduler( void ) PRIVILEGED_FUNCTION;
void vTaskEndScheduler( void ) PRIVILEGED_FUNCTION;
void vTaskSuspendAll( void ) PRIVILEGED_FUNCTION;
BaseType_t xTaskResumeAll( void ) PRIVILEGED_FUNCTION;

這一組函數API集主要用於調度器的啟動、停止控制:

vTaskStartScheduler,主要用於待用戶任務創建好後,硬體初始化後,啟動內核調度器vTaskEndScheduler,可用於停止內核調度器,一般很少用到,在一些安全相關的應用可能會在出故障時主動停止調度器。vTaskSuspendAll,掛起所有任務,可以用用戶邏輯主動掛起所有的任務xTaskResumeAll,恢復所有任務為就緒態。任務雜項API集

我根據代碼及注釋及自己理解,將這些API歸類到雜項API集合:

TickType_t xTaskGetTickCountFromISR( void ) PRIVILEGED_FUNCTION;
UBaseType_t uxTaskGetNumberOfTasks( void ) PRIVILEGED_FUNCTION;
char * pcTaskGetName( TaskHandle_t xTaskToQuery ) PRIVILEGED_FUNCTION;     
TaskHandle_t xTaskGetHandle( const char * pcNameToQuery ) PRIVILEGED_FUNCTION;   
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask ) PRIVILEGED_FUNCTION;
configSTACK_DEPTH_TYPE uxTaskGetStackHighWaterMark2( TaskHandle_t xTask ) PRIVILEGED_FUNCTION;
void vTaskSetApplicationTaskTag( TaskHandle_t xTask,
         TaskHookFunction_t pxHookFunction ) PRIVILEGED_FUNCTION;
TaskHookFunction_t xTaskGetApplicationTaskTag( TaskHandle_t xTask ) PRIVILEGED_FUNCTION;
TaskHookFunction_t xTaskGetApplicationTaskTagFromISR( TaskHandle_t xTask ) PRIVILEGED_FUNCTION;

void vTaskSetThreadLocalStoragePointer( TaskHandle_t xTaskToSet,
          BaseType_t xIndex,
          void * pvValue ) PRIVILEGED_FUNCTION;
void * pvTaskGetThreadLocalStoragePointer( TaskHandle_t xTaskToQuery,

void vApplicationStackOverflowHook( TaskHandle_t xTask,
           char * pcTaskName );
void vApplicationTickHook( void ); 
.
BaseType_t xTaskGenericNotifyStateClear( TaskHandle_t xTask,
                                         UBaseType_t uxIndexToClear ) PRIVILEGED_FUNCTION;

uint32_t ulTaskGenericNotifyValueClear( TaskHandle_t xTask,
                                        UBaseType_t uxIndexToClear,
                                        uint32_t ulBitsToClear ) PRIVILEGED_FUNCTION;
void vTaskSetTimeOutState( TimeOut_t * const pxTimeOut ) PRIVILEGED_FUNCTION;

BaseType_t xTaskCheckForTimeOut( TimeOut_t * const pxTimeOut,
                                 TickType_t * const pxTicksToWait ) PRIVILEGED_FUNCTION;

BaseType_t xTaskCatchUpTicks( TickType_t xTicksToCatchUp ) PRIVILEGED_FUNCTION;

這些函數具體作用就不贅述,這裡僅僅梳理分類,用到時候查手冊即可。

跨平臺移植接口
BaseType_t xTaskIncrementTick( void ) PRIVILEGED_FUNCTION;
void vTaskPlaceOnEventList( List_t * const pxEventList,
                            const TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;
void vTaskPlaceOnUnorderedEventList( List_t * pxEventList,
                                     const TickType_t xItemValue,
                                     const TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;
void vTaskPlaceOnEventListRestricted( List_t * const pxEventList,
                                      TickType_t xTicksToWait,
                                      const BaseType_t xWaitIndefinitely ) PRIVILEGED_FUNCTION;
BaseType_t xTaskRemoveFromEventList( const List_t * const pxEventList ) PRIVILEGED_FUNCTION;
void vTaskRemoveFromUnorderedEventList( ListItem_t * pxEventListItem,
                                        const TickType_t xItemValue ) PRIVILEGED_FUNCTION;

portDONT_DISCARD void vTaskSwitchContext( void ) PRIVILEGED_FUNCTION;
TickType_t uxTaskResetEventItemValue( void ) PRIVILEGED_FUNCTION;
TaskHandle_t xTaskGetCurrentTaskHandle( void ) PRIVILEGED_FUNCTION;
void vTaskMissedYield( void ) PRIVILEGED_FUNCTION;
BaseType_t xTaskGetSchedulerState( void ) PRIVILEGED_FUNCTION;
BaseType_t xTaskPriorityInherit( TaskHandle_t const pxMutexHolder ) PRIVILEGED_FUNCTION;
BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder ) PRIVILEGED_FUNCTION;
void vTaskPriorityDisinheritAfterTimeout( TaskHandle_t const pxMutexHolder,
UBaseType_t uxTaskGetTaskNumber( TaskHandle_t xTask ) PRIVILEGED_FUNCTION;
void vTaskSetTaskNumber( TaskHandle_t xTask,
                         const UBaseType_t uxHandle ) PRIVILEGED_FUNCTION;
void vTaskStepTick( const TickType_t xTicksToJump ) PRIVILEGED_FUNCTION;


eSleepModeStatus eTaskConfirmSleepModeStatus( void ) PRIVILEGED_FUNCTION;

TaskHandle_t pvTaskIncrementMutexHeldCount( void ) PRIVILEGED_FUNCTION;

void vTaskInternalSetTimeOutState( TimeOut_t * const pxTimeOut ) PRIVILEGED_FUNCTION;

這些接口不同硬體平臺需要做具化的移植,做差異化的處理,但是對於FreeRTOS統一了內部調用的接口。這樣的思路在應用開發時也可以考慮使用,對於公共部分可以抽象出統一的接口,這樣在不同平臺上可以很方便的進行移植。對於這些接口後面有機會學習整理分享。

對於用例圖中的其他部分,核心調度部分以及上下文切換,篇幅所限留在後面學習整理分享。

總結一下

本文基本學習梳理了一下對於FreeRTOS任務調度器外部接口、以及大體作用,基本組成情況,水平所限,文章中錯誤難免,歡迎交流指正。

相關焦點

  • 圖解FreeRTOS 原理系列之任務管理器基本框架!
    從本文開始計劃寫個圖解freeRTOS內核系列筆記分享給朋友們,希望大家喜歡。本文主要學習梳理FreeRTOS任務管理器的基本原理,大體框架。內核任務管理器需求先來對比一下裸奔系統與RTOS應用系統的編程模型,看看兩種編程的不同畫風。
  • Hadoop框架:Yarn基本結構和運行原理
    一、Yarn基本結構Hadoop三大核心組件:分布式文件系統HDFS、分布式計算框架MapReduce,分布式集群資源調度框架Yarn。Yarn包括兩個主要進程:資源管理器Resource-Manager,節點管理器Node-Manager。
  • FreeRTOS的直接任務(消息)通知
    嵌入式專欄   2   什麼是直接任務通知?大多數任務間通信方法都通過中介對象,例如隊列,信號量或事件組。發送任務寫入通信對象,接收任務從通信對象讀取。   比如FreeRTOS的隊列通信,首先創建隊列之前要定義一個隊列:       從FreeRTOS V10.4.0開始,每個任務都有一系列通知。每個通知都包含一個32位值和一個布爾狀態,它們一起僅消耗5個字節的RAM。
  • 如何改善FreeRTOS運行的速度、RAM大小
    大多數任務間通信方法都通過 中介對象 ,例如隊列,信號量或事件組。 發送任務寫入通信對象,接收任務從通信對象讀取。 從FreeRTOS V10.4.0開始,每個任務都有一系列通知。
  • 最初開發者分享Windows任務管理器的三個技巧
    IT之家5月27日消息 Windows任務管理器是IT專業人員非常依賴的應用之一,它可以在不需要第三方軟體的情況下,對設備上的一切運行情況進行監視,殺死進程、獲取資源使用信息等。IT之家了解到,Dave Plummer是最初的任務管理器開發者,他解釋說,他所創建的一些功能目前仍然存在,只是很少有人真正了解這些功能,因此Plummer本周在reddit上的一篇詳細帖子中透露了三個技巧。
  • 如何在Mac上打開任務管理器
    想知道如何在Mac上打開任務管理器?如果您習慣使用Windows,可能不知道這個重要實用程序在macOS上的位置,或者您以前從未需要打開它。無論哪種方式,我們都會向您展示多種方式來訪問Mac上的任務管理器。
  • stm32 freertos 之串口中斷
    IRQn  ; NVIC_InitStructure .NVIC_IRQChannelPreemptionPriority =6; NVIC_InitStructure .NVIC_IRQChannelCmd =ENABLE ; NVIC_Init(&NVIC_InitStructure ); }三、串口接收任務創建
  • 科技:如何在Mac上打開任務管理器
    導語:如何在Mac上打開任務管理器。MacOS具有與Windows任務管理器類似的功能,使用Windows的人知道打開任務管理器的用處,它顯示正在運行的進程,那些可以忽略的進程,那些在RAM上狼吞虎咽的進程,那些對網絡訪問過多的進程,可以終止幾個進程或啟動新的進程。
  • win7系統任務管理器的五種打開方式,很實用,學習一下
    xp系統如何啟動任務管理器,很簡單吧,ctrl鍵+alt鍵+delete鍵,直接可以啟動任務管理器,可是當你在win7作業系統按下ctrl鍵+alt鍵+delete鍵,是無法直接調用任務管理器的,接下來,我將給大家介紹一下,在win7作業系統中打開任務管理器的幾種方式,希望對大家有幫助,任務管理器是很重要的管理電腦的工具,例如你想徹底關掉某個程序的運行,就可以通過任務管理器來完成
  • Win10任務管理器中加入GPU性能監視器
    我們知道在Windows系統的「任務管理器」中,除了可以查看進程之外,還可以監視CPU、內存磁碟等硬體的運行情況,但一直不支持GPU。  但就在Windows 10 Build 16226中,GPU性能監視已經被加入到了任務管理器中,現在你可以像看CPU一樣查看每個GPU組件的相關信息以及顯存的使用情況。在「詳細信息」頁面還會顯示每個進程的GPU使用信息。
  • 如何用PE Explorer個性化XP任務管理器
    對於windows系統中的任務管理器想必大家都很熟悉吧,利用它可以查看、結束進程,運行新程序等!但有時我們並不想讓機器使用者做這些事,那怎麼辦呢?其實只需把這些功能屏蔽就即可,跟我一起來看看如何個性化XP的任務管理器吧。
  • Win10桌面圖標/任務欄不見了怎麼辦?重啟資源管理器
    這通常是由於Windows資源管理器意外退出或崩潰導致的。所以解決方法也很簡單,只需重新啟動Windows資源管理器(explorer.exe)即可。方法如下:有童鞋可能會說,這還不簡單,我只需按「Windows徽標鍵 + R」組合鍵調出「運行」窗口,運行「explorer」命令不就可以啟動Windows資源管理器了嗎?
  • 簡明易懂 圖解電源工作原理
    電源的分類  接下來我們來對比一下這兩種類型的電源有什麼不同之處?如下圖所示。  通過上面的圖解,我們知道計算機的直流電是通過電源做了四次轉變而來。  四部分的結構和作用  通過前面的圖解,我們知道了市電是通過四個部分,五次轉變而最終得到計算機的低壓直流電的。下面我們就來講解一下這是由電源的哪四個部分來完成這個轉變的。先來上一張電流流程圖。
  • Zenkit To Do for mac(任務管理器) 中文免費版
    Zenkit To Do for mac是一款簡單好用跨平臺的任務管理工具。Zenkit To Do mac免費版能夠組織您的任務,購物清單,會議,活動,旅行,想法,筆記,地點以及其他需要組織的事物,讓您有更多的時間處理生活中的重要事情。
  • 深入學習SAP UI5框架代碼系列之五:SAP UI5控制項的實例數據修改和...
    系列目錄(0) SAP UI5應用開發人員了解UI5框架代碼的意義(1) SAP UI5 module懶加載機制(2) SAP UI5 控制項渲染機制(3) HTML原生事件 VS SAP UI5 Semantic事件(4) SAP UI5控制項元數據的元數據實現(5) SAP UI5控制項的實例數據修改和讀取邏輯(本文)(6) SAP UI5控制項數據綁定的實現原理(7) SAP UI5控制項數據綁定的三種模式
  • 乾貨 | FreeRTOS 學習筆記——FreeRTOS的軟體結構
    要上手也很快,本篇我就記錄一下如何將 FreeRTOS 的代碼加到已有的工程裡面,作為一個備忘參考(網上也能隨便搜到很多關於怎麼使用 FreeRTOS, 怎麼創建任務等等的文章。在我的學習筆記系列裡面這部分內容倒不是首要的,因為我想分享的是從我對 FreeRTOS 代碼的分析和實踐了解到它是怎麼工作的,帶來了什麼好處)。
  • Win10加了個遊戲專用任務管理器 使用起來更為便利
    據IGN報導,Windows 10系統在自家的Xbox Game Bar插件中新增了一個「任務管理器」小組件,該組件可以讓玩家不離開遊戲的情況下,管理計算機的資源。
  • 神一般的Scrapy框架,Python中Scrap的基本結構和去重原理
    "Scrapy的基本結構是什麼樣的, Scrapy的指紋去重到底是什麼原理",面試官經常這麼問.1.scrapy的基本結構(五個部分都是什麼,請求發出去的整個流程)2.scrapy的去重原理(指紋去重到底是什麼原理)看來大家都發現了標題中Scrapy掉了一個y,以後小編會改正的,謝謝大家的提醒一、Scrapy
  • 深入學習SAP UI5框架代碼系列之六:SAP UI5控制項數據綁定的實現原理
    系列目錄(0) SAP UI5應用開發人員了解UI5框架代碼的意義(1) SAP UI5 module懶加載機制(2) SAP UI5 控制項渲染機制(3) HTML原生事件 VS SAP UI5 Semantic事件(4) SAP UI5控制項元數據的元數據實現(5) SAP UI5控制項的實例數據修改和讀取邏輯
  • 12冊電氣電工全彩圖解系列資料,電工快速入門與提高,通俗易懂
    12冊電氣電工全彩圖解系列資料,電工快速入門與提高,通俗易懂本書採用「全彩+圖解」方式編寫,能讓讀者輕鬆快速掌握電工技術,適合作為電氣電工自學圖書,電工全彩圖解系列資料包括:電工快速入門與提高,電工識圖,電路識圖,電工布線全彩,電工操作技能與技巧