OS Challenge

OS Challenge

rainbowYao Lv3

shell增强文档

下面将按照功能的增加来阐述自己的实现思路

实现不带.b后缀指令

实现思路

user/lib/spawn.cspawn函数中增加判断:若原始的路径打不来,则尝试打开原始的路径 + .b。先判断再加 .b 可以让带 .b 后缀的可执行文件不带 .b 执行,同时也支持其他格式的可执行文件

实现指令条件执行

实现思路

  1. 首先我对user/lib/libos.cuser/libn/wait.c中的exitwait做了处理,使他们可以利用ipc机制处理返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int wait_return(u_int envid) {
const volatile struct Env *e;
e = &envs[ENVX(envid)];
u_int who = 0;
int status = ipc_recv(&who, 0, 0);
if (who == envid) {
while (e->env_id == envid && e->env_status != ENV_FREE) {
syscall_yield();
}
return status;
}
return -1;
}

void exit(int status, int need) {
close_all();
if (need) {
env = &envs[ENVX(syscall_getenvid())];
ipc_send(env->env_parent_id, status, 0, 0);
}
syscall_env_destroy(0);
user_panic("unreachable code");
}

目前就有两套wait——exit进行父子进程的等待与销毁:

  • 子进程exit(0,0)配合父进程wait,子进程不传递返回值

  • 子进程exit(status,1)配合父进程wait_return,子进程传递返回值status

那么这个返回值的过程只需要发生在spawn出的子进程与runcmd中的父进程即可,则两套wait——exit需要考虑具体用哪个(这里的实现可拓展性太差,应该统一到一起的,我太懒了),值得一提的是spawn出的子进程运行在user/lib/libos.c中的libmain函数中

  1. 既然可以处理返回值,那么在user/sh.c中就要更改**_gettokenparsecmdruncmd函数,似的可以解析&& ||符号,处理&& ||符号时,我的处理是让子进程返回运行已解析的指令,父进程等待返回值后递归调用parsecmd**去处理,依靠函数调用传参来保存logic_op, last_logic_op, status等参数

测试

1
2
3
4
5
6
7
8
9
true && ls
true || ls
false && ls
false || ls
true && true && false || true && true && ls || ls && ls && ls || ls || ls
true && true && true && true && true && ls
false || false || false || false || false || ls
true && true && false || true && true && ls
false || false || true && false || ls

实现更多指令

实现思路

三个指令依次实现在各自的文件下

  • mkdir需要增加文件系统中对于权限位O_MKDIR的实现,对于指令参数的实现也可以参考已经给出的ls等指令的实现,利用ARGBEGIN宏与flag数组
  • touch利用O_RDONLY、O_CREAT调用已有的open接口即可
  • rm我使用stat接口来判断路径是文件还是目录

实现反引号

实现思路

这里的实现思路是在user/sh.c的**_gettoken中解析到``的时候读取反引号内的内容,然后开一个管道且创建一个子进程,子进程拿到管道的写端,调用runcmd**执行反引号的内容;父进程则拿到管道的读端,将从管道中读到的内容封装成一个字符串参数,之后返回w

其中有一些小细节,如不要让管道堵塞,父进程不能wait子进程执行结束再读,而是参考管道的实现,二者并行;再者需要更改gettoken函数,使其可以保存上面讲到父进程读出的字符串参数

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
//_gettoken	
if (*s == '`') {
char *start = ++s;
while (*s && *s != '`') s++;
if (*s == '`') {
*s++ = 0;
int p[2];
pipe(p);
int child = fork();
if (child == 0) {
dup(p[1], 1);
close(p[1]);
close(p[0]);
runcmd(start);
exit(0, 0);
} else {
close(p[1]);
char buffer[1024];
int n = 0;
while(1) {
int bytes_read = read(p[0], buffer + n, sizeof(buffer) - n - 1);
if (bytes_read <= 0) {
break;
}
n += bytes_read;
}
buffer[n] = 0;
close(p[0]);
wait(child);
memcpy(buf, buffer, strlen(buffer) + 1);
*p1 = 0;
*p2 = s;
return 'w';
}
}
}

int gettoken(char *s, char **p1) {
static int c, nc;
static char *np1, *np2;
static char buffer[1024];

if (s) {
nc = _gettoken(s, &np1, &np2, &buffer);
return 0;
}
c = nc;
if (np1 == 0) {
*p1 = buffer;
} else {
*p1 = np1;
}

nc = _gettoken(np2, &np1, &np2, &buffer);
return c;
}

实现注释功能

实现思路

最简单的功能,parsecmd中读到#后返回argc即可

实现历史指令

实现思路

类似于后台的实现,由于甚至不需要进程间共享,只需要在sh进程中处理即可,故此处不做过多赘述,可以参考后面后台的实现

实现一行多指令

实现思路

user/sh.cparsecmd函数中增加对于;的处理,开一个子进程返回处理当前已经解析出来的指令,让父进程等待子进程结束后继续解析

1
2
3
4
5
6
7
8
9
case ';':;
left = fork();
if (left > 0) {
wait(left);
return parsecmd(argv, rightpipe);
} else {
return argc;
}
break;

实现追加重定向

实现思路

新增了一个文件权限O_APPEND,只需要在打开文件的时候将文件的偏移量移动到文件末尾即可,追加重定向时用此权限位打开文件

1
2
3
4
//user/lib/file.c	
if (fd->fd_omode & O_APPEND) {
offset = f->f_file.f_size;
}

实现引号支持

实现思路

实现较为简单,只需要在**_gettoken**解析时候将引号内内容看成整体即可

实现前后台任务管理

实现思路

这里的重点在于需要有一共享内存区域供所有的进程访问和修改,故有两种处理方式:在内核态中通过系统调用来处理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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// job.c
#define JOB_PATH "jobfile"
#define MAXJOBS 64

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

struct Job jobs[MAXJOBS];
int next_job_id;
int job_count;

void init_job_data() {
int fd = open(JOB_PATH, O_CREAT | O_RDWR);
if (fd < 0) {
debugf("open jobfile fail\n");
return;
}
debugf("jobfile fd : %d\n", fd);
next_job_id = 1;
job_count = 0;
int next_job_id_tmp;
int job_count_tmp;
write(fd, &next_job_id, sizeof(int));
write(fd, &job_count, sizeof(int));
seek(fd, 0);
read(fd, &next_job_id_tmp, sizeof(int));
read(fd, &job_count_tmp, sizeof(int));
if (job_count == job_count_tmp && next_job_id == next_job_id_tmp) {
debugf("job_data init success!\n");
} else {
debugf("read and write init fail\n");
}
close(fd);
}

void read_job_data() {
int fd = open(JOB_PATH, O_RDONLY);
if (fd < 0) {
debugf("Failed to open job data file for writing\n");
return;
}
read(fd, &next_job_id, sizeof(int));
read(fd, &job_count, sizeof(int));
read(fd, jobs, sizeof(jobs));
close(fd);
}

void save_job_data() {
int fd = open(JOB_PATH, O_WRONLY);
if (fd < 0) {
debugf("Failed to open job data file for writing\n");
return;
}
write(fd, &next_job_id, sizeof(int));
write(fd, &job_count, sizeof(int));
write(fd, jobs, sizeof(jobs));
close(fd);
}

void add_job(int env_id, char *cmd);
void update_job_status(int env_id, const char *status);
void print_jobs();
void bring_job_to_foreground(int job_id);
void kill_job(int job_id);

调用以上函数,在runcmd中对内置指令进行处理即可

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
if (strcmp(argv[0], "jobs") == 0) {
print_jobs();
exit(0, 0);
}

if (strcmp(argv[0], "fg") == 0) {
if (argc < 2) {
printf("fg: job id required\n");
exit(0, 0);
}
int job_id = my_atoi(argv[1]);
bring_job_to_foreground(job_id);
exit(0, 0);
}

if (strcmp(argv[0], "kill") == 0) {
if (argc < 2) {
printf("kill: job id required\n");
return;
}
int job_id = my_atoi(argv[1]);
kill_job(job_id);
return;
}

if (background) {
add_job(child, buffer);
int status = wait_return(child);
update_job_status(child, "Done");
}

同时注意sh进程不等待fork出的子进程调用runcmd,子进程等待spawn出的进程结束后再销毁

  • 标题: OS Challenge
  • 作者: rainbowYao
  • 创建于 : 2024-07-02 21:26:56
  • 更新于 : 2024-09-18 09:28:40
  • 链接: https://redefine.ohevan.com/2024/07/02/OS-Challenge/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。