OS挑战性任务设计文档

\(\mathcal{Author:gpf}\)

OS仓库链接:solor-wind/BUAA_OS (github.com)

最好的指导书:YannaのBlog - 听铃,聆听,浮生清欢~ (yanna-zy.github.io)

指导书

使用vscode连接远程服务器

实现不带 .b 后缀指令

user/lib/spawn.c 中修改 spawn 函数,当直接执行失败时,再原参数后面加上".b"重新执行。

1
2
3
4
5
6
7
8
9
10
11
12
int fd;
if ((fd = open(prog, O_RDONLY)) < 0) {
int len=strlen(prog);
char myprog[len+2];
strcpy(myprog,prog);
myprog[len]='.';
myprog[len+1]='b';
myprog[len+2]='\0';
if ((fd = open(myprog, O_RDONLY)) < 0) {
return fd;
}
}

实现指令条件执行

在进程控制块中增加 env_return_value ,存储返回值,并编写相应系统调用来修改返回值。

1
2
3
4
5
6
7
int sys_env_set_return_value(u_int envid,int value,int isreceived){
struct Env* e;
try(envid2env(envid, &e, 0));
e->env_return_value=value;
e->env_isreceived=isreceived;
return 0;
}

user/lib/libos.c 中,将main函数返回值存储并送至父进程(spawn)的父进程(fork)处

1
2
int r=main(argc, argv);	
r=syscall_env_set_return_value(envs[ENVX(env->env_parent_id)].env_parent_id,r,1);

最后修改sh.c中的解析过程(以 && 为例),当前面指令执行为非0时,跳过后面的指令直至遇到 ||

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ch=gettoken(0, &t);
if (ch == '&') {
if(dupflag){
dup(0,1);
}else{
dup(1,0);
}
r=fork();
*rightpipe=r;
if(r==0)
{
return argc;
}
else
{
wait(r);
int value=env->env_return_value;
if(!value)
return parsecmd(argv, rightpipe,post);
else
{
close_all();
exit();
}
}
}

同时,对于管道指令,判断之后先存储argv[0]=t,并通过post表示递归调用argc时的初始值为1

实现更多指令

总体上,新建相应 .c 文件,而后在 user 目录下的 include.mk 中加上相应的 .b 文件即可,如下所示

1
2
3
4
USERAPPS     := num.b  \
touch.b \
mkdir.b \
rm.b \

touch

直接调用 open 函数即可,对返回值进行判断

1
2
3
4
f=open(argv[1],O_CREAT);
if(f<0){
printf("touch: cannot touch '%s': No such file or directory\n", argv[1]);
}

mkdir

类似touch,调用 open 函数,但需要在 fs/serv.c 中修改 serve_open 函数,当请求模式为 O_MKDIR 时,将文件的类型标记为目录。

1
2
3
4
5
6
7
8
if ((rq->req_omode & O_MKDIR) && (r = file_create(rq->req_path, &f)) < 0 &&
r != -E_FILE_EXISTS) {
ipc_send(envid, r, 0, 0);
return;
}
if(rq->req_omode & O_MKDIR){
f->f_type=FTYPE_DIR;
}

之后在mkdir中调用即可

rm

同样类似touch,但需要新增请求模式。

首先在 user/include/lib.h 中新增 #define O_TYPE 0x1000 ,表示对文件类型的查询。

其次在 user/lib/file.copen 函数中新增对模式的判断,如果是查询文件类型,则在ipc调用之后立刻返回。

1
2
3
4
r=fsipc_open(path,mode,fd);
if(mode&O_TYPE){
return r;
}

最后,也要在 fs/serv.c 中修改 serve_open 函数,当请求模式为 O_TYPE 时,找到文件后直接返回文件类型。

1
2
3
4
5
6
7
8
9
if ((rq->req_omode & O_TYPE) && (r = file_open(rq->req_path, &f)) < 0 &&
r != -E_FILE_EXISTS) {
ipc_send(envid, r, 0, 0);
return;
}
if(rq->req_omode & O_TYPE){
ipc_send(envid, f->f_type, 0, 0);
return;
}

实现注释

修改 user/sh.c 中的 main 函数,当 buf 中首次出现 # 时,相应位置赋值为为 '\0'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (r == 0) {
for(int i=0;buf[i];i++)
{
if(buf[i]=='#')
{
buf[i]='\0';
break;
}
}
runcmd(buf);
exit();
} else {
wait(r);
}

实现历史指令

历史指令的存储

分为两部分,一部分为全局变量,另一部分保存在 .mosh_history 中,两部分的内容一样,但全局变量更容易存取。其中histcmd相当于循环链表,hist表示下一条指令应当写入到哪里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#define HISTFILESIZE 20
int hist;//该写哪条指令了
char histcmd[HISTFILESIZE][1024];

void wirte_history(){
int tmpflag=0;
for(int i=0;buf[i];i++){
if(buf[i]!='\t'||buf[i]!='\r'||buf[i]!='\n'){
tmpflag=1;
}
}
if(!tmpflag){
return;
}

int f=open(".mosh_history",O_RDONLY);
if(f<0){
f=open(".mosh_history",O_CREAT);
}
close(f);

if(hist<HISTFILESIZE&&histcmd[hist][0]=='\0'){//还没满
f=open(".mosh_history",O_RWR|O_WRONLY);
strcpy(histcmd[hist],buf);
int len=strlen(buf);
histcmd[hist][len]='\n';
histcmd[hist][len+1]='\0';
write(f,histcmd[hist],strlen(histcmd[hist]));
close(f);
hist++;
if(hist==HISTFILESIZE){
hist=0;
}
}
else{
strcpy(histcmd[hist],buf);
int len=strlen(buf);
histcmd[hist][len]='\n';
histcmd[hist][len+1]='\0';

f=open(".mosh_history",O_WRONLY|O_TRUNC);
hist++;
if(hist==HISTFILESIZE){
hist=0;
}
int flag=hist;
write(f,histcmd[hist],strlen(histcmd[hist]));
close(f);
f=open(".mosh_history",O_RWR|O_WRONLY);
while(1){
hist++;
if(hist==HISTFILESIZE){
hist=0;
}
if(hist==flag){
break;
}
write(f,histcmd[hist],strlen(histcmd[hist]));
}
}
lasthist=-1;
lastcmd[0]='\0';
}

实现up和down

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
if(i>=2&&buf[i-2]==27&&buf[i-1]==91&&buf[i]==65){
i-=2;
printf("%c%c%c",27,91,66);
for(int j=0;j<i;j++){
printf("\b \b");
}
if(lasthist==-1){
strcpy(lastcmd,buf);
lastcmd[i]='\0';
}
if(lasthist==-1){
if(hist==0){
if(histcmd[HISTFILESIZE-1][0]!='\0'){
lasthist=HISTFILESIZE-1;
}else{
lasthist=0;//已经是最前面的指令
}
}else{
lasthist=hist-1;
}
}else{
if(lasthist==0&&histcmd[HISTFILESIZE-1][0]=='\0'){
;//已经是最前面的指令
}else{
if(lasthist==hist){
;//已经是最前面的指令
}else{
lasthist--;
if(lasthist<0){
lasthist=HISTFILESIZE-1;
}
}
}
}
strcpy(buf,histcmd[lasthist]);
if(buf[strlen(buf)-1]=='\n'){
buf[strlen(buf)-1]='\0';
}
printf("%s", buf);
i=strlen(buf)-1;
continue;
}
else if(i>=2&&buf[i-2]==27&&buf[i-1]==91&&buf[i]==66){
i-=2;
//printf("%c%c%c",27,91,65);
for(int j=0;j<i;j++){
printf("\b \b");
}
if(lasthist==-1){
strcpy(lastcmd,buf);
lastcmd[i]='\0';
}
if(lasthist==-1){
lasthist==-2;
}else{
if((hist==0&&lasthist==HISTFILESIZE-1)||(hist&&lasthist==hist-1)){
lasthist=-2;
}else{
lasthist++;
if(lasthist>=HISTFILESIZE){
lasthist=0;
}
if(histcmd[lasthist][0]=='\0'){
lasthist=-2;
}
}
}
if(lasthist<0){
strcpy(buf,lastcmd);
}else{
strcpy(buf,histcmd[lasthist]);
}
if(buf[strlen(buf)-1]=='\n'){
buf[strlen(buf)-1]='\0';
}
printf("%s", buf);
i=strlen(buf)-1;
continue;
}

实现history

runcmd 函数中进行判断即可

1
2
3
4
5
6
7
if(strcmp(argv[0],"history")==0){
int f=open(".mosh_history",O_RDONLY);
char tmp[10240];
readn(f,tmp,10240);
printf("%s\n",tmp);
exit();
}

实现一行多指令

修改 user/sh.c 中的 parsecmd 函数即可

1
2
3
4
5
6
7
8
9
10
11
12
13
case ';':
r=fork();
*rightpipe=r;
if(r==0)
{
return argc;
}
else
{
wait(r);
return parsecmd(argv, rightpipe);
}
break;

实现反引号

sh.c_gettoken 函数中,首先创建管道,然后fork,让子进程执行反引号中的内容,并在父进程中通过管道读取结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
else if(*s=='`'){
char cmd[1024];
char cmdans[1024];
char after[1024];
for(int i=1;s[i];i++){
if(s[i]=='`'){
cmd[i-1]='\0';
strcpy(after,s+i+1);
break;
}else{
cmd[i-1]=s[i];
}
}
int p[2];
pipe(p);
int r=fork();
if(r==0)
{
dup(p[1],1);
close(p[0]);
close(p[1]);
runcmd(cmd);
exit();
}
else
{
close(p[1]);
int len,pos=0;
char tmp[1024];
while((len=read(p[0],tmp,1024))>0){
tmp[len]='\0';
strcpy(cmdans+pos,tmp);
pos+=len;
}
close(p[0]);
}
strcpy(s,cmdans);
strcpy(s+strlen(cmdans),after);
s+=strlen(cmdans)-1;
}

实现重定向

首先在 user/include/lib.h 中新增 #define O_RWR 0x2000 ,表示重定向写入。

其次在 fs/serv.cserv_open 函数中新增对模式的判断,如果是重定向写入,则修改offset。

1
2
3
if(rq->req_omode & O_RWR){
ff->f_fd.fd_offset =ff->f_file.f_size;
}

最后在 sh.cparsecmd 中修改对 > 的处理

1
2
3
4
5
6
7
8
9
10
if(ch=='>'){
ch=gettoken(0, &t);
if (ch != 'w') {
debugf("syntax error: > not followed by word\n");
exit();
}
fd=open(t,O_RWR|O_WRONLY|O_CREAT);
}else{
fd=open(t,O_WRONLY|O_TRUNC|O_CREAT);
}

实现引号支持

sh.c_gettoken 函数中的第一个取空白代码段后面添加关于字符串的处理即可,即遇到引号则读到下一个引号为止,并返回 w 的token

1
2
3
4
5
6
7
8
9
10
11
if(*s=='\"'){
*s++=0;
*p1 = s;
while (*s && *s!='\"') {
//printf("%c",*s);
s++;
}
*s++=0;
*p2 = s;
return 'w';
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if(*s=='\"'){
char tmp[1024];
strcpy(tmp,s+1);
int i=0,j=0,flag=0;
for(;tmp[j];j++){
if(tmp[j]=='\"'&&!flag){
flag=j;
continue;
}else{
s[i++]=tmp[j];
}
}
s[i]='\0';
s+=flag-1;
}

实现前后台任务管理

env.h 文件中,新增宏定义、结构体定义、系统调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define MAXJOB 1024
#define Running 0
#define Done 1

struct Job
{
int job_id;
int status;
int env_id;
char cmd[1025];
};

int add_job(int envid,char cmd[]);
int set_job_status(int job_id,int status);
int get_job(int envid);
int get_job_envid(int job_id);
int get_jobs(struct Job usrjobs[]);
int print_jobs();

env.h 文件中,新增任务结构体、与系统调用实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
struct Job jobs[MAXJOB+1];
int job_cnt=0;

int add_job(int envid,char cmd[]){
if(job_cnt>=MAXJOB){
return -1;
}
job_cnt++;
jobs[job_cnt].job_id=job_cnt;
jobs[job_cnt].env_id=envid;
jobs[job_cnt].status=Running;
strcpy(jobs[job_cnt].cmd,cmd);
return 0;
}

int set_job_status(int job_id,int status){
if(job_id>0&&job_id<=job_cnt){
jobs[job_id].status=status;
return 0;
}else{
return -1;
}
}

int get_job(int envid){
for(int i=1;i<=job_cnt;i++){
if(jobs[i].env_id==envid){
return i;
}
}
return -1;
}

int get_job_envid(int job_id){
if(job_id>0&&job_id<=job_cnt){
return jobs[job_id].env_id;
}else{
return -1;
}
}

int get_jobs(struct Job usrjobs[]){

memcpy(usrjobs,jobs,sizeof(jobs));

return job_cnt;
}

int print_jobs(){
for(int i=1;i<=job_cnt;i++){
if(jobs[i].status==Running)
printk("[%d] %-10s 0x%08x %s\n", jobs[i].job_id, "Running", jobs[i].env_id, jobs[i].cmd);
else
printk("[%d] %-10s 0x%08x %s\n", jobs[i].job_id, "Done", jobs[i].env_id, jobs[i].cmd);
}
return 0;
}

syscall.h syscall_all.c syscall_lib.c lib.h 文件中,新增系统调用相关

kill的实现:

1
2
3
4
5
6
7
8
9
int sys_mykill(u_int envid) {
struct Env *e;
try(envid2env(envid, &e, 0));//不同

printk("[%08x] destroying %08x\n", curenv->env_id, e->env_id);
env_destroy(e);
return 0;
//return mykill(envid);
}

add_job与标记完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//sh.c,runcmd
if(child>0&&*post){
char tmp[1024];
int len=0;
for(int i=0;i<argc;i++){
strcpy(tmp+len,argv[i]);
len+=strlen(argv[i]);
tmp[len++]=' ';
tmp[len]='\0';
}
syscall_add_job(child,tmp);
}

//libos.c
int job_id=syscall_get_job(env->env_id);
if(job_id>0){
syscall_set_job_status(job_id,Done);
}

jobs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
else if(strcmp(argv[0],"jobs")==0){
syscall_print_jobs();
close_all();
exit();
}}else if(strcmp(argv[0],"fg")==0){
int job_id=0;
for(int i=0;argv[1][i];i++){
if(argv[1][i]>='0'&&argv[1][i]<='9'){
job_id*=10;
job_id+=argv[1][i]-'0';
}
}
int envid=syscall_get_job_envid(job_id);
if(envid!=-1){
if(syscall_get_job_status(job_id)!=Running){
printf("fg: (0x%08x) not running\n", envid);
}else{
wait(envid);
}
}else{
printf("fg: job (%d) do not exist\n", job_id);
}
close_all();
exit();
}else if(strcmp(argv[0],"kill")==0){
int job_id=0;
for(int i=0;argv[1][i];i++){
if(argv[1][i]>='0'&&argv[1][i]<='9'){
job_id*=10;
job_id+=argv[1][i]-'0';
}
}
int envid=syscall_get_job_envid(job_id);
if(envid!=-1){
if(syscall_get_job_status(job_id)!=Running){
printf("fg: (0x%08x) not running\n", envid);
}else{
syscall_set_job_status(job_id,Done);
syscall_mykill(envid);
}
}else{
printf("fg: job (%d) do not exist\n", job_id);
}
close_all();
exit();
}

其他

syscall_all.c

1
2
3
4
5
6
7
int sys_cgetc(void) {
int ch;
while ((ch = scancharc()) == 0) {
break;//新增,取消等待指令时的忙等
}
return ch;
}

runcmd用int* post,表示是否为后台指令


OS挑战性任务设计文档
https://solor-wind.github.io/2024/07/02/shell-challenge设计文档/
作者
gpf
发布于
2024年7月2日
许可协议