咱就是说,已经麻了,🍐巨大
现在就是非常紧张加焦虑,因为做这个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、实验所需的宏定义和全局变量为以下内容:
- #define MAXLINE 1024
- #define MAXARGS 128 //命令行最大参数数量
- #define M 256
- #define USED 0x1
- #define IS_TASK 0x2
- #define IS_SYSTEM 0x4
- #define BLOCKED 0x8
- #define PSINFO_VERSION 0
- #define STATE_RUN ‘R’
- const char *cputimenames[] = {“user”, “ipc”, “kernelcall”};
- #define CPUTIMENAMES ((sizeof(cputimenames)) / (sizeof(cputimenames[0]))) //恒等于3
- #define CPUTIME(m, i) (m & (1 << (i))) //保留第几位
- char prompt[] = "myshell> ";//这个表示已经运行起了
- char history[M][M];//这是历史命令的输入
- int n_his = 0;
- char *path = NULL;
- unsigned int nr_procs, nr_tasks;
- int slot = -1;
- 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。希望以后可以在实验过程中提高自学能力,完成度更高。