chinese直男口爆体育生外卖, 99久久er热在这里只有精品99, 又色又爽又黄18禁美女裸身无遮挡, gogogo高清免费观看日本电视,私密按摩师高清版在线,人妻视频毛茸茸,91论坛 兴趣闲谈,欧美 亚洲 精品 8区,国产精品久久久久精品免费

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

LuatOS框架的使用(上)

合宙LuatOS ? 來源:合宙LuatOS ? 作者:合宙LuatOS ? 2026-01-27 19:38 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

在資源受限的物聯(lián)網(wǎng)終端設(shè)備中,如何實(shí)現(xiàn)快速開發(fā)與穩(wěn)定運(yùn)行是關(guān)鍵挑戰(zhàn)。LuatOS框架通過將Lua語言與底層硬件抽象層深度融合,提供了一套簡潔高效的開發(fā)范式。本文將圍繞LuatOS框架的使用展開,從環(huán)境搭建、模塊調(diào)用到任務(wù)調(diào)度,全面解析其開發(fā)流程與最佳實(shí)踐。

本篇文章主要講LuatOS 框架;LuatOS 框架是整個 LuatOS 開發(fā)中最基礎(chǔ)也是最核心的內(nèi)容,無論使用 LuatOS 開發(fā)什么功能,都會用到它;

LuatOS 框架主要包含以下幾部分:

1、LuatOS 軟件的構(gòu)成

2、LuatOS 開發(fā)環(huán)境的說明

3、LuatOS 用戶腳本程序的運(yùn)行機(jī)制

4、LuatOS 的 sys 核心庫中任務(wù)、消息、定時器和調(diào)度器功能的使用

一、初步認(rèn)識 LuatOS 開發(fā)

現(xiàn)在我們開始正式進(jìn)入 LuatOS 開發(fā)的世界。

1.1 Lua 語言介紹

用戶基于 LuatOS 進(jìn)行軟件二次開發(fā),使用的編程語言為 Lua 語言,Lua 語言相對來說還是比較簡單的,大家只要有其他編程語言的開發(fā)經(jīng)驗(yàn),就不用在 Lua 語言學(xué)習(xí)上花費(fèi)太多時間,花個一兩天大概看一遍就夠了;后續(xù)可以邊看 LuatOS demo,邊學(xué)習(xí) Lua 語言,這樣學(xué)習(xí)效率更高。

1.2 LuatOS 軟件的構(gòu)成

1.2.1 LuatOS 軟件架構(gòu)

wKgZO2l4Q-GAVeINAAHY2lJJFtM876.png

LuatOS 軟件的總體架構(gòu)參考上圖,從二次開發(fā)的角度來看,主要理解幾個概念:內(nèi)核固件,標(biāo)準(zhǔn)庫,核心庫,擴(kuò)展庫,demo,project,用戶開發(fā)的 LuatOS 項(xiàng)目應(yīng)用軟件;

接下來我結(jié)合架構(gòu)圖逐一分析下這幾個概念(為了演示方便,在這里我打開兩個瀏覽器窗口,左邊是框架圖,右邊是文字介紹)。

1.2.2 LuatOS 內(nèi)核固件

1、內(nèi)核固件(又叫固件,底層固件,或者 core),我們會針對每種硬件編譯好并且對外發(fā)布內(nèi)核固件

2、內(nèi)核固件文件以 soc 做為后綴,例如:

(1) LuatOS-SoC_V2008_Air8000_1.soc 表示Air8000 硬件的V2008版本的1 號LuatOS 固件文件;

(2) LuatOS-SoC_V2008_Air780EHM.soc 表示Air780EHM 硬件的V2008版本的 LuatOS 固件文件;

(3) LuatOS-SoC_V1004_Air8101.soc 表示Air8101 硬件的V1004版本的 LuatOS 固件文件;

3、內(nèi)核固件包含主芯片系統(tǒng)平臺層(這里有一個嵌入式操作系統(tǒng),目前比較流行的是 FreeRTOS,接下來本文用到的嵌入式操作系統(tǒng)都會以 FreeRTOS 為例來說明),LuatOS 適配層,Lua 虛擬機(jī),Lua 標(biāo)準(zhǔn)庫,LuatOS 核心庫幾部分,除了主芯片系統(tǒng)平臺層的源碼沒有開放外,其余四部分的源碼全部開放;

4、內(nèi)核固件中和用戶軟件二次開發(fā)息息相關(guān)的兩部分是 Lua 標(biāo)準(zhǔn)庫和 LuatOS 核心庫,接下來我們看下這兩部分內(nèi)容;

1.2.3 Lua 標(biāo)準(zhǔn)庫

Lua 標(biāo)準(zhǔn)庫是 Lua 語言內(nèi)置的一組核心功能模塊,它們?yōu)?Lua 提供了基礎(chǔ)編程能力。標(biāo)準(zhǔn)庫的設(shè)計(jì)遵循 Lua 的“小而精”哲學(xué),僅包含最必要的功能。

Lua 標(biāo)準(zhǔn)庫已經(jīng)編譯到了 LuatOS 內(nèi)核固件中,用戶無法修改,可以直接使用。

主要包含以下幾種庫

基礎(chǔ)庫(Basic Library):支持 collectgarbage(垃圾回收)、_G(全局變量表)、ipairs(迭代數(shù)組元素)、pairs(迭代鍵值對)、tostring(轉(zhuǎn)換為字符串)、tonumber(轉(zhuǎn)換為數(shù)字)、type(獲取類型名)等功能函數(shù);

協(xié)程庫(Coroutine Library):支持 coroutine.create(創(chuàng)建協(xié)程)、coroutine.yield(掛起協(xié)程)、coroutine.resume(恢復(fù)協(xié)程)、coroutine.status(獲取協(xié)程狀態(tài))、coroutine.running(獲取正在運(yùn)行的協(xié)程)等功能函數(shù);

字符串處理庫(String Library):支持 string.len(獲取字符串長度)、string.match(字符串模式匹配)、string.byte(獲取字符的 ascii 碼)、string.char(獲取 ascii 碼對應(yīng)的字符)等功能函數(shù);

表處理庫(Table Library):支持 table.insert(插入元素)、table.remove(移除元素)、table.concat(將表中的元素連接為一個字符串)、table.unpack(解包表中的元素為多個返回值)等功能函數(shù);

此外還有數(shù)學(xué)庫,輸入輸出庫,操作系統(tǒng)庫,調(diào)試庫等功能模塊,在這里我就不逐一列舉了;

LuatOS 中使用的 Lua 是 5.3 版本

1.2.4 LuatOS 核心庫

LuatOS 核心庫是 專為嵌入式設(shè)備設(shè)計(jì)的 Lua 運(yùn)行時擴(kuò)展庫,針對物聯(lián)網(wǎng)(IoT)和資源受限環(huán)境(如 MCU)進(jìn)行了深度優(yōu)化和功能擴(kuò)展。

LuatOS 核心庫已經(jīng)編譯到了 LuatOS 內(nèi)核固件中,用戶無法修改,可以直接使用。

目前支持 74 個核心庫,核心庫中的所有功能模塊,我們在后續(xù)文章中都會逐一講解;

在核心庫中有一個 sys 庫,在本章中會重點(diǎn)介紹。

1.2.5 LuatOS 擴(kuò)展庫

LuatOS 擴(kuò)展庫是 在核心庫基礎(chǔ)上針對特定場景或硬件功能提供的附加功能模塊,用于增強(qiáng) LuatOS 在物聯(lián)網(wǎng)(IoT)和嵌入式系統(tǒng)中的能力。

LuatOS 擴(kuò)展庫是用 Lua 語言實(shí)現(xiàn)的功能模塊

用戶開發(fā)項(xiàng)目軟件時,需要主動加載擴(kuò)展庫文件,才能使用。

目前支持 19 個擴(kuò)展庫

擴(kuò)展庫中的部分功能模塊,我們在后續(xù)文章中都會進(jìn)行講解。

1.2.6 LuatOS demo

LuatOS demo 是在 Lua 標(biāo)準(zhǔn)庫、LuatOS 核心庫、LuatOS 擴(kuò)展庫的基礎(chǔ)上,我們針對獨(dú)立的應(yīng)用場景,開發(fā)的示例代碼合集。

展示了 Lua 標(biāo)準(zhǔn)庫、LuatOS 核心庫、LuatOS 擴(kuò)展庫的實(shí)際應(yīng)用方法,可以幫助開發(fā)者快速驗(yàn)證硬件功能、理解各種庫的 API 使用場景,開發(fā)者可以參考這些 demo 快速開發(fā)自己的項(xiàng)目。

1.2.7 LuaOS project

LuatOS project 是基于功能相對完整的硬件(例如整機(jī)開發(fā)板,硬件上集成了模組或者工業(yè)引擎,lcd,tp,攝像頭,485 接口,矢量字庫芯片,tf 卡,以太網(wǎng)等),開發(fā)的完整項(xiàng)目軟件。開發(fā)者可以參考這種硬件和項(xiàng)目軟件,更加快速地開發(fā)自己的項(xiàng)目。

1.2.8 用戶開發(fā)的 LuaOS 項(xiàng)目應(yīng)用軟件

用戶開發(fā)的 LuatOS 項(xiàng)目應(yīng)用軟件,是指開發(fā)者基于自己的實(shí)際項(xiàng)目需求(可以是演示 demo 需求,也可以是真實(shí)的項(xiàng)目需求),使用 Lua 腳本語言,調(diào)用 Lua 標(biāo)準(zhǔn)庫、LuatOS 核心庫、LuatOS 擴(kuò)展庫編寫的項(xiàng)目應(yīng)用腳本代碼。

用戶開發(fā)的 LuatOS 項(xiàng)目應(yīng)用軟件,我們開發(fā)的 LuatOS demo, LuatOS project,這三種軟件都屬于 LuatOS 項(xiàng)目應(yīng)用軟件,區(qū)別在于 demo 和 project 是我們開發(fā)的,另外一種是我們的用戶開發(fā)的。

1.3 LuatOS 項(xiàng)目應(yīng)用軟件(hello_luatos)開發(fā)調(diào)試過程

了解了 LuatOS 軟件構(gòu)成之后,我們接下來以一個 hello_luatos 項(xiàng)目為例,先總體看下用戶如何開發(fā)調(diào)試自己的項(xiàng)目應(yīng)用軟件。

再來回顧一下 LuatOS 軟件架構(gòu)圖:

用戶開發(fā)的項(xiàng)目應(yīng)用軟件,位于架構(gòu)圖中的最上層(也就是黃色背景的這一層);

開發(fā)用戶項(xiàng)目應(yīng)用軟件時,需要調(diào)用 Lua 標(biāo)準(zhǔn)庫、LuatOS 核心庫、LuatOS 擴(kuò)展庫來實(shí)現(xiàn)。

hello_luatos 項(xiàng)目應(yīng)用軟件的需求為:每隔一秒鐘通過日志輸出一次 Hello, LuatOS。

1.3.1 根據(jù)項(xiàng)目需求編寫項(xiàng)目應(yīng)用軟件代碼

開發(fā)項(xiàng)目軟件使用的編程語言為 Lua 腳本語言,編寫的腳本文件后綴為.lua;

代碼編寫工具推薦使用 Visual Studio Code;

hello_luatos 的項(xiàng)目軟件代碼我已經(jīng)編寫好了,源碼已經(jīng)提交到:https://gitee.com/openLuat/LuatOS/tree/master/module/Air8000/demo/luatos_framework/hello_luatos

打開這個路徑,可以看到,一共包含如下三個文件:

wKgZPGl4WkuAXLyRAAAQBJww2vw844.png

在這里我們先簡單的看下這三個文件的核心內(nèi)容,詳細(xì)內(nèi)容在后續(xù)章節(jié)會細(xì)細(xì)講解。

1、main.lua:這個文件的作用大家可以看文件頭注釋,是一個入口文件,類似于 C 語言的 main 函數(shù),LuatOS 項(xiàng)目軟件代碼是從 main.lua 開始執(zhí)行的,所以 main.lua 文件必須存在,并且文件名也必須是 main.lua

wKgZO2l4WiuAO034AAAqNPba9Rs898.png

在 main.lua 中還有一行代碼,如下圖所示,加載 hello_luatos 應(yīng)用功能模塊,也就是說執(zhí)行到這行代碼時,就會加載并且運(yùn)行 hello_luatos.lua 這個文件;和 main.lua 不同的是,hello_luatos.lua 的文件名可以根據(jù)自己的應(yīng)用功能模塊含義自定義(只要定義的文件名和 Lua 標(biāo)準(zhǔn)庫、LuatOS 核心庫、LuatOS 擴(kuò)展庫不要重復(fù)就行),文件名修改后,在 main.lua 中 require 新的文件名即可;例如文件名修改為 hello_air8000.lua,在 main.lua 中 require "hello_air8000"即可。

wKgZPGl4WomAW-pyAAAIJORqHNo599.png

2、hello_luatos.lua:還是先看下圖中的這個文件頭注釋,重點(diǎn)看下選中的幾行文字描述:

wKgZO2l4WsWANbB3AAA74f1NahA476.png

為什么單獨(dú)創(chuàng)建一個 hello_luatos.lua 去實(shí)現(xiàn)每隔一秒打印一次Hello, LuatOS的業(yè)務(wù)功能呢? 這個是為了應(yīng)用功能設(shè)計(jì)模塊化,不同功能模塊之間解耦,邏輯清晰,代碼閱讀成本低;這個是一個很好的設(shè)計(jì)思想,希望大家以后開發(fā)項(xiàng)目軟件都要遵循。

3、readme.md:這個文件是對當(dāng)前 demo 項(xiàng)目軟件的使用說明,從功能概述、硬件環(huán)境、軟件環(huán)境,操作步驟幾方面說明了當(dāng)前這個 demo 項(xiàng)目如何使用。

wKgZO2l4WyaAT1kUAADWSlY9OG4295.png

1.3.2 LuatOS 軟件的運(yùn)行載體

開發(fā)好 hello_luatos 的項(xiàng)目應(yīng)用軟件代碼后,這個項(xiàng)目完整的 LuatOS 軟件也就完成了;

完整的 LuatOS 軟件 = LuatOS 內(nèi)核固件 + LuatOS 擴(kuò)展庫 + hello_luatos 的項(xiàng)目應(yīng)用軟件;

完整的 LuatOS 軟件需要一個運(yùn)行載體,才能看到軟件的運(yùn)行效果;

目前提供了兩類運(yùn)行載體:

1、硬件核心板或者開發(fā)板:例如 Air8000 的核心板和開發(fā)板,Air780EHM/EHV/EGH/EPM 的核心板和開發(fā)板,Air8101 的核心板和開發(fā)板; 本篇中我們將使用 Air8000 的核心板來運(yùn)行 LuatOS 軟件進(jìn)行演示;

2、PC 模擬:直接可以在電腦上使用 LuatOS 模擬器來運(yùn)行,使用模擬器運(yùn)行有一些限制,模擬器支持的功能并不完整,例如和外設(shè)有關(guān)的功能,lcd,字庫等可能并不支持,但是一些純軟件的功能,例如 LuatOS 運(yùn)行框架,網(wǎng)絡(luò)應(yīng)用等還是可以模擬運(yùn)行的。所以本講的 LuatOS 運(yùn)行框架,我們也會使用 PC 模擬器來進(jìn)行演示,這樣可以節(jié)省硬件載體燒錄軟件所花費(fèi)的時間。

1.3.3 燒錄 完整的 hello_luatos 項(xiàng)目 LuatOS 軟件 到 Air8000 核心板 運(yùn)行

我們先來看下,完整的 hello_luatos 項(xiàng)目的 LuatOS 軟件第一種運(yùn)行載體(以 Air8000 核心板為例)上的運(yùn)行效果。

要將完整的 LuatOS 軟件燒錄到 Air8000 核心板中,并且觀察軟件的運(yùn)行效果,需要用到 Luatools 下載調(diào)試工具;

在這里我就不詳細(xì)說明 Luatools 的用法了,只重點(diǎn)說明一下燒錄 hello_luatos 這個完整的項(xiàng)目軟件過程中涉及到的一些關(guān)鍵操作:

(1)首先我們要準(zhǔn)備好一塊 Air8000 核心板、一根帶有數(shù)據(jù)傳輸功能的 type-c 接口的 usb 數(shù)據(jù)線、一個 WINDOWS10 以及以上版本操作系統(tǒng)的電腦;

(2)Air8000 核心板正面的供電/充電撥動開關(guān) 撥到供電一端,背面的USB ON/USB OFF撥動開關(guān) 撥到 USB ON 一端;

(3)type-c 接口的 usb 數(shù)據(jù)線連接 Air8000 核心板和電腦;

(4)打開 Luatools,勾選 4G 模塊 USB 打印,點(diǎn)擊項(xiàng)目管理測試,創(chuàng)建一個項(xiàng)目,選擇好 LuatOS 內(nèi)核固件,hello_luatos 的應(yīng)用軟件腳本代碼,勾選 USB BOOT 下載,然后點(diǎn)擊 下載底層和腳本 按鈕;

wKgZPGl4XEaAIUYQAAB7U_VsqMU825.pngwKgZO2l4XGOAMhvVAABqwfWp8Oo465.png


此時如果 Air8000 核心板已經(jīng)處于開機(jī)運(yùn)行狀態(tài),則等一段時間,會自動開始燒錄軟件; 如果處于開機(jī)運(yùn)行狀態(tài)下,沒有自動燒錄軟件,則用手按住核心板正面的下載按鈕不放開,然后再按一下核心板的復(fù)位按鈕就可以開始燒錄軟件; 如果沒有處于開機(jī)運(yùn)行狀態(tài),則用手按住核心板正面的下載按鈕不放開,然后再長按開機(jī)按鈕 2 秒,就可以開始燒錄軟件; 出現(xiàn)如下圖所示的下載完成狀態(tài)就表示燒錄成功:

wKgZPGl4XI-AR61LAAAhZXNmixI697.png

下載成功后,Air8000 核心板自動開機(jī)運(yùn)行,在 Luatools 主界面的日志窗口就可以看到運(yùn)行日志,我們可以看到大約每隔 1 秒鐘,日志輸出一次 Hello, LuatOS

wKgZO2l4XLaAN-N1AAA8WXHJ1qg574.png

下面我來實(shí)際演示這個過程。

1.3.4 使用 PC 模擬器 運(yùn)行 完整的 hello_luatos 項(xiàng)目 LuatOS 軟件

打開 Luatools,點(diǎn)擊 賬戶-> 打開資源下載 的菜單

wKgZO2l4XOmAc8o1AAAeicw2Uw0745.png

會彈出 Luatools 資源管理窗口

wKgZO2l4XQiACEHrAAB7QQ8sW6E116.png

勾選公共資源->LuatOS 的 PC 模擬器->VXXXX 版本下的默認(rèn)資源,然后點(diǎn)擊右上角的開始下載(非刷機(jī))按鈕;

下載成功后,點(diǎn)擊右上角的打開本地資源目錄按鈕,resource 目錄下的LuatOS_PC就是 LuatOS 的 PC 模擬器,找到最新版本的壓縮包,解壓縮之后,打開解壓縮后的目錄,有如下文件:

wKgZO2l4XVSAFbM3AAANOOFA66w059.png

其中 luatos-pc.exe 就是模擬器的可執(zhí)行文件,luatos.bat 就是運(yùn)行模擬器的批處理文件。

在這個目錄下,創(chuàng)建一個 cmd 命令行窗口的快捷方式,如下圖所示:

wKgZPGl4XXaAErqRAAAQ4rtJ9VE681.png

雙擊 cmd 命令行窗口,然后輸入下面一行命令,運(yùn)行 luatos 批處理文件,同時輸入要運(yùn)行的 luatos 項(xiàng)目配置文件

luatos --llt=H:Luatoolsprojectluatos_framework_hello_luatos_Air8000.ini

然后按回車鍵,就可以運(yùn)行 hello_luatos 項(xiàng)目軟件;

這行命令的前半部分固定為luatos --llt=,表示要加載 Luatools 創(chuàng)建的項(xiàng)目配置文件,根據(jù)配置文件找到所有的應(yīng)用腳本文件來運(yùn)行;

這行命令的后半部分為使用 Luatools 創(chuàng)建的 hello_luatos 項(xiàng)目軟件的配置文件的絕對路徑,根據(jù)你自己的實(shí)際情況進(jìn)行填寫;配置文件都存儲在 Luatools 目錄下的 project 目錄;

模擬器運(yùn)行后的效果如下圖所示:

wKgZO2l4Xa-ALFcRAACv2LmPwXY979.png

我們可以看到:每隔 1 秒鐘,日志輸出一次 Hello, LuatOS

下面我來實(shí)際演示下這個過程。

1.4 LuatOS 項(xiàng)目應(yīng)用軟件(hello_luatos)的運(yùn)行邏輯詳解

在使用 Air8000 核心板和模擬器實(shí)際運(yùn)行 hello_luatos 項(xiàng)目軟件之后,大家對這個項(xiàng)目實(shí)現(xiàn)的功能應(yīng)該是比較清楚了;

接下來,我用 Visual Studio Code,打開 hello_luatos 的項(xiàng)目應(yīng)用腳本 main.lua 和 hello_luatos.lua,逐行分析應(yīng)用邏輯的運(yùn)行過程。

在分析腳本代碼之前,先來看看簡化后的下面這張圖,這張圖說明了 LuatOS 項(xiàng)目應(yīng)用軟件的總體運(yùn)行過程

wKgZO2l4Xe6AAa-RAAH46s7p-l8055.png

從這張圖可以看出,在 LuatOS 內(nèi)核固件中有一個 FreeRTOS,F(xiàn)reeRTOS 運(yùn)行起來之后,創(chuàng)建了很多任務(wù),有軟件定時器任務(wù),TCP/IP 協(xié)議棧任務(wù),文件系統(tǒng)任務(wù),Lua 虛擬機(jī)任務(wù)等。

其中 Lua 虛擬機(jī)任務(wù)和 LuatOS 項(xiàng)目的應(yīng)用軟件關(guān)系最為密切;

Lua 虛擬機(jī)任務(wù)運(yùn)行起來之后,經(jīng)過必要的初始化動作,就會去尋找 main.lua 腳本文件,找到之后,從 main.lua 的第一行代碼開始解析執(zhí)行,main.lua 會執(zhí)行必要的初始化動作并且加載運(yùn)行其他的 Lua 腳本應(yīng)用功能模塊,main.lua 的最后一行代碼為 sys.run(),sys.run()是一個 while true 的循環(huán)函數(shù),實(shí)際上也是 Lua 虛擬機(jī)任務(wù)的處理函數(shù);

在這個 while 循環(huán)里面,不斷的分發(fā)處理各種消息,調(diào)度 LuatOS 項(xiàng)目應(yīng)用軟件的正常運(yùn)行。

在理解了 LuatOS 項(xiàng)目應(yīng)用軟件的基本運(yùn)行邏輯之后,接下來,我們一起來看下 hello_luatos 的項(xiàng)目應(yīng)用腳本 main.lua 和 hello_luatos.lua 的運(yùn)行過程。

hello_luatos 的應(yīng)用邏輯比較簡單,并且代碼中的注釋也比較詳細(xì),我們就邊看代碼邊講解。

二、全面認(rèn)識 LuatOS 運(yùn)行框架如何使用

我們在上一小節(jié)中分析了 hello_luatos 的應(yīng)用腳本代碼,雖然 hello_luatos 這個項(xiàng)目很小,但是我們已經(jīng)接觸到了:

1、LuatOS 中的兩個核心概念:任務(wù)和定時器;

2、LuatOS 的調(diào)度器:sys.run()函數(shù)

3、LuatOS 的一個核心庫 sys

從本章開始,我們一起系統(tǒng)性地學(xué)習(xí) LuatOS 運(yùn)行框架如何使用,主要包含以下幾項(xiàng)內(nèi)容:

1、LuatOS 的三個核心概念:任務(wù)(task),消息(message),定時器(timer);

2、LuatOS 的一個調(diào)度器:sys.run()函數(shù)

3、LuatOS 的一個核心庫:sys 核心庫

4、基于一個相對完整的 LuatOS 項(xiàng)目,分析本節(jié)課學(xué)習(xí)到的核心概念,調(diào)度器和核心庫知識,系統(tǒng)理解本節(jié)課的知識在實(shí)際項(xiàng)目中的應(yīng)用方法;

2.1 LuatOS 的任務(wù)(task)

2.1.1 基本概念

2.1.1.1 FreeRTOS task 和 LuatOS task

先來看一下這張圖,和上一張LuatOS項(xiàng)目應(yīng)用軟件的總體運(yùn)行過程圖相比,新增了一段說明文字以及幾個 LuatOS task 示意圖:

在 LuatOS 項(xiàng)目應(yīng)用軟件腳本運(yùn)行過程中,只要代碼可以被執(zhí)行到,就可以調(diào)用 sys.taskInit 和 sys.taskInitEx 兩個核心庫的 API,創(chuàng)建 LuatOS 的 task。

wKgZO2l4XsmADuBYAAJitibt_5I213.png

從這張圖中我們可以看到,有兩種任務(wù):

第一種是 LuatOS 內(nèi)核固件中的任務(wù),也就是 FreeRTOS 創(chuàng)建的任務(wù);這種任務(wù)我們把它命名為 FreeRTOS task;

第二種是 LuatOS 項(xiàng)目應(yīng)用腳本中的任務(wù),也就是用戶腳本代碼中調(diào)用 sys.taskInit 和 sys.taskInitEx 兩個 API 創(chuàng)建的任務(wù),這種任務(wù)我們把它命名為 LuatOS task;嚴(yán)格意義上說,LuatOS task 并不是真正的 task,而是利用了 Lua 中的協(xié)程(coroutine)概念,來等價實(shí)現(xiàn)的一種 task 效果,因?yàn)?task 的受眾面比協(xié)程的受眾面要廣的很多,所以我們在 LuatOS 中,將協(xié)程(coroutine)包裝成了 task 的概念,這樣更容易理解和使用;關(guān)于協(xié)程(coroutine)的知識不屬于本課程講解的范疇,大家有興趣的可以借助網(wǎng)絡(luò)資源自行學(xué)習(xí)。

在這里我使用一個形象的比喻,爭取可以讓大家更加直觀的理解這兩種任務(wù)的聯(lián)系和區(qū)別:

1、有一個森林,這個森林就是 FreeRTOS;

2、森林里生長了很多棵大樹,每一棵大樹都是 FreeRTOS 創(chuàng)建的一個任務(wù),就是 FreeRTOS task;

3、這些大樹分成了兩種:

一種是長出了樹干,樹干上還有很多樹枝,這一種大樹只有一棵,就是 Lua 虛擬機(jī)任務(wù)

一種是只長了光禿禿的樹干,樹干上沒有樹枝,這種大樹有很多,除 Lua 虛擬機(jī)之外,其余所有任務(wù)都是這種大樹

4、Lua 虛擬機(jī)任務(wù)這棵大樹的樹干上長出的所有樹枝,就是一個個的 LuatOS task

根據(jù)以上描述畫一張簡圖如下:

wKgZPGl4X2aAdoXQAAKass2YwnM676.png


第一次接觸 LuatOS 開發(fā)的用戶,對 LuatOS task 的功能特性可能會有誤區(qū),所以在這里我們先對比下 FreeRTOS task 和 LuatOS task 的一些重要的功能特性區(qū)別;

LuatOS 內(nèi)核固件中的任務(wù)(FreeRTOS task)和 LuatOS 項(xiàng)目應(yīng)用腳本中的任務(wù)(LuatOS task)的重要區(qū)別如下:

wKgZPGl4X9yAEzgpAAEQqt6tflY955.png

通過上面這個表格中的文字描述,可能理解的不是很直觀,接下來舉兩個例子,來實(shí)際說明一下 LuatOS task 的特性。

2.1.1.2 LuatOS 的多任務(wù)調(diào)度機(jī)制

第一個例子用來說明 LuatOS task 的協(xié)作式的任務(wù)調(diào)度機(jī)制;

核心代碼片段如下,我們首先分析下這段代碼的業(yè)務(wù)邏輯

wKgZO2l4YCmAc1klAATRPpDLcNs136.png

我們使用 PC 模擬器來實(shí)際運(yùn)行一下這個例子:

我已經(jīng)在 Luatools 工具上創(chuàng)建了一個 luatos_framework_luatos_task_Air8000 項(xiàng)目,并且已經(jīng)把應(yīng)用腳本添加到這個項(xiàng)目下,為了節(jié)省時間,使用模擬器來運(yùn)行一下這個例子來看看實(shí)際的效果:

打開 cmd 命令行窗口,輸入命令

luatos --llt=H:Luatoolsprojectluatos_framework_luatos_task_Air8000.ini

運(yùn)行日志如下

wKgZPGl4a2KATo3lAASae401H7o631.png

下面我們結(jié)合代碼來分析一下關(guān)鍵步驟的運(yùn)行日志;

通過以上代碼和日志的分析,可以知道,在這個程序中有 task1 和 task2 兩個 LuatOS task;

當(dāng) task1 內(nèi)部的代碼運(yùn)行到 sys.wait(500)時,task1 會掛起自己,task1 掛起之后,下一個恢復(fù)運(yùn)行的 task 就是 task2,為什么呢?因?yàn)?task2 掛起時長是 300ms,task1 的掛起時長是 500ms,task1 要等 500ms 之后才能恢復(fù)運(yùn)行,task2 最長只需要等待 300ms 就可以恢復(fù)運(yùn)行,所以接下來肯定是 task2 先恢復(fù)運(yùn)行;

當(dāng) task2 內(nèi)部的代碼運(yùn)行到 sys.wait(300)時,task2 會掛起自己,task2 掛起之后,下一個恢復(fù)運(yùn)行的 task 有可能是 task1,也有可能是 task2,為什么呢?因?yàn)?task2 掛起時長是 300ms,task1 的掛起時長是 500ms;

如果 task1 之前已經(jīng)被掛起的時長超過了 200ms,接下來不到 300ms,task1 就應(yīng)該重新恢復(fù)運(yùn)行,這種情況下,因?yàn)?task2 在 300ms 之后就能恢復(fù)運(yùn)行,所以 task1 就應(yīng)該先恢復(fù)運(yùn)行;

如果 task1 之前已經(jīng)被掛起的時長小于 200ms,接下來超過 300ms,task1 才能重新恢復(fù)運(yùn)行,這種情況下,因?yàn)?task2 在 300ms 之后就能恢復(fù)運(yùn)行,所以 task2 就應(yīng)該先恢復(fù)運(yùn)行;

從這個例子,我們可以看出,task1 和 task2 都會通過 sys.wait(timeout)函數(shù)將自己掛起來實(shí)現(xiàn)不同 task 之間的任務(wù)調(diào)度;

如果我們簡單地方修改一下代碼,如下圖所示,注釋掉黃色背景的一行代碼-- sys.wait(500) :

wKgZO2l4ao-AZukdAATMIzfiD-0725.png

會發(fā)生什么事情呢?在模擬器上實(shí)際運(yùn)行一下看看:

wKgZO2l4axmAblCZAAlWG33KogU154.png

可以看到,task1 一直在運(yùn)行,task2 永遠(yuǎn)得不到運(yùn)行;

因?yàn)?task 沒有優(yōu)先級的概念,task1 首先運(yùn)行,并且 task1 沒有掛起自己,所以 task1 就會一直運(yùn)行,task2 就沒機(jī)會運(yùn)行;

2.1.1.3 LuatOS 的多任務(wù)訪問共享資源方式

第二個例子用來說明 LuatOS task 的多任務(wù)如何使用共享資源;

核心代碼片段如下,我們首先分析下這段代碼的業(yè)務(wù)邏輯

wKgZPGl4bAuAKqp_AAXMYhqqQfg003.png

我們使用 PC 模擬器來實(shí)際運(yùn)行一下這個例子:

我已經(jīng)在 Luatools 工具上創(chuàng)建了一個 luatos_framework_luatos_task_Air8000 項(xiàng)目,并且已經(jīng)把應(yīng)用腳本添加到這個項(xiàng)目下,為了節(jié)省時間,使用模擬器來運(yùn)行一下這個例子來看看實(shí)際的效果:

打開 cmd 命令行窗口,輸入命令

luatos --llt=H:Luatoolsprojectluatos_framework_luatos_task_Air8000.ini

運(yùn)行日志如下

wKgZPGl4bsmAeqn1AAbWo-miTSg057.png

可以看到:

1、task1 中 for 循環(huán)前后,全局共享變量的值增加了 100;

2、task2 中 for 循環(huán)前后,全局共享變量的值增加了 100;

非常有規(guī)律,說明:

1、task1 在 for 循環(huán)執(zhí)行過程中,沒有被其他 task(這個 demo 中就是 task2)打斷;

2、task2 在 for 循環(huán)執(zhí)行過程中,沒有被其他 task(這個 demo 中就是 task1)打斷;

3、如果被打斷了,循環(huán)前后的值就不會增加 100,要比 100 要大;

如果我們簡單地修改一下代碼,如下圖所示,打開黃色背景的兩行代碼 sys.wait(5) :

wKgZO2l4byCAXLmbAANV25ZHCZU343.png

會發(fā)生什么事情呢?在模擬器上實(shí)際運(yùn)行一下看看

wKgZO2l4b2KAQpreAALI2QQFGsE004.png

可以看到:

無論是 task1 還是 task2,都不是增加了 100

1、task1 中 for 循環(huán)前后,全局共享變量的值增加了 200;

2、task2 中 for 循環(huán)前后,全局共享變量的值增加了 199;

說明:

1、task1 在 for 循環(huán)執(zhí)行過程中,task2 插入執(zhí)行了;

2、task2 在 for 循環(huán)執(zhí)行過程中,task1 插入執(zhí)行了;

看到這里,就出現(xiàn)了共享資源沖突的問題,但這個沖突完全是我們自己寫 LuatOS 腳本代碼故意讓他沖突的;

task1 和 task2 在各自的 for 循環(huán)執(zhí)行了 sys.wait(5)語句,執(zhí)行這個語句,就是把自己掛起,讓其他 task 運(yùn)行;

只要我們在編碼時,在一個 task 內(nèi)部操作共享資源過程中,不要主動掛起這個 task,就不會存在共享資源競爭和沖突的問題;

講到這里,大家對 LuatOS task 的概念應(yīng)該有了一個基本的認(rèn)識,接下來我們詳細(xì)看下 LuatOS task 如何使用。

注意事項(xiàng):

若無特別指定是 FreeRTOS task 還是 LuatOS task,后續(xù)本文中的任務(wù)(或者 task)都是指 LuatOS task;

2.1.2 作用

我們首先想一個問題,為什么要有 task 這個概念,task 有什么用呢?

2.1.2.1 編程設(shè)計(jì)更簡單

帶著這個問題,我先來回顧一下 LuatOS 的最初的一次版本演變過程;

在 LuatOS 誕生的前一兩年,應(yīng)該是 2012 年到 2013 年,具體的時間記不清楚了;

當(dāng)時在 LuatOS 中是沒有 task 這個概念的,沒有 task 的話,寫代碼以及邏輯跳轉(zhuǎn)太繁瑣了;

例如有個簡單的指示燈閃爍功能需求:一個指示燈亮 500 毫秒,然后滅 500 毫秒,一直這樣循環(huán);

如果沒有 task,核心代碼片段如下:

wKgZO2l4b-2AEVdBAAF6D5pjUys298.png


在這段代碼中,需要通過定時器不斷地進(jìn)行異步處理,根據(jù)異步處理邏輯代碼運(yùn)行會不斷地發(fā)生跳轉(zhuǎn),設(shè)計(jì)或者閱讀代碼時,用戶的思路也要不斷地跳來跳去,使用起來比較繁瑣;

后來 LuatOS 支持了 task,這個功能需求,使用 task 來實(shí)現(xiàn),核心代碼片段如下:

wKgZO2l4cCuAJAfeAAFQ2_j551A561.png


在一個 task 的處理函數(shù)中線性的直來直去的控制指示燈閃爍,比在非 task 中繞來繞去的控制指示燈閃爍,思路更清晰,邏輯也更簡單。

通過剛才的這個例子,我們基本可以明白,LuatOS 最初設(shè)計(jì) task 時,很重要的一個原因就是為了讓用戶編程設(shè)計(jì)更簡單。

2.1.2.2 其他作用

到后來,隨著 LuatOS task 的應(yīng)用越來越廣泛,LuatOS task 在以下幾方面所起的作用也越來越明顯:

1、實(shí)現(xiàn)多任務(wù)協(xié)作式的并發(fā)執(zhí)行;

2、更簡捷地支持了模塊化解耦設(shè)計(jì);

3、更簡捷的處理異步事件,可以很方便地將異步處理邏輯封裝成同步處理邏輯;

這些作用,在接下來的內(nèi)容中都會講解到,我們繼續(xù)往下看;

2.1.3 創(chuàng)建

2.1.3.1 創(chuàng)建 API

怎么創(chuàng)建一個 task,在 sys 核心庫中提供了兩個 api:

sys.taskInit(task_func, ...)

sys.taskInitEx(task_func, task_name, non_targeted_msg_cbfunc, ...)

這兩個 api 分別創(chuàng)建兩種 task,首先我們給這兩種 task 起個名字,一種叫基礎(chǔ) task,一種叫高級 task;

sys.taskInit(task_func, ...)創(chuàng)建的 task 是基礎(chǔ) task;

sys.taskInitEx(task_func, task_name, non_targeted_msg_cbfunc, ...)創(chuàng)建的 task 是高級 task;

從設(shè)計(jì)原理的角度來看,基礎(chǔ) task 和高級 task 的區(qū)別是:

(1) 所有的基礎(chǔ) task 共享一個全局消息隊(duì)列;

(2) 每個高級 task 都有自己獨(dú)立的消息隊(duì)列,同時又能使用全局消息隊(duì)列;

從用戶使用的角度來看,基礎(chǔ) task 和高級 task 的區(qū)別是:

(1) 基礎(chǔ) task 如果阻塞功能使用不當(dāng),可能會丟失自己應(yīng)該處理的消息;

(2) 高級 task 如果阻塞功能使用不當(dāng),不會丟失自己應(yīng)該處理的消息;

雖然從設(shè)計(jì)原理來看,高級 task 比基礎(chǔ) task 使用起來不容易犯錯;

但是由于基礎(chǔ) task 使用起來簡潔,基礎(chǔ) task 還是需要掌握,一旦掌握之后,也不容易犯錯;

接下來我們先看下這兩個 api 的說明,結(jié)合說明再運(yùn)行一些實(shí)際的例子,來理解如何創(chuàng)建 task;

sys.taskInit(task_func, ...)

功能:創(chuàng)建并且啟動運(yùn)行一個基礎(chǔ) task;這里表述的是基礎(chǔ) task,既然有基礎(chǔ) task,肯定還會有另外一種 task,在這里我們先不展開說明,后續(xù)的小節(jié)內(nèi)容會講到。

注意事項(xiàng):

1、可以在能夠執(zhí)行到的任意代碼位置使用此函數(shù);關(guān)于這一點(diǎn),我們在這里先不展開講,等到下一小節(jié)再展開;

2、在 LuatOS 中,對創(chuàng)建的 task 數(shù)量沒有特別限制,只要 ram 夠用,可以一直創(chuàng)建;不同的硬件,用戶可用 RAM 資源也不同,例如:

(1) Air8000 系列的用戶可用 RAM 為 4MB

(2) Air780 系列中 Air780EPM 用戶可用 RAM 為 1MB,Air780EHM/EHV/EGH 用戶可用 RAM 為 4MB

(3) Air8101 系列的用戶可用 RAM 為 2MB

(4) PC 模擬器用戶可用 RAM 為 2MB

下面這個例子用來說明如何查看用戶可用 ram 信息;

我們首先分析下這段代碼的業(yè)務(wù)邏輯

wKgZPGl4cZeAPrVVAATDJqVyxbw569.png

我們在模擬器上實(shí)際運(yùn)行一下看看,輸入命令luatos --llt=H:Luatoolsprojectluatos_framework_luatos_task_Air8000.ini

運(yùn)行日志如下

wKgZPGl4cfyAaDxQAAMW3Z5KfI4057.png

通過上圖的這份日志,可以看出,Lua虛擬機(jī)中的用戶可用ram信息:

- 第一個數(shù)值為分配給Lua虛擬機(jī)的總ram為2097144字節(jié),將近2MB字節(jié);這個值在運(yùn)行過程中一直保持不變;

- 第二個數(shù)值為LuatOS應(yīng)用程序運(yùn)行過程中實(shí)時使用的ram,根據(jù)業(yè)務(wù)邏輯會發(fā)生動態(tài)變化,我們演示的這個demo業(yè)務(wù)邏輯比較簡單,所有變化幅度比較小,一直在35KB左右波動;如果業(yè)務(wù)邏輯復(fù)雜,波動幅度就會比較大;

- 第三個數(shù)值為LuatOS應(yīng)用程序運(yùn)行過程中歷史最高使用的ram,我們演示的這個demo中,為37040字節(jié),即歷史最高水位; 平時開發(fā)過程中,大家可以加上這段代碼,實(shí)時觀察下第二個數(shù)值和第三個數(shù)值,如果這兩個數(shù)值頻繁的接近第一個數(shù)值,那就說明你的程序占用的內(nèi)存就比較多了,此時就需要重點(diǎn)分析解決問題,否則很容易就會造成內(nèi)存不足而導(dǎo)致重啟;(一般來說,不會出現(xiàn)這樣的問題;關(guān)于如何分析解決內(nèi)存使用接近上限的問題,后續(xù)會有文章專門來講,今天在這里就不說了)

知道了怎么查看用戶可用ram信息后,我們再來看一個問題,每創(chuàng)建一個基礎(chǔ)task需要占用多少ram資源? 下面這個例子用來說明如何分析一個基礎(chǔ)task占用的ram資源; 這個例子的核心代碼片段如下,我們首先分析下這段代碼的業(yè)務(wù)邏輯

wKgZPGl4cvWAL0RKAAWmZyTokIY562.png

在模擬器上運(yùn)行下面一段代碼實(shí)際看下日志

wKgZO2l4czCAHSQUAAEKULVgH4E457.png

從日志可以看出: led task創(chuàng)建前,Lua ram使用35296字節(jié),led task創(chuàng)建后,Lua ram使用36112字節(jié),led這個task創(chuàng)建并且運(yùn)行需要816字節(jié)的ram; 因?yàn)閘ed task的邏輯很簡單,所以占用的ram較小,如果創(chuàng)建并且運(yùn)行一個邏輯復(fù)雜的task,占用的ram就會變大; 因?yàn)閠ask的ram消耗主要包括兩部分: - 一部分是創(chuàng)建開銷,這個相對較小,每個task的創(chuàng)建開銷基本一樣; - 一部分是運(yùn)行開銷,這個和task內(nèi)部創(chuàng)建的所有局部變量、表、字符串等都有關(guān)系,不同task的消耗不一樣,差別就比較大;

剛才這個例子演示的task很簡單,創(chuàng)建并且運(yùn)行消耗了816字節(jié)的ram; 大家可能會有疑問,這個demo只有這個task是我自己寫的,僅消耗了816字節(jié)的ram,為什么日志中打印的總消耗ram是36112字節(jié)呢?剩余的35KB左右的ram被誰用去了? Lua虛擬機(jī)初始化運(yùn)行時,會創(chuàng)建Lua狀態(tài)機(jī),加載標(biāo)準(zhǔn)庫/核心庫中的函數(shù)名和常量,加載運(yùn)行main.lua以及main.lua中require的其余l(xiāng)ua文件,這些都需要消耗ram,可以說,這些都是LuatOS應(yīng)用程序運(yùn)行的基本開銷,不同的LuatOS應(yīng)用項(xiàng)目,這個基本開銷也不一樣,主要取決于初始化過程中用戶main.lua以及其他應(yīng)用腳本的復(fù)雜度;

了解了用戶可用ram以及每個基礎(chǔ)task大概占用的ram之后,我們再回到原始的問題:在LuatOS中,對創(chuàng)建的task數(shù)量沒有特別限制,只要ram夠用,可以一直創(chuàng)建。 帶著這個問題,我們再回到剛才的日志截圖,以剛才的簡單demo為例,初始化之后,創(chuàng)建用戶的第一個task之前,剩余的可用ram是2097144 - 35296 = 2061848字節(jié),假設(shè)創(chuàng)建并且運(yùn)行每個task需要消耗816字節(jié),則應(yīng)該可以創(chuàng)建 2053120/816 = 2526個task。

下面這個例子用來說明可以創(chuàng)建多少個task; 這個例子的核心代碼片段如下,我們首先分析下這段代碼的業(yè)務(wù)邏輯

wKgZO2l4c42ABbFuAAKv15QnnfU066.png

在模擬器上實(shí)際運(yùn)行一下看看,輸入命令luatos --llt=H:Luatoolsprojectluatos_framework_luatos_task_Air8000.ini

運(yùn)行日志如下

wKgZPGl4c9KAV5r1AAIK39BE2fQ967.png

可以看到,創(chuàng)建了2366個task之后,就提示memory不足,報(bào)錯了; 和計(jì)算的理論值2526個有一定誤差,這個在可以接受的范圍內(nèi),一方面是因?yàn)榇嬖趦?nèi)存碎片(上圖的日志還有2097144-2042032=55112字節(jié)的ram無法使用,如果這部分ram可以利用,還能再多創(chuàng)建55112/816=67個task),另一方面可能還會存在一些我們沒考慮到的其他一些地方需要消耗比較少的ram; 至此,大家應(yīng)該理解了在LuatOS中,對創(chuàng)建的task數(shù)量沒有特別限制,只要ram夠用,可以一直創(chuàng)建所表達(dá)的意思。

參數(shù)

task_func

wKgZPGl4dDiAZuVKAAJha8vrujo338.png


下面這個例子用來說明_task_func 參數(shù)的一種典型的錯誤使用方式_;

核心代碼片段如下,我們首先分析下這段代碼的業(yè)務(wù)邏輯

wKgZPGl4dGeAAMF0AAJb-aIT7HA192.png


在模擬器上實(shí)際運(yùn)行一下看看,輸入命令

luatos --llt=H:Luatoolsprojectluatos_framework_luatos_task_Air8000.ini

運(yùn)行日志如下

wKgZO2l4dKuAOD1ZAABPcCs3yu4822.png

黃色背景提示出現(xiàn)的錯誤是指:創(chuàng)建 task 的時候,傳入的第一個參數(shù)期望是 function 類型,但是實(shí)際卻傳入了 nil 類型;

為什么會出現(xiàn)這個錯誤呢,和 Lua 語言的解析執(zhí)行順序有關(guān);

Lua 解析器是按照從上到下的順序解析執(zhí)行代碼的,首先執(zhí)行第 1 行的sys.taskInit(led_task_func),執(zhí)行到這里發(fā)現(xiàn)led_task_func變量不存在,所以就報(bào)錯;因?yàn)榇藭r還沒有執(zhí)行過第 3 行;犯的錯誤是:先使用后定義。

怎么解決這個問題呢?有兩種方式:

一種方式是還是在同一個 lua 文件中,參考以下代碼,調(diào)整一下 led_task_func 函數(shù)定義和使用的順序,先定義,再使用就沒問題

wKgZO2l4dOiAK1ufAADGmUK4J20013.png

另一種方式是以模塊化的方式創(chuàng)建多個 lua 文件,函數(shù)定義和聲明放到一個單獨(dú)的 lua 庫文件中,函數(shù)使用放到另一個 lua 應(yīng)用文件中,在應(yīng)用文件中,加載庫文件,就可以使用庫文件的函數(shù);這種方式我們在這里就不介紹了,后續(xù)有完整的項(xiàng)目 demo,大家可以參考;

這兩種解決方式,本質(zhì)上,采用的都是:先定義后使用的思路。

...

參數(shù)含義:task的處理函數(shù)攜帶的可變參數(shù);

數(shù)據(jù)類型:任意數(shù)據(jù)類型;

取值范圍:無特別限制;

是否必選:可選傳入此參數(shù);

注意事項(xiàng):是Lua語言的一種語法,表示可變參數(shù),參數(shù)數(shù)量可以是0個,1個,2個,...,多個;

參數(shù)示例:

不傳入,或者bool類型的true,或者number類型的2,或者string類型的"led",或者table類型的{name="LuatOS", password="123456"}等等等等;

總之,任何數(shù)據(jù)類型的任何自定義內(nèi)容都行;

下面這個例子用來說明_可變參數(shù)...的使用方式_;

核心代碼片段如下,我們首先分析下這段代碼的業(yè)務(wù)邏輯

wKgZO2l4eciAcsuaAANyASFg17o347.png


在模擬器上實(shí)際運(yùn)行一下看看,輸入命令

luatos --llt=H:Luatoolsprojectluatos_framework_luatos_task_Air8000.ini

運(yùn)行日志如下

wKgZPGl4efiAeV0RAADaEH-tQlo465.png

從日志可以看出,sys.taskInit(led_task_func, "arg1", 3, nil, true, led_task_func) 的可變參數(shù),"arg1", 3, nil, true, led_task_func 按照順序傳遞給了任務(wù)處理函數(shù) led_task_func;

返回值

local task_object = sys.taskInit(task_func, ...)

有一個返回值 task_object

task_object

含義說明:創(chuàng)建的task對象;如果為thread類型,表示創(chuàng)建成功;如果為nil類型,表示創(chuàng)建失敗;

數(shù)據(jù)類型:thread或者nil;

取值范圍:無特別限制;

注意事項(xiàng):

雖然這個函數(shù)有返回值,但是這個返回值在整個LuatOS系統(tǒng)中,沒有應(yīng)用場景;

所以不用深入了解這個返回值的用途;

sys.taskInitEx(task_func, task_name, non_targeted_msg_cbfunc, ...)

功能

創(chuàng)建并且啟動運(yùn)行一個高級 task;剛才我們看過的 api,sys.taskInit 是創(chuàng)建一個基礎(chǔ) task;在這里,sys.taskInitEx 是創(chuàng)建一個高級 task,看到這里,我們接觸到了兩種 task:基礎(chǔ) task 和高級 task,等我們把這個 api 看完,再總結(jié)下基礎(chǔ) task 和高級 task 的區(qū)別;

sys.taskInitEx這個 api 和sys.taskInit這個 api 的區(qū)別主要體現(xiàn)在task_name, non_targeted_msg_cbfunc這兩個參數(shù)上,其余內(nèi)容完全一樣;

所以我們接下來我們重點(diǎn)看一下這兩個參數(shù),其余內(nèi)容就簡單的過一遍;

注意事項(xiàng)

1、可以在能夠執(zhí)行到的任意代碼位置使用此函數(shù);關(guān)于這一點(diǎn),我們在這里先不展開講,等到下一小節(jié)再展開;

2、在 LuatOS 中,對創(chuàng)建的 task 數(shù)量沒有特別限制,只要 ram 夠用,可以一直創(chuàng)建;這一點(diǎn)在介紹 sys.taskInit 時已經(jīng)講過,內(nèi)容完全一樣,這里就不重復(fù)介紹了;

參數(shù)

task_func

wKgZPGl4esaAAYFMAANUqNCMKOA650.png

task_name

參數(shù)含義:task的名稱;

數(shù)據(jù)類型:推薦string類型(雖然number類型也行,但是不好理解,不要使用);

取值范圍:任意string類型的字符串都行,無特別限制;

是否必選:必須傳入此參數(shù);

注意事項(xiàng):

在一個的LuatOS項(xiàng)目中,創(chuàng)建的所有高級task的task_name不能重復(fù);

目前在核心庫中沒有檢查是否重復(fù),需要用戶自行保證;

參數(shù)示例:"LED_TASK"、"GPIO_TASK"等任意自定義的字符串;

task_name 這個參數(shù),字面意思來看,表示 task 的名稱;實(shí)際上,在 sys 核心庫內(nèi),會根據(jù)這個 task_name 創(chuàng)建一個資源表,資源表中有一個消息隊(duì)列,這個消息隊(duì)列就可以看做是這個高級 task 專有的消息隊(duì)列;所以說,當(dāng)一個高級 task 有了自己的消息隊(duì)列,誰都可以發(fā)送定向消息到這個 task 的消息隊(duì)列中,提高了消息處理的效率;

我們再看一下創(chuàng)建基礎(chǔ) task 的 api,sys.taskInit(task_func, ...),沒有 task_name,在 sys 核心庫內(nèi),不會為基礎(chǔ) task 創(chuàng)建獨(dú)立的消息隊(duì)列。雖然如此,但是會創(chuàng)建一個全局的消息隊(duì)列,所有的基礎(chǔ) task 和高級 task 都會共享使用這一個全局消息隊(duì)列。

現(xiàn)在大家對基礎(chǔ) task,高級 task,獨(dú)立消息隊(duì)列,全局消息隊(duì)列先有一個基本的認(rèn)識,后續(xù)我們還會通過示例以及文字描述來做進(jìn)一步總結(jié)。

non_targeted_msg_cbfunc

wKgZPGl4fIOABbRiAAY9k-d19es344.png

這個參數(shù)的意思是:當(dāng)一個高級 task 在讀取自己的獨(dú)立消息隊(duì)列中的某一種消息時,發(fā)現(xiàn)讀出來的消息不是自己期望的消息,則直接把這個消息丟給_non_targeted_msg_cbfunc_處理。

下面這個例子用來說明如何使用;

這個例子的核心代碼片段如下,我們首先分析下這段代碼的業(yè)務(wù)邏輯

wKgZPGl4fMCASSb8AA4VEdyqwlM560.png

我們在模擬器上實(shí)際運(yùn)行一下看看,輸入命令

luatos --llt=H:Luatoolsprojectluatos_framework_luatos_task_Air8000.ini

運(yùn)行日志如下:

wKgZPGl4fPiAWjUpAAFvnbuk938166.png


紅色背景的日志為非目標(biāo)消息回調(diào)函數(shù)的運(yùn)行邏輯;

綠色背景的日志為目標(biāo)消息的運(yùn)行邏輯;

從日志可以看出,在高級 task 內(nèi)部使用 sys.waitMsg 等待目標(biāo)消息(消息名為"MQTT_EVENT")時,如果收到了非目標(biāo)消息,都給非目標(biāo)消息回調(diào)函數(shù) mqtt_client_main_cbfunc 去處理了。

參數(shù)含義:task的處理函數(shù)攜帶的可變參數(shù);

數(shù)據(jù)類型:任意數(shù)據(jù)類型;

取值范圍:無特別限制;

是否必選:可選傳入此參數(shù);

注意事項(xiàng):...是Lua語言的一種語法,表示可變參數(shù),參數(shù)數(shù)量可以是0個,1個,2個,...,多個;

參數(shù)示例:

不傳入,或者boolean類型的true,或者number類型的2,或者string類型的"led",或者table類型的{name="LuatOS", password="123456"}等等等等;

總之,任何數(shù)據(jù)類型的任何自定義內(nèi)容都行;

返回值

local task_object = sys.taskInitEx(task_func, task_name, non_targeted_msg_cbfunc, ...)

有一個返回值 task_object

task_object

含義說明:表示創(chuàng)建的task對象;如果為thread類型,表示創(chuàng)建成功;如果為nil類型,表示創(chuàng)建失??;

數(shù)據(jù)類型:thread或者nil;

取值范圍:無特別限制;

注意事項(xiàng):雖然這個函數(shù)有返回值,但是這個返回值在整個LuatOS系統(tǒng)中,沒有應(yīng)用場景,所以不用深入了解這個返回值的用途;

基礎(chǔ) task 和高級 task 的區(qū)別

現(xiàn)在我們回顧一下剛才講的兩個 api,一個是創(chuàng)建基礎(chǔ) task,一個是創(chuàng)建高級 task;

在這里我們對基礎(chǔ) task 和高級 task 先做一個簡單的總結(jié):

task分為基礎(chǔ)task和高級task兩種;

從設(shè)計(jì)原理的角度來看,基礎(chǔ)task和高級task的區(qū)別是:

(1) 所有的基礎(chǔ)task共享一個全局消息隊(duì)列;

(2) 每個高級task都有自己獨(dú)立的消息隊(duì)列,同時又能使用全局消息隊(duì)列;

從用戶使用的角度來看,基礎(chǔ)task和高級task的區(qū)別是:

(1) 基礎(chǔ)task如果阻塞功能使用不當(dāng),可能會丟失自己應(yīng)該處理的消息;

(2) 高級task如果阻塞功能使用不當(dāng),不會丟失自己應(yīng)該處理的消息;

雖然從設(shè)計(jì)原理來看,高級task比基礎(chǔ)task使用起來不容易犯錯;

但是由于基礎(chǔ)task使用起來簡潔,基礎(chǔ)task還是需要掌握,一旦掌握之后,也不容易犯錯;

sys核心庫提供的task管理功能有以下幾種:

(1) 基礎(chǔ)task的創(chuàng)建和啟動運(yùn)行:sys.taskInit(task_func, ...)

(2) 高級task的創(chuàng)建和啟動運(yùn)行:sys.taskInitEx(task_func, task_name, non_targeted_msg_cbfunc, ...)


在這里,大家對這兩種 task 的區(qū)別有一個基本的認(rèn)識就行,在后續(xù)消息章節(jié),我們還會進(jìn)一步深入講解;

2.1.3.2 什么時間創(chuàng)建

理解了怎么創(chuàng)建 task 之后,我們來看下一個問題;

在什么時間點(diǎn)創(chuàng)建 task,在代碼的什么位置創(chuàng)建 task?

task 的創(chuàng)建時間點(diǎn)非常靈活,例如:

1、每個應(yīng)用功能模塊初始化時,可以創(chuàng)建 task;

2、檢測到某個事件發(fā)生時,例如 4G 模組檢測到插入了一張 sim 卡時,可以創(chuàng)建 task;

3、在一個 task 的處理函數(shù)中運(yùn)行時,可以創(chuàng)建另外一個 task;

4、......等等各種場景

總結(jié)下來只有一句話:只要寫的一段代碼能被執(zhí)行到,在這段代碼中就可以使用 api 創(chuàng)建 task。

那是不是意味著,在項(xiàng)目應(yīng)用軟件代碼的任何位置都能創(chuàng)建 task,也并不是,只有一個例外;

在 main.lua 中 sys.run()之后不能寫代碼創(chuàng)建 task;

為什么有這個限制,我們在 1.4 章節(jié),基于 hello_luatos 講解 LuatOS 應(yīng)用軟件的運(yùn)行邏輯時已經(jīng)講到,我們再根據(jù)下面這張圖以及 hello_luatos 代碼來簡單的回顧一下:

wKgZO2l4f06ADxONAAJeFHBoR54665.png

2.1.3.3 應(yīng)用腳本代碼運(yùn)行位置

剛才在學(xué)習(xí) sys.taskInit 和 sys.taskInitEx 這兩個 api 時,有以下兩段話:

只要寫的一段代碼能被執(zhí)行到,在這段代碼中就可以使用這兩個 api 創(chuàng)建 task;

回調(diào)函數(shù)是在 task 之外的業(yè)務(wù)邏輯中被執(zhí)行的; 在回調(diào)函數(shù)內(nèi)部無法使用 sys.wait(timeout)、sys.waitUntil(msg, timeout)、sys.waitMsg(task_name, msg, timeout)等必須用在 task 中的函數(shù);

從這兩段話中,引出一個問題:在 LuatOS 應(yīng)用腳本開發(fā)過程中,我們所編寫的應(yīng)用腳本代碼,存在兩種業(yè)務(wù)運(yùn)行的邏輯環(huán)境:

1、一種是在 task 的任務(wù)處理函數(shù)內(nèi)部的業(yè)務(wù)邏輯環(huán)境中運(yùn)行,我們簡稱為:在 task 內(nèi)部運(yùn)行;

2、一種是在 task 的任務(wù)處理函數(shù)外部的業(yè)務(wù)邏輯環(huán)境中運(yùn)行,我們簡稱為:在 task 外部運(yùn)行;

怎么理解這兩種業(yè)務(wù)邏輯運(yùn)行環(huán)境?我們看下面這張圖

看右邊生長出分支的這棵大樹,這棵大樹就是 FreeRTOS 創(chuàng)建的 Lua 虛擬機(jī) task,是一個 FreeRTOS task;

在這個 Lua 虛擬機(jī) FreeRTOS task 上,這棵大樹再分為兩部分:

1、樹干部分:樹干部分運(yùn)行的業(yè)務(wù)邏輯環(huán)境就是 LuatOS task 外部運(yùn)行環(huán)境;

2、樹枝部分:每個樹枝都是一個獨(dú)立的 LuatOS task,樹枝部分運(yùn)行的業(yè)務(wù)邏輯環(huán)境就是 LuatOS task 內(nèi)部運(yùn)行環(huán)境;

在這里,大家只要知道有 task 內(nèi)部運(yùn)行和 task 外部運(yùn)行兩種環(huán)境即可,后續(xù)我們講解其他功能時,會用到這兩種概念,并且也會結(jié)合示例來說明二者的差別;

wKgZO2l4f8-AAF4MAALlCDYTNzg628.png

2.1.4 狀態(tài)

task 被創(chuàng)建之后,就會按照代碼中設(shè)計(jì)的業(yè)務(wù)邏輯來來運(yùn)行,task 的狀態(tài)有三種:

運(yùn)行狀態(tài),阻塞狀態(tài),死亡狀態(tài);

我們先來看下一個 task 的狀態(tài)機(jī)

wKgZO2l4gAGAHEeFAAFEO_T2PvE958.png

1、調(diào)用 sys.taskInit 或者 sys.taskInitEx 兩個 api 后,創(chuàng)建的 task 就會進(jìn)入運(yùn)行狀態(tài);

2、當(dāng)發(fā)生以下兩種事件中的任意一種,運(yùn)行狀態(tài)切換為死亡狀態(tài),一旦進(jìn)入死亡狀態(tài),這個 task 也就結(jié)束了

(1) task 的任務(wù)處理函數(shù),按照正常的業(yè)務(wù)邏輯已經(jīng)運(yùn)行結(jié)束,正常退出函數(shù);也就是狀態(tài)機(jī)中的 normal exit

(2) task 的任務(wù)處理函數(shù),在運(yùn)行過程中,出現(xiàn)了異常,例如對非 number 類型的變量進(jìn)行數(shù)據(jù)運(yùn)算,把一個 number 類型當(dāng)做 table 來用等等等等,一旦出現(xiàn)類似的異常, 任務(wù)處理函數(shù)就會異常退出;也就是狀態(tài)機(jī)中的 abnormal exit

3、當(dāng)發(fā)生以下三種事件中的任意一種,運(yùn)行狀態(tài)切換為阻塞狀態(tài),一旦進(jìn)入阻塞狀態(tài),就是 task 任務(wù)處理函數(shù)中的代碼把自己給掛起了

(1) 任務(wù)處理函數(shù)中執(zhí)行到 sys.wait(timeout),表示在這里阻塞等待 timeout 毫秒的時間;

(2) 任務(wù)處理函數(shù)中執(zhí)行到 sys.waitUntil(msg, timeout),表示在這里阻塞等待全局的 msg 消息,或者阻塞等待 timeout 毫秒的時間;

(3) 任務(wù)處理函數(shù)中執(zhí)行到 sys.waitMsg(task_name, msg, timeout),表示在這里阻塞等待定向發(fā)給 task_name 的 msg 消息,或者阻塞等待 timeout 毫秒的時間;

4、當(dāng)發(fā)生以下三種事件中的任意一種,阻塞狀態(tài)切換為運(yùn)行狀態(tài)

(1) 任務(wù)處理函數(shù)中執(zhí)行到 sys.wait(timeout),sys.waitUntil(msg, timeout),sys.waitMsg(task_name, msg, timeout)創(chuàng)建的定時器超時時間到達(dá),此時會產(chǎn)生一個定時器到達(dá)事件

(2) 任意代碼處執(zhí)行 sys.publish(msg, ...),發(fā)布了全局 msg 消息,在等待這個全局 msg 消息的所有處于阻塞狀態(tài)的 task 都能依次接收到此消息,并且從阻塞狀態(tài)切換為運(yùn)行狀態(tài)繼續(xù)運(yùn)行

(3) 任意代碼處執(zhí)行 sys.sendMsg(task_name, msg, arg2, arg3, arg4),向名稱為 task_name 的高級 task 發(fā)送了定向 msg 消息,在等待這個定向 msg 消息的并且處于阻塞狀態(tài)的 task 可以接收到此消息,并且從阻塞狀態(tài)切換為運(yùn)行狀態(tài)繼續(xù)運(yùn)行

通過以上的 task 狀態(tài)機(jī)介紹,我們可以看出,運(yùn)行狀態(tài)和阻塞狀態(tài)之間的切換最為復(fù)雜;

這兩種狀態(tài)之間的切換,用到定時器和消息兩種事件,接下來我們來重點(diǎn)看下消息和定時器兩個概念。

2.2 LuatOS的消息(message)

2.2.1 基本概念

LuatOS 的消息機(jī)制是LuatOS task協(xié)作和事件驅(qū)動編程的核心部分,消息機(jī)制包括消息的訂閱、發(fā)布/發(fā)送、接收處理;

消息需要存儲到消息隊(duì)列中,消息隊(duì)列中的消息遵循先進(jìn)先出(FIFO)原則;

根據(jù)消息隊(duì)列的創(chuàng)建位置,把LuatOS 的消息隊(duì)列分為兩種: 內(nèi)核消息隊(duì)列和應(yīng)用消息隊(duì)列;

內(nèi)核消息隊(duì)列是內(nèi)核固件中創(chuàng)建的,應(yīng)用消息隊(duì)列是擴(kuò)展庫腳本和項(xiàng)目應(yīng)用腳本中創(chuàng)建的;

wKgZO2l4gPuAUHRwAAJY7QiAPZ4457.png

根據(jù)消息存儲使用的消息隊(duì)列類型以及消息發(fā)送方的不同,對消息做以下劃分:

wKgZO2l4gTuALjtJAAUMA0xy3IU558.png

2.2.2 內(nèi)核消息

看下面這張圖,黃色背景的就是內(nèi)核消息隊(duì)列;

FreeRTOS創(chuàng)建的每一個task都有一個消息隊(duì)列,這種消息隊(duì)列就叫內(nèi)核消息隊(duì)列;

內(nèi)核消息隊(duì)列是FreeRTOS直接管理的,存儲每個FreeRTOS task的內(nèi)核消息的隊(duì)列;

內(nèi)核消息的發(fā)送,由內(nèi)核固件自行處理,不開放給LuatOS用戶使用;

wKgZO2l4gXiAOU0WAALpcxok11M910.pngwKgZPGl4gaCAXdvyAAH6RaZfFB4517.png

從內(nèi)核消息的接收處理方式來劃分,內(nèi)核消息可以分為定時器消息和非定時器消息兩大類

2.2.2.1 非定時器消息

在LuatOS的應(yīng)用腳本程序中,針對某一種消息或者某一類消息,注冊回調(diào)函數(shù),當(dāng)內(nèi)核固件中的驅(qū)動程序或者FreeRTOS的task給FreeRTOS的Lua虛擬機(jī)task發(fā)送消息后,在sys.run()調(diào)度器中會讀取內(nèi)核消息隊(duì)列中的消息,讀到消息后,直接執(zhí)行注冊的回調(diào)函數(shù);在這里我舉兩個例子,同時結(jié)合上面的兩張圖說明一下這個過程:

第一個例子是串口接收數(shù)據(jù)處理,核心代碼如下:

wKgZPGl4geKAF5jCAAII-mw9N6A896.png

第二個例子是mqtt客戶端異步消息處理,核心代碼如下:

wKgZPGl4gg6AA019AAN7UyfvDJw237.png

除了上面列舉的這兩種非定時器消息的uart,mqtt內(nèi)核消息處理之外,LuatOS應(yīng)用腳本開發(fā)中,還會用到很多種類似的內(nèi)核消息處理邏輯,例如socket異步消息處理,http異步消息處理等等,這些內(nèi)核消息的處理邏輯都是完全一樣,只要注冊一個回調(diào)處理數(shù)據(jù)即可,后續(xù)我們講解具體的功能模塊時,會詳細(xì)講解;

2.2.2.2 定時器消息

第二類內(nèi)核消息是定時器消息,和第一類的內(nèi)核消息相比,定時器消息不僅僅可以通過注冊回調(diào)函數(shù)來處理,還能控制task的阻塞和運(yùn)行,所以定時器消息功能更加強(qiáng)大,使用起來更加靈活,定時器內(nèi)容在后續(xù)章節(jié)會重點(diǎn)講解,在這里就暫時跳過了;

2.2.3 應(yīng)用消息

應(yīng)用消息隊(duì)列由Lua虛擬機(jī)內(nèi)部管理,應(yīng)用消息隊(duì)列中的消息訂閱,發(fā)布/發(fā)送,和接收處理,LuatOS用戶可以直接控制;

應(yīng)用消息隊(duì)列中的消息,和LuatOS應(yīng)用腳本程序的關(guān)系最為密切,本章節(jié),我們將重點(diǎn)學(xué)習(xí)應(yīng)用消息隊(duì)列中的消息如何使用;

應(yīng)用消息隊(duì)列又可以進(jìn)一步劃分為全局消息隊(duì)列和定向消息隊(duì)列:

全局消息隊(duì)列中存儲的全局消息有系統(tǒng)全局消息用戶全局消息

定向消息隊(duì)列中存儲的定向消息有系統(tǒng)定向消息用戶定向消息;

2.2.3.1 系統(tǒng)全局消息

系統(tǒng)全局消息是內(nèi)核固件的C代碼中調(diào)用sys.publish接口發(fā)布的消息,例如"IP_READY";

LuatOS用戶在腳本程序中可以使用sys.subscribe和sys.waitUntil訂閱和接收處理;

這部分消息牽涉到的功能模塊比較多,,有個簡單的認(rèn)識就行,這些系統(tǒng)全局消息,在后續(xù)的LuatOS課程中,每講到一個主題,都會講解對應(yīng)的系統(tǒng)全局消息;

在本章節(jié)就不細(xì)講了。

2.2.3.2 系統(tǒng)定向消息

系統(tǒng)定向消息是內(nèi)核固件的C代碼中調(diào)用sys.sendMsg接口發(fā)布的消息,例如socket.EVENT;

LuatOS用戶在腳本程序中可以使用sys.waitMsg接收處理;

但是由于這部分內(nèi)容和內(nèi)核固件的耦合比較大,一般來說都是LuatOS的擴(kuò)展庫去使用,目前只有在socket功能有關(guān)的擴(kuò)展庫中使用過系統(tǒng)定向消息,用戶編寫的Lua應(yīng)用腳本程序中很少用到,所以在這里也不細(xì)講了;

在后續(xù)的LuatOS課程中,每講到一個主題,如果有對應(yīng)的系統(tǒng)定向消息,我們再逐一講解;

接下來我們重點(diǎn)看下用戶全局消息和用戶定向消息:

從消息接收處理方的角度來劃分,消息可以分為全局消息和定向消息;

全局消息是指消息可以被任意訂閱方接收處理;

定向消息是指消息只能被指定的task接收處理;

sys核心庫提供的全局消息管理功能有以下幾種:

(1) 全局消息發(fā)布:sys.publish(msg, ...)

(2) 全局消息訂閱:sys.subscribe(msg, msg_cbfunc)

(3) 全局消息取消訂閱:sys.unsubscribe(msg, msg_cbfunc)

(4) 全局消息阻塞等待(只能在task中使用):sys.waitUntil(msg, timeout)

sys核心庫提供的定向消息管理功能有以下幾種:

(1) 定向消息發(fā)布:sys.sendMsg(task_name, msg, arg2, arg3, arg4)

(2) 定向消息阻塞等待(只能在task中使用):sys.waitMsg(task_name, msg, timeout)

(3) 定向消息清除:sys.cleanMsg(task_name)


2.2.3.3 用戶全局消息

用戶全局消息處理的完整周期

用戶全局消息處理的完整周期包括以下幾部分:

wKgZO2l4g5-ATsOqAAJ-7JWDRTM032.pngwKgZPGl4g7OAKFbcAAH7Mo2bLQU890.png

1、消息訂閱:有兩個api可以訂閱全局消息

sys.subscribe(msg, cbfunc):為全局消息msg訂閱一個回調(diào)函數(shù)cbfunc;相當(dāng)于在全局消息訂閱表中增加一項(xiàng)[msg] = {cbfunc = true}

sys.waitUntil(msg, timeout):為全局消息msg訂閱一個task,這個api只能在task任務(wù)處理函數(shù)的業(yè)務(wù)邏輯中使用;相當(dāng)于在全局消息訂閱表中增加一項(xiàng)[msg] = {task = true}

2、消息發(fā)布:有一個api可以發(fā)布全局消息

sys.publish(msg, ...):發(fā)布一條全局消息;相當(dāng)于在全局消息隊(duì)列中的隊(duì)尾位置增加一項(xiàng){msg, ...}

3、消息調(diào)度處理:有一個api對全局消息進(jìn)行調(diào)度處理

sys.run():讀取全局消息并且分發(fā)給訂閱者去處理;

從全局消息隊(duì)列中取出第一條消息{msg1, ...}

根據(jù)消息名msg1,到全局消息訂閱表中找msg1的訂閱者

如果訂閱者是回調(diào)函數(shù)bfunc1,則執(zhí)行cbfunc1,執(zhí)行完之后,并不會自動從全局消息訂閱表中刪除msg1對應(yīng)的cbfunc1 = true標(biāo)記,只有腳本程序中主動調(diào)用sys.unsubscribe(msg1, cbfunc1),才會刪除這個標(biāo)記;

如果訂閱者是一個task1,task1處于阻塞狀態(tài),則讓task1退出阻塞狀態(tài),繼續(xù)運(yùn)行;然后自動從全局消息訂閱表中刪除msg1對應(yīng)的task1 = true標(biāo)記

在全局消息表中處理完msg1的所有訂閱者;

從全局消息隊(duì)列中刪除{msg1, ...}這條消息

4、刪除訂閱者:訂閱者有兩種,刪除的方式各不相同

回調(diào)函數(shù)類型的訂閱者,在消息調(diào)度處理過程中,不會自動刪除;只有腳本程序中主動調(diào)用sys.unsubscribe(msg, cbfunc),才會刪除

task類型的訂閱者,在消息調(diào)度處理過程中,處理完之后,會自動刪除;

5、刪除消息:sys.run()消息調(diào)度處理過程中,自動刪除;

有一個重要注意事項(xiàng):

全局消息必須先訂閱,然后再發(fā)布,這樣才能保證發(fā)布的消息可以被訂閱者處理;怎么理解呢?我們再看下上面這張圖,在sys.sun()調(diào)度時,首先從全局消息隊(duì)列中取出隊(duì)首的一條消息,然后從全局消息訂閱表中找到這條消息對應(yīng)的所有訂閱者進(jìn)行處理,處理完之后,就刪除了這條消息,所以此時如果訂閱者還沒有準(zhǔn)備好,則無法處理消息;

了解了用戶全局消息的完整生命周期后,接下來我們:

1、先看下在整個生命周期中用到的幾個sys核心庫的api

2、寫一個完整的demo示例來實(shí)際運(yùn)行演示一下如何使用;

sys.publish(msg, ...)

功能

發(fā)布一個全局消息;

注意事項(xiàng)

可以在能夠執(zhí)行到的任意代碼位置使用此函數(shù);

sys.publish(msg, ...)是全局消息的生產(chǎn)者,全局消息有生產(chǎn)就會有消費(fèi),不然消息就沒有存在的意義了;

有兩個接口可以注冊全局消息的消費(fèi)者:

1、一個是sys.subscribe(msg, msg_cbfunc)中注冊的msg_cbfunc消息回調(diào)函數(shù);

2、一個是sys.waitUntil(msg, timeout)所在的task;

所以全局消息的生產(chǎn)者和消費(fèi)者的使用組合,有以下兩種:

1、sys.publish(msg, ...) 和 sys.subscribe(msg, msg_cbfunc)

在sys.publish(msg, ...)之前,必須使用sys.subscribe(msg, msg_cbfunc)注冊消息回調(diào)函數(shù);

這樣才能保證發(fā)布的msg消息可以被msg_cbfunc消息回調(diào)函數(shù)處理;

2、sys.publish(msg, ...) 和 sys.waitUntil(msg, timeout)

在sys.publish(msg, ...)之前,必須保證task正在sys.waitUntil(msg, timeout)代碼處,處于阻塞等待狀態(tài);

這樣才能保證發(fā)布的msg消息可以被task處理;

參數(shù)

msg

參數(shù)含義:消息的名稱;

數(shù)據(jù)類型:推薦string類型(雖然number類型也行,但是不好理解,不要使用);

取值范圍:任意string類型的字符串都行,無特別限制;

是否必選:必須傳入此參數(shù);

注意事項(xiàng):暫無;

參數(shù)示例:"SEND_DATA_REQ"等任意自定義的字符串;

...

參數(shù)含義:消息攜帶的可變參數(shù);

數(shù)據(jù)類型:任意數(shù)據(jù)類型;

取值范圍:無特別限制;

是否必選:可選傳入此參數(shù);

注意事項(xiàng):...是Lua語言的一種語法,表示可變參數(shù),參數(shù)數(shù)量可以是0個,1個,2個,...,多個;

參數(shù)示例:

不傳入,或者boolean類型的true,或者number類型的2,或者string類型的"led",或者table類型的{name="LuatOS", password="123456"}等等等等;

總之,任何數(shù)據(jù)類型的任何自定義內(nèi)容都行;

返回值

nil

示例

wKgZO2l4hMiAPfgoAABrk40xdbw489.png

sys.subscribe(msg, msg_cbfunc)

功能

訂閱一個全局消息的回調(diào)函數(shù);

注意事項(xiàng)

可以在能夠執(zhí)行到的任意代碼位置使用此函數(shù);

sys.publish(msg, ...) 和 sys.subscribe(msg, msg_cbfunc)配合使用時:

在sys.publish(msg, ...)之前,必須使用sys.subscribe(msg, msg_cbfunc)注冊消息回調(diào)函數(shù);

這樣才能保證發(fā)布的msg消息可以被msg_cbfunc消息回調(diào)函數(shù)處理;

同一個全局消息msg,可以多次調(diào)用sys.subscribe(msg, msg_cbfunc)訂閱多個不同的回調(diào)函數(shù);

參數(shù)

msg

參數(shù)含義:全局消息的名稱,和sys.publish(msg, ...)中的msg保持一致;

數(shù)據(jù)類型:推薦string類型(雖然number類型也行,但是不好理解,不要使用);

取值范圍:任意string類型的字符串都行,無特別限制;

是否必選:必須傳入此參數(shù);

注意事項(xiàng):暫無;

參數(shù)示例:"SEND_DATA_REQ"等任意自定義的字符串;


msg_cbfunc

wKgZO2l4hb-AFspFAAVJJsZGhwY248.png

返回值

nil

示例

wKgZO2l4hquAZPMYAAHND7TnlkU437.png

sys.unsubscribe(msg, msg_cbfunc)

功能

取消訂閱一個全局消息的回調(diào)函數(shù);

注意事項(xiàng)

可以在能夠執(zhí)行到的任意代碼位置使用此函數(shù);

參數(shù)

msg

參數(shù)含義:全局消息的名稱,和sys.subscribe(msg, msg_cbfunc)中的msg保持一致;

數(shù)據(jù)類型:推薦string類型(雖然number類型也行,但是不好理解,不要使用);

取值范圍:任意string類型的字符串都行,無特別限制;

是否必選:必須傳入此參數(shù);

注意事項(xiàng):暫無;

參數(shù)示例:"SEND_DATA_REQ"等任意自定義的字符串;

msg_cbfunc

wKgZO2l4h1yADlm0AATpNo_PMoQ837.png

返回值

nil

示例

wKgZPGl4h5CAeIG2AAJub8Wlxk8864.png

sys.waitUntil(msg, timeout)

功能

在task中阻塞等待一個全局消息;

注意事項(xiàng)

只能在基礎(chǔ)task和高級task處理函數(shù)的業(yè)務(wù)邏輯中使用此函數(shù);

sys.publish(msg, ...) 和 sys.waitUntil(msg, timeout)配合使用時:

在sys.publish(msg, ...)之前,必須保證task正在sys.waitUntil(msg, timeout)代碼處,處于阻塞等待狀態(tài);

這樣才能保證發(fā)布的msg消息可以被task處理;

同一個全局消息msg,可以被多個正在sys.waitUntil(msg, timeout)代碼處阻塞等待的task處理;

參數(shù)

msg


參數(shù)含義:全局消息的名稱,和sys.publish(msg, ...)中的msg保持一致;

數(shù)據(jù)類型:推薦string類型(雖然number類型也行,但是不好理解,不要使用);

取值范圍:任意string類型的字符串都行,無特別限制;

是否必選:必須傳入此參數(shù);

注意事項(xiàng):暫無;

參數(shù)示例:"SEND_DATA_REQ"等任意自定義的字符串;


timeout

參數(shù)含義:阻塞等待全局消息msg的超時時長,單位毫秒;

數(shù)據(jù)類型:number或者nil;

取值范圍:

大于等于1,小于等于0x7FFFFFFF,之間的所有正整數(shù);最大時長0x7FFFFFFF毫秒 ≈ 596小時 ≈ 24.85天;

如果為nil,表示一直阻塞等待全局消息,不會超時;

是否必選:可選傳入此參數(shù);

注意事項(xiàng):

此處的超時機(jī)制基于軟件定時器實(shí)現(xiàn),受系統(tǒng)負(fù)載、任務(wù)數(shù)量、消息堆積、網(wǎng)絡(luò)中斷優(yōu)先級最高等多因素影響,無法實(shí)現(xiàn)高精度

尤其是幾個毫秒級別,幾十毫秒級別,幾百毫秒級別的超時時長,誤差都比較大,秒級別以上的超時時長誤差較小;

例如:設(shè)置的1毫秒,可能要等幾十毫秒;設(shè)置的幾十毫秒,可能要等上百毫秒;設(shè)置的幾百毫秒,可能要等幾百幾十毫秒;

可以簡單的認(rèn)為會延遲幾十毫秒左右,以此來評估超時時長精度是否可以滿足業(yè)務(wù)需求;

調(diào)用本接口時,如果傳入了timeout參數(shù),則sys核心庫內(nèi)部會自動創(chuàng)建并且運(yùn)行一個軟件定時器,超時時長到達(dá)后,會自動刪除這個定時器;

LuatOS應(yīng)用程序中可用的軟件定時器總數(shù)量為64個;

注意控制自已應(yīng)用程序中的同時運(yùn)行的軟件定時器總數(shù)量不要超過64個,否則創(chuàng)建新的定時器會返回失??;

參數(shù)示例:50、1000、60000等;


返回值

local result, arg1, arg2, arg3, argN = sys.waitUntil(msg, timeout)

有數(shù)量不固定的返回值:

第一個返回值為result

剩余的返回值arg1, arg2, arg3, argN,表示可變數(shù)量的返回值,只有當(dāng)?shù)谝粋€返回值result為true時,這些可變數(shù)量的返回值才有意義,和sys.publish(msg, ...)中...表示的可變參數(shù)一一對應(yīng)

result

含義說明:阻塞等待的結(jié)果;true表示收到了msg消息,false表示超時沒有收到msg消息;

數(shù)據(jù)類型:boolean;

取值范圍:true或者false;

注意事項(xiàng):暫無;


arg1, arg2, arg3, argN

含義說明:

當(dāng)result為true時,arg1, arg2, arg3, argN表示sys.publish(msg, ...)中...可變參數(shù),從前到后一一對應(yīng);

當(dāng)result為false時,arg1, arg2, arg3, argN全部都為nil,沒有任何意義;

數(shù)據(jù)類型:

當(dāng)result為true時,arg1, arg2, arg3, argN的數(shù)據(jù)類型和sys.publish(msg, ...)中...可變參數(shù)的數(shù)據(jù)類型,從前到后一一對應(yīng);

當(dāng)result為false時,arg1, arg2, arg3, argN全部都為nil類型;

取值范圍:無特別限制;

注意事項(xiàng):暫無;


正確示例

wKgZPGl4iHiAUZNrAAK5Yh0Kg0A008.png

錯誤示例

wKgZO2l4iOeAcazJAAU1eaQ4BHQ479.png


用戶全局消息處理的代碼示例

在了解了全局消息的幾個api之后,我們再看下圖回顧一下全局消息處理的完整周期

wKgZPGl4iSqAPBeEAAJVyg56vnM642.png

下面這個例子用來說明用戶全局消息的完整處理過程;

這個例子的完整代碼鏈接:global_msg_receiver1.luaglobal_msg_receiver2.luaglobal_msg_sender.lua

因?yàn)槭侨齻€文件,并且代碼比較多,所以在這里我就不粘貼代碼到這里了,直接打開vscode來分析代碼的業(yè)務(wù)邏輯;

我們在模擬器上實(shí)際運(yùn)行一下看看,輸入命令

luatos --llt=H:Luatoolsprojectluatos_framework_luatos_task_Air8000.ini

運(yùn)行15秒鐘的日志如下,為了方便分析,我把日志做了處理,每個訂閱者訂閱同一類型的消息日志匯總到了一起:

init_subscribe_cbfunc訂閱者

init_subscribe_cbfunc訂閱的task內(nèi)部發(fā)布的消息,運(yùn)行正常,符合預(yù)期,每秒接收到一次task內(nèi)部發(fā)布的消息;

因?yàn)?0秒鐘之后,取消了訂閱,所以只收到前10條消息

wKgZPGl4icmAG1aKAAE8geQZICQ141.png

init_subscribe_cbfunc訂閱的timer發(fā)布的消息,運(yùn)行正常,符合預(yù)期,每秒接收到一次task內(nèi)部發(fā)布的消息;

因?yàn)?0秒鐘之后,取消了訂閱,所以只收到前10條消息

wKgZPGl4ie-AEa4aAAEo3E25Wg4744.png

delay_subscribe_cbfunc訂閱者

delay_subscribe_cbfunc訂閱的task內(nèi)部發(fā)布的消息,我們可以看到是從第6條消息開始接收處理,前面的5條丟失了;這是因?yàn)閟ys.timerStart(sys.subscribe, 5000, "SEND_DATA_REQ", delay_subscribe_cbfunc)這行代碼,是開機(jī)之后延遲5秒,然后才訂閱了"SEND_DATA_REQ"回調(diào)函數(shù)的delay_subscribe_cbfunc,所以前5秒的消息都無法接收處理;這段運(yùn)行日志就可以驗(yàn)證本文前面描述的這段話:

在sys.publish(msg, ...)之前,必須使用sys.subscribe(msg, msg_cbfunc)注冊消息回調(diào)函數(shù);這樣才能保證發(fā)布的msg消息可以被msg_cbfunc消息回調(diào)函數(shù)處理;再簡單點(diǎn)來說,訂閱要在發(fā)布前,就像郵局定報(bào)紙一樣,你只有了訂閱服務(wù),在郵局發(fā)報(bào)紙的時候才不會把你漏掉;

wKgZO2l4ipGAXazVAACxUGa3Lvo444.png

同理,delay_subscribe_cbfunc訂閱的timer內(nèi)部發(fā)布的消息,表現(xiàn)也是一樣

wKgZO2l4iraAWXbUAACveS1UH0c482.png

task(success_wait_until_base_task_func)訂閱者

task任務(wù)處理函數(shù)success_wait_until_base_task_func中通過sys.waitUntil("SEND_DATA_REQ")訂閱的task內(nèi)部發(fā)布的消息,運(yùn)行正常,符合預(yù)期,每秒接收到一次task內(nèi)部發(fā)布的消息,一共收到15條消息;

wKgZO2l4ivWAQ928AAIzlFNL-6I915.png

task任務(wù)處理函數(shù)success_wait_until_base_task_func中通過sys.waitUntil("SEND_DATA_REQ")訂閱的timer發(fā)布的消息,運(yùn)行正常,符合預(yù)期,每秒接收到一次task內(nèi)部發(fā)布的消息,一共收到15條消息;

wKgZPGl4ix-AVRHpAAI--cQerSQ309.png

task(lost_wait_until_base_task_func)訂閱者

task任務(wù)處理函數(shù)lost_wait_until_base_task_func中通過sys.waitUntil("SEND_DATA_REQ")訂閱的task內(nèi)部發(fā)布的消息,運(yùn)行異常,一共收到0條消息;15條消息全部丟失

為什么task內(nèi)部發(fā)布的15條消息,task(lost_wait_until_base_task_func)全部丟失,我們添加一些日志來分析一下,如下兩部分代碼塊的黃色背景為新增的日志打印代碼

wKgZO2l4i0-AbMQGAAUrXu7Kb7E008.png

再來看一下運(yùn)行日志

wKgZPGl4i62AQ-JCAAu8fkhRtlg722.png

在這段日志中:

lost_wait_until_base_task_func before wait 3000,表示接下來lost_wait_until_base_task_func的task阻塞等待3秒鐘,此狀態(tài)下,其他地方發(fā)布的"SEND_DATA_REQ"無法處理,會丟失;global_sender_msg_task_func這個task所發(fā)布的消息"SEND_DATA_REQ",都是在lost_wait_until_base_task_func before wait 3000之后發(fā)布的,所以消息全部丟失;

lost_wait_until_base_task_func before wait SEND_DATA_REQ,表示接下來lost_wait_until_base_task_func的task阻塞等待消息"SEND_DATA_REQ",此狀態(tài)下,其他地方發(fā)布的"SEND_DATA_REQ"可以被處理;在lost_wait_until_base_task_func before wait SEND_DATA_REQ之后,都是global_sender_msg_timer_cbfun這個定時器回調(diào)函數(shù)在發(fā)布"SEND_DATA_REQ",所以這個時間點(diǎn)的"SEND_DATA_REQ"都可以被處理;

task任務(wù)處理函數(shù)lost_wait_until_base_task_func中通過sys.waitUntil("SEND_DATA_REQ")訂閱的定時器發(fā)布的消息,運(yùn)行異常,一共收到4條消息;11條消息全部丟失

wKgZPGl4i-yAD74nAACcGd_Q74A230.png


這段運(yùn)行日志就可以驗(yàn)證本文前面描述的這段話:

在sys.publish(msg, ...)之前,必須保證task正在sys.waitUntil(msg, timeout)代碼處,處于阻塞等待狀態(tài);

這樣才能保證發(fā)布的msg消息可以被task處理;

我們再看下這個訂閱者task的任務(wù)處理函數(shù)代碼,在如下代碼的第4行和第5行,都會使task阻塞,其中第4行會阻塞3秒,這3秒內(nèi),無論是task發(fā)布的消息還是timer發(fā)布的消息,都無法接收處理,直接丟失;3秒后,在第5行阻塞等待"SEND_DATA_REQ"消息,等到一條消息后,重新又阻塞3秒,在本demo中的表現(xiàn)就是每隔3秒接收到一次task或者timer發(fā)布的消息;

wKgZPGl4jBuAT2jFAAFyVw-rBgM284.png

如何避免這種問題呢?

在接收消息的task任務(wù)處理函數(shù)業(yè)務(wù)邏輯中,最好只有sys.waitUntil,并且也只有一處sys.waitUntil阻塞當(dāng)前task;上面這個代碼塊,只要把第4行去掉就沒有問題了;

2.2.3.4 用戶定向消息

用戶定向消息處理的完整周期

簡化后用戶定向消息處理的完整周期包括以下幾部分:

wKgZO2l4jFaADhZPAAP1TiESXFw913.pngwKgZPGl4jG-AetcJAAH1SJqu7yE038.png

消息發(fā)送:有一個api可以發(fā)送定向消息

sys.sendMsg(task_name, msg, arg2, arg3, arg4):發(fā)送一條定向消息給task_name;相當(dāng)于在定向消息隊(duì)列中的隊(duì)尾位置增加一項(xiàng){msg, arg2, arg3, arg4}

消息接收:有一個api可以接收定向消息

sys.waitMsg(task_name, msg, timeout):讀取task_name的一條定向消息,如果定向消息隊(duì)列中有指定的msg消息,則讀出處理;如果有其他非指定的消息,給非目標(biāo)消息回調(diào)函數(shù)處理;如果沒有消息,則阻塞等待

消息調(diào)度處理:有一個api對定向消息進(jìn)行調(diào)度處理

如果發(fā)送定向消息后,接收者高級task處于阻塞狀態(tài),則sys.run()調(diào)度器會控制處于阻塞狀態(tài)的task,退出阻塞狀態(tài),讀取定向消息進(jìn)行處理;

刪除接收者:在消息調(diào)度處理過程中,sys.waitMsg(task_name, msg, timeout)讀出指定的msg消息,處理完之后,會自動刪除接收者,只有等到下次運(yùn)行到sys.waitMsg(task_name, msg, timeout)時才會創(chuàng)建新的接收者;

刪除消息:sys.run()消息調(diào)度處理過程中,sys.waitMsg(task_name, msg, timeout)讀出指定的msg消息,處理完消息之后會自動刪除;

上一小節(jié)我們討論了用戶全局消息,還記得用戶全局消息處理有兩個注意事項(xiàng):

1、在sys.publish(msg, ...)之前,必須使用sys.subscribe(msg, msg_cbfunc)注冊消息回調(diào)函數(shù);這樣才能保證發(fā)布的msg消息可以被msg_cbfunc消息回調(diào)函數(shù)處理;

2、在sys.publish(msg, ...)之前,必須保證task正在sys.waitUntil(msg, timeout)代碼處,處于阻塞等待狀態(tài);這樣才能保證發(fā)布的msg消息可以被task處理;

現(xiàn)在我們討論的用戶定向消息已經(jīng)不存在這個問題,消息發(fā)布和消息處理沒有嚴(yán)格的時序要求,可以先發(fā)送定向消息,消息會存儲到定向消息隊(duì)列中,只有當(dāng)調(diào)用sys.waitMsg(task_name, msg, timeout)接口主動去讀取定向消息時,才會從定向消息隊(duì)列中讀出消息進(jìn)行處理,所以不存在消息丟失的問題;但是如果使用不當(dāng),可能會出現(xiàn)消息處理延遲的問題;

了解了用戶定向消息的完整生命周期后,接下來我們:

1、先看下在整個生命周期中用到的幾個sys核心庫的api

2、寫一個完整的demo示例來實(shí)際運(yùn)行演示一下如何使用;

sys.sendMsg(task_name, msg, arg2, arg3, arg4)

功能

向名稱為task_name的task發(fā)布一個定向消息;

注意事項(xiàng)

可以在能夠執(zhí)行到的任意代碼位置使用此函數(shù);

sys.sendMsg(task_name, msg, arg2, arg3, arg4)是定向消息的生產(chǎn)者,定向消息有生產(chǎn)就會有消費(fèi),不然消息就沒有存在的意義了;

sys.waitMsg(task_name, msg, timeout)所在的task是定向消息的消費(fèi)者;

sys.sendMsg(task_name, msg, arg2, arg3, arg4) 和 sys.waitMsg(task_name, msg, timeout)配合使用;

在sys.sendMsg(task_name, msg, arg2, arg3, arg4)之前,需要保證名稱為task_name的task已經(jīng)被創(chuàng)建,否則定向消息也會丟失;

參數(shù)

task_name

參數(shù)含義:task的名稱,和sys.taskInitEx(task_func, task_name, non_targeted_msg_cbfunc, ...)中的task_name保持一致;

數(shù)據(jù)類型:推薦string類型(雖然number類型也行,但是不好理解,不要使用);

取值范圍:任意string類型的字符串都行,無特別限制;

是否必選:必須傳入此參數(shù);

注意事項(xiàng):暫無;

參數(shù)示例:"LED_TASK"、"GPIO_TASK"等任意自定義的字符串;


msg

參數(shù)含義:消息的名稱;

數(shù)據(jù)類型:推薦string類型(雖然number類型也行,但是不好理解,不要使用);

取值范圍:任意string類型的字符串都行,無特別限制;

是否必選:必須傳入此參數(shù);

注意事項(xiàng):暫無;

參數(shù)示例:"SEND_DATA_REQ"等任意自定義的字符串;


arg2

參數(shù)含義:msg消息攜帶的第一個參數(shù);

數(shù)據(jù)類型:任意數(shù)據(jù)類型;

取值范圍:無特別限制;

是否必選:可選傳入此參數(shù);

注意事項(xiàng):暫無;

參數(shù)示例:

不傳入,或者boolean類型的true,或者number類型的2,或者string類型的"led",或者table類型的{name="LuatOS", password="123456"}等等等等;

總之,任何數(shù)據(jù)類型的任何自定義內(nèi)容都行;


arg3

參數(shù)含義:msg消息攜帶的第二個參數(shù);

數(shù)據(jù)類型:任意數(shù)據(jù)類型;

取值范圍:無特別限制;

是否必選:可選傳入此參數(shù);

注意事項(xiàng):暫無;

參數(shù)示例:

不傳入,或者boolean類型的true,或者number類型的2,或者string類型的"led",或者table類型的{name="LuatOS", password="123456"}等等等等;

總之,任何數(shù)據(jù)類型的任何自定義內(nèi)容都行;


arg4

參數(shù)含義:msg消息攜帶的第三個參數(shù);

數(shù)據(jù)類型:任意數(shù)據(jù)類型;

取值范圍:無特別限制;

是否必選:可選傳入此參數(shù);

注意事項(xiàng):暫無;

參數(shù)示例:

不傳入,或者boolean類型的true,或者number類型的2,或者string類型的"led",或者table類型的{name="LuatOS", password="123456"}等等等等;

總之,任何數(shù)據(jù)類型的任何自定義內(nèi)容都行;


返回值

local result = sys.sendMsg(task_name, msg, arg2, arg3, arg4)

result

含義說明:定向消息發(fā)布結(jié)果,成功返回true,失敗返回false;

數(shù)據(jù)類型:boolean;

取值范圍:true或者false;

注意事項(xiàng):暫無;


示例

wKgZO2l4j0uAL2euAACOd9SUZ00875.png

sys.waitMsg(task_name, msg, timeout)

功能

在task中阻塞等待名稱為task_name的task的定向消息;

注意事項(xiàng)

只能在高級task處理函數(shù)的業(yè)務(wù)邏輯中使用此函數(shù);

sys.sendMsg(task_name, msg, arg2, arg3, arg4)是定向消息的生產(chǎn)者,定向消息有生產(chǎn)就會有消費(fèi),不然消息就沒有存在的意義了;

sys.waitMsg(task_name, msg, timeout)所在的task是定向消息的消費(fèi)者;

sys.sendMsg(task_name, msg, arg2, arg3, arg4) 和 sys.waitMsg(task_name, msg, timeout)配合使用;

在sys.sendMsg(task_name, msg, arg2, arg3, arg4)之前,需要保證名稱為task_name的task已經(jīng)被創(chuàng)建,否則定向消息也會丟失;

參數(shù)

task_name

參數(shù)含義:task的名稱,和sys.taskInitEx(task_func, task_name, non_targeted_msg_cbfunc, ...)中的task_name保持一致;

數(shù)據(jù)類型:推薦string類型(雖然number類型也行,但是不好理解,不要使用);

取值范圍:任意string類型的字符串都行,無特別限制;

是否必選:必須傳入此參數(shù);

注意事項(xiàng):暫無;

參數(shù)示例:"LED_TASK"、"GPIO_TASK"等任意自定義的字符串;


msg

參數(shù)含義:消息的名稱,和sys.sendMsg(task_name, msg, arg2, arg3, arg4)中的msg保持一致;

數(shù)據(jù)類型:推薦string類型(雖然number類型也行,但是不好理解,不要使用);

取值范圍:任意string類型的字符串都行,無特別限制;

是否必選:必須傳入此參數(shù);

注意事項(xiàng):暫無;

參數(shù)示例:"SEND_DATA_REQ"等任意自定義的字符串;


timeout

參數(shù)含義:阻塞等待定向消息msg的超時時長,單位毫秒,nil表示一直阻塞等待;

數(shù)據(jù)類型:number或者nil;

取值范圍:

大于等于1,小于等于0x7FFFFFFF,之間的所有正整數(shù);最大時長0x7FFFFFFF毫秒 ≈ 596小時 ≈ 24.85天;

如果為nil,表示一直阻塞等待全局消息,不會超時;

是否必選:可選傳入此參數(shù);

注意事項(xiàng):

此處的超時機(jī)制基于軟件定時器實(shí)現(xiàn),受系統(tǒng)負(fù)載、任務(wù)數(shù)量、消息堆積、網(wǎng)絡(luò)中斷優(yōu)先級最高等多因素影響,無法實(shí)現(xiàn)高精度;

尤其是幾個毫秒級別,幾十毫秒級別,幾百毫秒級別的超時時長,誤差都比較大,秒級別以上的超時時長誤差較??;

例如:設(shè)置的1毫秒,可能要等幾十毫秒;設(shè)置的幾十毫秒,可能要等上百毫秒;設(shè)置的幾百毫秒,可能要等幾百幾十毫秒;

可以簡單的認(rèn)為會延遲幾十毫秒左右,以此來評估超時時長精度是否可以滿足業(yè)務(wù)需求;

調(diào)用本接口時,如果傳入了timeout參數(shù),則sys核心庫內(nèi)部會自動創(chuàng)建并且運(yùn)行一個軟件定時器,超時時長到達(dá)后,會自動刪除這個定時器;

LuatOS應(yīng)用程序中可用的軟件定時器總數(shù)量為64個;

注意控制自已應(yīng)用程序中的同時運(yùn)行的軟件定時器總數(shù)量不要超過64個,否則創(chuàng)建新的定時器會返回失敗;

參數(shù)示例:50、1000、60000等;


返回值

local message = sys.waitMsg(task_name, msg, timeout)

有一個返回值為message

message

含義說明:

阻塞等待的結(jié)果;table類型表示接收到msg消息,false表示超時沒有收到msg消息;

當(dāng)接收到msg消息時,message[1],message[2],message[3],message[4]和sys.sendMsg(task_name, msg, arg2, arg3, arg4)中的 msg, arg2, arg3, arg4 一一對應(yīng);

當(dāng)超時沒有收到msg消息時,message為false;

數(shù)據(jù)類型:table或者boolean;

取值范圍:無特別限制;

注意事項(xiàng):暫無;


示例

wKgZPGl4kCKAZRHbAAPz6rlPN8w945.png

用戶定向消息處理的代碼示例

在了解了定向消息的兩個api之后,我們再看下圖回顧一下定向消息處理的完整周期

wKgZO2l4kIaAMrk4AAJPv_zc334783.png

下面這個例子用來說明用戶定向消息的完整處理過程;

這個例子的完整代碼鏈接:tgted_msg_receiver.luatargeted_msg_sender.lua

核心代碼片段如下,我們首先分析下這兩段代碼的業(yè)務(wù)邏輯

wKgZPGl4kUGANjYqAAtFCMrTnJ8067.png

我們在模擬器上實(shí)際運(yùn)行一下看看,輸入命令

luatos --llt=H:Luatoolsprojectluatos_framework_luatos_task_Air8000.ini

運(yùn)行15秒鐘的日志如下,為了方便分析,我把日志做了處理,每個消息接收者的日志匯總到了一起:

normal_wait_msg_task接收者

nromal_wait_msg_task的任務(wù)處理函數(shù)中,調(diào)用sysplus.waitMsg("nromal_wait_msg_task", "SEND_DATA_REQ")接收消息,在15秒的時間內(nèi),一共收到15條消息,運(yùn)行正常,符合預(yù)期,每秒接收到一次消息;

wKgZPGl4kXyAGyfhAALC9rV6F48731.png

delay_wait_msg_task接收者

delay_wait_msg_task的任務(wù)處理函數(shù)中,調(diào)用sysplus.waitMsg("nromal_wait_msg_task", "SEND_DATA_REQ")接收消息,在15秒的時間內(nèi),一共收到5條消息,和代碼設(shè)計(jì)相符,每延時3秒接收到一次消息;

但是我們再仔細(xì)觀察下收到消息,消息的計(jì)數(shù)參數(shù)依次為1,2,3,4,5,說明雖然延時收到消息,但是消息并沒有出現(xiàn)丟失;

這一點(diǎn)兒和上一小節(jié)講的全局消息的處理完全不同,全局消息在這種情況下會丟失消息;

wKgZO2l4kaKACU2rAADx08tBc3Y335.png

2.2.3.5 用戶定向消息 vs 用戶全局消息,如何選擇?

在了解了用戶定向消息和用戶全局消息之后,大家可能會有一個疑問:我平時開發(fā)程序時,怎么判斷應(yīng)該使用定向消息還是全局消息呢?一般來說,建議大家按照以下幾步來做選擇:

1、首先確認(rèn)你的程序中有沒有使用高級task,因?yàn)橛行?yīng)用功能模塊是必須要使用高級task的,例如創(chuàng)建一個socket時,要使用高級task;如果使用了高級task,那就只能使用用戶定向消息了;如果沒有使用高級task,繼續(xù)向下判斷;

如果你對用戶全局消息的處理過程以及其中容易出現(xiàn)的消息丟失問題,非常清楚,知道如何規(guī)避這種消息丟失問題,則可以使用全局消息,因?yàn)槿窒⑹褂闷饋砗唵危?/p>

如果你對用戶全局消息的處理過程以及其中容易出現(xiàn)的問題都不是很清楚,這種情況下,建議使用定向消息和高級task,因?yàn)檫@種方式下,只要在發(fā)送定向消息前,高級task已經(jīng)創(chuàng)建,則消息不會丟失;

由于篇幅過長,更多精彩內(nèi)容,請看下篇~


審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 物聯(lián)網(wǎng)
    +關(guān)注

    關(guān)注

    2950

    文章

    48158

    瀏覽量

    418917
  • LuatOS
    +關(guān)注

    關(guān)注

    0

    文章

    173

    瀏覽量

    2751
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關(guān)推薦
    熱點(diǎn)推薦

    LuatOS的內(nèi)存分配機(jī)制

    不同 LuatOS 硬件平臺在內(nèi)存布局存在差異,Lua 運(yùn)行內(nèi)存、系統(tǒng)內(nèi)存與 PSRAM 擴(kuò)展內(nèi)存的分配方式各有區(qū)別。部分型號將系統(tǒng)與腳本內(nèi)存統(tǒng)一放在 PSRAM 中,部分則采用 SRAM 運(yùn)行系統(tǒng)、PSRAM 承載腳本的分離架構(gòu),以適配不同功耗與性能需求。
    的頭像 發(fā)表于 04-16 12:37 ?145次閱讀
    <b class='flag-5'>LuatOS</b>的內(nèi)存分配機(jī)制

    LuatOS 框架的嵌入式系統(tǒng)架構(gòu)設(shè)計(jì)原理

    API 接口,使開發(fā)者能用高級語言快速實(shí)現(xiàn)底層控制。這一設(shè)計(jì)顯著提升了開發(fā)效率,同時保持了良好的運(yùn)行性能。 一、LuatOS框架中的概念 ? 在LuatOS中,有三個核心概念和一個調(diào)度器: 1、三個核心概念:任務(wù)(task),
    的頭像 發(fā)表于 02-03 15:57 ?274次閱讀
    <b class='flag-5'>LuatOS</b> <b class='flag-5'>框架</b>的嵌入式系統(tǒng)架構(gòu)設(shè)計(jì)原理

    LuatOS 系統(tǒng)框架的模塊化設(shè)計(jì)原理

    裁剪功能,適應(yīng)不同規(guī)模的嵌入式項(xiàng)目需求。 一、LuatOS框架中的概念 ? 在LuatOS中,有三個核心概念和一個調(diào)度器: 1、三個核心概念:任務(wù)(task),消息(message),定時器(timer
    的頭像 發(fā)表于 02-03 15:56 ?306次閱讀
    <b class='flag-5'>LuatOS</b> 系統(tǒng)<b class='flag-5'>框架</b>的模塊化設(shè)計(jì)原理

    基于LuatOS的MQTT物聯(lián)網(wǎng)通信全解

    在構(gòu)建物聯(lián)網(wǎng)終端設(shè)備時,通信協(xié)議的選擇直接決定系統(tǒng)的穩(wěn)定性與擴(kuò)展性。LuatOS通過內(nèi)置MQTT客戶端支持,使開發(fā)者能以極少代碼實(shí)現(xiàn)設(shè)備云。本文將從協(xié)議原理到代碼實(shí)現(xiàn),全面解析基于LuatOS
    的頭像 發(fā)表于 01-29 19:42 ?347次閱讀
    基于<b class='flag-5'>LuatOS</b>的MQTT物聯(lián)網(wǎng)通信全解

    輕松掌握——LuatOS socket基礎(chǔ)知識和應(yīng)用開發(fā)

    socket課程主要包含以下幾個部分: 1、TCP/IP總體介紹; 2、LuatOS的?4G/WiFi/以太網(wǎng)?三種網(wǎng)絡(luò)環(huán)
    的頭像 發(fā)表于 01-28 20:07 ?403次閱讀
    輕松掌握——<b class='flag-5'>LuatOS</b>  socket基礎(chǔ)知識和應(yīng)用開發(fā)

    解鎖:LuatOS框架的使用(下篇)

    接上一篇 2.3 LuatOS 的定時器(timer) 對于 LuatOS 應(yīng)用程序來說,定時器本質(zhì)也算是一種特殊的消息,因?yàn)槎〞r器太常用了,所以把他單獨(dú)拎出來,單獨(dú)的一個章節(jié)進(jìn)行講解
    的頭像 發(fā)表于 01-28 13:18 ?300次閱讀
    解鎖:<b class='flag-5'>LuatOS</b><b class='flag-5'>框架</b>的使用(下篇)

    LuatOS-Air腳本移植到LuatOS版本注意事項(xiàng)

    一、lua版本不一樣 LuatOS-Air使用的是lua5.1版本,本身不支持位移運(yùn)算符。 LuatOS使用的是lua5.3版本,取消了module(..., package.seeall)這種形式
    的頭像 發(fā)表于 01-17 14:48 ?1283次閱讀
    <b class='flag-5'>LuatOS</b>-Air腳本移植到<b class='flag-5'>LuatOS</b>版本注意事項(xiàng)

    LuatOS-Air轉(zhuǎn)LuatOS常見故障排查手冊

    當(dāng)LuatOS-Air腳本在LuatOS環(huán)境中運(yùn)行失敗,問題往往集中在幾個關(guān)鍵模塊:任務(wù)調(diào)度、外設(shè)驅(qū)動、網(wǎng)絡(luò)配置和固件版本匹配。本文以故障排查的邏輯為主線,列出常見報(bào)錯現(xiàn)象、可能原因及快速修復(fù)
    的頭像 發(fā)表于 01-13 19:20 ?291次閱讀
    <b class='flag-5'>LuatOS</b>-Air轉(zhuǎn)<b class='flag-5'>LuatOS</b>常見故障排查手冊

    警惕兼容性陷阱:LuatOS-Air腳本在LuatOS中的運(yùn)行異常分析

    即使語法正確的LuatOS-Air腳本,在LuatOS環(huán)境中也可能出現(xiàn)“靜默失敗”——程序無報(bào)錯但功能未執(zhí)行。這類問題多與系統(tǒng)事件循環(huán)、模塊加載時機(jī)或硬件抽象層調(diào)用方式有關(guān)。本文通過多個真實(shí)
    的頭像 發(fā)表于 01-13 19:20 ?329次閱讀
    警惕兼容性陷阱:<b class='flag-5'>LuatOS</b>-Air腳本在<b class='flag-5'>LuatOS</b>中的運(yùn)行異常分析

    掌握LuatOS系統(tǒng)消息:新手也能看懂的列表詳解

    視角出發(fā),用通俗語言解析其工作原理與配置方法。此處列舉了LuatOS框架中自帶的系統(tǒng)消息列表。 ? 一、sys ? 文檔鏈接:https://docs.openluat.com/osapi/core
    的頭像 發(fā)表于 01-13 18:12 ?253次閱讀
    掌握<b class='flag-5'>LuatOS</b>系統(tǒng)消息:新手也能看懂的列表詳解

    LuatOS腳本開發(fā)入門:嵌入式運(yùn)行框架全解析!

    想搞懂LuatOS如何運(yùn)行Lua腳本?本文深入剖析其嵌入式運(yùn)行框架,涵蓋虛擬機(jī)加載、任務(wù)協(xié)程、系統(tǒng)初始化等關(guān)鍵環(huán)節(jié),適合初學(xué)者。 一、LuatOS 編程起步 1.1 底層固件怎么啟動 Luat
    的頭像 發(fā)表于 09-26 17:45 ?660次閱讀
    <b class='flag-5'>LuatOS</b>腳本開發(fā)入門:嵌入式運(yùn)行<b class='flag-5'>框架</b>全解析!

    嵌入式開發(fā)新選擇:LuatOS腳本框架入門教程

    LuatOS正成為嵌入式開發(fā)的新趨勢!本教程帶你從基礎(chǔ)入手,全面了解其基于Lua的腳本開發(fā)模式與輕量級運(yùn)行框架。 一、LuatOS 編程起步 1.1 底層固件怎么啟動 LuatOS
    的頭像 發(fā)表于 09-26 17:34 ?781次閱讀
    嵌入式開發(fā)新選擇:<b class='flag-5'>LuatOS</b>腳本<b class='flag-5'>框架</b>入門教程

    Task任務(wù):LuatOS實(shí)現(xiàn)“任務(wù)級并發(fā)”的核心引擎

    Task任務(wù)通過其強(qiáng)大的并發(fā)處理能力,使LuatOS能夠在單線程環(huán)境中模擬多線程執(zhí)行,通過協(xié)程的掛起與恢復(fù)機(jī)制,實(shí)現(xiàn)任務(wù)級的并行操作,顯著提升系統(tǒng)效能。 sys核心庫是LuatOS運(yùn)行框架庫,也是
    的頭像 發(fā)表于 08-28 13:49 ?657次閱讀
    Task任務(wù):<b class='flag-5'>LuatOS</b>實(shí)現(xiàn)“任務(wù)級并發(fā)”的核心引擎

    揭秘LuatOS Task:多任務(wù)管理的“智能中樞”

    Task任務(wù)作為LuatOS的核心組成部分,通過智能化的任務(wù)管理機(jī)制,實(shí)現(xiàn)任務(wù)的創(chuàng)建、調(diào)度與協(xié)同運(yùn)行,讓復(fù)雜應(yīng)用得以高效并行處理,滿足實(shí)時場景下的嚴(yán)苛需求。 sys核心庫是LuatOS運(yùn)行框架
    的頭像 發(fā)表于 08-28 13:48 ?810次閱讀
    揭秘<b class='flag-5'>LuatOS</b> Task:多任務(wù)管理的“智能中樞”

    解碼LuatOS:短信功能的底層運(yùn)作機(jī)制

    短信功能在LuatOS中的運(yùn)行并非表面所見那么簡單。本文將深入系統(tǒng)底層,解碼其通信協(xié)議、數(shù)據(jù)處理與系統(tǒng)交互,呈現(xiàn)完整的運(yùn)作圖譜。 我們這期主要拆解airsms.lua文件,講清楚,短信功能
    的頭像 發(fā)表于 06-27 18:05 ?827次閱讀
    解碼<b class='flag-5'>LuatOS</b>:短信功能的底層運(yùn)作機(jī)制