OS实训1——Minix3 shell的简单实现

  • shell实现要求
  • shell实现代码
    • 参数设置
    • Shell主体
    • 三个较简单命令
    • history n
    • mytop
    • 后台运行&
    • 重定向、管道
    • 实验结果

shell实现要求

Shell主体为一个while循环,对用户键盘输入命令进行操作并给出反馈。
该实验中,我们主要实现以下几个命令:

  1. cd:shell也是一个程序,启动时迷你型会给它分配一个当前工作目录,利用chdir系统调用可以移动Shell的工作目录。
  2. history n:保存每次输入的命令,打印最近n条命令。
  3. exit:退出Shell的while循环,结束Shell的main函数。
  4. mytop:参考Minix终端输入top命令的输出信息,最终输出总体内存大小、空闲内存大小、缓存大小和总体CPU使用占比。
  5. 后台运行:对末尾包含&参数的命令,通过将子进程的标准输入、输出映射成 /dev/null来屏蔽键盘和控制台,并调用signal(SIGCHLD,SIG_IGN),使minix接管此进程,shell不等待进程结束,直接返回。
  6. 重定向:利用dup2函数,将某个打开文件的文件描述符fd映射到标准输入or输出,实现覆盖写“>”、追加写“>>”和文件输入“<”。
  7. 管道:利用fork及dup2实现进程间同步。

shell实现代码

对输入命令,Shell先将其分解成单词序列,再根据命令名称分为两类分别处理。
一类是shell内置命令(如cd,history,exit),识别后,执行对应操作。
另一类是program命令(如/bin/ 目录下的ls,grep ),识别后,创建一个新进程执行命令,并等待进程结束。

参数设置

#define M 256
#define SIZE 256char path[M], input[M], *command[M], *temp, history[M][M];      //save path, input command, processes command, a temporary char, historical command int i, j, n_com, n_his = 0;        //n_com: the number of blocks of a command, n_his:the number of all commandsint k, h, flag_back, flag_out, flag_add, flag_in, flag_pipe, pid, pid2, status, status2, fd1, fd2;      //flag: 分别标识后台运行、覆盖写、追加写、输入、管道状态 char file_out[M], file_in[M], *command2[M];char buffer[SIZE] = "", *pagesize, *total, *freepage, *cached, *temp1;        //*pagesize不要赋为pagesize[SIZE],否则后期无法用 atoi()转为数字 int count = 0, fd;

Shell主体

int main(int argc, char *argv[]) {while (1) {//get path getcwd(path, sizeof(path) - 1);printf("shell:%s%% ", path);//initialize parametermemset(input, 0, sizeof(input));memset(command, 0, sizeof(command));n_com = 0;flag_back = 0;flag_out = 0;flag_add = 0;flag_in = 0;flag_pipe = 0;memset(command2, 0, sizeof(command2));gets(input);        //get command/* save command * ——!!! The code should follow "gets(input)", or we can only get command[0] (it was cut by "strtok").  */for (i = 0; i < M; ++i) {history[n_his][i] = input[i];            //notice:history[0] can't be empty}++n_his;//break command to get parameter temp = strtok(input, " ");        //char *strtok(char *s, char *delim):以delim中的字符为分界符,将s切分成一个个子串while (temp != NULL) {command[n_com] = temp;n_com++;temp = strtok(NULL, " ");     //将strtok第一个参数赋为空值NULL,继续分解字符串}/* 命令相关操作 */}return 0;}

该部分是在进行解析命令之前做的准备工作,将输入的字符串命令分割成块并依次存入command中。在此之前,注意要将输入的命令存入history数组中作为命令的记录。

三个较简单命令

//parse the command
if (!strcmp(command[0], "cd")) {if (n_com != 1) {chdir(command[1]);}
} else if (!strcmp(command[0], "exit")) {if (n_com == 1) {break;}
} else if (!strcmp(input, "\000")) {continue;
}

如上实现的是对cd、exit和无输入情况的实现,其中对n_com != 1进行判断以确保命令只有1块。

history n

else if (!strcmp(command[0], "history")) {if (n_com != 1) {int t = atoi(command[1]);            // notice: command[1] is a string, not a char! for (j = n_his - t; j < n_his; ++j) {printf("%d ", j + 1);puts(history[j]);}}
}

该命令接上一部分的if-else语句,因为command[1]是字符串,故需用atoi()函数来确定需要的最近n条命令,并用for循环将其打印到屏幕。

mytop

else if (!strcmp(command[0], "mytop")) {if (n_com == 1) {// method 1: 内存 int memory_sum=0,memory_free=0,cache_size=0;fd = open("/proc/meminfo",O_RDONLY);count=read(fd,buffer,SIZE);temp1=strtok(buffer," ");pagesize=temp1;temp1=strtok(NULL," ");total=temp1;temp1=strtok(NULL," ");freepage=temp1;temp1=strtok(NULL," ");temp1=strtok(NULL," ");cached=temp1;close(fd);memory_sum=atoi(pagesize)*atoi(total)*1.0/1024;memory_free=atoi(pagesize)*atoi(freepage)/1024;cache_size=atoi(pagesize)*atoi(cached)/1024;printf("main memory: %dK total, %dK free, %dK cached\n",memory_sum,memory_free,cache_size);}
}

这是一种输出总体内存大小、空闲内存大小和缓存大小的方法。

在/proc/meminfo中,可查看内存信息。文件中的每个参数对应含义依次是页面大小pagesize, 总页数量total , 空闲页数量free ,最大页数量largest ,缓存页数量cached 。
计算内存大小公式: (pagesize * total)/1024

输出结果为:

*
接下来第二种方法,是参考top源码的代码实现的,有兴趣的话可以在此处https://github.com/0xffea/MINIX3/blob/master/usr.bin/top/top.c查看top源码

else if (!strcmp(command[0], "mytop")) {if (n_com == 1) {//method 2 ——reference source of topint r, c, s = 0;int cputimemode = 1;    /* bitmap. */if (chdir(_PATH_PROC) != 0) {perror("chdir to " _PATH_PROC);return 1;}system_hz = (u32_t) sysconf(_SC_CLK_TCK);getkinfo();while ((c = getopt(argc, argv, "s:B")) != EOF) {switch (c) {case 's':s = atoi(optarg);break;case 'B':blockedverbose = 1;break;default:fprintf(stderr,"Usage: %s [-s<secdelay>] [-B]\n",argv[0]);return 1;}}if (s < 1)s = 2;/* Catch window size changes so display is updated properly* right away.*/signal(SIGWINCH, sigwinch);/* while循环控制无限动态刷新,直到 Ctrl-C * 或用for循环控制刷新次数,便于退出回到shell */for (j=0;j<10;j++){//               while (1) { slot = 0;            // notice: slot should be reset before each dynamic refresh ——注意每次动态刷新都要重置为0 //控制动态更新间隔所需参数fd_set fds; int ns; struct timeval tv; showtop(cputimemode, r); //打印结果 tv.tv_sec = s;tv.tv_usec = 0;FD_ZERO(&fds);FD_SET(STDIN_FILENO, &fds);if ((ns = select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv)) < 0 //设置动态更新间隔sleep && errno != EINTR) {perror("select");sleep(1);}if (ns > 0 && FD_ISSET(STDIN_FILENO, &fds)) {char c;if (read(STDIN_FILENO, &c, 1) == 1) {switch (c) {case 'q':putchar('\r');return 0;break;case 'o':order++;if (order > ORDER_HIGHEST)order = 0;break;case TIMECYCLEKEY:cputimemode++;if (cputimemode >= (1L << CPUTIMENAMES))cputimemode = 1;break;}}}}}

其中,用while或for循环控制其动态更新,而此时若不注意重置slot的话,则会导致slot最终大于nr_total,使屏幕一直输出:“top: unreasonable endpoint number xxx”。

后台运行&

if (!strcmp(command[n_com - 1], "&")) {flag_back = 1;
}
if ((pid = fork()) < 0) {printf("fork error\n");return -1;
}
if (pid == 0) {if (flag_back) {//    fd1=open("/dev/null",O_RDWR | O_CREAT | O_TRUNC, 0644);fd1 = open("/dev/null", O_RDONLY);dup2(fd1, 0);dup2(fd1, 1);dup2(fd1, 2);signal(SIGCHLD, SIG_IGN);}
}
else {if (flag_back) {printf("[process id %d]\n", pid);        //若为后台程序,则输出进程号continue;} else {if (waitpid(pid, &status, 0) == -1) {}}
}

在该实验中,若为&后台运行命令,则&必定出现在输入命令的最后一部分,故检验command[n_com-1]位来确定是否为&命令,以标识flag_back的状态。

之后,需fork进程,并在子进程中将标准输入、输出、错误都重定向到“/dev/null”,最后用 signal(SIGCHLD, SIG_IGN)来使Minix接管此进程。

最后,在父进程中附加代码printf("[process id %d]\n", pid),使得有后台进程时,输出其进程号。

重定向、管道

状态确定:

for (k = 0; k < n_com; k++) {if (!strcmp(command[k], ">")) {flag_out = 1;strcpy(file_out, command[k + 1]);  //">" 后面即使重定向指向的文件
//     for (h = k; h < n_com - 2; h++) {      //在该实验中不需要
//         command[h] = command[h + 2];
//     }command[n_com - 2] = NULL;n_com -= 2;k--;} else if (!strcmp(command[k], ">>")) {flag_add = 1;strcpy(file_out, command[k + 1]); command[n_com - 2] = NULL;n_com -= 2;k--;} else if (!strcmp(command[k], "<")) {flag_in = 1;strcpy(file_in, command[k + 1]); command[n_com - 2] = NULL;n_com -= 2;k--;} else if (!strcmp(command[k], "|")) {flag_pipe = 1;for (h = 0; h < k; h++) {command2[h] = command[h];     //将 "|" 之前的命令移至command2中 }command2[k] = NULL;for (h = 0; h < n_com - k - 1; h++) {command[h] = command[h + k + 1];  //重置command保存 "|" 之后的命令}command[n_com - k - 1] = NULL;break;}

此处,由于这些命令的标识符可能出现在输入命令中的任意一处,故需用一个for循环来确认其位置,而后设置它们的状态flag_xxx为1。而标识符“>”、“>>”、“<”之后一位则是重定向指向的文件,故将它们赋为file_out、file_in的值,便于之后重定向操作处理。

对管道的操作稍复杂,要将“|”之前的命令移到command[2]中保存备用,再更新command数组存“|”之后的命令备用。

命令执行:

if ((pid = fork()) < 0) {printf("fork error\n");return -1;
}
if (pid == 0) {if (flag_out)            // >{fd1 = open(file_out, O_RDWR | O_CREAT | O_TRUNC, 0644);if (fd1 < 0) {printf("open error!\n");return 0;}dup2(fd1, 1);}if (flag_add)        // >> : O_APPEND{fd1 = open(file_out, O_RDWR | O_CREAT | O_APPEND, 0644);if (fd1 < 0) {printf("open error!\n");return 0;}dup2(fd1, 1);}if (flag_in)            // <{fd1 = open(file_in, O_RDONLY);//                   fd1 = open(file_in,O_WRONLY|O_CREAT|O_TRUNC, 0644);if (fd1 < 0) {printf("open error!\n");return 0;}dup2(fd1, 0);}if (flag_pipe)            // |{if ((pid2 = fork()) < 0) {printf("fork2 error\n");return -1;} else if (pid2 == 0)        //子子进程中以写方式打开pipe {fd2 = open("/tmp/tempfile", O_WRONLY | O_CREAT | O_TRUNC, 0644);        //新建一个临时文件,后删除 dup2(fd2, 1);execvp(command2[0], command2);exit(0);}if (waitpid(pid2, &status2, 0) == -1)        //等待子子进程结束以回收 {}fd2 = open("/tmp/tempfile", O_RDONLY);        //子进程中以读方式打开管道 dup2(fd2, 0);}execvp(command[0], command);        //执行命令remove("/tmp/tempfile");exit(0);
} else {if (waitpid(pid, &status, 0) == -1) {}
}

同&命令一样,fork一个进程(它们和&在同一个fork的进程中,只不过此处省略了&的相关操作)。根据各flag_xxx的状态来确定要执行哪一个操作。

在重定向中,用fd1 = open(file_xxx, XXX);打开所需文件,在“>”中需用到可读可写O_RDWR | O_CREAT | O_TRUNC, 0644 ,在“>>”中需将O_TRUNC 改为O_APPEND 追加写模式,而“<”则用只读O_RDONLY。之后用dup2()函数分别重定向它们的标准输入、输出即可。

在管道中,由于涉及到“|”前后两个文件的协调控制问题,故需在子进程中再fork一个进程,在子子进程中重定向前一个文件的标准输出,让其写入一个新建的临时文件“/tempfile”中。而后,等待该子子进程结束并将其回收后,在重定向后一个文件的标注输入,将前一个文件写入临时文件“/tempfile”中的内容传给后一个文件,以达到管道的控制目的。

在对以上操作定义完后,用execvp(command[0], command)将命令执行,且对管道命令,还要讲中途新建的文件“/tempfile”删除,执行完成后退出子进程。最后在父进程中等待子进程结束并将其回收。

至此,shell的全部内容已完成。

实验结果

  1. cd
  2. ls –a –l
  3. ls –a –l > result.txt
  4. vi result.txt
  5. grep a < text.txt (事先ls –a –l > text.txt)
  6. ls –a –l | grep a
  7. vi result.txt &
  8. history 9
  9. mytop (实际运行时是动态刷新的)
  10. exit

OS实训1——Minix3 shell的简单实现相关推荐

  1. 软件实训-用例图2.0 + 简单用例描述

    顶层用例图: 小提示: 用例A include 用例B:用例A的实现需要用例B的参与. 用例A extend 用例B: 用例A可以独立实现,但存在触发条件执行用例B. 用例A generalizati ...

  2. Android 实训 Day2——项目实战(简单的登录页面)

    1.首先,我们准备好需要用的图片,这里我只用了四张照片,分别是用户头像,用户名图标,密码图标,容器背景. 将照片粘贴到res/mipmap 目录,或者res/drawble目录下: 新建一个Empty ...

  3. 实训1 构建一个计算列表中位数的函数

    做这类题的时候,我觉得简直就是拉低你们大学生的智商.之前写了好多你们大学生的实训题,说实话,简单的一批~ 在此我特别想说明一些问题,遇到问题先动动脑子,再来网上搜索答案,我 是为了给徒弟面子,写了这些 ...

  4. 计算机常用工具软件实训总结报告,计算机常用工具软件实训报告.doc

    文档介绍: 计算机常用工具软件实训报告图像浏览及处理工具软件报告<计算机常用工具软件-----isee>实训报告班级:工业设计10班姓名:实训地点:1-706指导教师:年月日:2011-1 ...

  5. 计算机软件安装的实训报告,计算机常用工具软件实训报告

    图像浏览及处理工具软件报告 <计算机常用工具软件----- isee>实训报告 班级:工业设计10班 姓名: 实训地点:1-706 指导教师: 年月日:2011-12-11--2011-1 ...

  6. zigbee无线传感网实训---linux命令的简单了解(one day)

    嵌入式软件介绍:         1.VMware 虚拟机             安装虚拟机将芯片虚拟化一块出来运行linux系统         2.Ubuntu18.04            ...

  7. 实训1_获取产业数据并存储_预处理与简单分析

    目录 1. 实训一. 获取产业数据并存储.预处理与分析 1.1 实训内容概述 1.2 实训知识点: 1.2.1 爬取网页数据 1.2.1.1 一般格式 1.2.1.2 采用pandas读取网页表格数据 ...

  8. Arm Mbed OS 更适合大学实训平台

    目前大学里面的嵌入式程序,物联网,电子专业的实验平台大多数采用各种各样的开发板.以前使用51系列单片机,现在许多大学开始使用Arm 32位 cortex-M系列单片机.软件多数采用keil开发环境裸机 ...

  9. Python实训day10pm【os模块-处理Excel统计学生观看直播时长】

    Python实训-15天-博客汇总表 学以致用,课堂练习:考勤目录中存放的是所有的考勤excel表格,从第1天~第9天,表中关键的信息就是,学生当天看了多久的直播. 要求,利用所学知识,读取每个exc ...

最新文章

  1. android jar 电子书下载,【Android】Gradle project sync jar包长时间下载不下来的解决办法...
  2. abaqus二次开发python 建立集合,ABAQUS二次开发-Python脚本运行方式
  3. Hibernate反向生成映射文件点击Hibernate reserve Engineering的时候没反应的解决方法
  4. [architecture]-CPU(ARM)启动的第一条指令
  5. 消费级GPU、速度提升3000倍,微软FastNeRF实现200FPS高保真神经渲染
  6. TIOBE 7 月编程语言榜:TypeScript 进入前 50 名
  7. 关于Servlet和异步Servlet
  8. java中怎么让原有的集合反转_Java如何反转集合中的元素?
  9. CCF201812-2 小明放学
  10. Linux perf tools
  11. Spring Cloud源码分析——Ribbon客户端负载均衡
  12. Java中this关键字的几种用法
  13. UIImageView 与 UIImage 区别
  14. C#—接口和抽象类的区别?
  15. 解析银行卡卡BIN的来龙去脉
  16. 计算机车辆识别检测毕业设计,车辆识别论文,关于基于计算机网络技术的车辆识别技术相关参考文献资料-免费论文范文...
  17. 合肥二手房房价分析(多元线性回归)
  18. [人工智能-综述-10]:模型评估 - 常见的模型评估指标与方法大全、汇总
  19. POI使用公式的问题,POI对excel函数的支持
  20. 捕鱼达人(unity实现)

热门文章

  1. 极限与连续和可导的关系
  2. DO447使用过滤器和插件转换器--使用过滤器处理网络地址
  3. 记一次博客被日的分析过程
  4. 怎么做相册的图片音乐相册?音乐相册软件推荐。​
  5. 2009 Record Vol.1 两个人天堂|Our Heaven|
  6. 双重差分法|DID|PSM|平行趋势检验|安慰剂检验|Stata代码
  7. 体验ephemeral-storage特性来对Kubernetes中的应用做存储的限制和隔离
  8. 清华计算几何大作业思路分析和代码实现
  9. 单例模式,自定义cell加长版,对控件的圆润度设置还有另一种跳界面方式(很多界面)
  10. 我还是从前那个少年计算机版音乐,我还是从前那个少年