shell挑战性任务


前言:项目代码在slkfoiw/BUAA-OS-lab (github.com)的master分支,有些地方设计得可能不是很完美,也可能存在错误,欢迎批评指正

实现不带.b指令

在spawn函数里,当尝试打开文件(文件名存储在prog中)失败时,在prog字符串后面添加“.b”后再尝试打开一次

实现指令条件执行

step1:在gettoken里添加检测条件运算符”&&“,”||“的部分

step2:在parsecmd中处理指令:跟管道类似,需要调用fork分别处理运算符两边的两条指令,不同的是”&&“,”||“两边的指令不一定都要执行,而且右边的指令需要等左边的指令处理完,得到返回值才能确定是否处理,所以我在parsecmd函数添加了两个参数:

  • is_executable:用来记录当前处理的指令能否执行
  • return_value:用来记录处理到目前这条指令时,整个式子的返回值,有三种可能,我用宏定义如下:
//前面的指令还无法确定返回值,如cmd1 && cmd2 || cmd3 的cmd1执行成功,当前在处理cmd2
#define UNCERTAIN -1
//前面的指令已经能确定返回值
#define CERTAIN0 0
#define CERTAIN1 1

当is_executable==1时,return_value一定是UNCERTAIN;当is_executable==0时,return_value一定是CERTAIN0或CERTAIN1

fork出的child处理左边的指令

  • 若能执行(is_executable==1),执行完后用ipc发送返回值给parent进程
  • 若不能执行(is_executable==0),说明返回值已确定,当前指令不需要执行,直接通过ipc发送return_value给parent进程

parent进程处理右边的指令

  • 接收child发来的返回值,并等待child进程结束
  • 根据&&、||的不同设置 is_executable, return_value的值,然后继续调用parsecmd解析指令

实现更多指令

首先建三个文件touch.c、mkdir.c、rm.c,然后修改include.mk里,使它们能编译成可执行文件

touch:创建很简单,open一下,权限设置为O_CREAT就行。主要复杂在错误处理。判断文件所在目录是否存在,解析文件路径,找到最后一个”/“,”/“之前的部分就是目录,用open判断目录是否存在

mkdir:

修改serve_open函数,加入对O_MKDIR权限的处理

修改file_create函数,添加一个type参数,用来给文件的f_type字段赋值

rm:核心就是调用remove。不过需要判断是否为目录,这里要用到stat函数

实现反引号

gettoken里添加检测”`”的部分

parsecmd里添加处理“`”的部分,需要设置一个全局标记flag来判断是左反引号还是右反引号

实现注释功能

gettoken里添加检测”#”的部分

parsecmd里添加处理“#”的部分,后面的不要,直接开始处理指令就行了

实现历史指令

最耗时的指令之一,首先需要注意history是内置指令,也就是说不要新建一个history.c文件,然后调用spawn去fork出一个新进程处理。实现过程中需要注意:避免不同进程修改 跟history相关的数据 导致数据不一致。

跟history指令有关的函数如下(都在sh.c中):

void history_init();//在shell进程开始时初始化跟history相关的数据,如打开.mosh_history文件
void save_history();//将每次输入的指令保存到.mosh_history文件
void add_one_history(const char *command);//向历史记录里加入一条指令
int get_last_history(char *oldbuf, char *newbuf);//获得上一条指令
int get_next_history(char *newbuf);//获得下一条执行
void history();//执行history指令

注意凡是要修改数据的函数都需要在main所在进程里调用

实现一行多指令

跟管道指令类似,fork一下就行啦,不用创建管道了。

实现追加重定向

parsecmd里在读到一个“>”后加一个判断,后面一个token是否也为”>”

修改open函数实现追加功能,主要是要修改offset。

实现引号支持

修改gettoken函数,读到左边引号时一直读到下去,直到读到右边引号才能结束。我还加了一些异常处理,防止输入错误

实现前后台任务管理

另一个实现起来超级耗时的指令。同样,因为都是内置指令,不需要新建文件、新建进程。但是我还是在runcmd进程里处理的(因为需要解析指令参数),然后通过exit传回值,在main进程里修改与jobs相关的数据。

主要函数如下(都在sh.c中):

void add_one_job(int env_id, char *cmd);//添加一条后台指令
void update_jobs_status();//更新后台指令的状态
void update_jobs();//由于挑战性任务跟实际Linux运行不太一样,不需要删除已经结束的指令记录,所以这个函数没用
void print_all_jobs();//打印所有后台指令
int fg_job(int job_id);//执行fg指令
int kill_job(int job_id);//执行kill指令

Author: CuberSugar
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source CuberSugar !
  TOC