赞
踩
系列篇从内核视角用一句话概括shell
的底层实现为:两个任务,三个阶段。其本质是独立进程,因而划到进程管理模块。每次创建shell
进程都会再创建两个任务。
VT
规范组装成一句句的命令。而按命令生命周期可分三个阶段.
<ESC>
,\t
,\b
,\n
,\r
,四个方向键0x41
~ 0x44
的处理。编辑部分由客户端任务完成,后两个部分由服务端任务完成,命令全局注册由内核完成。
从用户视角看,shell
是用户窥视和操作内核的一个窗口,内核并非铁板一块,对应用层开了两个窗口,一个是系统调用,一个就是shell
,由内核提供实现函数,由用户提供参数执行。区别是 shell
是由独立的任务去完成,可通过将shell
命令序列化编写成独立的,简单的shell
程序,所以shell
也是一门脚本语言,系统调用是依附于应用程序的任务去完成,能做的有限。通过shell
窗口能看到 cpu
的运行情况,内存的消耗情况,网络的链接状态等等。
与shell
对应的概念是kernel
,在鸿蒙内核,这两部分代码是分开放的,shell
代码在 查看 shell 代码 ,目录结构如下.
├─include │ dmesg.h │ dmesg_pri.h │ shcmd.h │ shcmdparse.h │ shell.h │ shell_lk.h │ shell_pri.h │ shmsg.h │ show.h │ └─src ├─base │ shcmd.c │ shcmdparse.c │ shell_lk.c │ shmsg.c │ show.c │ └─cmds date_shellcmd.c dmesg.c hwi_shellcmd.c shell_shellcmd.c watch_shellcmd.c
跟进程,任务一样,每个概念的背后需要一个主结构体来的支撑,shell
的主结构体就是ShellCB
,掌握它就可以将shell
拿捏的死死的,搞不懂这个结构体就读不懂shell
的内核实现.所以在上面花再多功夫也不为过.
typedef struct { UINT32 consoleID; //控制台ID UINT32 shellTaskHandle; //shell服务端任务ID UINT32 shellEntryHandle; //shell客户端任务ID VOID *cmdKeyLink; //待处理的shell命令链表 VOID *cmdHistoryKeyLink;//已处理的历史记录链表,去重,10个 VOID *cmdMaskKeyLink; //主要用于方向键上下遍历历史命令 UINT32 shellBufOffset; //buf偏移量 UINT32 shellKeyType; //按键类型 EVENT_CB_S shellEvent; //事件类型触发 pthread_mutex_t keyMutex; //按键互斥量 pthread_mutex_t historyMutex; //历史记录互斥量 CHAR shellBuf[SHOW_MAX_LEN]; //shell命令buf,接受键盘的输入,需要对输入字符解析. CHAR shellWorkingDirectory[PATH_MAX];//shell的工作目录 } ShellCB; //一个shell命令的结构体,命令有长有短,鸿蒙采用了可变数组的方式实现 typedef struct { UINT32 count; //字符数量 LOS_DL_LIST list; //双向链表 CHAR cmdString[0]; //字符串,可变数组的一种实现方式. } CmdKeyLink; enum { STAT_NOMAL_KEY, //普通的按键 STAT_ESC_KEY, //<ESC>键在VT控制规范中时控制的起始键 STAT_MULTI_KEY //组合键 };
解读
Shell
命令,关于控制台请自行翻看控制台篇.
Shell
命令 CONSOLE_SERIAL
。telnet
工具中输入Shell
命令 CONSOLE_TELNET
。shellTaskHandle
和shellEntryHandle
编辑/处理shell
命令的两个任务ID,本篇重点说后一个.cmdKeyLink
,cmdHistoryKeyLink
,cmdMaskKeyLink
是三个类型为CmdKeyLink
的结构体,本质是双向链表,对应编辑shell
命令过程中的三个功能.
cmdKeyLink
待执行的命令链表cmdHistoryKeyLink
存储命令历史记录的,即: history
命令显示的内容cmdMaskKeyLink
记录按上下方向键输出的内容,这个有点难理解,自行在shell
中按上下方向键自行体验shellBufOffset
和shellBuf
是成对出现的,其中存放的就是用户敲入处理后的字符.keyMutex
和historyMutex
为操作链表所需的互斥锁,内核用的最多的就是这类锁.shellEvent
用于任务之间的通讯,比如.
SHELL_CMD_PARSE_EVENT
:编辑完成了通知解析任务开始执行CONSOLE_SHELL_KEY_EVENT
:收到来自控制台的CTRL + C
信号产生的事件.shellKeyType
按键的类型,分三种 普通,键,组合键shellWorkingDirectory
工作区就不用说了,从哪个目录进入shell
的//shell进程的入口函数 int main(int argc, char **argv) { //... g_shellCB = shellCB;//全局变量,说明鸿蒙同时只支持一个shell进程 return OsShellCreateTask(shellCB);//初始化两个任务 } //创建shell任务 STATIC UINT32 OsShellCreateTask(ShellCB *shellCB) { UINT32 ret = ShellTaskInit(shellCB);//执行shell命令的任务初始化 if (ret != LOS_OK) { return ret; } return ShellEntryInit(shellCB);//通过控制台接收shell命令的任务初始化 } //进入shell客户端任务初始化,这个任务负责编辑命令,处理命令产生的过程,例如如何处理方向键,退格键,回车键等 LITE_OS_SEC_TEXT_MINOR UINT32 ShellEntryInit(ShellCB *shellCB) { UINT32 ret; CHAR *name = NULL; TSK_INIT_PARAM_S initParam = {0}; if (shellCB->consoleID == CONSOLE_SERIAL) { name = SERIAL_ENTRY_TASK_NAME; } else if (shellCB->consoleID == CONSOLE_TELNET) { name = TELNET_ENTRY_TASK_NAME; } else { return LOS_NOK; } initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)ShellEntry;//任务入口函数 initParam.usTaskPrio = 9; /* 9:shell task priority */ initParam.auwArgs[0] = (UINTPTR)shellCB; initParam.uwStackSize = 0x1000; initParam.pcName = name; initParam.uwResved = LOS_TASK_STATUS_DETACHED; ret = LOS_TaskCreate(&shellCB->shellEntryHandle, &initParam);//创建任务 #ifdef LOSCFG_PLATFORM_CONSOLE (VOID)ConsoleTaskReg((INT32)shellCB->consoleID, shellCB->shellEntryHandle);//将任务注册到控制台 #endif return ret; }
解读
main
为shell
进程的主任务,每个进程都会创建一个默认的线程(任务),这个任务的入口函数就是大家熟知的main
函数,不清楚的自行翻看任务管理各篇有详细的说明.main
任务再创建两个任务,即本篇开头说的两个任务,本篇重点说其中的一个 ShellEntry
,任务优先级为9
,算是较高优先级.0x1000 = 4K
,因任务只负责编辑处理控制台输入的字符,命令的执行在其他任务,所以4K的内核空间足够使用.ShellEntry
为入口函数,这个函数的实现为本篇的重点LITE_OS_SEC_TEXT_MINOR UINT32 ShellEntry(UINTPTR param) { CHAR ch; INT32 n = 0; ShellCB *shellCB = (ShellCB *)param; CONSOLE_CB *consoleCB = OsGetConsoleByID((INT32)shellCB->consoleID);//获取控制台 if (consoleCB == NULL) { PRINT_ERR("Shell task init error!\n"); return 1; } (VOID)memset_s(shellCB->shellBuf, SHOW_MAX_LEN, 0, SHOW_MAX_LEN);//重置shell命令buf while (1) { #ifdef LOSCFG_PLATFORM_CONSOLE if (!IsConsoleOccupied(consoleCB)) {//控制台是否被占用 #endif /* is console ready for shell ? */ n = read(consoleCB->fd, &ch, 1);//从控制台读取一个字符内容,字符一个个处理 if (n == 1) {//如果能读到一个字符 ShellCmdLineParse(ch, (pf_OUTPUT)dprintf, shellCB); } if (is_nonblock(consoleCB)) {//在非阻塞模式下暂停 50ms LOS_Msleep(50); /* 50: 50MS for sleep */ } #ifdef LOSCFG_PLATFORM_CONSOLE } #endif } } //对命令行内容解析 LITE_OS_SEC_TEXT_MINOR VOID ShellCmdLineParse(CHAR c, pf_OUTPUT outputFunc, ShellCB *shellCB) { const CHAR ch = c; INT32 ret; //不是回车键和字符串结束,且偏移量为0 if ((shellCB->shellBufOffset == 0) && (ch != '\n') && (ch != '\0')) { (VOID)memset_s(shellCB->shellBuf, SHOW_MAX_LEN, 0, SHOW_MAX_LEN);//重置buf } //遇到回车或换行 if ((ch == '\r') || (ch == '\n')) { if (shellCB->shellBufOffset < (SHOW_MAX_LEN - 1)) { shellCB->shellBuf[shellCB->shellBufOffset] = '\0';//字符串结束 } shellCB->shellBufOffset = 0; (VOID)pthread_mutex_lock(&shellCB->keyMutex); OsShellCmdPush(shellCB->shellBuf, shellCB->cmdKeyLink);//解析回车或换行 (VOID)pthread_mutex_unlock(&shellCB->keyMutex); ShellNotify(shellCB);//通知任务解析shell命令 return; } else if ((ch == '\b') || (ch == 0x7F)) { /* backspace or delete(0x7F) */ //遇到删除键 if ((shellCB->shellBufOffset > 0) && (shellCB->shellBufOffset < (SHOW_MAX_LEN - 1))) { shellCB->shellBuf[shellCB->shellBufOffset - 1] = '\0';//填充`\0` shellCB->shellBufOffset--;//buf减少 outputFunc("\b \b");//回调入参函数 } return; } else if (ch == 0x09) { /* 0x09: tab *///遇到tab键 if ((shellCB->shellBufOffset > 0) && (shellCB->shellBufOffset < (SHOW_MAX_LEN - 1))) { ret = OsTabCompletion(shellCB->shellBuf, &shellCB->shellBufOffset);//解析tab键 if (ret > 1) { outputFunc("OHOS # %s", shellCB->shellBuf);//回调入参函数 } } return; } /* parse the up/down/right/left key */ ret = ShellCmdLineCheckUDRL(ch, shellCB);//解析上下左右键 if (ret == LOS_OK) { return; } if ((ch != '\n') && (ch != '\0')) {//普通的字符的处理 if (shellCB->shellBufOffset < (SHOW_MAX_LEN - 1)) {//buf范围 shellCB->shellBuf[shellCB->shellBufOffset] = ch;//直接加入 } else { shellCB->shellBuf[SHOW_MAX_LEN - 1] = '\0';//加入字符串结束符 } shellCB->shellBufOffset++;//偏移量增加 outputFunc("%c", ch);//向终端输出字符 } shellCB->shellKeyType = STAT_NOMAL_KEY;//普通字符 }
解读
ShellEntry
内部是个死循环,不断的读取控制台输入的每个字符,注意是按字符处理.tab
,backspace
,delete
,esc
等控制键,相当于重新认识了下Ascii
表.可以把shell
终端理解为一个简单的编辑器.
tab
键 是要补齐命令的内容,目前鸿蒙支持如下命令: 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
例如:当在控制台按下 ch
和 tab
键后会输出以下三个
chgrp chmod chown
内容,这些功能对使用者而已看似再平常不过,但都需要内核一一实现.
shellBuf
存储编辑结果,当按下回车键时,将结果保存并交付给下一个阶段使用.也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线【包含了大厂APP实战项目开发】。
gitee.com/MNxiaona/733GH
gitee.com/MNxiaona/733GH
1.基本概念
2.构建第一个ArkTS应用
3.……
gitee.com/MNxiaona/733GH
1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……
1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……
gitee.com/MNxiaona/733GH
gitee.com/MNxiaona/733GH
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。