Try   HackMD

[Legato] Legato 框架基本介紹

  • Legato Application Framework 是由 Sierra Wireless Inc. 公司所開發的框架,主要是用來開發 Internet of Everything。
  • Qualcomm 的 TelAF 是基於 Legato 所開發的,所以在使用 TelAF 前要先稍微瞭解 Legato 才能比較快上手

Legato 的 System、App、Executable、以及 Component 架構

image

(圖片擷取自 Legato 官網:https://docs.legato.io/latest/conceptsEnvironment.html)

Legato 框架有幾個重要的概念,分別是 Component、Executable、App、以及 System。

image

從上圖可以看到,使用 Legato Application Framework 的話,我們的系統裡面會有一個一個的 App,而每個 App 裡面可以由一個或多個 Executable 組成,而每個 Executable 可以由一個或多個 Component 組成。

Component

  • 一個 Component 是一個 Legato App 最小的組成單位,其 entry point 是 COMPONENT_INIT
  • 寫在 COMPONENT_INIT 內的東西,在程式剛執行時的 start-up 階段就會馬上被該程式的 main thread 執行,主要是用來初始化該 Component 用的,所以一個 Component 只會有一個 COMPONENT_INIT
    此外,COMPONENT_INIT 不可 return 東西。
  • 一個 Component 會以一個資料夾表示,該資料夾的名稱就是該 Component 的名稱,該資料夾內的檔案都是屬於該 Component 的,這些檔案可以是 source code、pre-compiled binary files、resource files(如音檔、圖片檔等等)、或任何該 Component 所需的檔案。
  • 每個 Component 都一定要有一個 Component.cdef 檔案(cdefComponent DEFinition),用來告訴 build tools 該怎麼 build 這個 component。
  • Component.cdef 檔案也可用來描述說這個 Component 提供了怎樣的 API 或者是需要什麼 API。
  • 更多關於 Component 細節的說明可參考官網的說明:

Executable

  • 一個 Executable 由一個或多個 Component 組成。
  • 一個 Executable 會由一個 process 去執行。
  • 所以一個 Executable 可以由多個 Component 組成,而這些 Component 都執行在同一個 process 內,所以這些 Component 可互相存取彼此的資源。
  • Executable 是由 .adef 檔案做設定(後面會介紹)。
  • Executable 若要去存取另一個 Executable(不論是否在同一個 App 內)的東西,僅能透過 IPC 機制(後面會再說明)。

App

Legato Application Framework 的一個 App 跟平常說的一個應用程式(application)是不太一樣的。

  • 一個 App 由一個或多個 Executable 組成,在同一個 App 內的 Executable 都是在相同的執行環境內執行,也就是執行在同一個 sandbox,他們的環境變數和資源訪問權限都是一樣的。
  • App 之間被隔離,無法直接存取其他 App 的資源。若需存取,僅可透過IPC 機制(後面會再說明)。這樣的設計可以增加系統的安全性。
  • 每個 App 都會有一個 .adef 檔案(Application DEFinition),定義這個 App 該怎麼執行。
  • .adef 的名稱就會是這個 App 的名稱,例如 helloPrintApp.adef,則使用 app status 所看到的該 App 名稱就會是 helloPrintApp
  • Legato 使用 sandbox 為執行在同一個系統的 App 提供安全防護,sandbox 會將 App 與系統的其他部分隔離,使得 original equipment manufacturer(OEM)和 independent software vendor(ISV)的 Apps 可以安全地共存在同一個裝置,而不用擔心互相干擾。
  • 每個 App 都有自己獨特的 user ID、group ID、以及 root directory。
  • Legato sandbox 使用 chroot 來限制一個 App 只能看到其 directory 下的檔案和目錄。檔案使用 bind mounts 機制來跟 chroot 產生的隔離環境做連結。Processes 只能存取其 root directory 下的檔案和目錄。
  • 只有開啟特定功能的 processes 可以看到其 root directory 以外的東西;若是 sandboxed Apps 則無這種能力。
  • 我們可以在 .adef 檔案的 sandboxed section 設定該 App 是否要執行在 sandbox。
  • unsandboxed app 可以看到 target device 的 root file system;sandboxed app 則無法看到 target device 的 root file system,因為它會有一個隔離的 root file system。每個 app 都有自己獨特的 user 和 primary groups IDs。App 的 user name 和 primary group name 都是 appxxxx,其中的 xxxx 是 App 的名稱。
  • 更多 sandbox 機制的細節可參考官網說明:

System

由於目前我在使用上都沒用到 System 層級的東西,所以對他較不熟,有需要可以參考官網的說明(後續摸熟的話會再補充 >_<):

前面簡單的介紹完了 Component、Executable、以及 App,來看一下在 target machine 上的 Legato App 會怎麼呈現~

我們可以在前面安裝的 TelAF simulation docker container 內執行 app status,就可以看到目前系統內有安裝的 App 有哪些,以及他們的狀態,如下圖中一項一項的都是一個 App:

Screen Shot 2024-09-22 at 11.02.15 PM (2) (1)

其中 taf 開頭的 App 都是 TelAF 提供的 service,例如 tafDataCallSvc 就會提供跟 Data call 有關的 service,若我們的 App 想要使用 Data call 的 API,就必須要去存取這個 App,並且確保它是 running 的狀態。

Basic Legato App Structure

接下來,來看一個簡單的 Legato App 的架構會是長怎樣~
假設目前有一個 Legato App 的資料夾,叫做 helloServer,其架構如下圖:

legato_basic_example_structure

首先,一個 Legato App 一定會有:

  • 一個 .adef 檔案:其檔名就會是該 App 的名稱,也就是執行 app status 所看到的名稱,所以這個 App 的名稱是 helloServerApp
  • 一個或多個 Component 資料夾:
    • Component 資料夾的名稱就會是該 Component 的名稱,如此例,該 Component 名稱即為 ServerComp
    • 每個 Component 的資料夾內一定會有一個 Component.cdef 檔案
      • Component.cdef 的名稱是固定的,不可擅自修改。
    • 與該 Component 相關的檔案都會放在此資料夾內,像是 Component.cdef 和 source code file(如 server.c)等等。

此外,一個 Legato App 若有提供 service 給其他人使用,那就還會有 .api 檔案:

  • .api 檔案使用 Legato 的 interface definition language(IDL)撰寫 API 介面。
  • 我們在 .api 檔案內定義每個 API 的名稱、參數、return type、以及其他資訊等等。
  • 此例子是因為 serverComp 有提供 API 給其他 App 使用,所以才會有 .api 檔案。

上圖右側則是一個簡單的示意圖:

  • 這個 App 的名稱是 helloServerApp,其內含有一個 Executable,名稱是 serverExec(Executable 的名稱由 helloServerApp.adef 內定義)
  • 此 Executable 內有一個 Component,其名稱是 serverComp
  • serverComp 這個 Component 有提供一個 API 介面,它會被 expose 出來讓其他 App 可以使用(這部分是由 helloServerApp.adef 內的 extern section 所定義),該 API interface 名稱是 server_interface,想使用這個 API 的 App 只要連接到 helloServerAppserver_interface 即可(怎麼連接到別人的 API,後面會再介紹~)。

Application Definition Files & Component Definition Files

接下來,從前面這個 helloServerApp 例子來簡單介紹一下 .adefComponent.cdef 裡面會長怎樣~

(這裡只是做基本的簡單介紹,讓第一次接觸的人有些概念,詳細內容還是要參閱官網喔~)

image

Application definition files 和 component definition files 都是由一個一個的 section 所組成,上圖僅列出部分 section,各 section 詳細的說明可參閱官網說明。

(各 section 的擺放順序,我目前測試起來好像沒有一定要特定的順序,官網也沒特別提到)

Application Definition File ( .adef )

  • Application definition file 由許多 section 構成,一個最簡單的 App 至少一定要有 executablesprocesses 這兩個 sections。

  • executables section:

    • 列出要建立哪些 Executable,以及這些 Executable 由哪些 Component 構成。這些 Component 的 COMPONENT_INIT 會在 process 的 start-up 階段被執行。
    • 建立好的 Executable 會被移到 App 的 bin directory 內。
  • processes section:

    • 當 App 啟動時,有哪些 processes 要被執行。
    • 一個 Executable 由一個 process 執行。
    • 我們可以為同一個 processes section 內的 process 設定環境變數(藉由 envVar 這個 subsection),但若要給不同 process 設定不同的環境變數,則要寫在不同的 processes section。
  • extern section:

    • 此例子由於此 App 有提供 API 給其他 App 使用,所以會用到 extern section 將其 API interface 露出來給其他 App 使用。
      其他 App 若要使用此 App 的 API,則必須在其 .adefbindings section 做設定(後面會再提到)。
  • 其他重要的 section 還有 bindingsbundles 等等,更多 Application Definition 的細節請參閱 Legato 官網的說明:

    processes section 底下有 faultAction 這個 subsection,千萬不要在 TelAF simulation 內測試:

    ​​​​faultAction: reboot
    

    也就是如果有錯誤發生,process crash 了,那要採取什麼行動,reboot 則是重新開機。
    我這樣測試後,我的 TelAF simulation 就壞掉惹QQ,刪掉重新安裝 TelAF simulation 也沒用 😭 最後只能靠著之前 VirtualBox 的 snapshot 才救回來 @_@

Component Definition File ( .cdef )

API Files & Source Code Files

接下來來看 helloServerAppserver_api.api 以及 server.c

PS. .api 檔的命名並沒有特別規範,這裡只是為了幫助識別,才命名成 server_api

legato_basic_example_api_sourceCode

這個 server_api.api 檔案有定義兩個 API,第一個是 server_func1,第二個是 server_func2

  • server_func1
    • 沒有寫出 return type,所以是 return void
      • return type 會寫在 FUNCTION 跟 API name 之間,也就是 FUNCTIONserver_func1 之間。
    • 此 API 有一個參數,是 string 型態的參數,且是 IN 參數。
    • server.c 裡面,API 的 function name 的組成規則是 <interface_name>_<API_name>
      • interface_name 是在 Component.cdefprovides section 的 server_interface = server_api.api 所定義的 interface name。
        這樣宣告的話 interface_name 就會是 server_interface
      • 所以在 server.c 裡面,此 API 的 function name 就是 server_interface_server_func1,該 function 的參數是 const char *message
  • server_func2
    • 此 API 有 return 東西,是 return int32
    • 且此 API 有三個參數,分別是 n1n2、和 res,其中 n1n2IN 參數,而 resOUT 參數

若要確認 API 在 C 語言確切的 function prototype,可以用以下方式確認:

假如在 ~/test/ 內有 <yourApp>.adef.api 檔案以及 Component 的資料夾。

接下來在 ~/test/ 內執行:mkapp -t simulation <yourApp>.adef

則會在執行 mkapp 的資料夾內產生 _build_<yourApp> 資料夾,查看 _build_<yourApp>/simulation/api/<一串數字>/server/<interface_name>_server.h 這份檔案的內容即可確認在 C source code 裡面該使用怎樣的 function prototype。

我們的 source file 要依據這份檔案內的 function prototype 去實作 API 的內容。

image

Legato 官網有 API 檔更多的說明:

Communication Mechanisms in Legato

legato_3_types_of_communications

Legato 的 communication 的機制可以探討以下三種情況:

  1. 同一個 Executable 內不同 Components 之間的溝通
  2. 同一個 App 內的 Executables 之間的溝通
  3. 不同 Apps 之間的溝通

1. 同一個 Executable 內不同 Components 之間的溝通

由於是位在同一個 Executable 內的 Components,也就是都在同一個 process 內,所以可以存取彼此的東西。

如下例,有兩個 Component,serverCompclientComp,server 提供 server_showMsg 這個 function,並將 function prototype 放在 server.h

Client 若要使用的話,就要在 source code 內 include 該 header file,並且在 client 的 Component.cdefcflags section 內將 serverComp 的路徑加入 search path,讓 build tool 能找到 server.h 即可。

legato_communication_type1

helloIPC1App.adef

executables:
{
    helloExec = ( clientComp serverComp )
}

processes:
{
    run:
    {
        helloProc = ( helloExec )
    }
}

server.h

Server 端把欲提供出去的東西寫在一個 header file,server.h。以此例而言,server 分享了一個 int 型態的變數 num,以及一個 function server_ShorMsg

欲分享出去的東西前面都要加上 LE_SHARED 這個 macro,此 macro 可將 symbol 分享出去

#ifndef SERVER_H_INC
#define SERVER_H_INC

LE_SHARED int num;
LE_SHARED void server_showMsg(const char *message);

#endif

server.c

#include "legato.h"
#include "interfaces.h"
#include "server.h"

int num = 100;

void server_showMsg(const char *msg)
{
    LE_INFO("Client says: '%s', and the value of num is %d", msg, num);
}

COMPONENT_INIT
{
}

serverComp/Component.cdef

sources:
{
    server.c
}

client.c

#include "legato.h"
#include "interfaces.h"
#include "server.h" // 記得要 include server.h

COMPONENT_INIT
{
    LE_INFO("Asking server to print 'Hello, world!' and print the value of num");
    server_showMsg("Hello, world!");

    LE_INFO("client printing the value of num: %d", num);
    LE_INFO("client changes the value of num to 777");
    num = 777;
    server_showMsg("Changed num to 777");
}

clientComp/Component.cdef

sources:
{
    client.c
}

cflags:
{
    // 將以下這個新的 directory 加入到 search path,這樣 build tool 就會知道去哪找 server.h
    -I $CURDIR/../serverComp
}

Result

image

2. 同一個 App 內不同 Executables 之間的溝通

此情況需要透過 IPC 的機制來溝通,且需要定義 API。

如下例,有兩個 Component,clientCompserverComp。在 helloIPC2App.adef 內,將這兩個 Component 各自放到一個 Executable 內執行,由於這樣就是執行在不同的 process 內,無法直接存取彼此的東西,因此需要定義 API 檔案,並透過 IPC 的機制來溝通。

image

helloIPC2App.adef

executables:
{
    clientExec = ( clientComp )
    serverExec = ( serverComp )
}
 
processes:
{
    run:
    {
        serverProc = ( serverExec )
        clientProc = ( clientExec )
    }
}
 
bindings:
// bindings section 讓 client 端的 IPC API interface 能連結到 server 端的 interface
// client 端的 interface 就是寫在 client Component.cdef 內 requires section 內的
// server 端的 interface 就是寫在 server Component.cdef 內 provides section 內的
// 若是同一個 App 內的 interfaces 的 binding,稱做 internal binding,也就是這個例子
// 若是 App 跟 App 之間的 interfaces 的 binding,則稱做 external binding
{
    // binding 的寫法是 [client 端的 interface] -> [server 端的 interface]
    // 由於此例子是 internal binding,所以寫法會是 clientExe.clientComponent.clientInterface -> serverExe.serverComponent.serverInterface
    clientExec.clientComp.client_interface -> serverExec.serverComp.server_interface
    // 這裡的 client_interface 是定義在 client 的 Component.cdef 內的 requires section
 
    // 若是 external binding,由於 client app 理論上看不到 server app 內 component 的結構,所以應該是寫 client 的 interface 要連接到某個 App 的 interface
    // 所以寫法會是:
    // clientExe.clientComponent.clientInterface -> otherApp.serverInterface
 
    // 更多細節請參閱官網 https://docs.legato.io/latest/defFilesAdef.html#defFilesAdef_bindings
}

server_api.api

DEFINE MESSAGE_LEN = 100;
 
FUNCTION showMsg
(
    string msg[MESSAGE_LEN] IN
);

serverComp/Component.cdef

provides:
{
    api:
    {
        // server component 提供的 API 被定義在 server_api.api
        // 我們可以給這個 API 定義一個 interface 名稱,這裡命名為 server_interface
        server_interface = server_api.api
        // 這也是為什麼 helloIPC2App.adef 的 bindings section 的 clientExec.clientComp.client_interface -> serverExec.serverComp.server_interface 的 server_interface 的由來
        // server.c 內的 function 名稱也因此是 server_interface_showMsg
 
        // 我們也可以不自己定義一個 interface 名稱,直接用以下寫法:
        server_api.api
        // 這樣的話 server 端的 interface 名稱就是該 api 的檔名,也就是 server_api
        // adef 內的 bindings section 就要改成:clientExec.clientComp.client_interface -> serverExec.serverComp.server_api
        // server.c 內的 function 名稱也要改為 server_api_showMsg
    }
}
 
sources:
{
    server.c
}

server.c

#include "legato.h"
#include "interfaces.h"
 
// function 名稱的組成規則是 <interface_name>_<API_name>
// interface_name 就是定義在 server Component.cdef 內 provides section 所定義的 interface 名稱
void server_interface_showMsg(const char *msg)
{
    LE_INFO("Client says '%s'", msg);
}
 
COMPONENT_INIT
{
}

clientComp/Component.cdef

requires:
{
    api:
    {
        // client 這個 component 需要 server_api.api
        // 我們可以自己重新命名 interface 名稱,這裡重新命名成 client_interface
        // 這樣 client.c 內使用 server_api.api 裡面的 API 時,function 名稱會是 client_interface_<API name>
        // 這也是 adef 的 bindings section 的 clientExec.clientComp.client_interface 的 client_interface 的由來
        client_interface = server_api.api
 
        // 我們也可以不自己定義 interface 名稱,直接用以下寫法:
        server_api.api
        // 這樣產生的 interface 名稱就會是該 .api 檔的檔名,也就是 server_api
        // 這樣寫的話記得要修改 helloIPC2App.adef 內 bindings section 的 client 的 interface 名稱:
        // clientExec.clientComp.server_api -> serverExec.serverComp.server_interface
        // 此外,client.c 內 function 名稱也會改為 server_api_showMsg
    }
}
 
sources:
{
    client.c
}

client.c

#include "legato.h"
#include "interfaces.h"
 
COMPONENT_INIT
{
    LE_INFO("Asking server to print 'Hello, world!'");
     
    // function 名稱是 <interface_name>_<API_name>
    // interface_name 就是 client 的 Component.cdef 內 requires section 所定義的 interface 名稱
    client_interface_showMsg("Hello, world!");
}

Result

image2024-9-11_15-33-37_compressed

3. 不同 App 之間的溝通

此情況僅可透過 IPC 溝通,且需要 API。這是最安全的溝通方式,也是 Legato 官方比較推薦的作法。

以下例子,server 和 client 都有各自的 App:

  • Server 藉由其 .adefextern section 將其想給其他 App 使用的 IPC API interface 露出來,其他 App 若想使用該 interface 則需藉由其 .adefbindings section 來連接到該 interface。
  • Client 藉由其 .adefbindings section 使其 interface 和 server 的 interface 做連結。

image2024-9-20_10-29-26

helloIPC3ServerApp.adef

executables:
{
    serverExec = ( serverComp )
}
  
processes:
{
    run:
    {
        serverProc = ( serverExec )
    }
}
  
extern:
{
    // 可以給 interface 設定一個別名,也可以不設定
    // aliasName = exeName.componentName.interfaceName
    serverInterfaceAliasName = serverExec.serverComp.server_interface
    // 有設定別名的話,client 的 adef 的 bindings section 就是寫:
    // clientExec.clientComp.client_interface = serverApp.serverInterfaceAliasName
 
    // 若沒有要給別名,就這樣寫:
    serverExec.serverComp.server_interface
    // 這樣的話,client 的 adef 的 bindings section 就是寫:
    // clientExec.clientComp.client_interface = serverApp.server_interface
}

server_api.api

DEFINE MESSAGE_LEN = 100;
  
FUNCTION showMsg
(
    string msg[MESSAGE_LEN] IN
);

serverComp/Component.cdef

provides:
{
    api:
    {
        // 這個 Component 有提供 API,API 定義在 server_api.api
        // 可以自己設定 server 端的 interface,這裡將 server 端的 interface 設定成 server_interface
        server_interface = server_api.api
        // server.c 裡的 function 名稱 就是 server_interface_showMsg
 
        // 若沒有要自己設定 server 端的 interface,這樣寫就好:
        server_api.api
        // 這樣 server 端的 interface 就是 server_api
        // server.c 裡的 function 名稱 就是 server_api_showMsg
    }
}
 
sources:
{
    server.c
}

server.c

#include "legato.h"
#include "interfaces.h"
 
// function name 的組成規則是 <interface_name>_<API_name>
// interface_name 就是定義在 Component.cdef 的 provides section 內的 api section 的
void server_interface_showMsg(const char *msg)
{
    LE_INFO("Client says: '%s'", msg);
}
 
COMPONENT_INIT
{
}

helloIPC3ClientApp.adef

executables:
{
    clientExec = ( clientComp )
}
  
processes:
{
    run:
    {
        clientProc = ( clientExec )
    }
}
  
bindings:
{
    // 由於 clientApp 會用到 serverApp 所提供的 API,所以要先將 client 的 interface 連接到 server 的 interface
    // 這是 App 跟 App 間的 communication,是 external binding,所以連接的規則是:
    // clientExec.clientComp.clientInterface -> serverApp.serverInterface
    clientExec.clientComp.client_interface -> helloIPC3ServerApp.serverInterfaceAliasName
}

clientComp/Component.cdef

requires:
{
    api:
    {
        // client 需要用到 server_api.api
        // 這裡有自己設定 client 端的 interface 名稱為 client_interface
        client_interface = server_api.api      
 
        // 若沒自己設定 interface 名稱,就這樣寫:
        server_api.api
        // 這樣 client 端的 interface 名稱就是 server_api
    }
}
sources:
{
    client.c
}

client.c

#include "legato.h"
#include "interfaces.h"
 
COMPONENT_INIT
{
    LE_INFO("Asking server to print 'Hello, world!'");
 
    // function name 的組成規則是 <interface_name>_<API_name>
    // interface_name 就是寫在 Component.cdef 的 requires section 的 api section 內的 client_interface
    client_interface_showMsg("Hello, world!");
}

Make App

此例子在 make app 時,記得要 include server_api.api 所在的 path:

~/helloIPC3$ mkapp -t simulation -i ./ helloIPC3Server/helloIPC3ServerApp.adef 
~/helloIPC3$ mkapp -t simulation -i ./ helloIPC3Client/helloIPC3ClientApp.adef

Result

image2024-9-11_17-27-58_compressed

Overview

由於一開始在接觸 Legato 時搞不太懂 interface name 的規則,所以自己做了以下小整理~

image

image

我們在使用 TelAF 時,若要使用 TelAF 的某個 service,就是用第三種方式 bind 到該 service 的 API interface。

譬如說若要用 Data Call 相關的 API,官網( https://docs.qualcomm.com/bundle/publicresource/topics/80-41102-2/page_c_tafDataCallSvc.html?product=1601111740010418 )就會告知說我們自己開發的 App 的 .adefbindings section 要做怎樣的設定才能連結到他們 Data Call 的 service。

image2024-9-12_8-57-47