用C语言写一个shell项目附源码||操作系统实验课

咱就是说,已经麻了,🍐巨大

现在就是非常紧张加焦虑,因为做这个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. #define USED 0x1
  5. #define IS_TASK 0x2
  6. #define IS_SYSTEM 0x4
  7. #define BLOCKED 0x8
  8. #define PSINFO_VERSION 0
  9. #define STATE_RUN ‘R’
  10. const char *cputimenames[] = {“user”, “ipc”, “kernelcall”};
  11. #define CPUTIMENAMES ((sizeof(cputimenames)) / (sizeof(cputimenames[0]))) //恒等于3
  12. #define CPUTIME(m, i) (m & (1 << (i))) //保留第几位
  13. char prompt[] = "myshell> ";//这个表示已经运行起了
  14. char history[M][M];//这是历史命令的输入
  15. int n_his = 0;
  16. char *path = NULL;
  17. unsigned int nr_procs, nr_tasks;
  18. int slot = -1;
  19. 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。希望以后可以在实验过程中提高自学能力,完成度更高。

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值