一、shell介绍
shell是用户与操作系统进行交互的媒介。目前为止我们已经实现了大部分的系统调用,因此可以来实现一个简单的shell来进行与操作系统的交互。初步的shell将完成一些简单的命令如 ls cd pwd rm cat 等。 实现这些命令还需要先实现几个系统调用。
二、系统调用readdir和clear
readdir是用来读取目录,它的代码如下:
int sys_readdir(fd_t fd, dirent_t *dir, u32 count)
{
return sys_read(fd, (char *)dir, sizeof(dirent_t));
}
如上所示,读取目录直接调用了文件读这个系统调用。当读取目录时,一次读取一个目录项,这样实现比较简单。假设目录包含以下文件:file1.txt。 一次读取一个目录项结构,通过这个结构的name属性就可以获取目录项的名字。
clear这个功能我们在做显卡驱动的时候就已经实现过 cosolse_clear。 直接把这个函数注册为clear的系统调用即可。
三、shell实现
shell的实现大致逻辑是,设置命令提示符等基础环境和按行读取键盘输入,然后解析键盘输入,进行命令的执行。我们先看shell的主函数,然后通过主函数再具体的实现:
#define MAX_CMD_LEN 256
#define MAX_ARG_NR 16
#define MAX_PATH_LEN 1024
#define BUFLEN 1024
static char cwd[MAX_PATH_LEN];
static char cmd[MAX_CMD_LEN];
static char *argv[MAX_ARG_NR];
static char buf[BUFLEN];
int osh_main()
{
memset(cmd, 0, sizeof(cmd)); // 初始化命令为0
memset(cwd, 0, sizeof(cwd)); // 初始化当前目录为0
builtin_logo(); // 打印系统logo,其实就是在终端打印了字符串。
while (true)
{
print_prompt(); // 打印命令提示符,获取当前目录。
readline(cmd, sizeof(cmd)); // 读取一行输入,并赋值给cmd
if (cmd[0] == 0)
{
continue;
}
int argc = cmd_parse(cmd, argv, ' '); // 把读取的指令按照空格切分为多个token, 因为有些命令是有参数的
if (argc < 0 || argc >= MAX_ARG_NR)
{
continue;
}
execute(argc, argv); // 执行执行
}
return 0;
}
从上我们可以看到,shell的主函数就是循环读取键盘输入,一次读取一行。然后将读取到的命令交给execute函数来处理。接下来我们看execute函数的实现:
static void execute(int argc, char *argv[])
{
char *line = argv[0];
....
if (!strcmp(line, "pwd"))
{
return builtin_pwd();
}
if (!strcmp(line, "exit"))
{
int code = 0;
if (argc == 2)
{
code = atoi(argv[1]);
}
exit(code);
}
printf("osh: command not found: %s\n", argv[0]);
}
如上为execute,可以看到,execute将会根据命令比对来决定去执行那个函数,这个我们为了节省篇幅删除了大部分判断,例如当命令为 pwd 时,就去执行builtin_pwd函数。那接下来就是命令的具体实现。因为命令还是比较多的,我们这里只列举几个:
oid builtin_pwd() // 获取当前目录
{
getcwd(cwd, MAX_PATH_LEN);
printf("%s\n", cwd);
}
void builtin_clear() // 清屏,直接调用clear系统调用
{
clear();
}
void builtin_ls() // ls命令
{
fd_t fd = open(cwd, O_RDONLY, 0);
if (fd == EOF)
return;
lseek(fd, 0, SEEK_SET);
dentry_t entry;
while (true)
{
int len = readdir(fd, &entry, 1);
if (len == EOF)
break;
if (!entry.nr)
continue;
if (!strcmp(entry.name, ".") || !strcmp(entry.name, ".."))
{
continue;
}
printf("%s ", entry.name);
}
printf("\n");
close(fd);
}
如上所示,写好命令的实现之后,shell基本就做好了。接下来就需要再init进程中启动shell就可以了。
四、启动shell
shell实现之后,就需要再用户init进程中启动shell了。如下所示:
static void user_init_thread()
{
while (true) // 这个线程一直运行不会退出
{
u32 status;
pid_t pid = fork(); // 2. 创建一个子进程
if (pid)
{
pid_t child = waitpid(pid, &status); // 等待刚才 fork 出的子进程结束
printf("wait pid %d status %d %d\n", child, status, time());
}
else
{
osh_main(); // 子进程执行用户shell
}
}
}
父进程会waitpid阻塞自己,一直等到子进程结束。这个流程就是fork 一个子进程 → 子进程运行 shell → 父进程等待子进程结束 →子进程结束后父进程打印退出信息 → 重新 fork 启动新的 shell。这样,用户每次退出 shell,系统都会自动重新启动一个新的 shell,死循环来保证有一个用户界面一直存在。
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付