Lab2: 实验目的:学习unix的shell如何使用系统调用

一. 实验准备

Your job is to write a simple shell for xv6. It should be able to run commands with arguments, handle input and output redirection, and set up two-element pipelines. Your shell should act like the xv6 shell sh for these examples as well as similar commands:
我需要写一个简单的shell,它要支持运行带参数的命令,输入输出重定向,设置两个管道;我的shell 应该支持那些指令,那么面向测试程序编程,可以看看testsh.c中t1~t9函数,一共有9条,详见:user/testsh.c
提示:

  • 我的shell应该写在user/nsh.c下,然后在makefile里添加它并编译;

  • 我的shell应以@开头而不是$,方便和xv6shell区分开;运行效果像下面这样:

  • 不要使用内存分配器函数,如malloc();尽量使用栈内存和全局变量;

  • 可以限制cmd名字的长度,参数的数量,和单个参数的长度等;

  • 可以使用testsh测试你的nsh.c;testsh会将输出重定向,你也可以修改testsh的代码,方便你看到自己程序的输出;

  • 大佬c语言书里的代码时很有用的,多看看,如5.12节的gettoken(); 这个函数sh.c有用到

  • 参考资料:这里可以参考哈工大的实验链接,它也是用的mit 6.s081的课程,https://hitsz-lab.gitee.io/os_lab/lab2/part3/,可以把它当成中文版的翻译;

二. 知识点

  1. int unlink(const char*);函数,关闭给定文件的所有文件描述符的链接,然后删除文件;如果给定的是一个文件的软链接,则删除这个软链接
  2. int chdir(const char*);是C语言中的一个系统调用函数(同cd),用于改变当前工作目录,其参数为Path 目标目录,可以是绝对目录或相对目录。
  3. gets(char *buf, int max)从标准输入中读取字符,直到遇到\n\r
  4. fprintf(int fd, const char *fmt, ...) 指定给指定文件描述符fd中写入字符串

三. 任务分解

我自己实现的shell都要完成那些任务

  1. main()函数, shell的main函数不需要参数,它从标准输入中读取数据
int main(void)
{static char buf[100];int fd;// Ensure that three file descriptors are open.while ((fd = open("console", O_RDWR)) >= 0){if (fd >= 3){close(fd);break;}}// Read and run input commands.// getcmd 从标准输入中读取字符,直到遇到`\n`获`\r`。while (getcmd(buf, sizeof(buf)) >= 0){if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){// Chdir must be called by the parent, not the child.buf[strlen(buf)-1] = 0;  // chop \nif(chdir(buf+3) < 0)fprintf(2, "cannot cd %s\n", buf+3);continue;}if (fork1() == 0){ // 在子进程中运行指令char *argv[MAXARGS];int argc;parsecmd(buf, argv, &argc); // 解析指令,将指令按照空格填充到argv中runcmd(argc, argv); // 运行指令,运行后执行cmd}// 父进程等子进程执行完指令后继续从标准输入读取下一条指令wait(0);}exit(0);
}
  1. 解析指令, 将指令按照空格填充到argv中
void parsecmd(char *buf, char *argv[], int *argc)
{int i = 0, j = 0;for (; buf[j] != '\n' && buf[j] != '\0'; j++){// 当buf[j]属于whitespace的string时,返回对应的下标while (strchr(whitespace, buf[j])){j++;}// 此时得到的j一定不是whitespaceargv[i++] = buf + j; // buf+j是地址,这里也可以用&buf[j]// 在找到下一个whitespacewhile (0 == strchr(whitespace, buf[j])){j++;}buf[j] = '\0';// 这里buf[j]="\0"; }argv[i] = 0; *argc = i;
}
  1. 执行指令
void runcmd(int argc, char *argv[])
{// 先看argv里有没有'|',需要管道执行的for (int i = 0; i < argc; i++){if (!strcmp(argv[i], "|")) // strcmp返回的是两个str相减,为0说明找到了{myPipe(argc, argv); // 待实现 有管道的时候是否需要退出}}// 这里输入输出重定向,最终要实现的是 echo < inputfile > outfile 替换为 echo \0for (int i = 0; i < argc; i++){// 判断输出重定向if (!strcmp(argv[i], ">")){if (argc - 1 != i){close(1);open(argv[i + 1], O_CREATE | O_WRONLY);argv[i] = '\0'; // 这样后续执行的时候 只执行>之前的命令}else{fprintf(2, "%s\n", "output redirect error"); //fprintf 指定给指定文件描述符fd中写入字符串}}// 判断输入重定向if (!strcmp(argv[i], "<")){if (argc - 1 != i){close(0);open(argv[i + 1], O_RDONLY);argv[i] = '\0'; // 这样后续执行的时候 只执行<之前的命令}else{fprintf(2, "%s\n", "input redirect error"); //fprintf 指定给指定文件描述符fd中写入字符串}}}// 这个指令既没有输入重定向,也没有输出重定向,也没有pipe,这里执行直接执行if (argv[0] == 0)exit(-1);exec(argv[0], argv);fprintf(2, "exec %s failed\n", argv[0]);
}
  1. 管道, cat readme | grep xxx子进程执行’|‘左边的,我原以为父进程等待子进程执行完后,在执行’|'右边的;但实际这里不用等子进程执行完,剩余的grep xxx会自己读取标准输入的内容,然后过滤结果到标准输出;
    grep xxx [file] // file为空时,缺省为标准输入
void myPipe(int argc, char *argv[])
{// 找到argv中的"|",然后更换为"\0";int i = 0;for (i = 0; i < argc; i++){if (!strcmp(argv[i], "|")) // strcmp返回的是两个str相减,为0说明找到了{argv[i] = 0;break;}}// 此时指令已经从i出分开 [0,i)出为第一条指令c长度为i; [i+1,argc) 为第二条指令,长度为argc-i-1// 这里左边的指令要输出到右边的指令// 利用管道int fds[2];pipe(fds);int pid = fork();if (pid < 0){fprintf(2, "myPipe exec failed,fork error.\n");}else if (0 == pid){// 子进程 执行左边的指令,然后将标准输出重定向到fds[1] fds1close(1);dup(fds[1]); // 利用最低的fd拷贝一份fds[1] ,详见book-riscv-rev0.pdf 1.3 pipeclose(fds[1]);close(fds[0]);runcmd(i, argv);}else{// 父进程 读取fds[0]中的数据,然后执行右边的指令// 父进程这里需要等到读到数据后才执行么?或者是等到子进程退出后再执行么?// 不需要,具体读取标准输入的数据交给runcmd去执行,这里只做输入重定向close(0);dup(fds[0]);close(fds[0]);close(fds[1]);runcmd(argc - i - 1, argv + i + 1);}
}

四. 思考问题

  1. shell里打印在控制台的$文件字符为啥是打印在标准错误的fd中,而不是打印在标准输出的fd中?

    个人认为和shell 的main()函数里下面的代码有关, 标准输入,输出,错误都是console文件,这个console文件应该就是屏幕这个黑框,都显示在上面,我们执行个错误指令,对应的错误信息也会显示出来;
    那都会显示为啥不直接写到标准输入中,如果有人读标准输入,也就会多读一个’′,因此写在′',因此写在'′,因此写在′'标准错误中

// 假如说标准输入,输出,错误都关了,那就得打开3次while((fd = open("console", O_RDWR)) >= 0){if(fd >= 3){close(fd);break;}}

五. 结果

最后附上代码的得分结果,完结,撒花:

Lab2_Simple Shell_xv6_2019相关推荐

最新文章

  1. python requests max retries_我可以为request.request设置max_retries吗?
  2. 菜鸟教程python3-Python数据分析,学习路径拆解及资源推荐
  3. 五分钟叫你看懂美国金融危机的成因和巨大危害[转]
  4. java8 stream中的惰性求值
  5. python3网络爬虫开发实战下载_【Python3网络爬虫开发实战】 1.1-Python3的安装
  6. 精简JRE第二步 ─ 精简lib目录
  7. [js] 如何提升JSON.stringify的性能
  8. spring+mybatis+springMVC+redis缓存+mysql+bootstrap+异步提交----联系人小demo
  9. 训练日志 2019.4.13
  10. 《用python写网络爬虫》 编写第一个网络爬虫
  11. 开幕倒计时3天 | 2019中国大数据技术大会(BDTC)邀您一同共赴大数据+AI盛宴!...
  12. python3.6 asyncio_python3.6以上 asyncio模块的异步编程模型 async await语法
  13. 基于STM32的USB枚举过程学习笔记
  14. linux卸载windows boot,windows和Linux双系统卸载Linux系统
  15. latex添加代码注释_在代码中添加注释:好的,坏的和丑陋的。
  16. ARFoundation系列讲解 - 80 AR内容制作一
  17. 第二十天: Linux文件管理+Linux备份压缩+网络与磁盘管理+shell与安装
  18. vue源码分析系列三:render的执行过程和Virtual DOM的产生
  19. 开源分享,让技术发光——最受欢迎“开发者布道师”评选结果来啦!
  20. 首都师范 博弈论 6 5 5无限次重复博弈中的策略选择

热门文章

  1. java框架搭建视频,看完跪了
  2. Android ActivityResultContracts 请求权限(封装;含android 11权限变更)
  3. java异常[java.util.regex.patternsyntaxexception dangling meta character ‘+‘ near index]解决
  4. 阿里云SLB负载均衡配置方法(云起实验室)
  5. BUU-偶尔的刷题-[BJDCTF2020]认真你就输了
  6. 解决光学鼠标(光电鼠标)漂移问题.2021-10-08
  7. python 数据分析模块_Python数据分析pandas模块用法实例详解
  8. 以太网PHY接口:MII RMII GMII RGMII SGMII
  9. 性能测试常见指标有哪些
  10. 阿里云AliYun物联网平台使用-申请免费试用及完成初始配置