v72.01 鴻蒙核心原始碼分析(Shell解析篇) | 應用窺伺核心的視窗 | 百篇部落格分析OpenHarmony原始碼
子曰:“苟正其身矣,於從政乎何有?不能正其身,如正人何?” 《論語》:子路篇
百篇部落格系列篇.本篇為:
v72.xx 鴻蒙核心原始碼分析(Shell解析篇) | 應用窺視核心的視窗
程序管理相關篇為:
- v02.06 鴻蒙核心原始碼分析(程序管理) | 誰在管理核心資源
- v24.03 鴻蒙核心原始碼分析(程序概念) | 如何更好的理解程序
- v45.05 鴻蒙核心原始碼分析(Fork) | 一次呼叫 兩次返回
- v46.05 鴻蒙核心原始碼分析(特殊程序) | 老鼠生兒會打洞
- v47.02 鴻蒙核心原始碼分析(程序回收) | 臨終託孤的短命娃
- v48.05 鴻蒙核心原始碼分析(訊號生產) | 年過半百 活力十足
- v49.03 鴻蒙核心原始碼分析(訊號消費) | 誰讓CPU連續四次換棧執行
- v71.03 鴻蒙核心原始碼分析(Shell編輯) | 兩個任務 三個階段
- v72.01 鴻蒙核心原始碼分析(Shell解析) | 應用窺伺核心的視窗
系列篇從核心視角用一句話概括shell
的底層實現為:兩個任務,三個階段。其本質是獨立程序,因而劃到程序管理模組。每次建立shell
程序都會再建立兩個任務。
- 客戶端任務(ShellEntry): 負責接受來自終端(控制檯)敲入的一個個字元,字元按
VT
規範組裝成一句句的命令。 - 服務端任務(ShellTask): 對命令進行解析並執行,將結果輸出到控制檯。 而按命令生命週期可分三個階段.
- 編輯: 鴻蒙在這個部分實現了一個簡單的編輯器功能,處理控制檯輸入的每個字元,主要包括了對控制字元 例如
<ESC>
,\t
,\b
,\n
,\r
,四個方向鍵0x41
~0x44
的處理。 - 解析: 對編輯後的字串進行解析,解析出命令項和引數項,找到對應的命令項執行函式。
- 執行: 命令可通過靜態和動態兩種方式註冊到核心,解析出具體命令後在登錄檔中找到對應函式回撥。將結果輸出到控制檯。
編輯部分由客戶端任務完成,後兩個部分由服務端任務完成,命令全域性註冊由核心完成。
- 本篇主要說 服務端任務 和 解析/執行過程.
- 客戶端任務 和 編輯過程 已在(Shell編輯篇)中說明,請自行翻看.
總體過程
- 第一步: 將支援的
shell
命令註冊進全域性連結串列,支援靜態和動態兩種方式,內容包括命令項,引數資訊和回撥函式. - 第二步: 由獨立任務解析出使用者輸入的命令列,拆分出命令項和引數內容
- 第三步: 通過命令項在全域性連結串列中遍歷找到已註冊的回撥函式,並執行.
結構體
鴻蒙對命令的註冊用了三個結構體,個人感覺前兩個可以合成一個,降低程式碼閱讀難度.
STATIC CmdModInfo g_cmdInfo;//shell 命令模組資訊,上面掛了所有的命令項(ls,cd ,cp ==)
typedef struct {//命令項
CmdType cmdType; //命令型別
//CMD_TYPE_EX:不支援標準命令引數輸入,會把使用者填寫的命令關鍵字遮蔽掉,例如:輸入ls /ramfs,傳入給註冊函式的引數只有/ramfs,而ls命令關鍵字並不會被傳入。
//CMD_TYPE_STD:支援的標準命令引數輸入,所有輸入的字元都會通過命令解析後被傳入。
const CHAR *cmdKey; //命令關鍵字,例如:ls 函式在Shell中訪問的名稱。
UINT32 paraNum; //呼叫的執行函式的入參最大個數,暫不支援。
CmdCallBackFunc cmdHook;//命令執行函式地址,即命令實際執行函式。
} CmdItem;
typedef struct { //命令節點
LOS_DL_LIST list; //雙向連結串列
CmdItem *cmd; //命令項
} CmdItemNode;
/* global info for shell module */
typedef struct {//shell 模組的全域性資訊
CmdItemNode cmdList; //命令項節點
UINT32 listNum;//節點數量
UINT32 initMagicFlag;//初始魔法標籤 0xABABABAB
LosMux muxLock; //操作連結串列互斥鎖
CmdVerifyTransID transIdHook;//暫不知何意
} CmdModInfo;
解讀
CmdItem
為註冊的內容載體結構體,cmdHook
為回撥函式,是命令的真正執行體.- 通過雙向連結串列
CmdItemNode.list
將所有命令穿起來 CmdModInfo
記錄命令數量和操作的互斥鎖,shell
的魔法數字為0xABABABAB
第一步 | Shell 註冊
-
靜態巨集方式註冊,連結時處理 靜態註冊命令方式一般用在系統常用命令註冊,鴻蒙已支援以下命令.
arp cat cd chgrp chmod chown cp cpup date dhclient dmesg dns format free help hwi ifconfig ipdebug kill log ls lsfd memcheck mkdir mount netstat oom partinfo partition ping ping6 pwd reset rm rmdir sem statfs su swtmr sync systeminfo task telnet test tftp touch umount uname watch writeproc
例如註冊
ls
命令SHELLCMD_ENTRY(ls_shellcmd, CMD_TYPE_EX, "ls", XARGS, (CMD_CBK_FUNC)osShellCmdLs)
需在連結選項中新增連結該新增命令項引數,具體在
liteos_tables_ldflags.mk
檔案的LITEOS_TABLES_LDFLAGS
項下新增-uls_shellcmd
。至於SHELLCMD_ENTRY
是如何實現的在連結階段的註冊,請自行翻看(內聯彙編篇),有詳細說明實現細節. -
動態命令方式,執行時處理 動態註冊命令方式一般用在使用者命令註冊,具體實現程式碼如下:
osCmdReg(CMD_TYPE_EX, "ls", XARGS, (CMD_CBK_FUNC)osShellCmdLs) { // .... //5.正式建立命令,掛入連結串列 return OsCmdItemCreate(cmdType, cmdKey, paraNum, cmdProc);//不存在就註冊命令 } //建立一個命令項,例如 chmod STATIC UINT32 OsCmdItemCreate(CmdType cmdType, const CHAR *cmdKey, UINT32 paraNum, CmdCallBackFunc cmdProc) { CmdItem *cmdItem = NULL; CmdItemNode *cmdItemNode = NULL; //1.構造命令節點過程 cmdItem = (CmdItem *)LOS_MemAlloc(m_aucSysMem0, sizeof(CmdItem)); if (cmdItem == NULL) { return OS_ERRNO_SHELL_CMDREG_MEMALLOC_ERROR; } (VOID)memset_s(cmdItem, sizeof(CmdItem), '\0', sizeof(CmdItem)); cmdItemNode = (CmdItemNode *)LOS_MemAlloc(m_aucSysMem0, sizeof(CmdItemNode)); if (cmdItemNode == NULL) { (VOID)LOS_MemFree(m_aucSysMem0, cmdItem); return OS_ERRNO_SHELL_CMDREG_MEMALLOC_ERROR; } (VOID)memset_s(cmdItemNode, sizeof(CmdItemNode), '\0', sizeof(CmdItemNode)); cmdItemNode->cmd = cmdItem; //命令項 cmdItemNode->cmd->cmdHook = cmdProc;//回撥函式 osShellCmdLs cmdItemNode->cmd->paraNum = paraNum;//`777`,'/home' cmdItemNode->cmd->cmdType = cmdType;//關鍵字型別 cmdItemNode->cmd->cmdKey = cmdKey; //`chmod` //2.完成構造後掛入全域性連結串列 (VOID)LOS_MuxLock(&g_cmdInfo.muxLock, LOS_WAIT_FOREVER); OsCmdAscendingInsert(cmdItemNode);//按升序方式插入 g_cmdInfo.listNum++;//命令總數增加 (VOID)LOS_MuxUnlock(&g_cmdInfo.muxLock); return LOS_OK; }
第二步 解析 | ShellTask
//shell 服務端任務初始化,這個任務負責解析和執行命令
LITE_OS_SEC_TEXT_MINOR UINT32 ShellTaskInit(ShellCB *shellCB)
{
CHAR *name = NULL;
TSK_INIT_PARAM_S initParam = {0};
//輸入Shell命令的兩種方式
if (shellCB->consoleID == CONSOLE_SERIAL) { //通過串列埠工具
name = SERIAL_SHELL_TASK_NAME;
} else if (shellCB->consoleID == CONSOLE_TELNET) {//通過遠端工具
name = TELNET_SHELL_TASK_NAME;
} else {
return LOS_NOK;
}
initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)ShellTask;//任務入口函式,主要是解析shell命令
initParam.usTaskPrio = 9; /* 9:shell task priority */
initParam.auwArgs[0] = (UINTPTR)shellCB;
initParam.uwStackSize = 0x3000;
initParam.pcName = name;
initParam.uwResved = LOS_TASK_STATUS_DETACHED;
(VOID)LOS_EventInit(&shellCB->shellEvent);//初始化事件,以事件方式通知任務解析命令
return LOS_TaskCreate(&shellCB->shellTaskHandle, &initParam);//建立任務
}
LITE_OS_SEC_TEXT_MINOR UINT32 ShellTask(UINTPTR param1,
UINTPTR param2,
UINTPTR param3,
UINTPTR param4)
{
UINT32 ret;
ShellCB *shellCB = (ShellCB *)param1;
(VOID)param2;
(VOID)param3;
(VOID)param4;
while (1) {
PRINTK("\nOHOS # ");//讀取shell 輸入事件 例如: cat weharmony.net 命令
ret = LOS_EventRead(&shellCB->shellEvent,
0xFFF, LOS_WAITMODE_OR | LOS_WAITMODE_CLR, LOS_WAIT_FOREVER);
if (ret == SHELL_CMD_PARSE_EVENT) {//獲得解析命令事件
ShellCmdProcess(shellCB);//處理命令
} else if (ret == CONSOLE_SHELL_KEY_EVENT) {//退出shell事件
break;
}
}
OsShellKeyDeInit((CmdKeyLink *)shellCB->cmdKeyLink);//
OsShellKeyDeInit((CmdKeyLink *)shellCB->cmdHistoryKeyLink);
(VOID)LOS_EventDestroy(&shellCB->shellEvent);//登出事件
(VOID)LOS_MemFree((VOID *)m_aucSysMem0, shellCB);//釋放shell控制塊
return 0;
}
解讀
-
任務優先順序和 客戶端任務 一樣同為
9
-
指定核心棧大小為
0x3000 = 12K
,因任務負責命令的解析和執行,所以需要更大的核心空間. -
任務的入口函式
ShellTask
,一個死迴圈在以LOS_WAIT_FOREVER
方式死等事件發生.SHELL_CMD_PARSE_EVENT
通知開始解析事件,該事件由 客戶端任務ShellEntry
檢測到回車鍵時發出.
STATIC VOID ShellNotify(ShellCB *shellCB) { (VOID)LOS_EventWrite(&shellCB->shellEvent, SHELL_CMD_PARSE_EVENT); }
CONSOLE_SHELL_KEY_EVENT
收到exit
命令時將發出該事件,退出shell
回收資源 鴻蒙核心是如何管理和使用事件的請自行翻看(事件控制篇)
-
層層跟進
ShellCmdProcess
,解析出命令項和引數內容,最終跑到OsCmdExec
中遍歷 已註冊的命令表,找出命令對應的函式完成回撥.LITE_OS_SEC_TEXT_MINOR UINT32 OsCmdExec(CmdParsed *cmdParsed, CHAR *cmdStr) { UINT32 ret; CmdCallBackFunc cmdHook = NULL; CmdItemNode *curCmdItem = NULL; UINT32 i; const CHAR *cmdKey = NULL; if ((cmdParsed == NULL) || (cmdStr == NULL) || (strlen(cmdStr) == 0)) { return (UINT32)OS_ERROR; } ret = OsCmdParse(cmdStr, cmdParsed);//解析出命令關鍵字,引數 if (ret != LOS_OK) { goto OUT; } //遍歷命令註冊全域性連結串列 LOS_DL_LIST_FOR_EACH_ENTRY(curCmdItem, &(g_cmdInfo.cmdList.list), CmdItemNode, list) { cmdKey = curCmdItem->cmd->cmdKey; if ((cmdParsed->cmdType == curCmdItem->cmd->cmdType) && (strlen(cmdKey) == strlen(cmdParsed->cmdKeyword)) && (strncmp(cmdKey, (CHAR *)(cmdParsed->cmdKeyword), strlen(cmdKey)) == 0)) {//找到命令的回撥函式 例如: ls <-> osShellCmdLs cmdHook = curCmdItem->cmd->cmdHook; break; } } ret = OS_ERROR; if (cmdHook != NULL) {//執行命令,即回撥函式 ret = (cmdHook)(cmdParsed->paramCnt, (const CHAR **)cmdParsed->paramArray); } OUT: for (i = 0; i < cmdParsed->paramCnt; i++) {//無效的命令要釋放掉儲存引數的記憶體 if (cmdParsed->paramArray[i] != NULL) { (VOID)LOS_MemFree(m_aucSysMem0, cmdParsed->paramArray[i]); cmdParsed->paramArray[i] = NULL; } } return (UINT32)ret; }
第三步 | 執行
想知道有哪些系統shell
命令,可以搜尋關鍵詞SHELLCMD_ENTRY
拿到所有通過靜態方式註冊的命令.
其中有網路的,程序的,任務的,記憶體的 等等,此處列出幾個常用的shell
命令的實現.
ls 命令
SHELLCMD_ENTRY(ls_shellcmd, CMD_TYPE_EX, "ls", XARGS, (CmdCallBackFunc)osShellCmdLs);
/*******************************************************
命令功能
ls命令用來顯示當前目錄的內容。
命令格式
ls [path]
path為空時,顯示當前目錄的內容。
path為無效檔名時,顯示失敗,提示:
ls error: No such directory。
path為有效目錄路徑時,會顯示對應目錄下的內容。
使用指南
ls命令顯示當前目錄的內容。
ls可以顯示檔案的大小。
proc下ls無法統計檔案大小,顯示為0。
*******************************************************/
int osShellCmdLs(int argc, const char **argv)
{
char *fullpath = NULL;
const char *filename = NULL;
int ret;
char *shell_working_directory = OsShellGetWorkingDirtectory();//獲取當前工作目錄
if (shell_working_directory == NULL)
{
return -1;
}
ERROR_OUT_IF(argc > 1, PRINTK("ls or ls [DIRECTORY]\n"), return -1);
if (argc == 0)//木有引數時 -> #ls
{
ls(shell_working_directory);//執行ls 當前工作目錄
return 0;
}
filename = argv[0];//有引數時 -> #ls ../harmony or #ls /no such file or directory
ret = vfs_normalize_path(shell_working_directory, filename, &fullpath);//獲取全路徑,注意這裡帶出來fullpath,而fullpath已經在核心空間
ERROR_OUT_IF(ret < 0, set_err(-ret, "ls error"), return -1);
ls(fullpath);//執行 ls 全路徑
free(fullpath);//釋放全路徑,為啥要釋放,因為fullpath已經由核心空間分配
return 0;
}
task 命令
SHELLCMD_ENTRY(task_shellcmd, CMD_TYPE_EX, "task", 1, (CmdCallBackFunc)OsShellCmdDumpTask);
LITE_OS_SEC_TEXT_MINOR UINT32 OsShellCmdDumpTask(INT32 argc, const CHAR **argv)
{
UINT32 flag = 0;
#ifdef LOSCFG_KERNEL_VM
flag |= OS_PROCESS_MEM_INFO;
#endif
if (argc >= 2) { /* 2: The task shell name restricts the parameters */
goto TASK_HELP;
}
if (argc == 1) {
if (strcmp("-a", argv[0]) == 0) {
flag |= OS_PROCESS_INFO_ALL;
} else if (strcmp("-i", argv[0]) == 0) {
if (!OsShellShowTickRespo()) {
return LOS_OK;
}
goto TASK_HELP;
} else if (strcmp("-t", argv[0]) == 0) {
if (!OsShellShowSchedParam()) {
return LOS_OK;
}
goto TASK_HELP;
} else {
goto TASK_HELP;
}
}
return OsShellCmdTskInfoGet(OS_ALL_TASK_MASK, NULL, flag);
TASK_HELP:
PRINTK("Unknown option: %s\n", argv[0]);
PRINTK("usage: task or task -a\n");
return LOS_NOK;
}
cat 命令
SHELLCMD_ENTRY(cat_shellcmd, CMD_TYPE_EX, "cat", XARGS, (CmdCallBackFunc)osShellCmdCat);
/*****************************************************************
cat用於顯示文字檔案的內容。cat [pathname]
cat weharmony.txt
*****************************************************************/
int osShellCmdCat(int argc, const char **argv)
{
char *fullpath = NULL;
int ret;
unsigned int ca_task;
struct Vnode *vnode = NULL;
TSK_INIT_PARAM_S init_param;
char *shell_working_directory = OsShellGetWorkingDirtectory();//顯示當前目錄 pwd
if (shell_working_directory == NULL)
{
return -1;
}
ERROR_OUT_IF(argc != 1, PRINTK("cat [FILE]\n"), return -1);
ret = vfs_normalize_path(shell_working_directory, argv[0], &fullpath);//由相對路徑獲取絕對路徑
ERROR_OUT_IF(ret < 0, set_err(-ret, "cat error"), return -1);
VnodeHold();
ret = VnodeLookup(fullpath, &vnode, O_RDONLY);
if (ret != LOS_OK)
{
set_errno(-ret);
perror("cat error");
VnodeDrop();
free(fullpath);
return -1;
}
if (vnode->type != VNODE_TYPE_REG)
{
set_errno(EINVAL);
perror("cat error");
VnodeDrop();
free(fullpath);
return -1;
}
VnodeDrop();
(void)memset_s(&init_param, sizeof(init_param), 0, sizeof(TSK_INIT_PARAM_S));
init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)osShellCmdDoCatShow;
init_param.usTaskPrio = CAT_TASK_PRIORITY; //優先順序10
init_param.auwArgs[0] = (UINTPTR)fullpath; //入口引數
init_param.uwStackSize = CAT_TASK_STACK_SIZE;//核心棧大小
init_param.pcName = "shellcmd_cat"; //任務名稱
init_param.uwResved = LOS_TASK_STATUS_DETACHED | OS_TASK_FLAG_SPECIFIES_PROCESS;
init_param.processID = 2; /* 2: kProcess */ //核心任務
ret = (int)LOS_TaskCreate(&ca_task, &init_param);//建立任務顯示cat內容
if (ret != LOS_OK)
{
free(fullpath);
}
return ret;
}
你能看明白這些命令的底層實現嗎? 如果看明白了,可能會不由得發出 原來如此 的感嘆!
百篇部落格分析.深挖核心地基
- 給鴻蒙核心原始碼加註釋過程中,整理出以下文章。內容立足原始碼,常以生活場景打比方儘可能多的將核心知識點置入某種場景,具有畫面感,容易理解記憶。說別人能聽得懂的話很重要! 百篇部落格絕不是百度教條式的在說一堆詰屈聱牙的概念,那沒什麼意思。更希望讓核心變得栩栩如生,倍感親切.確實有難度,自不量力,但已經出發,回頭已是不可能的了。 😛
- 與程式碼有bug需不斷debug一樣,文章和註解內容會存在不少錯漏之處,請多包涵,但會反覆修正,持續更新,v**.xx 代表文章序號和修改的次數,精雕細琢,言簡意賅,力求打造精品內容。
按功能模組:
前因後果 | 基礎工具 | 載入執行 | 程序管理 |
---|---|---|---|
總目錄 排程故事 記憶體主奴 原始碼註釋 原始碼結構 靜態站點 | 雙向連結串列 點陣圖管理 用棧方式 定時器 原子操作 時間管理 | ELF格式 ELF解析 靜態連結 重定位 程序映像 | 程序管理 程序概念 Fork 特殊程序 程序回收 訊號生產 訊號消費 Shell編輯 Shell解析 |
編譯構建 | 程序通訊 | 記憶體管理 | 任務管理 |
編譯環境 編譯過程 環境指令碼 構建工具 gn應用 忍者ninja | 自旋鎖 互斥鎖 程序通訊 訊號量 事件控制 訊息佇列 | 記憶體分配 記憶體管理 記憶體彙編 記憶體對映 記憶體規則 實體記憶體 | 時鐘任務 任務排程 任務管理 排程佇列 排程機制 執行緒概念 併發並行 CPU 系統呼叫 任務切換 |
檔案系統 | 硬體架構 | ||
檔案概念 檔案系統 索引節點 掛載目錄 根檔案系統 字元裝置 VFS 檔案控制代碼 管道檔案 | 彙編基礎 彙編傳參 工作模式 暫存器 異常接管 彙編彙總 中斷切換 中斷概念 中斷管理 |
百萬漢字註解.精讀核心原始碼
四大碼倉中文註解 . 定期同步官方程式碼
鴻蒙研究站( weharmonyos ) | 每天死磕一點點,原創不易,歡迎轉載,請註明出處。若能支援點贊則更佳,感謝每一份支援。
- 鴻蒙輕核心原始碼分析:虛實對映
- 鴻蒙輕核心原始碼分析:虛實對映
- 鴻蒙輕核心原始碼分析:虛擬記憶體
- 鴻蒙輕核心原始碼分析:虛擬記憶體
- 鴻蒙輕核心原始碼分析:虛擬記憶體
- 鴻蒙輕核心原始碼分析:異常鉤子模組系統中斷異常,如何轉儲異常資訊
- 鴻蒙輕核心原始碼分析:異常鉤子模組系統中斷異常,如何轉儲異常資訊
- v72.01 鴻蒙核心原始碼分析(Shell解析篇) | 應用窺伺核心的視窗 | 百篇部落格分析OpenHarmony原始碼
- 鴻蒙核心原始碼分析(記憶體管理篇) | 虛擬記憶體全景圖是怎樣的 | 百篇部落格分析OpenHarmony原始碼 | v12.05
- 鴻蒙核心原始碼分析(重定位篇) | 與國際接軌的對外部發言人 | 百篇部落格分析OpenHarmony原始碼 | v55.02
- 鴻蒙核心原始碼分析(Shell編輯篇) | 兩個任務,三個階段 | 百篇部落格分析OpenHarmony原始碼 | v71.01
- 鴻蒙核心原始碼分析(管道檔案篇) | 如何降低資料流動成本 | 百篇部落格分析OpenHarmony原始碼 | v70.01
- 鴻蒙核心原始碼分析(字元裝置篇) | 位元組為單位讀寫的裝置 | 百篇部落格分析OpenHarmony原始碼 | v67.01
- 鴻蒙核心原始碼分析(根檔案系統) | 先掛到`/`上的檔案系統 | 百篇部落格分析OpenHarmony原始碼 | v66.01
- 鴻蒙核心原始碼分析(掛載目錄篇) | 為何檔案系統需要掛載 | 百篇部落格分析OpenHarmony原始碼 | v65.01
- 鴻蒙核心原始碼分析(索引節點篇) | 誰是檔案系統最重要的概念 | 百篇部落格分析OpenHarmony原始碼 | v64.01
- 鴻蒙輕核心M核原始碼分析系列六 任務及任務排程(3)任務排程模組
- 鴻蒙核心原始碼分析(檔案系統篇) | 用圖書管理說檔案系統 | 百篇部落格分析OpenHarmony原始碼 | v63.01
- 鴻蒙核心原始碼分析(檔案概念篇) | 為什麼說一切皆是檔案 | 百篇部落格分析OpenHarmony原始碼 | v62.01
- 鴻蒙輕核心原始碼分析:掌握訊號量使用差異