1 Linux shell
1.1 Linux console
@ init/main.c
static int __ref kernel_init(void *unused)
{
kernel_init_freeable();
[…]
if (ramdisk_execute_command) {
if (!run_init_process(ramdisk_execute_command))
return 0;
pr_err("Failed to execute %s\n", ramdisk_execute_command);
}
[…]
}
static noinline void __init kernel_init_freeable(void)
{
[…]
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
pr_err("Warning: unable to open an initial console./n");
(void) sys_dup(0);
(void) sys_dup(0);
[…]
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
[…]
}
从上面的代码中可以看到,它先open了/dev/console,在open的时候,会去找进程没使用的最小文件序号,而当前进程没有打开任何文件,所以sys_open()的时候肯定会找到0,然后两次调用sys_dup(0)来复制文件描述符0,复制后的文件描述符肯定是1、2,这样0、1、2 就建立起来了。
然后kernel_init调用run_init_process()->do_execve()将当前进程替换成了用户空间的一个进程,这也就是用户空间init进程的由来;此后用户空间的进程全是它的子孙进程,也就共享了0、1、2的文件描述符了,这也就是我们所说的stdin、stdout和stderr。
uart_driver.state包括uart_driver.nr个slot,每个slot就对应一个uart_port。
/dev/console - 设备号(5,1)
/dev/ptmx:Multiplex
/dev/tty - 设备号(5,0),进程的控制终端(/dev/tty)存放在task_struct.signal->tty(函数__proc_set_tty)中;使用tty或者busybox tty查看(对应函数ttyname(int))
/dev/tty0 - 设备号(4,0),虚拟终端,主要指显示屏,Ubuntu上可以使用ps -ax查看
1.2 获取进程tty name
#include <stdio.h>
#include <stdlib.h>
#include "fcntl.h"
#include "unistd.h"
static void redirect_std_ioe(void)
{
int fd = open("/dev/kmsg", O_WRONLY);
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
close(fd);
}
int main(int argc, char **argv)
{
printf("stdin: %s\n", ttyname(0));
printf("stdout: %s\n", ttyname(1));
printf("stderr: %s\n", ttyname(2));
// demo
redirect_std_ioe();
return 0;
}
需要注意的是子进程的stdin, stdout和stderr继承于父进程。
1.3 Arrow Keys
每个方向键包含3个键值,如下所示,在写shell解析程序时需要注意,只有捕获到3个键值时才认为方向键按下了。
\033[2J - 清屏(0x1b, 0x5b, ‘2’, ‘J’)
Format:ESC[2J - 清除显示,清除屏幕并将光标位置移到起始位置(0 行,0 列)。
\033[2J\033[0;0H - 清屏且将光标置顶(0x1b, 0x5b, ‘0’, ‘;’, ‘0’, ‘H’)
Format:ESC[X;YH - 光标位置,移动光标到指定的位置(坐标),如果不指定位置,光标将移动到起始位置,即屏幕的左上角(0 行,0 列)。
// up - "\033[A"
// down - "\033[B"
// left - "\033[D"
// right - "\033[C"
// Arrow keys contain three characters
// they are 0x1b, 0x5b, <A|B|D|C>
// Putty <Terminal->Keyboard->The Function keys and keypad>
// choose Linux
Refer to “linux 读取键盘上下左右键小程序”
http://blog.csdn.net/cean1024/article/details/72903069
1.4 Android mksh
修改mksh默认的列长度
@ external/mksh/src/sh.h
EXTERN mksh_ari_t x_cols E_INIT(80); /* tty columns */
EXTERN mksh_ari_t x_lins E_INIT(24); /* tty lines */
1.5 Android指定service的console
lsof -p <PID>
@ init.xxx.rc
service xxx /system/bin/xxx
class main
console // 将stdio定向到/dev/console,否则到/dev/null
1.6 adb shell
注意的是adb shell使用的是pty,即伪终端(ptmx和pts)。
@ adb/services.c
static int create_subproc_pty(const char *cmd, const char *arg0, const char *arg1, pid_t *pid)
{
[...]
if (*pid == 0) {
init_subproc_child();
int pts = unix_open(devname,
O_RDWR | O_CLOEXEC);
if (pts < 0) {
fprintf(stderr,
"child failed to open pseudo-term slave: %s\n",
devname);
exit(-1);
}
// 重定向shell进程的stdin, stdout和stderr到伪终端pts,使用busybox tty查看shell的控制终端
// shell进程从pts读数据
dup2(pts, STDIN_FILENO);
// shell进程向pts写数据
dup2(pts, STDOUT_FILENO);
// shell进程向pts写数据
dup2(pts, STDERR_FILENO);
adb_close(pts);
adb_close(ptm);
execl(cmd, cmd, arg0, arg1, NULL);
fprintf(stderr,
"- exec '%s' failed: %s (%d) -\n",
cmd, strerror(errno), errno);
exit(-1);
} else {
return ptm; // adb从USB ep读数据,再向ptm写数据;或者从ptm读数据,再写到USB ep
}
}
1.7 拓展
自定义shell终端提示符
https://www.cnblogs.com/lienhua34/p/5018119.html
ANSI的Esc屏幕控制:
http://blog.csdn.net/lzuacm/article/details/8993785
\033[2J - 清屏(0x1b, 0x5b, ‘2’, ‘J’)
Format:ESC[2J - 清除显示,清除屏幕并将光标位置移到起始位置(0 行,0 列)。
\033[2J\033[0;0H - 清屏且将光标置顶(0x1b, 0x5b, ‘0’, ‘;’, ‘0’, ‘H’)
Format:ESC[X;YH - 光标位置,移动光标到指定的位置(坐标),如果不指定位置,光标将移动到起始位置,即屏幕的左上角(0 行,0 列)。
console参考代码
https://wenku.baidu.com/view/5387f1d94028915f804dc2dc.html
2 RTOS
2.1 eCos
/*
* minishell.c
*
* Copyright (C) 2017
*
* Author: George Tso <zoosenpin@163.com>
*
*/
#include <oem-portmisc.h>
#define PROMT "shell>"
#define SHELL_MAX_CMD_SZ 512
#define SHELL_MAX_ARGV 25
static char *argv_array[SHELL_MAX_ARGV];
static char cur_cmd_buf[SHELL_MAX_CMD_SZ];
static cyg_mutex_t shell_mutex;
static int ser_fd;
typedef enum {
TYPE_SHELL_PBUS = 1,
TYPE_SHELL_SYSTEM = 2,
TYPE_SHELL_WIRELESS = 3,
} enum_type_shell_cmd_t;
typedef struct shell_cmd {
char *name;
int (*func)(struct shell_cmd *, int, char **);
char *help;
int mode; // future use
struct shell_cmd *prev;
struct shell_cmd *next;
} shell_cmd_t;
static shell_cmd_t header_list;
static void print_help(shell_cmd_t *cmd_list)
{
char buf[SZ_256];
shell_cmd_t *cp;
memset(buf, 0, SZ_256);
cyg_mutex_lock(&shell_mutex);
for (cp = cmd_list->next;
cp;
cp = cp->next) {
strcat(buf, cp->name);
strcat(buf, " ");
}
cyg_mutex_unlock(&shell_mutex);
ser_printf("%s\n", buf);
}
static int get_argc(const char *string)
{
int argc = 0;
char *p = (char *)string;
while (*p) {
if (*p != ' ' && *p) {
argc++ ;
while (*p != ' ' && *p) p++;
continue;
}
p++ ;
}
if (argc >= SHELL_MAX_ARGV)
argc = SHELL_MAX_ARGV - 1;
return argc;
}
static char** get_argv(const char* string)
{
char *p;
int n = 0;
memset(argv_array,
0,
SHELL_MAX_ARGV * sizeof(char *));
p = (char* )string;
while (*p) {
argv_array[n] = p ;
while(*p != ' ' && *p) p++;
*p++ = '\0';
while(*p == ' ' && *p) p++;
n++ ;
if (n == SHELL_MAX_ARGV)
break;
}
return (char** )&argv_array;
}
static void get_cmd_from_ser(char *buf,
char *promt)
{
fd_set rd_set;
int clen, size=0;
int err;
int readlen;
struct timeval timeout;
unsigned char c;
while (1) {
if (size >= (SHELL_MAX_CMD_SZ - 1)) {
buf[0] = '\0';
return;
}
FD_ZERO(&rd_set);
timeout.tv_sec = 1;
timeout.tv_usec = 0;
FD_SET(ser_fd, &rd_set);
c = 0;
err = select(ser_fd + 1,
&rd_set,
NULL,
NULL,
&timeout);
if (err == 0) // select timeout
continue;
if (err < 0) {
ser_printf("select returned error: %s\n",
strerror(err));
break;
}
if (FD_ISSET(ser_fd, &rd_set)) {
readlen = read(ser_fd, &c, 1);
if (readlen < 1)
continue;
}
switch (c) {
case 0x8:
case 0x7f:
// erase one character + 'backspace' char
size -= 1;
if (size < 0) {
size = 0;
} else {
clen = 3;
ser_printf("\b \b");
}
continue;
case 0x3b:
case 0xd:
if (FD_ISSET(ser_fd, &rd_set)) {
clen = 1;
ser_printf("\n");
} else {
clen = 1;
//write(ser_fd, "\r\n", 2);
ser_printf("\n");
}
buf[size] = '\0';
// got a command
return;
case 0xa:
if (FD_ISSET(ser_fd, &rd_set)) {
clen = 1;
ser_printf("\r");
}
continue;
}
if (FD_ISSET(ser_fd, &rd_set)) {
clen = 1;
ser_printf("%c", c);
}
buf[size++] = c;
}
}
static void cmd_parser_thread(
cyg_addrword_t data)
{
char **argv;
int argc;
int count;
u32 enter_lvl = 0;
shell_cmd_t *cp;
shell_cmd_t *cur_cmd_list =
(shell_cmd_t *)data;
if (!cur_cmd_list) {
return;
}
while (1) {
ser_printf("%s", PROMT);
get_cmd_from_ser(cur_cmd_buf,
PROMT);
argc = get_argc((const char *)cur_cmd_buf);
argv = (char **)get_argv(
(const char *)cur_cmd_buf);
if(argc < 1) continue;
if ((!strcmp(argv[0], "help") ||
(*(argv[0]) == '?'))) {
print_help(cur_cmd_list);
} else if (!strcmp(argv[0], "ls" )) {
print_help(cur_cmd_list);
} else if (!strcmp(argv[0], "cd")) {
// TODO
if (argc > 1 &&
!strcmp(argv[1], "..")) {
if (0 == enter_lvl) {
continue;
} else if (enter_lvl > 0) {
enter_lvl--;
}
} else if (argc > 1) {
enter_lvl++;
//strcpy(argv[0], argv[1]);
}
} else {
cyg_mutex_lock(&shell_mutex);
for (cp = cur_cmd_list->next,count = 0;
cp;
cp = cp->next) {
if (!strcasecmp(argv[0],
cp->name) &&
cp->func) {
cp->func(cp, argc, argv);
break;
}
}
cyg_mutex_unlock(&shell_mutex);
}
}
}
int __api oem_register_shell_cmd(
enum_type_shell_cmd_t type,
shell_cmd_t *new_c)
{
shell_cmd_t *cur_c = &header_list;
while (cur_c) {
if (cur_c == new_c) {
return -1;
}
cur_c = cur_c->next;
}
cyg_mutex_lock(&shell_mutex);
cur_c = &header_list;
while (cur_c && cur_c->next)
cur_c = cur_c->next;
cur_c->next = new_c;
new_c->next = NULL;
new_c->prev = cur_c;
cyg_mutex_unlock(&shell_mutex);
return 0;
}
int __api oem_unregister_shell_cmd(
enum_type_shell_cmd_t type,
shell_cmd_t *new_c)
{
bool found = false;
shell_cmd_t *cur_c = &header_list;
while (cur_c) {
if (cur_c == new_c) {
found = true;
break;
}
cur_c = cur_c->next;
}
if (!found) {
return -1;
}
cyg_mutex_lock(&shell_mutex);
if (!new_c->next) {
new_c->prev->next = NULL;
new_c->prev = NULL;
} else {
new_c->next->prev = new_c->prev;
new_c->prev->next = new_c->next;
new_c->next = NULL;
new_c->prev = NULL;
}
cyg_mutex_unlock(&shell_mutex);
return 0;
}
// DEMO
static int nvm_op(shell_cmd_t *cmd,
int argc, char **argv)
{
int32 ret;
u32 d32_val1, d32_val2;
if (argc < 2) {
ser_printf("%s", cmd->help);
return -1;
}
if (2 == argc) {
d32_val1 = strtoul(argv[1], NULL, 0);
ret = nv_read(d32_val1, &d32_val2, 4);
ser_printf("%d\n", d32_val2);
} else if (3 == argc) {
d32_val1 = strtoul(argv[1], NULL, 0);
d32_val2 = strtoul(argv[2], NULL, 0);
ret = nv_write(d32_val1, &d32_val2, 4);
ret = nv_commit();
if (0 == ret) {
ser_printf("succeeded in writing %d to nv%d\n",
d32_val2, d32_val1);
} else {
ser_printf("failed to write %d to nv%d\n",
d32_val2, d32_val1);
}
} else {
return -1;
}
return ret;
}
shell_cmd_t nvm_cmd = {
"nvm", nvm_op,
"usage:\nnvm [id]\nnvm [id] [val]\n", 0, 0
};
static int shell_init(void)
{
ser_fd = open("/dev/ser0", O_RDWR);
if (ser_fd < 0) {
ser_printf(
"open(/dev/ser0) returned error: %s\n",
strerror(ser_fd));
return -1;
}
cyg_mutex_init(&shell_mutex);
memset(&header_list, 0, sizeof(header_list));
oem_register_shell_cmd(
TYPE_SHELL_SYSTEM,
&nvm_cmd);
return 0;
}
static char stack[SHELL_THREAD_STACK_SZ];
static cyg_thread thread_data;
static cyg_handle_t thread_handle;
void shell_thread_init(void)
{
if (0 == shell_init()) {
cyg_thread_create(
SHELL_THREAD_PRIORITY,
cmd_parser_thread,
(cyg_addrword_t)&header_list,
"cmd_parser_thread",
&stack[0],
SHELL_THREAD_STACK_SZ,
&thread_handle,
&thread_data
);
cyg_thread_resume(thread_handle);
}
}
2.2 FreeRTOS
@flash.ld
SECTIONS
{
[...]
.ram_text :
{
[...]
. = ALIGN(4);
PROVIDE (__gcc_section_cmds = .);
KEEP(*(SORT(.oem_rodata.cmds*)))
PROVIDE (__gcc_section_cmds_end = .);
[...]
}
[...]
}
@console.h
typedef struct cli_node {
const char *name;
int16_t type;
int (*func)(struct cli_node *, int, char **);
const char *help;
/* void *prvdata; */
struct cli_node *prev;
struct cli_node *next;
/* directory node */
struct cli_node *de_parent;
struct cli_node *de_child;
/* foreground proc handler */
struct list_head fg_list
int (*kill_cb)(struct cli_node *);
} cli_node_t;
extern const cli_node_t __gcc_section_cmds[];
extern const cli_node_t __gcc_section_cmds_end[];
#if 1
#define DECLARE_CONSOLE_COMMAND(NAME, ROUTINE, HELP) \
static const char __con_node_label_##NAME[] = #NAME;\
static const cli_node_t __con_node_##NAME \
__attribute__((used,section(".oem_rodata.cmds." #NAME))) =\
{ \
.name = __con_node_label_##NAME, \
.type = 0, \
.func = ROUTINE, \
.help = HELP, \
}
#define DECLARE_CONSOLE_COMMAND_WKILL(NAME, ROUTINE, HELP, KILL_CB) \
static const char __con_node_label_##NAME[] = #NAME; \
static const cli_node_t __con_node_##NAME \
__attribute__((used,section(".oem_rodata.cmds." #NAME))) = \
{ \
.name = __con_node_label_##NAME, \
.type = 0, \
.func = ROUTINE, \
.help = HELP, \
.prev = NULL, \
.next = NULL, \
.de_parent = NULL, \
.de_child = NULL, \
.fg_list = { \
.next = NULL, \
.prev = NULL, \
}, \
.kill_cb = KILL_CB, \
}
#else
#define DECLARE_CONSOLE_COMMAND(NAME, ROUTINE, HELP) \
int (ROUTINE)(struct cli_node *cmd, int argc, char **argv) __attribute__((unused))
#define DECLARE_CONSOLE_COMMAND_WKILL(NAME, ROUTINE, HELP, KILL_CB)\
int (ROUTINE)(struct cli_node *cmd, int argc, char **argv) __attribute__((unused)); \
int (KILL_CB)(struct cli_node *cmd) __attribute__((unused))
#endif
@ console.c
static bool gcc_section_cmd(int argc,
char **argv, cli_node_t **cli_head)
{
const cli_node_t *cli_node;
uint8_t match_length = strlen(argv[0]);
for (cli_node = __gcc_section_cmds;
cli_node < __gcc_section_cmds_end;
cli_node++) {
if (!strncasecmp(argv[0],
cli_node->name,
match_length)) {
if (cli_node->func &&
(CLI_PROC_ACTION_FG == cli_node->func(cli_node, argc, argv))) {
fg_proc_list_add(cli_node);
}
return true;
}
}
return false;
}
static void list_gcc_section_cmds(void)
{
const cli_node_t *cli_node;
char buf[SZ_256], *cp = buf;
int16_t cp_sz = SZ_256, ret;
ret = snprintf(cp, cp_sz,
"OEM_RODATA.CMDS:"CRLF);
if (ret > 0) {
cp += ret;
cp_sz -= ret;
}
for (cli_node = __gcc_section_cmds;
cli_node < __gcc_section_cmds_end;
cli_node++) {
ret = snprintf(cp, cp_sz,
"x %s"CRLF, cli_node->name);
if (ret > 0) {
cp += ret;
cp_sz -= ret;
}
}
console_puts("%s", buf);
}
3 Abbreviations
lex:lexical,词法分析
PS: Prompt Statement