现在我们已经有了fork()和exec(),我们的shell终于可以开始写了。这篇文章就记录如何使用fork()和exec()来实现一个简单的shell。
shell可以很复杂,但目前我们只实现一种功能,那就是读取命令并执行之(如果命令存在的话)。
这个简单的shell可以这么实现:
代码 kernel/main.c,shabby_shell。
/**
* A very very simple shell.
*
* @param tty_name TTY file name.
*/
void shabby_shell(const char * tty_name)
{
int fd_stdin = open(tty_name, O_RDWR);
assert(fd_stdin == 0);
int fd_stdout = open(tty_name, O_RDWR);
assert(fd_stdout == 1);
char rdbuf[128];
while (1) {
write(fd_stdout, "$ ", 2);
int r = read(fd_stdin, rdbuf, 70);
rdbuf[r] = 0;
int argc = 0;
char * argv[PROC_ORIGIN_STACK];
char * p = rdbuf;
char * s;
int word = 0;
char ch;
do {
ch = *p;
if (*p != ' ' && *p != 0 && !word) {
s = p;
word = 1;
}
if ((*p == ' ' || *p == 0) && word) {
word = 0;
argv[argc++] = s;
*p = 0;
}
p++;
} while(ch);
argv[argc] = 0;
int fd = open(argv[0], O_RDWR);
if (fd == -1) {
if (rdbuf[0]) {
write(fd_stdout, "{", 1);
write(fd_stdout, rdbuf, r);
write(fd_stdout, "}\n", 2);
}
} else {
close(fd);
int pid = fork();
if (pid != 0) { /* parent */
int s;
wait(&s);
} else { /* child */
execv(argv[0], argv);
}
}
}
close(fd_stdout);
close(fd_stdin);
}
void Init()
{
int fd_stdin = open("/dev_tty0", O_RDWR);
assert(fd_stdin == 0);
int fd_stdout = open("/dev_tty0", O_RDWR);
assert(fd_stdout == 1);
printf("Init() is running ...\n");
/* extract 'cmd.tar' */
untar("/cmd.tar");
char * tty_list[] = {"/dev_tty1", "/dev_tty2"};
int i;
for (i = 0; i < sizeof(tty_list) / sizeof(tty_list[0]); i++) {
int pid = fork();
if (pid != 0) { /* parent process */
printf("{parent is running, child pid:%d}\n", pid);
} else { /* child process */
printf("{child is running, pid:%d}\n", getpid());
close(fd_stdin);
close(fd_stdout);
shabby_shell(tty_list[i]);
assert(0);
}
}
while (1) {
int s;
int child = wait(&s);
printf("child (%d) exited with status: %d.\n", child, s);
}
assert(0);
}
我们用Init进程启动两个shell,分别运行在TTY1和TTY2上。两个shell都是Init进程的子进程,同时它们也将生成自己的子进程。由于它实在很简陋,所以给它起名为shabby_shell。
shabby_shell用read()读取用户输入,然后fork出一个子进程,在子进程中将输入交给execv()来执行。如果用户的输入并不是一个合法的命令,那么shabby_shell只是将命令行回显出来,不做其它任何处理。
让我们make运行一下,用一下我们的这个shabby_shell,效果如下图所示。
在左图中,我们使用了一个echo命令和一个不合法的命令。在右图中,我们还使用了pwd命令。在我们的系统中,pwd可以永远打印“/”,因为我们的文件系统是扁平的。因此pwd是个比echo还要简单的程序。从现在开始我们可以为自己的操作系统编写应用程序了,比如ls、rm等命令,都比较容易实现,有兴趣的话可以试试。
想想真是不可思议,我们居然有了自己的shell,可以执行自己的应用程序。我们的试验品已经越来越像是个能用的操作系统了。
虽然书上这一章的标题叫做“内存管理”,但其实并没有涉及太多“管理”的事情,只是围绕如何通过实现fork()、exit()、wait()以及exec()等系统调用来实现一个简单的shell,并用它来执行自己的应用程序。不过尽管如此,内存管理的框架我们却已经建立起来了。不需要太多努力,我们就可以进一步实现诸如brk()这样的系统调用,从而进一步让用户进程可以使用malloc(),逐步地,内存管理就可以完善起来了。
欢迎关注我的公众号
?
公众号中对应文章附有当前文章代码下载说明。