本文主要是介绍操作系统真象还原实验记录之实验三十一:实现简单的shell,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
操作系统真象还原实验记录之实验三十一:实现简单的shell
Windows中,图形界面的资源管理器和命令行窗口都是交互接口,尽管这些交互接口名字及外观各异,但他们往往统称外壳程序
shell的功能是获取用户的输入,分析输入的命令,判断内部命令还是外部命令,然后执行不同的命令策略
1.简单shell雏形
shell.c之print_prompt函数
#define MAX_ARG_NR 16 // 加上命令名外,最多支持15个参数
#define cmd_len 128 //最大支持键入128个字符的命令行输入/* 存储输入的命令 */
static char cmd_line[cmd_len] = {0};
//char final_path[MAX_PATH_LEN] = {0}; // 用于洗路径时的缓冲/* 用来记录当前目录,是当前目录的缓存,每次执行cd命令时会更新此内容 */
char cwd_cache[64] = {0};/* 输出提示符 */
void print_prompt(void) {printf("[rabbit@localhost %s]$ ", cwd_cache);
}
输出命令提示符
shell.c之readline函数
/* 从键盘缓冲区中最多读入count个字节到buf。*/
static void readline(char* buf, int32_t count) {assert(buf != NULL && count > 0);char* pos = buf;while (read(stdin_no, pos, 1) != -1 && (pos - buf) < count) { // 在不出错情况下,直到找到回车符才返回switch (*pos) {/* 找到回车或换行符后认为键入的命令结束,直接返回 */case '\n':case '\r':*pos = 0; // 添加cmd_line的终止字符0putchar('\n');return;case '\b':if (buf[0] != '\b') { // 阻止删除非本次输入的信息--pos; // 退回到缓冲区cmd_line中上一个字符putchar('\b');}break;/* 非控制键则输出字符 */default:putchar(*pos);pos++;}}printf("readline: can`t find enter_key in the cmd_line, max num of char is 128\n");
}
从键盘缓冲区一个字符一个字符读入buf,每读一个字符,就处理一个字符于显示屏。
当该字符非控制键时,则将kbd_buf的该字符读入buf即pos,同时pos++指向buf下一个应该读入的位置,表示该字符已读入buf;
当该字符是控制键时,
若是回车或换行,则意味着命令结束,将pos所指置为0即\0表字符串结尾。显示屏换行再直接return;
若是退格,如果buf[0] == ‘\b’即命令行第一个字符就是退格,则跳过此字符不执行退格,因为会退掉命令行提示符;如果buf[0] != ‘\b’,意味着buf中一定有字符可以退格,那么执行退格,在显示屏上清除那个字符,并且pos要减一,指向退格前一个字符,此字符已被退格清除,pos指向它,这样下次读入的时候,就可以覆盖此字符。
shell.c之my_shell函数
/* 简单的shell */
void my_shell(void) {cwd_cache[0] = '/';while (1) {print_prompt();
// memset(final_path, 0, MAX_PATH_LEN);memset(cmd_line, 0, cmd_len);readline(cmd_line, cmd_len);if (cmd_line[0] == 0) { // 若只键入了一个回车continue;}}panic("my_shell: should not be here");
}
死循环不断打印命令行提示符并调用readline来处理键盘缓冲区字符于显示屏。
main.c之main与init函数
int main(void) {put_str("I am kernel\n");init_all();cls_screen();console_put_str("[rabbit@localhost /]$ ");while(1);return 0;
}/* init进程 */
void init(void) {uint32_t ret_pid = fork();if(ret_pid) { // 父进程while(1);} else { // 子进程my_shell();}panic("init: should not be here");
}
主线程执行main函数清屏并且打印了命令行提示符,然后进入死循环
init进程执行init函数,调用fork创建子进程,然后init进程进入if死循环,init的子进程进入my_shell(),
三个线程/进程相互切换,只有init的子进程是像shell一样在反复读键盘命令。其他两个都是死循环浪费cpu。
my_shell()循环打印命令行提示符并调用readline处理键盘缓冲区字符。readline调用read系统调用,执行sys_read,由于参数是标准输入,sys_read会调用ioq_getchar(&kbd_buf)读键盘缓冲区,如果键盘缓冲区没有数据,那么进程会被阻塞。当敲击键盘,键盘中断处理程序会调用ioq_putchar(&kbd_buf, cur_char);向缓冲区输入一个字符,同时唤醒阻塞的进程,sys_read立刻读此字符处理于屏幕上。
实验结果以及main的一个问题
不知道有没有人会对这节书上的代码抱有疑问,因为书上没有解释。
我们知道main函数里面执行了
cls_screen();console_put_str("[rabbit@localhost /]$ ");
第二句代码my_shell里面也有,看上去多余
但如果将上述二句代码都注释掉,你会发现看不到my_shell中print_prompt打印的命令行提示符。
事实是,程序先清屏然后打印了main的命令行提示符,之后进入my_shell,非常奇怪的跳过了print_prompt(),执行了readline,没错,经过我的试验,程序第一次命令行,没有执行print_prompt(),但是一定执行了readline,因为不能退格,走了readline的退格switch、case。
这我无法理解。
第二次回顾os开发这个项目,看到下面的评论,又重新思考了一下这种现象的可能性:
main函数在时间片结束后,未执行到清屏函数,进程调度到init父进程、idle进程、最后调度到init子进程,init子进程执行到readline向键盘缓冲区读数据时被阻塞,因为键盘缓冲区为空消费者被阻塞,只能被生产者唤醒。因此切换回main函数,main执行完清屏与打印提示符后,执行while死循环消耗光时间片,然后进程调度到init父进程执行while死循环,init父进程时间片消耗完在调度回main的while死循环。cpu在执行main或者init父进程的死循环时,只要敲击了键盘,则会触发键盘中断,键盘中断处理程序会向键盘缓冲区写入数据并唤醒init子进程。当init子进程被唤醒后,下次调度回到init子进程的readline中,readline就可以读出命令展示于显示屏,然后while循环再次打印命令提示符后再次执行readline,如果键盘缓冲区被读完了,则再次阻塞。
2.添加Ctrl+u和Ctrl+l
keyboard.c的intr_keyboard_handler增加
/* 如果cur_char不为0,也就是ascii码为除'\0'外的字符就加入键盘缓冲区中 */if (cur_char) {/***************** 快捷键ctrl+l和ctrl+u的处理 ********************** 下面是把ctrl+l和ctrl+u这两种组合键产生的字符置为:* cur_char的asc码-字符a的asc码, 此差值比较小,* 属于asc码表中不可见的字符部分.故不会产生可见字符.* 我们在shell中将ascii值为l-a和u-a的分别处理为清屏和删除输入的快捷键*/if ((ctrl_down_last && cur_char == 'l') || (ctrl_down_last && cur_char == 'u')) {cur_char -= 'a';}/****************************************************************//* 若kbd_buf中未满并且待加入的cur_char不为0,* 则将其加入到缓冲区kbd_buf中 */if (!ioq_full(&kbd_buf)) {ioq_putchar(&kbd_buf, cur_char);}return;}
把Ctrl+u和Ctrl+l减’a’。
shell.c之readline增加
/* ctrl+l 清屏 */case 'l' - 'a': /* 1 先将当前的字符'l'-'a'置为0 */*pos = 0;/* 2 再将屏幕清空 */clear();/* 3 打印提示符 */print_prompt();/* 4 将之前键入的内容再次打印 */printf("%s", buf);break;/* ctrl+u 清掉输入 */case 'u' - 'a':while (buf != pos) {putchar('\b');*(pos--) = 0;}break;
3.解析键入的字符
shell.c之cmd_parse
/* 分析字符串cmd_str中以token为分隔符的单词,将各单词的指针存入argv数组 */
static int32_t cmd_parse(char* cmd_str, char** argv, char token) {assert(cmd_str != NULL);int32_t arg_idx = 0;while(arg_idx < MAX_ARG_NR) {argv[arg_idx] = NULL;arg_idx++;}char* next = cmd_str;int32_t argc = 0;/* 外层循环处理整个命令行 */while(*next) {/* 去除命令字或参数之间的空格 */while(*next == token) {next++;}/* 处理最后一个参数后接空格的情况,如"ls dir2 " */if (*next == 0) {break; }argv[argc] = next;/* 内层循环处理命令行中的每个命令字及参数 */while (*next && *next != token) { // 在字符串结束前找单词分隔符next++;}/* 如果未结束(是token字符),使tocken变成0 */if (*next) {*next++ = 0; // 将token字符替换为字符串结束符0,做为一个单词的结束,并将字符指针next指向下一个字符}/* 避免argv数组访问越界,参数过多则返回0 */if (argc > MAX_ARG_NR) {return -1;}argc++;}return argc;
}
函数接受命令字符串cmd_str,以及分隔符token()就是’ ‘即空格字符。功能是将cmd_str的命令字依次放入argv数组中。
ASCLL中空格字符值是32,c语言中字符串结尾标志’\0’值是0.
argv是char*数组,也就是字符串数组。
模拟函数流程
假设命令字符串是“ is dir2 ”
那么这个函数会把“is”,“dir2”放入argv数组。
首先,一个while先把is前面的分隔符空格去除,然后将is放入argv[0],如果is后面是个分隔符空格,那么就把‘\0’放入is后面作为argv[0]字符串的结束符。接着遍历dir2。
同样的逻辑,清除dir2前面的空格,将dir2放入argv[1],遇到cmd_str的‘\0’,循环会结束,返回argv。
如果dir2后面有2个及以上的空格,那么会把dir2后的空格替换成’\0’,同时开始第三次大循环遍历不断跳过空格,当next指向’\0’后,则需要if来break退出循环。
shell.c之my_shell修改
char* argv[MAX_ARG_NR] = {NULL};
int32_t argc = -1;/* 简单的shell */
void my_shell(void) {cwd_cache[0] = '/';while (1) {print_prompt();
printf("what the fuck?");
// memset(final_path, 0, MAX_PATH_LEN);memset(cmd_line, 0, cmd_len);readline(cmd_line, cmd_len);if (cmd_line[0] == 0) { // 若只键入了一个回车continue;}argc = -1;argc = cmd_parse(cmd_line, argv, ' ');if (argc == -1) {printf("num of arguments exceed %d\n", MAX_ARG_NR);continue;}int32_t arg_idx=0;while(arg_idx < argc){printf("%s ", argv[arg_idx]);arg_idx++;}printf("\n");}panic("my_shell: should not be here");
}
在readline后,命令字符串已经保存在buf了,对其调用cmd_parse,得到的argv依次打印字符串。
实验效果
4.添加系统调用
thread.c之sys_ps内核实现
/* 以填充空格的方式输出buf */
static void pad_print(char* buf, int32_t buf_len, void* ptr, char format) {memset(buf, 0, buf_len);uint8_t out_pad_0idx = 0;switch(format) {case 's':out_pad_0idx = sprintf(buf, "%s", ptr);break;case 'd':out_pad_0idx = sprintf(buf, "%d", *((int16_t*)ptr));case 'x':out_pad_0idx = sprintf(buf, "%x", *((uint32_t*)ptr));}while(out_pad_0idx < buf_len) { // 以空格填充buf[out_pad_0idx] = ' ';out_pad_0idx++;}sys_write(stdout_no, buf, buf_len - 1);
}/* 用于在list_traversal函数中的回调函数,用于针对线程队列的处理 */
static bool elem2thread_info(struct list_elem* pelem, int arg UNUSED) {struct task_struct* pthread = elem2entry(struct task_struct, all_list_tag, pelem);char out_pad[16] = {0};pad_print(out_pad, 16, &pthread->pid, 'd');if (pthread->parent_pid == -1) {pad_print(out_pad, 16, "NULL", 's');} else { pad_print(out_pad, 16, &pthread->parent_pid, 'd');}switch (pthread->status) {case 0:pad_print(out_pad, 16, "RUNNING", 's');break;case 1:pad_print(out_pad, 16, "READY", 's');break;case 2:pad_print(out_pad, 16, "BLOCKED", 's');break;case 3:pad_print(out_pad, 16, "WAITING", 's');break;case 4:pad_print(out_pad, 16, "HANGING", 's');break;case 5:pad_print(out_pad, 16, "DIED", 's');}pad_print(out_pad, 16, &pthread->elapsed_ticks, 'x');memset(out_pad, 0, 16);ASSERT(strlen(pthread->name) < 17);memcpy(out_pad, pthread->name, strlen(pthread->name));strcat(out_pad, "\n");sys_write(stdout_no, out_pad, strlen(out_pad));return false; // 此处返回false是为了迎合主调函数list_traversal,只有回调函数返回false时才会继续调用此函数
}/* 打印任务列表 */
void sys_ps(void) {char* ps_title = "PID PPID STAT TICKS COMMAND\n";sys_write(stdout_no, ps_title, strlen(ps_title));list_traversal(&thread_all_list, elem2thread_info, 0);
}
ps用来打印进程的pid、ppid、状态、运行时间片和进程名。
sys_ps调用list_traversal,遍历所有存在的进程,获得该pcb,执行elem2thread_info;
而elem2thread_info则是不断调用pad_print来打印进程信息。
pad_print:以固定长度打印字符串,不够后面用空格填充。
补全所有内核已实现的系统调用
syscall.h
enum SYSCALL_NR {SYS_GETPID,SYS_WRITE,SYS_MALLOC,SYS_FREE,SYS_FORK,SYS_READ,SYS_PUTCHAR,SYS_CLEAR,SYS_GETCWD,SYS_OPEN,SYS_CLOSE,SYS_LSEEK,SYS_UNLINK,SYS_MKDIR,SYS_OPENDIR,SYS_CLOSEDIR,SYS_CHDIR,SYS_RMDIR,SYS_READDIR,SYS_REWINDDIR,SYS_STAT,SYS_PS};
syscall.c
/* 获取当前工作目录 */
char* getcwd(char* buf, uint32_t size) {return (char*)_syscall2(SYS_GETCWD, buf, size);
}/* 以flag方式打开文件pathname */
int32_t open(char* pathname, uint8_t flag) {return _syscall2(SYS_OPEN, pathname, flag);
}/* 关闭文件fd */
int32_t close(int32_t fd) {return _syscall1(SYS_CLOSE, fd);
}/* 设置文件偏移量 */
int32_t lseek(int32_t fd, int32_t offset, uint8_t whence) {return _syscall3(SYS_LSEEK, fd, offset, whence);
}/* 删除文件pathname */
int32_t unlink(const char* pathname) {return _syscall1(SYS_UNLINK, pathname);
}/* 创建目录pathname */
int32_t mkdir(const char* pathname) {return _syscall1(SYS_MKDIR, pathname);
}/* 打开目录name */
struct dir* opendir(const char* name) {return (struct dir*)_syscall1(SYS_OPENDIR, name);
}/* 关闭目录dir */
int32_t closedir(struct dir* dir) {return _syscall1(SYS_CLOSEDIR, dir);
}/* 删除目录pathname */
int32_t rmdir(const char* pathname) {return _syscall1(SYS_RMDIR, pathname);
}/* 读取目录dir */
struct dir_entry* readdir(struct dir* dir) {return (struct dir_entry*)_syscall1(SYS_READDIR, dir);
}/* 回归目录指针 */
void rewinddir(struct dir* dir) {_syscall1(SYS_REWINDDIR, dir);
}/* 获取path属性到buf中 */
int32_t stat(const char* path, struct stat* buf) {return _syscall2(SYS_STAT, path, buf);
}/* 改变工作目录为path */
int32_t chdir(const char* path) {return _syscall1(SYS_CHDIR, path);
}/* 显示任务列表 */
void ps(void) {_syscall0(SYS_PS);
}
syscall_init.c
/* 初始化系统调用 */
void syscall_init(void) {put_str("syscall_init start\n");syscall_table[SYS_GETPID] = sys_getpid;syscall_table[SYS_WRITE] = sys_write;syscall_table[SYS_MALLOC] = sys_malloc;syscall_table[SYS_FREE] = sys_free;syscall_table[SYS_FORK] = sys_fork;syscall_table[SYS_READ] = sys_read;syscall_table[SYS_PUTCHAR] = console_put_char;syscall_table[SYS_CLEAR] = cls_screen;syscall_table[SYS_GETCWD] = sys_getcwd;syscall_table[SYS_OPEN] = sys_open;syscall_table[SYS_CLOSE] = sys_close;syscall_table[SYS_LSEEK] = sys_lseek;syscall_table[SYS_UNLINK] = sys_unlink;syscall_table[SYS_MKDIR] = sys_mkdir;syscall_table[SYS_OPENDIR] = sys_opendir;syscall_table[SYS_CLOSEDIR] = sys_closedir;syscall_table[SYS_CHDIR] = sys_chdir;syscall_table[SYS_RMDIR] = sys_rmdir;syscall_table[SYS_READDIR] = sys_readdir;syscall_table[SYS_REWINDDIR] = sys_rewinddir;syscall_table[SYS_STAT] = sys_stat;syscall_table[SYS_PS] = sys_ps;put_str("syscall_init done\n");
}
5.路径解析转换
buildin_cmd.c
/* 将路径old_abs_path中的..和.转换为实际路径后存入new_abs_path */
static void wash_path(char* old_abs_path, char* new_abs_path) {assert(old_abs_path[0] == '/');char name[MAX_FILE_NAME_LEN] = {0}; char* sub_path = old_abs_path;sub_path = path_parse(sub_path, name);if (name[0] == 0) { // 若只键入了"/",直接将"/"存入new_abs_path后返回 new_abs_path[0] = '/';new_abs_path[1] = 0;return;}new_abs_path[0] = 0; // 避免传给new_abs_path的缓冲区不干净strcat(new_abs_path, "/");while (name[0]) {/* 如果是上一级目录“..” */if (!strcmp("..", name)) {char* slash_ptr = strrchr(new_abs_path, '/');/*如果未到new_abs_path中的顶层目录,就将最右边的'/'替换为0,这样便去除了new_abs_path中最后一层路径,相当于到了上一级目录 */if (slash_ptr != new_abs_path) { // 如new_abs_path为“/a/b”,".."之后则变为“/a”*slash_ptr = 0;} else { // 如new_abs_path为"/a",".."之后则变为"/"/* 若new_abs_path中只有1个'/',即表示已经到了顶层目录,就将下一个字符置为结束符0. */*(slash_ptr + 1) = 0;}} else if (strcmp(".", name)) { // 如果路径不是‘.’,就将name拼接到new_abs_pathif (strcmp(new_abs_path, "/")) { // 如果new_abs_path不是"/",就拼接一个"/",此处的判断是为了避免路径开头变成这样"//"strcat(new_abs_path, "/");}strcat(new_abs_path, name);} // 若name为当前目录".",无须处理new_abs_path/* 继续遍历下一层路径 */memset(name, 0, MAX_FILE_NAME_LEN);if (sub_path) {sub_path = path_parse(sub_path, name);}}
}/* 将path处理成不含..和.的绝对路径,存储在final_path */
void make_clear_abs_path(char* path, char* final_path) {char abs_path[MAX_PATH_LEN] = {0};/* 先判断是否输入的是绝对路径 */if (path[0] != '/') { // 若输入的不是绝对路径,就拼接成绝对路径memset(abs_path, 0, MAX_PATH_LEN);if (getcwd(abs_path, MAX_PATH_LEN) != NULL) {if (!((abs_path[0] == '/') && (abs_path[1] == 0))) { // 若abs_path表示的当前目录不是根目录/strcat(abs_path, "/");}}}strcat(abs_path, path);wash_path(abs_path, final_path);
}
wash_path函数:将旧绝对路径old_abs_path转化为绝对路径new_abs_path(不含"。"、“。。”)
假设old_abs_path=/a/b/…/c
1.首先path_parse获得a存入sub_path,同时new_abs_path[0] = ‘/’,进入while循环。
2.由于a不是“。”也不是“。。”,直接加入new_abs_path,再path_parse获得b。
3.b也不是“。”和“。。”,但由于此时new_abs_path为/a,所以加入了/b于new_abs_path。再path_parse获得“。。”
4.“。。”的作用就是要清除当前目录,所以strrchr将slash_ptr指向到/a/b中ab之间的/,将其修改为\0即可。再path_parse获得c。
5.由于new_abs_path目前为/a0b,strcmp、strcat都会认为这是/a,所以加入/c,new_abs_path变成/a/c。
假设old_abs_path=/…
那么strrchr将slash_ptr指向的/等于new_abs_path即首地址,意味着这个/是根目录/,所以*(slash_ptr+1) = 0,new_abs_path变成了/0即/
如果old_abs_path中有“。”表示当前目录,new_abs_path什么也不做
make_clear_abs_path:接受path,调用getcwd获取当前工作目录,将path加入构成可含"…"、“。”的绝对路径。
调用wash_path,再转化成不含“。”、“。。”的绝对路径。
shell.c用于测试
/* 简单的shell */
void my_shell(void) {cwd_cache[0] = '/';while (1) {print_prompt();
printf("what the fuck?");
// memset(final_path, 0, MAX_PATH_LEN);memset(cmd_line, 0, cmd_len);readline(cmd_line, cmd_len);if (cmd_line[0] == 0) { // 若只键入了一个回车continue;}argc = -1;argc = cmd_parse(cmd_line, argv, ' ');if (argc == -1) {printf("num of arguments exceed %d\n", MAX_ARG_NR);continue;}char buf[MAX_PATH_LEN] = {0};int32_t arg_idx=0;while(arg_idx < argc){make_clear_abs_path(argv[arg_idx], buf);printf("%s -> %s", argv[arg_idx], buf);arg_idx++;}printf("\n");}panic("my_shell: should not be here");
}
只修改了这两句。
make_clear_abs_path(argv[arg_idx], buf);
printf(“%s -> %s”, argv[arg_idx], buf);
实验效果
6.实现ls、cd、mkdir、ps、rm等命令
buildin_cmd.c之shell 内部命令的内建函数
/* pwd命令的内建函数 */
void buildin_pwd(uint32_t argc, char** argv UNUSED) {if (argc != 1) {printf("pwd: no argument support!\n");return;} else {if (NULL != getcwd(final_path, MAX_PATH_LEN)) {printf("%s\n", final_path); } else {printf("pwd: get current work directory failed.\n");}}
}/* cd命令的内建函数 */
char* buildin_cd(uint32_t argc, char** argv) {if (argc > 2) {printf("cd: only support 1 argument!\n");return NULL;}/* 若是只键入cd而无参数,直接返回到根目录. */if (argc == 1) {final_path[0] = '/';final_path[1] = 0;} else {make_clear_abs_path(argv[1], final_path);}if (chdir(final_path) == -1) {printf("cd: no such directory %s\n", final_path);return NULL;}return final_path;
}/* ls命令的内建函数 */
void buildin_ls(uint32_t argc, char** argv) {char* pathname = NULL;struct stat file_stat;memset(&file_stat, 0, sizeof(struct stat));bool long_info = false;uint32_t arg_path_nr = 0;uint32_t arg_idx = 1; // 跨过argv[0],argv[0]是字符串“ls”while (arg_idx < argc) {if (argv[arg_idx][0] == '-') { // 如果是选项,单词的首字符是-if (!strcmp("-l", argv[arg_idx])) { // 如果是参数-llong_info = true;} else if (!strcmp("-h", argv[arg_idx])) { // 参数-hprintf("usage: -l list all infomation about the file.\n-h for help\nlist all files in the current dirctory if no option\n"); return;} else { // 只支持-h -l两个选项printf("ls: invalid option %s\nTry `ls -h' for more information.\n", argv[arg_idx]);return;}} else { // ls的路径参数if (arg_path_nr == 0) {pathname = argv[arg_idx];arg_path_nr = 1;} else {printf("ls: only support one path\n");return;}}arg_idx++;} if (pathname == NULL) { // 若只输入了ls 或 ls -l,没有输入操作路径,默认以当前路径的绝对路径为参数.if (NULL != getcwd(final_path, MAX_PATH_LEN)) {pathname = final_path;} else {printf("ls: getcwd for default path failed\n");return;}} else {make_clear_abs_path(pathname, final_path);pathname = final_path;}if (stat(pathname, &file_stat) == -1) {printf("ls: cannot access %s: No such file or directory\n", pathname);return;}if (file_stat.st_filetype == FT_DIRECTORY) {struct dir* dir = opendir(pathname);struct dir_entry* dir_e = NULL;char sub_pathname[MAX_PATH_LEN] = {0};uint32_t pathname_len = strlen(pathname);uint32_t last_char_idx = pathname_len - 1;memcpy(sub_pathname, pathname, pathname_len);if (sub_pathname[last_char_idx] != '/') {sub_pathname[pathname_len] = '/';pathname_len++;}rewinddir(dir);if (long_info) {char ftype;printf("total: %d\n", file_stat.st_size);while((dir_e = readdir(dir))) {ftype = 'd';if (dir_e->f_type == FT_REGULAR) {ftype = '-';} sub_pathname[pathname_len] = 0;strcat(sub_pathname, dir_e->filename);memset(&file_stat, 0, sizeof(struct stat));if (stat(sub_pathname, &file_stat) == -1) {printf("ls: cannot access %s: No such file or directory\n", dir_e->filename);return;}printf("%c %d %d %s\n", ftype, dir_e->i_no, file_stat.st_size, dir_e->filename);}} else {while((dir_e = readdir(dir))) {printf("%s ", dir_e->filename);}printf("\n");}closedir(dir);} else {if (long_info) {printf("- %d %d %s\n", file_stat.st_ino, file_stat.st_size, pathname);} else {printf("%s\n", pathname); }}
}/* ps命令内建函数 */
void buildin_ps(uint32_t argc, char** argv UNUSED) {if (argc != 1) {printf("ps: no argument support!\n");return;}ps();
}/* clear命令内建函数 */
void buildin_clear(uint32_t argc, char** argv UNUSED) {if (argc != 1) {printf("clear: no argument support!\n");return;}clear();
}/* mkdir命令内建函数 */
int32_t buildin_mkdir(uint32_t argc, char** argv) {int32_t ret = -1;if (argc != 2) {printf("mkdir: only support 1 argument!\n");} else {make_clear_abs_path(argv[1], final_path);/* 若创建的不是根目录 */if (strcmp("/", final_path)) {if (mkdir(final_path) == 0) {ret = 0;} else {printf("mkdir: create directory %s failed.\n", argv[1]);}}}return ret;
}/* rmdir命令内建函数 */
int32_t buildin_rmdir(uint32_t argc, char** argv) {int32_t ret = -1;if (argc != 2) {printf("rmdir: only support 1 argument!\n");} else {make_clear_abs_path(argv[1], final_path);/* 若删除的不是根目录 */if (strcmp("/", final_path)) {if (rmdir(final_path) == 0) {ret = 0;} else {printf("rmdir: remove %s failed.\n", argv[1]);}}}return ret;
}/* rm命令内建函数 */
int32_t buildin_rm(uint32_t argc, char** argv) {int32_t ret = -1;if (argc != 2) {printf("rm: only support 1 argument!\n");} else {make_clear_abs_path(argv[1], final_path);/* 若删除的不是根目录 */if (strcmp("/", final_path)) {if (unlink(final_path) == 0) {ret = 0;} else {printf("rm: delete %s failed.\n", argv[1]);}}}return ret;
}
buildin_pwd:除命令无参数,则调用系统调用getcwd打印当前工作目录。
buildin_cd:
若只键入cd而无参数,直接返回到根目录,
cd有参数,调用make_clear_abs_path将进程当前工作目录加上参数转化成绝对路径返回。
buildin_ls:
buildin_ps:除命令无额外参数,则系统调用ps,打印进程的pid、ppid、状态、运行时间片和进程名。、
buildin_clear:除命令无额外参数,则调用系统调用clear清屏。
buildin_mkdir:除命令必须要一个目录路径名参数,调用make_clear_abs_path得到参数对应的绝对路径,只要这个绝对路径不是根目录,则调用mkdir系统调用创建此目录。
buildin_rmdir:同上。
buildin_rm:同上,调用系统调用unlink删除文件和目录。
shell.c之my_shell增加实现解析命令调用内建函数
/* 简单的shell */
void my_shell(void) {cwd_cache[0] = '/';while (1) {print_prompt();
printf("what the fuck?");memset(final_path, 0, MAX_PATH_LEN);memset(cmd_line, 0, cmd_len);readline(cmd_line, cmd_len);if (cmd_line[0] == 0) { // 若只键入了一个回车continue;}argc = -1;argc = cmd_parse(cmd_line, argv, ' ');if (argc == -1) {printf("num of arguments exceed %d\n", MAX_ARG_NR);continue;}if (!strcmp("ls", argv[0])) {buildin_ls(argc, argv);} else if (!strcmp("cd", argv[0])) {if (buildin_cd(argc, argv) != NULL) {memset(cwd_cache, 0, MAX_PATH_LEN);strcpy(cwd_cache, final_path);}} else if (!strcmp("pwd", argv[0])) {buildin_pwd(argc, argv);} else if (!strcmp("ps", argv[0])) {buildin_ps(argc, argv);} else if (!strcmp("clear", argv[0])) {buildin_clear(argc, argv);} else if (!strcmp("mkdir", argv[0])){buildin_mkdir(argc, argv);} else if (!strcmp("rmdir", argv[0])){buildin_rmdir(argc, argv);} else if (!strcmp("rm", argv[0])) {buildin_rm(argc, argv);} }panic("my_shell: should not be here");
}
my_shell调用cmd_parse后,已经获得参数数组argv,命令就是argv[0],命令后面的参数就是argv[1]等等,argc就是参数个数。
实验效果
这篇关于操作系统真象还原实验记录之实验三十一:实现简单的shell的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!