用C语言写一个shell项目附源码||操作系统实验课 青旅半醒 2024-05-11 09:16 25阅读 0赞 ## 咱就是说,已经麻了,?巨大 ## 现在就是非常紧张加焦虑,因为做这个shell 的ddl真的快了,但是我还是不咋懂怎么用C语言写出来,像只无头苍蝇一样就很焦虑。 ### 写在前面 ### #### 以下是我的shell实验报告,希望对大家有用。有很多不会的地方借鉴了大神的代码,再次感谢!完整代码详见https://github.com/DannieZhai/OperateSystem\_Project/tree/main #### 分隔线。。。。。。 -------------------- Shell是一个用C语言编写的程序,是用户使用Linux系统的桥梁。这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。实际上,shell是一个命令解释器,它解释由用户输入的命令并且把它们送到内核。不仅如此,shell也有自己的编程语言,但是我们的任务是用熟悉的C语言来实现一个拥有基础功能的shell。 ## 一、实验目的 ## 使用C语言实现一个具有基本功能的shell,并在虚拟机上成功运行,了解其基本原理。 ## 二、实验要求 ## 本实验要求实现以下几个基本功能: 1、能解析带参数的程序并运行; 2、实现工作路径移动cd命令; 3、实现退出命令exit; 4、显示最近执行的指令history; 5、实现后台运行功能; 6、实现管道功能; 7、实现重定向功能; ## 三、实验环境安装 ## 我们所使用的Windows或者iOS系统都是较为常用的系统,我们的机器也因此称为物理机;但是对于本课程实验而言,需要Linux操作系统,所以安装VMware虚拟机。 1、下载VMware正版软件; 2、下载Minux3.3镜像文件; 3、在VMware中配置虚拟机,修改一些参数; 4、新建虚拟机,设置用户名和密码,密码需牢记; 5、安装开发环境,如编译器、编辑器、链接库等等; 6、若是虚拟机开启较为繁琐,可以采用MobaXterm远程访问,测试代码; 7、通过FileZilla软件将写好的shell传输到虚拟机中去。 ## 四、实验描述 ## ### 1、实验所需的宏定义和全局变量为以下内容: ### 1. \#define MAXLINE 1024 2. \#define MAXARGS 128 //命令行最大参数数量 3. \#define M 256 4. 5. \#define USED 0x1 6. \#define IS\_TASK 0x2 7. \#define IS\_SYSTEM 0x4 8. \#define BLOCKED 0x8 9. \#define PSINFO\_VERSION 0 10. 11. \#define STATE\_RUN ‘R’ 12. const char \*cputimenames\[\] = \{“user”, “ipc”, “kernelcall”\}; 13. \#define CPUTIMENAMES ((sizeof(cputimenames)) / (sizeof(cputimenames\[0\]))) //恒等于3 14. \#define CPUTIME(m, i) (m & (1 << (i))) //保留第几位 15. 16. char prompt\[\] = "myshell> ";//这个表示已经运行起了 17. char history\[M\]\[M\];//这是历史命令的输入 18. int n\_his = 0; 19. char \*path = NULL; 20. unsigned int nr\_procs, nr\_tasks; 21. int slot = -1; 22. int nr\_total; ### 2、实验所用函数列出如下: ### 1. //封装好的fork函数 2. pid_t Fork(void); 3. //实现内置命令、program命令和后台运行等功能 4. void exeCommand(char *cmdline); 5. //解析命令行解析命令行,得到参数序列,并判断是前台作业还是后台作业 6. int parseline(const char *cmdline, char **argv); 7. //读取/proc/kinfo得到总的进程和任务数num_total 8. void getkinfo(); 9. //在/proc/meminfo中查看内存信息,计算出内存大小并打印 10. int print_memory(); 11. //计算总体CPU使用占比并打印结果 12. void print_procs(struct proc *proc1, struct proc *proc2, int cputimemode); 13. //计算cputicks,CPU的频率 14. u64_t cputicks(struct proc *p1, struct proc *p2, int timemode); 15. void get_procs(); 16. //读取目录下每一个文件信息 17. void parse_dir(); 18. //在/proc/pid/psinfo中,查看进程pid的信息 19. void parse_file(pid_t pid); 20. //内置命令实现函数 21. int builtin_cmd(char **argv); 22. //实现管道,完成进程间的通信 ## 五、实验中函数解析: ## ### 1、shell内置命令 ### #### 1)改变路径的cd命令: #### Cd命令是一个shell的基本命令,首先,输入cd命令;其次,采用chdir()函数改变工作目录;然后,利用getcwd()函数得到当前的目录。 我的思想如下: 1. 1. if (!strcmp(argv[0], "cd")) 2. { 3. if (!argv[1]) 4. { 5. argv[1] = "."; 6. } 7. int ret; 8. ret = chdir(argv[1]); //改变工作目录 9. if (ret < 0) 10. { 11. printf("No such directory!\n"); 12. } 13. else 14. { 15. path = getcwd(NULL, 0); //利用getcwd取当前所在目录 16. } 17. return 1; 18. } #### 2)显示历史命令行的history命令: #### 怎样才能显示历史命令呢?需要先保存我们所有输入的命令。由于shell是一个while大循环,我将采用二维数组的方式记录,二维数组也就相当于一个指针。 首先,如果用户只输入“history”,打印所有命令,将数组的元素一一打印;如果用户输入“history加数字”,我们就打印所需要的指令。最后,当然避免不了错误,就直接打印error即可。 1. if (!strcmp(argv[0], "history")) 2. { 3. if (!argv[1]) 4. { //当只输入history时,打印已有的所有指令 5. for (int j = 1; j <= n_his; j++) 6. { 7. printf("%d ", j); 8. puts(history[j - 1]); 9. } 10. } 11. else 12. { 13. int t = atoi(argv[1]); 14. if (n_his - t < 0) 15. { //如果history后未带参数或带的参数大于已有指令数 16. printf("history error\n"); 17. } 18. else 19. { 20. for (int j = n_his - t; j < n_his; j++) 21. { 22. printf("%d ", j + 1); 23. puts(history[j]); 24. } 25. } 26. } 27. return 1; 28. } #### 3)遇到错误的exit命令: #### 此命令较为简单,当用户输入的命令就是“exit”时,我们就结束这个shell。 1. if (!strcmp(argv[0], "exit")) 2. { //strcmp若两个字符串相等则返回0 3. exit(0); 4. } #### 4)显示信息的mytop命令: #### 首先,利用getkinfo()函数读取总的进程数和任务数;其次,采用print\_memory()函数查看内存信息;最后,就可以利用已知信息,计算总体cpu使用占比并且打印结果了。 1. if (!strcmp(argv[0], "mytop")) 2. { 3. int cputimemode = 1;//计算CPU的时钟周期 4. getkinfo(); 5. print_memory(); 6. //得到prev_proc 7. get_procs(); 8. if (prev_proc == NULL) 9. { 10. get_procs();//得到proc 11. } 12. print_procs(prev_proc, proc, cputimemode); 13. return 1; 14. } 15. return 0; 因为之前对mytop的命令不太熟悉,所以借鉴了提示中的思路,比如:查看内存信息、查看总的进程数量、得到关于进程的有关信息等等。 ### 2、program命令 ### #### 1) 实现重定向功能 #### 重定向功能是指改变数据的所在位置,箭头的方向就是数据的流向。具体分类有输入重定向、输出重定向、追加输入输出重定向、错误的输入输出重定向等等。 输出重定向: 1. case 1: //(>输出用的多) 2. pid = Fork(); 3. if (pid == 0) 4. { 5. fd = open(file, O_RDWR | O_CREAT | O_TRUNC, 0644);//得到file(>后)的文件描述符好!! 6. if (fd == -1) 7. { 8. printf("open %s error!\n", file); 9. } 10. dup2(fd, 1); //将结果输出到file,因此映射为标准输出1标准输出 11. close(fd); 12. execvp(argv[0], argv);//执行前半部分指令 13. exit(0); 14. } 15. if (waitpid(pid, &status, 0) == -1) 16. { 17. printf("wait for child process error\n"); 18. } 19. break; 输入重定向: 1. case 2: //(<) 输入重定向 2. pid = Fork(); 3. if (pid == 0) 4. { 5. fd = open(file, O_RDONLY); 6. dup2(fd, 0); //0标准输入,隐射到标准输入 7. close(fd); 8. execvp(argv[0], argv); 9. exit(0); 10. } 11. if (waitpid(pid, &status, 0) == -1) 12. { 13. printf("wait for child process error\n"); 14. } 15. //对于参数pid,》0回收指定id的子进程,-1回收任意子进程 16. //0回收和当前调用waitpid一个组的所有子进程 17. //《-1回收指定进程组内的任意子进程 #### 2) 实现管道功能 #### 所谓管道功能,就是说把前一个进程的输出结果作为后一个进程的输入。 1. void pipeline(char *process1[],char *process2[]){ 2. int fd[2]; 3. pipe(&fd[0]); 4. int status; 5. pid_t pid; 6. pid=Fork(); 7. if(pid==0){ 8. close(fd[0]); 9. close(1); 10. dup(fd[1]);//fd[1]管道写入端,映射到标准输出1 11. close(fd[1]); 12. execvp(process1[0],process1);//执行前部分指令,结果输出到管道 13. }else{ 14. close(fd[1]); 15. close(0); 16. dup(fd[0]);//fd[0]管道读入端,映射到标准输入0 17. close(fd[0]); 18. //waitpid(pid,&status,0);//等待子进程结束,管道中有内容了,再执行 19. execvp(process2[0],process2);//从管道读入 20. } 21. } #### 3) 实现后台运行命令 #### &符号表示先将输入的命令挂起,在后台运行。 1. case 4: //(&后台运行) 2. pid = Fork(); 3. signal(SIGCHLD, SIG_IGN); 4. if (pid == 0) 5. { 6. signal(SIGCHLD, SIG_IGN); 7. //如果将此信号的处理方式设为忽略, 8. //可让内核把僵尸子进程转交给init进程去处理 9. // /dev/null 10. //黑洞black hole通常被用于丢弃不需要的输出流,或作为用于输入流的空文件 11. close(0); 12. open("/dev/null",O_RDONLY); 13. close(1); 14. open("/dev/null",O_WRONLY); 15. execvp(argv[0], argv); 16. exit(0); 17. } 18. //后台运行,不用等待结束 19. break; 20. default: 21. break; ## 六、实验反思与总结 ## 此次实验是让我们自己编写一个shell,刚开始确实觉得难度很大,但是随着一点点做起来,还是收获良多。其中借阅了很多资料,问了同学很多问题,总算实现了自己的shell。希望以后可以在实验过程中提高自学能力,完成度更高。
还没有评论,来说两句吧...