6.2 SHELL 命令行
6.2 SHELL 命令行
📚 本节导读
学习时长: 约 45 分钟
难度级别: ⭐⭐⭐☆☆
前置知识: C 语言函数指针、链接器段(section)机制、第4章内核组件
🎯 学习目标
- 理解 Shell 在 OneOS 中的角色和架构设计
- 掌握
sh_cmd_entry_t结构体和sh_cmd_func_t函数指针类型 - 掌握
SH_CMD_EXPORT命令注册机制及其底层实现原理 - 理解利用链接器段
FSymTab实现命令自动注册的方法 - 掌握 Shell 核心 API(
sh_exec、sh_get_prompt、控制台连接管理) - 了解内置命令(help、set_prompt)及内核/文件系统命令
- 理解 Kconfig 配置项(任务参数、历史记录、认证等)
- 能够编写自定义 Shell 命令并注册到系统
一、概述
Shell 是 OneOS 的命令行交互工具,通过串口(控制台)提供人机交互界面。它是嵌入式开发中最常用的调试和系统管理手段之一,开发者可以通过 Shell 执行预定义的命令来查看系统状态、控制设备、调试代码等。
Shell 的核心设计特点包括:
- 命令自动注册:利用链接器段(section)机制,命令在编译时自动注册到全局命令表,无需手动调用注册函数
- 历史记录:支持上下键浏览历史命令,提高操作效率
- 命令补全:支持 Tab 键命令自动补全(需启用相关配置)
- 认证支持:可选的密码认证机制,保护系统安全
- 控制台切换:支持断开和重连控制台,方便与其他模块共享串口
- 描述信息:可选保留命令描述信息,通过
help命令查看
Shell 的源码位于 components/shell/ 目录,核心头文件为 components/shell/include/shell.h。
二、核心数据结构
2.1 命令处理函数类型
Shell 命令的处理函数遵循统一的函数签名(源码 shell.h 第34行):
typedef os_err_t (*sh_cmd_func_t)(int32_t argc, char **argv);| 参数 | 类型 | 说明 |
|---|---|---|
argc | int32_t | 命令参数个数(包含命令名本身) |
argv | char ** | 参数数组,argv[0] 为命令名,argv[1] 起为实际参数 |
| 返回值 | os_err_t | OS_SUCCESS 表示执行成功,其他值表示错误 |
2.2 命令入口结构体
每个 Shell 命令都对应一个命令入口结构体,存储在 FSymTab 段中(源码 shell.h 第36-46行):
struct sh_cmd_entry
{
const char *name; /* 命令名称 */
#if defined(SHELL_USING_DESCRIPTION)
const char *desc; /* 命令描述(条件编译) */
#endif
sh_cmd_func_t func; /* 命令处理函数地址 */
};
typedef struct sh_cmd_entry sh_cmd_entry_t;当 SHELL_USING_DESCRIPTION 启用时,结构体包含 desc 字段,用于 help 命令显示命令说明。禁用时,仅保留 name 和 func 字段,以节省 ROM 空间。
三、命令注册机制
3.1 SH_CMD_EXPORT 宏
Shell 使用 SH_CMD_EXPORT 宏将函数导出为 Shell 命令(源码 shell.h 第88行):
#define SH_CMD_EXPORT(cmd, func, desc) SH_FUNCTION_EXPORT_CMD(func, __cmd_##cmd, desc)参数说明:
| 参数 | 说明 |
|---|---|
cmd | 命令名(用户输入的命令字符串) |
func | 命令处理函数 |
desc | 命令描述(help 命令显示) |
使用示例:
SH_CMD_EXPORT(my_cmd, my_cmd_handler, "This is my custom command");3.2 底层实现机制
SH_CMD_EXPORT 最终展开为 SH_FUNCTION_EXPORT_CMD 宏(源码 shell.h 第57-74行):
当启用描述信息时(SHELL_USING_DESCRIPTION):
#define SH_FUNCTION_EXPORT_CMD(func, cmd, desc) \
const char __fsym_##cmd##_name[] = #cmd; \
const char __fsym_##cmd##_desc[] = desc; \
OS_USED const sh_cmd_entry_t __fsym_##cmd OS_SECTION("FSymTab") = \
{ \
__fsym_##cmd##_name, \
__fsym_##cmd##_desc, \
(sh_cmd_func_t)func \
};当禁用描述信息时:
#define SH_FUNCTION_EXPORT_CMD(func, cmd, desc) \
const char __fsym_##cmd##_name[] = #cmd; \
OS_USED const sh_cmd_entry_t __fsym_##cmd OS_SECTION("FSymTab")= \
{ \
__fsym_##cmd##_name, \
(sh_cmd_func_t)func \
};3.3 段导出原理
命令注册的核心是 GCC 的 __attribute__((section("FSymTab"))) 机制,通过 OS_SECTION("FSymTab") 宏实现:
- 每个
SH_CMD_EXPORT调用会生成一个sh_cmd_entry_t类型的全局变量 - 该变量被放入名为
FSymTab的专用链接器段中 - 链接器将所有
FSymTab段中的变量连续排列,形成全局命令表 - Shell 启动时,通过查询
FSymTab段的起始和结束地址,遍历所有已注册的命令
这种方式的优势在于:
- 零运行时开销:无需在运行时调用注册函数
- 自动发现:链接器自动收集所有命令,无需手动维护命令列表
- 条件编译友好:未编译的模块不会产生命令入口
四、核心 API 详解
4.1 执行 Shell 命令
os_err_t sh_exec(const char *cmd);以字符串形式执行一条 Shell 命令。该函数会解析命令字符串,查找对应的命令入口,并调用其处理函数。
| 参数 | 类型 | 说明 |
|---|---|---|
cmd | const char * | 完整的命令字符串,如 "help"、"set_prompt OneOS>" |
| 返回值 | os_err_t | OS_SUCCESS 成功,其他值表示错误 |
4.2 获取当前提示符
const char *sh_get_prompt(void);获取当前 Shell 提示符字符串。默认提示符由 SHELL_PROMPT_SIZE 配置决定。
4.3 控制台连接管理
void sh_disconnect_console(void); /* 断开控制台 */
void sh_reconnect_console(void); /* 重连控制台 */这两个函数用于控制 Shell 与串口控制台的连接状态:
sh_disconnect_console:断开 Shell 与控制台的连接,释放串口供其他模块使用sh_reconnect_console:重新建立 Shell 与控制台的连接,恢复 Shell 交互
典型应用场景:需要临时使用串口进行固件升级或数据传输时,先断开 Shell,完成后再重连。
五、内置命令
5.1 Shell 内置命令
Shell 在 components/shell/source/shell_buildin_cmd.c 中提供了两个基础内置命令:
(1)help - 显示所有命令
static os_err_t sh_help(int32_t argc, char **argv)
{
// 遍历 FSymTab 段中的所有命令入口
// 筛选出用户命令(跳过以 "__cmd_" 为前缀的内部命令)
// 输出每个命令的名称和描述
}
SH_CMD_EXPORT(help, sh_help, "Obtain help of commands");执行 help 命令会列出所有已注册的 Shell 命令及其描述信息。
(2)set_prompt - 设置提示符
static os_err_t sh_set_prompt(int32_t argc, char **argv)
{
// 参数检查:argc 必须为 2
// 调用 sh_do_set_prompt 设置新的提示符字符串
}
SH_CMD_EXPORT(set_prompt, sh_set_prompt, "Set shell prompt");用法示例:
OneOS> set_prompt MyDevice>
MyDevice>5.2 内核命令
内核模块导出了大量 Shell 命令,用于查看和调试系统状态(位于 kernel/source/ 目录):
| 命令 | 功能 | 来源文件 |
|---|---|---|
show_task | 显示所有任务信息 | os_task.c |
show_sem | 显示信号量信息 | os_sem.c |
show_mutex | 显示互斥锁信息 | os_mutex.c |
show_event | 显示事件信息 | os_event.c |
show_mq | 显示消息队列信息 | os_mq.c |
show_mb | 显示邮箱信息 | os_mb.c |
show_timer | 显示定时器信息 | os_timer_hash.c / os_timer_slist.c |
show_heap | 显示堆内存信息 | os_memory.c |
show_mem | 显示内存使用情况 | os_memory.c |
show_mempool | 显示内存池信息 | os_mem_pool.c |
check_mem | 检查内存数据完整性 | os_memory.c |
trace_mem | 追踪任务内存使用 | os_memory.c |
version | 显示 OneOS 版本信息 | os_version.c |
sem_trace_add | 添加信号量追踪 | os_sem_trace.c |
sem_trace_del | 删除信号量追踪 | os_sem_trace.c |
sem_trace_show | 显示信号量追踪信息 | os_sem_trace.c |
5.3 文件系统命令
文件系统组件(components/fs/)提供了常用的文件操作命令:
| 命令 | 功能 | 说明 |
|---|---|---|
ls | 列出目录内容 | 类似 Linux ls |
cd | 切换工作目录 | 类似 Linux cd |
pwd | 显示当前目录 | 类似 Linux pwd |
mkdir | 创建目录 | 类似 Linux mkdir |
cat | 查看文件内容 | 类似 Linux cat |
echo | 输出字符串到文件 | 类似 Linux echo |
rm | 删除文件 | 类似 Linux rm |
cp | 复制文件 | 类似 Linux cp |
mv | 移动/重命名文件 | 类似 Linux mv |
df | 显示磁盘使用情况 | 类似 Linux df |
mkfs | 格式化磁盘 | 创建文件系统 |
mount | 挂载文件系统 | 类似 Linux mount |
umount | 卸载文件系统 | 类似 Linux umount |
show_fd | 显示文件描述符信息 | 查看打开的文件 |
fs_info | 显示文件系统挂载信息 | 查看挂载点详情 |
5.4 Dlog 日志命令
Dlog 组件提供了运行时日志控制命令:
| 命令 | 功能 |
|---|---|
dlog_glvl_ctrl | Dlog 全局级别控制 |
dlog_tlvl_ctrl | Dlog tag 级别控制 |
dlog_gtag_ctrl | Dlog 全局 tag 控制 |
dlog_gkw_ctrl | Dlog 全局关键词控制 |
dlog_flush | 刷新 Dlog 缓存 |
5.5 测试命令
Atest 测试框架提供了测试管理命令:
| 命令 | 功能 |
|---|---|
atest_run | 运行测试用例 |
atest_list | 列出所有测试用例 |
六、Kconfig 配置详解
Shell 的配置项位于 components/shell/Kconfig,在 menu "Shell" 下。
6.1 基础配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
OS_USING_SHELL | bool | y | 启用 Shell 功能 |
6.2 任务配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
SHELL_TASK_NAME | string | "shell" | Shell 任务名称 |
SHELL_TASK_PRIORITY | int | 20(32级) | Shell 任务优先级 |
SHELL_TASK_STACK_SIZE | int | 2048 | Shell 任务栈大小(字节) |
6.3 历史记录
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
SHELL_USING_HISTORY | bool | y | 启用历史命令功能 |
SHELL_HISTORY_LINES | int | 5 | 历史命令保存行数 |
启用历史记录后,可以通过上下方向键浏览之前执行过的命令。
6.4 命令描述
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
SHELL_USING_DESCRIPTION | bool | y | 保留命令描述信息(会占用 ROM 空间) |
6.5 回显控制
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
SHELL_ECHO_DISABLE_DEFAULT | bool | n | 默认关闭回显 |
6.6 缓冲区大小
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
SHELL_CMD_SIZE | int | 80 | 命令行缓冲区大小(字符) |
SHELL_PROMPT_SIZE | int | 256 | 提示符字符串大小(字符) |
SHELL_ARG_MAX | int | 10 | 命令参数最大数量 |
6.7 认证支持
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
SHELL_USING_AUTH | bool | n | 启用 Shell 认证 |
SHELL_PASSWORD_MIN | int | 6 | 密码最小长度 |
SHELL_PASSWORD_MAX | int | OS_NAME_MAX | 密码最大长度 |
SHELL_DEFAULT_PASSWORD | string | "" | 默认密码(空字符串表示无密码) |
启用认证后,用户在进入 Shell 前需要输入密码进行验证。
七、使用示例
7.1 自定义命令注册
#include <shell.h>
#include <os_util.h>
/* 无参数的命令 */
static os_err_t sh_hello(int32_t argc, char **argv)
{
OS_UNREFERENCE(argc);
OS_UNREFERENCE(argv);
os_kprintf("Hello from OneOS Shell!\r\n");
return OS_SUCCESS;
}
SH_CMD_EXPORT(hello, sh_hello, "Say hello");7.2 带参数的命令处理
#include <shell.h>
#include <os_util.h>
#include <stdlib.h>
/* 带参数的命令:my_echo <message> */
static os_err_t sh_my_echo(int32_t argc, char **argv)
{
int32_t i;
if (argc < 2)
{
os_kprintf("Usage: my_echo <message>\r\n");
return OS_INVAL;
}
for (i = 1; i < argc; i++)
{
os_kprintf("%s ", argv[i]);
}
os_kprintf("\r\n");
return OS_SUCCESS;
}
SH_CMD_EXPORT(my_echo, sh_my_echo, "Echo a message");7.3 LED 控制命令
#include <shell.h>
#include <os_util.h>
#include <stdlib.h>
#include <string.h>
static os_err_t sh_led(int32_t argc, char **argv)
{
int32_t led_num;
int32_t state;
if (argc != 3)
{
os_kprintf("Usage: led <num> <on|off>\r\n");
os_kprintf(" num: LED number (0-3)\r\n");
os_kprintf(" state: on or off\r\n");
return OS_INVAL;
}
led_num = atoi(argv[1]);
if (led_num < 0 || led_num > 3)
{
os_kprintf("Error: LED number must be 0-3\r\n");
return OS_INVAL;
}
if (strcmp(argv[2], "on") == 0)
{
state = 1;
}
else if (strcmp(argv[2], "off") == 0)
{
state = 0;
}
else
{
os_kprintf("Error: state must be 'on' or 'off'\r\n");
return OS_INVAL;
}
/* 调用底层 LED 驱动 */
led_control(led_num, state);
os_kprintf("LED %d: %s\r\n", led_num, state ? "ON" : "OFF");
return OS_SUCCESS;
}
SH_CMD_EXPORT(led, sh_led, "Control LED on/off");7.4 执行 Shell 命令
#include <shell.h>
void execute_shell_commands(void)
{
/* 执行 help 命令 */
sh_exec("help");
/* 执行带参数的命令 */
sh_exec("set_prompt OneOS>");
/* 执行自定义命令 */
sh_exec("hello");
sh_exec("my_echo Hello World");
sh_exec("led 0 on");
}7.5 控制台切换示例
#include <shell.h>
void temporary_use_console(void)
{
/* 断开 Shell 控制台,释放串口 */
sh_disconnect_console();
/* 使用串口进行自定义操作(如固件升级) */
firmware_upgrade_via_uart();
/* 恢复 Shell 控制台 */
sh_reconnect_console();
}📝 本节小结
本节详细介绍了 OneOS Shell 命令行交互工具,核心要点如下:
- 核心数据结构:
sh_cmd_entry_t结构体存储命令名称、描述和处理函数,sh_cmd_func_t定义了统一的命令处理函数签名 - 命令注册机制:通过
SH_CMD_EXPORT宏和链接器段FSymTab,实现编译时自动注册,零运行时开销 - 核心 API:
sh_exec执行命令、sh_get_prompt获取提示符、sh_disconnect/reconnect_console管理控制台连接 - 内置命令:
help查看所有命令、set_prompt设置提示符,以及内核和文件系统组件导出的大量系统管理命令 - Kconfig 配置:支持任务参数、历史记录、命令描述、回显控制、认证等灵活配置
- 自定义命令:通过
SH_CMD_EXPORT宏可轻松注册自定义命令,支持参数解析和错误处理
🔗 相关链接
- 6.1 日志系统 Dlog
- 6.3 测试框架 Atest
- [第6章 REA