编辑
2025-03-30
工作知识
0
请注意,本文编写于 68 天前,最后修改于 68 天前,其中某些信息可能已经过时。

目录

一、初始化shell
二、代码入口
三、流程解析
3.1 登录提示
3.2 login输入
3.3 等待用户输入
3.4 脚本运行
3.5 命令运行
四、参考文献

rtems支持简易的shell功能,本文基于rtems,进行shell的代码分析,了解其实现原理

一、初始化shell

为了让rtems支持shell,我们可以调用rtems_shell_init函数来初始化shell。这里举例如下:

rtems_shell_init( "SHLL", /* task_name */ RTEMS_MINIMUM_STACK_SIZE * 5, /* task_stacksize */ 100, /* task_priority */ "/dev/foobar", /* devname */ /* device is currently ignored by the shell if it is not a pty */rtems_shell_login_check false, /* forever */ true, /* wait */ rtems_shell_login_check /* login */ );

这里解释如下:

* @param task_name Name of the shell task. * @param task_stacksize The size of the stack. If 0 the default size is used. * @param task_priority The priority the shell runs at. * @param forever Repeat logins. * @param wait Caller should block until shell exits. * @param login_check User login check function, NULL disables login checks.

二、代码入口

而rtems_shell_init的实际实现是通过rtems_shell_run实现,如下:

return rtems_shell_run( task_name, /* task_name */ task_stacksize, /* task_stacksize */ task_priority, /* task_priority */ devname, /* devname */ forever, /* forever */ wait, /* wait */ "stdin", /* input */ "stdout", /* output */ false, /* output_append */ to_wake, /* wake_on_end */ false, /* echo */ login_check /* login check */ );

这里我们重点看两个函数:

sc = rtems_task_create( name, task_priority, task_stacksize, RTEMS_PREEMPT | RTEMS_TIMESLICE | RTEMS_NO_ASR, RTEMS_LOCAL | RTEMS_FLOATING_POINT, &task_id ); sc = rtems_task_start(task_id, rtems_shell_task, (rtems_task_argument) shell_env);

关于task的create和start,这里关于Init函数的时候解析过了。我们注意entry_point函数rtems_shell_task,其实现如下:

static rtems_task rtems_shell_task(rtems_task_argument task_argument) { rtems_shell_env_t *shell_env = (rtems_shell_env_t*) task_argument; rtems_id wake_on_end = shell_env->wake_on_end; rtems_shell_main_loop( shell_env ); rtems_shell_clear_shell_std_handles(); if (wake_on_end != RTEMS_INVALID_ID) rtems_event_send (wake_on_end, RTEMS_EVENT_1); rtems_task_exit(); }

这里重点函数是rtems_shell_main_loop,它调用

result = shell_main_loop(shell_env, interactive, line_editor_output);

shell_main_loop是shell的主要实现。

三、流程解析

3.1 登录提示

对于console,第一时间可以看到如下日志:

Welcome to rtems-6.0.0 (AArch64/AArch64-LP64/zynqmp_qemu) Copyright (C) 1989, 2021 RTEMS Project and contributors Login into RTEMS

此实现主要在rtems_shell_login函数,它会校验devname字段,如果不是pty,则打开issue文件,判断类型,issue文件内容如下:

static void rtems_shell_init_once(void) { struct passwd pwd; struct passwd *pwd_res; pthread_key_create(&rtems_shell_current_env_key, rtems_shell_env_free); /* dummy call to init /etc dir */ getpwuid_r(0, &pwd, NULL, 0, &pwd_res); rtems_shell_create_file("etc/issue", "\n" "Welcome to @V\\n" "Login into @S\\n"); rtems_shell_create_file("/etc/issue.net", "\n" "Welcome to %v\n" "running on %m\n"); rtems_shell_init_commands(); rtems_shell_register_monitor_commands(); }

这里rtems_shell_init_environment会被rtems_shell_run调用。同样的,我们可以在shell查看issue的值,如下:

SHLL [/] # cat etc/issue Welcome to @V\nLogin into @S\n

所以在rtems_shell_login中会读到V和S,如下:

static bool rtems_shell_login(rtems_shell_env_t *env, FILE * in,FILE * out) { FILE *fd; int c; time_t t; if (out) { if ((env->devname[5]!='p')|| (env->devname[6]!='t')|| (env->devname[7]!='y')) { fd = fopen("/etc/issue","r"); if (fd) { while ((c = fgetc(fd)) != EOF) { if (c=='@') { switch (c = fgetc(fd)) { ...... case 'S': fprintf(out,"RTEMS"); break; case 'V': fprintf( out, "%s\n%s", rtems_get_version_string(), rtems_get_copyright_notice() ); break; case '@': fprintf(out,"@"); break; default : fprintf(out,"@%c",c); break; } } else if (c=='\\') { switch(c=fgetc(fd)) { case '\\': fprintf(out,"\\"); break; case 'b': fprintf(out,"\b"); break; case 'f': fprintf(out,"\f"); break; case 'n': fprintf(out,"\n"); break; case 'r': fprintf(out,"\r"); break; case 's': fprintf(out," "); break; case 't': fprintf(out,"\t"); break; case '@': fprintf(out,"@"); break; } } else { fputc(c,out); } } fclose(fd); } } }

对于S,直接输出RTEMS即可,对于V,输出_RTEMS_version和_Copyright_Notice。这两个全局变量定义如下:

const char _RTEMS_version[] = "rtems-" RTEMS_VERSION " (" CPU_NAME "/" CPU_MODEL_NAME "/" RTEMS_XSTRING( RTEMS_BSP ) ")"; RTEMS_SECTION(".rtemsroset.copyright") const char _Copyright_Notice[] = "Copyright (C) 1989, 2021 RTEMS Project and contributors";

3.2 login输入

对于login,其显示如下:

/dev/asdasd login: root Password:

在上述提示完成之后,函数rtems_shell_login_prompt可显示如上信息。简要实现如下

for (i = 0; i < 3; ++i) { char user [32]; char passphrase [128]; fprintf( out, "%s login: ", device ); fflush( out ); result = rtems_shell_get_text( in, out, user, sizeof(user) ); if ( !result ) break; if (0 == strlen(user)) continue; fflush( in); fprintf( out, "Password: "); fflush( out); result = rtems_shell_get_text( in, NULL, passphrase, sizeof(passphrase) ); if ( !result ) break; fputc( '\n', out); result = check( user, passphrase ); if (result) break; fprintf( out, "Login incorrect\n\n"); sleep( 2); }

这里提供了3次机会,每次通过rtems_shell_get_text获取文本,rtems_shell_get_text是通过c库的getc获取字符,最后通过check回调检查是否登录正常。这里的check回调是rtems_shell_login_check函数,此函数校验密码。

3.3 等待用户输入

在登录完成之后,shell开始等待用户输入,其实现函数如下:

for (;;) { const char *c; int argc; char *argv[RTEMS_SHELL_MAXIMUM_ARGUMENTS]; /* Prompt section */ if (prompt) { rtems_shell_get_prompt(shell_env, prompt, RTEMS_SHELL_PROMPT_SIZE); } /* getcmd section */ cmd = rtems_shell_line_editor(cmds, cmd_count, RTEMS_SHELL_CMD_SIZE, prompt, stdin, line_editor_output); if (cmd == -1) continue; /* empty line */ if (cmd == -2) { result = false; break; /*EOF*/ } line++; if (shell_env->echo) fprintf(stdout, "%d: %s\n", line, cmds[cmd]); /* evaluate cmd section */ c = cmds[cmd]; while (*c) { if (!isblank((unsigned char)*c)) break; c++; } if (*c == '\0') /* empty line */ continue; if (*c == '#') { /* comment character */ cmds[cmd][0] = 0; continue; } if (!strcmp(cmds[cmd],"bye") || !strcmp(cmds[cmd],"exit")) { fprintf(stdout, "Shell exiting\n" ); break; } /* exec cmd section */ /* TODO: * To avoid user crash catch the signals. * Open a new stdio files with posibility of redirection * * Run in a new shell task background. (unix &) * Resuming. A little bash. */ memcpy (cmd_argv, cmds[cmd], RTEMS_SHELL_CMD_SIZE); if (!rtems_shell_make_args(cmd_argv, &argc, argv, RTEMS_SHELL_MAXIMUM_ARGUMENTS)) { int exit_code; rtems_shell_winsize(); exit_code = rtems_shell_execute_cmd(argv[0], argc, argv); if (shell_env->exit_code != NULL) *shell_env->exit_code = exit_code; if (exit_code != 0 && shell_env->exit_on_error) shell_env->exit_shell = true; } /* end exec cmd section */ if (shell_env->exit_shell) break; }

首先我们留意shell的左边,如下

SHLL [/] #

此代码实现为rtems_shell_get_prompt

void rtems_shell_get_prompt( rtems_shell_env_t *shell_env, char *prompt, size_t size ) { char buf[256]; char *cwd; /* XXX: show_prompt user adjustable */ cwd = getcwd(buf,sizeof(buf)); cwd = cwd != NULL ? cwd : "?"; snprintf(prompt, size - 1, "%s%s[%s] %c ", ((shell_env->taskname) ? shell_env->taskname : ""), ((shell_env->taskname) ? " " : ""), cwd, geteuid()?'$':'#'); }

然后通过rtems_shell_line_editor获取用户的输入信息,这里实现了rtems_shell_getchar封装的fgetc获取每行的输入

最后通过rtems_shell_execute_cmd执行代码,其实现如下

int rtems_shell_execute_cmd(const char *cmd, int argc, char *argv[]) { rtems_shell_cmd_t *shell_cmd; if (argv[0] == NULL) { return -1; } shell_cmd = rtems_shell_lookup_cmd(argv[0]); if (shell_cmd != NULL && !rtems_shell_can_see_cmd(shell_cmd)) { shell_cmd = NULL; } if (shell_cmd == NULL) { return rtems_shell_script_file(argc, argv); } else if (rtems_shell_can_execute_cmd(shell_cmd)) { return shell_cmd->command(argc, argv); } else { fprintf(stderr, "%s: Permission denied\n", cmd); return -1; } }

这里有脚本方式和命令行方式

3.4 脚本运行

脚本运行的主要函数是rtems_shell_script_file,它在rtems_shell_main_joel函数中根据rtems_shell_script来实现脚本运行,如下

result = rtems_shell_script( taskName, /* the name of the task */ stackSize, /* stack size */ taskPriority, /* task priority */ scriptFile, /* the script file */ outputFile, /* where to redirect the script */ 0, /* run once and exit */ 1, /* we will wait */ verbose /* do we echo */ );

3.5 命令运行

根据上面分析,脚本运行方式最后也是执行的命令,最终函数在shell_cmd->command(argc, argv);这里的command函数由自己实现在rtems_shell_Initial_commands/rtems_shell_Initial_aliases中。这里由rtems_shell_init_once调用时会主动调用rtems_shell_init_commands

static void rtems_shell_init_commands(void) { rtems_shell_cmd_t * const *c; rtems_shell_alias_t * const *a; for ( c = rtems_shell_Initial_commands ; *c ; c++ ) { rtems_shell_add_cmd_struct( *c ); } for ( a = rtems_shell_Initial_aliases ; *a ; a++ ) { rtems_shell_alias_cmd( (*a)->name, (*a)->alias ); } }

四、参考文献