详细分析
一直想弄懂FinSH是怎么扫描内部函数表,并执行相关函数的,看了自动初始化后,终于有了点头绪
先展开MSH_CMD_EXPORT (finsh.h,落后版本可能在finsh_api.h)
源宏定义
#define MSH_CMD_EXPORT(command, desc)
FINSH_FUNCTION_EXPORT_CMD(command, _cmd##command, desc)
#define FINSH_FUNCTION_EXPORT_CMD(name, cmd, desc)
const char _fsym##cmd##_name[] SECTION(".rodata.name") = #cmd;
const char __fsym_##cmd##_desc[] SECTION(".rodata.name") = #desc;
const struct finsh_syscall __fsym_##cmd SECTION("FSymTab")=
{
__fsym_##cmd##_name,
__fsym_##cmd##_desc,
(syscall_func)&name
};
举个例子MSH_CMD_EXPORT(msh_test, this is a msh test)
展开后
FINSH_FUNCTION_EXPORT_CMD(msh_test, __cmd_msh_test, this is a msh test)
/再展开/
const char __fsym___cmd_msh_test_name[] SECTION(".rodata.name") = "__cmd_msh_test";
const char __fsym___cmd_msh_test_desc[] SECTION(".rodata.name") = "this is a msh test";
const struct finsh_syscall __fsym___cmd_msh_test SECTION("FSymTab")=
{
__fsym___cmd_msh_test_name,/* "__cmd_msh_test" /
__fsym___cmd_msh_test_desc,/ "this is a msh test" */
(syscall_func)&msh_test
};
再看finsh_syscall和syscall_func (finsh.h)
/函数指针/
typedef long (syscall_func)(void);
/ system call table /
struct finsh_syscall
{
const char name; /* the name of system call /
#if defined(FINSH_USING_DESCRIPTION) && defined(FINSH_USING_SYMTAB)
const char desc; /* description of system call /
#endif
syscall_func func; / the function address of system call */
};
extern struct finsh_syscall *_syscall_table_begin, *_syscall_table_end;
finsh_system_init (shell.c)再看finsh的初始化与线路创建函数,(gcc下)我删减后的(解析都在注释里)
int finsh_system_init(void)
{
rt_err_t result = RT_EOK;
rt_thread_t tid;
/* GNU GCC Compiler and TI CCS /
extern const int __fsymtab_start;
extern const int __fsymtab_end;
extern const int __vsymtab_start;
extern const int __vsymtab_end;
/ 上面四个变量是链接脚本导出的符号,在ROM区,具体如下图 /
/ 初始化全局变量_syscall_table_begin(函数表结构体头)和_syscall_table_end(函数表结构体尾)/
finsh_system_function_init(&__fsymtab_start, &__fsymtab_end);
/ 导出变量,与导出函数类似 /
finsh_system_var_init(&__vsymtab_start, &__vsymtab_end);
/ create or set shell structure */
/*shell 主要存放控制台相关,此处仅列举几种
*shell->line[FINSH_CMD_SIZE]:当前行输入
*shell->rx_sem:控制台串口信号量
*shell->device:控制台串口设备
*/
shell = (struct finsh_shell *)rt_calloc(1, sizeof(struct finsh_shell));
/创建finsh线程,当信号量被释放后读取串口数据,存储解析/
tid = rt_thread_create(FINSH_THREAD_NAME,
finsh_thread_entry, RT_NULL,
FINSH_THREAD_STACK_SIZE, FINSH_THREAD_PRIORITY, 10);
/初始化信号量,再串口中断中释放/
rt_sem_init(&(shell->rx_sem), "shrx", 0, 0);
rt_thread_startup(tid);
return 0;
}
/自动初始化/
INIT_APP_EXPORT(finsh_system_init);
上面提到的finsh_system_function_init
void finsh_system_function_init(const void *begin, const void *end)
{
_syscall_table_begin = (struct finsh_syscall *) begin;
_syscall_table_end = (struct finsh_syscall *) end;
}
上面提到的extern const int __fsymtab_start等量在链接脚本中的体现
再看finsh线程序,只看finsh_thread_entry (shell.c)是怎么扫描并执行函数的部分,
同样大量剪切后的,解析全部在注解里
void finsh_thread_entry(void parameter)
{
char ch;
rt_kprintf(FINSH_PROMPT);/ 输出msh> */
while (1)
{
ch = finsh_getchar();/阻塞等待信号量释放/
.../大量解析命令部分,不看/
msh_exec(shell->line, shell->line_position);/执行当前行/
...
}
}
展开msh_exec (msh.c)精简版
int msh_exec(char cmd, rt_size_t length)
{
int cmd_ret;
/ Exec sequence:
built-in command
module(if enabled)
chdir to the directry(if possible)
*/
_msh_exec_cmd(cmd, length, &cmd_ret);
return cmd_ret;
}
展开_msh_exec_cmd (msh.c)完整版
static int _msh_exec_cmd(char *cmd, rt_size_t length, int *retp)
{
int argc;
rt_size_t cmd0_size = 0;
cmd_function_t cmd_func;
char argv[RT_FINSH_ARG_MAX];
RT_ASSERT(cmd);
RT_ASSERT(retp);
/ find the size of first command /
while ((cmd[cmd0_size] != ' ' && cmd[cmd0_size] != '\t') && cmd0_size < length)
cmd0_size ++;
if (cmd0_size == 0)
return -RT_ERROR;
/ 到前文提到的_syscall_table_begin与和_syscall_table_end之前查找到函数地址 /
cmd_func = msh_get_cmd(cmd, cmd0_size);
if (cmd_func == RT_NULL)
return -RT_ERROR;
/ 下面是含参部分,没有研究 /
/ split arguments /
memset(argv, 0x00, sizeof(argv));
argc = msh_split(cmd, length, argv);
if (argc == 0)
return -RT_ERROR;
/ 最终执行函数 /
/ exec this command */
*retp = cmd_func(argc, argv);
return 0;
}
终于到了最后,展开msh_get_cmd (msh.c)
static cmd_function_t msh_get_cmd(char *cmd, int size)
{
struct finsh_syscall index;
cmd_function_t cmd_func = RT_NULL;
for (index = _syscall_table_begin;
index < _syscall_table_end;
FINSH_NEXT_SYSCALL(index)) / 每次自增一个finsh_syscall结构体 /
{
/ MSH_CMD_EXPORT导出的函数名,前六个字符是"_cmd" /
if (strncmp(index->name, "_cmd", 6) != 0) continue;
if (strncmp(&index->name[6], cmd, size) == 0 &&
index->name[6 + size] == '\0')
{
/ 把前面宏定义保存的函数名去掉__cmd_与控制台读取到的函数名比较
对了就返回函数地址,自此大功告成! */
cmd_func = (cmd_function_t)index->func;
break;
}
}
return cmd_func;
}
大功告成了!(嘘,擦汗)
总结
MSH_CMD_EXPORT(command, desc)将任数地址,任数名称,任数描述保存到finsh_syscall结构体
再将每个结finsh_syscall体通过RT_SECTION("FSymTab")强制保存到ROM空间的部分FSymTab,并在链接脚引导出FSymTab的起点和结束地址__fsymtab_start和__fsymtab_end并赋值给struct finsh_syscall *_syscall_table_begin, *_syscall_table_end
以提供FinSH线程序finsh_thread_entry根据控制台输入的函数名称扫描函数表(保存在_syscall_table_begin与_syscall_table_end之间)并执行相应函数
补充流程图
题外话
现在了的都是的的的的的的的的执行执行执行导出的函数比较比较的话的话的话的话的话的话的话的话的话的话的话的话的话的话的话的话的话的话的的的的的的的的线程finsh_thread_entry栈就就要要要设设点点的串口输入在控制台实际现在最入口的串口显示了(大部分都是用MSH导出包含参数,但就是折腾一下)
finsh_thread_entry
void msh_test()
{
rt_device_t uart=rt_device_find(RT_CONSOLE_DEVICE_NAME);
rt_kprintf("enter msh test\n");
rt_size_t rsize=0;
uint8_t ch=0;
while(1){
/*串口接收中断时先把数据写入接收FIFO,
*再释放信号量,所以这里是从接收FIFO读
*没有影响,而finsh线程被阻塞在这里,
*所以信号量也不会被finsh持有
*总而言之,finsh线程被阻塞在这里(msh_test函数)了,
*不会响应任何命令*/
rsize = rt_device_read(uart, 0, &ch, 1);
if(rsize){
if(ch=='q')
{
rt_kprintf("leave msh test\n");
break;
}
rt_device_write(uart,0,&ch,1);
rt_kprintf("\nrsize:%d\n", rsize);
}
rt_thread_mdelay(100);
}
}
MSH_CMD_EXPORT(msh_test, block finsh)
荧幕rt_kprintf("\nrsize:%d\n", rsize);,经典回显(笑)
原作者:初级踩坑仔