OS-lab6-challenge

lab6挑战性任务

重要声明:不保证里面的代码完全正确,只是提供一个参考思路,可能会有微小的bug,所以一定要理解了自己写,禁止直接复制

与任务无关的修改

1 .

image-20220613160723831

在spawn中如下修改

与任务无关,只是为了方便测试,这样敲命令的时候就不用加后面的.b

2 怎么添加指令

如何把文件放进我们的磁盘?

这个需要查看构建磁盘镜像的Makefile

找到这一段,把你自己写的程序放进去就行了

(当然也要在user/Makefile里面添加

image-20220613161050262

因此,要添加一条指令有这几步

  1. 在user里面写这个指令的主函数(参考 user/ls.c
  2. 在user/Makefile里面添加
  3. 在 fs/Makefile里面添加

touch、mkdir

这两个比较相似,一并加了

创建文件,但是我们的文件系统还没有创建文件的用户接口,需要自己写一个

  1. 创建文件的时候 ipc 要传递什么? 路径、文件类型

    在 include/fs.h 中写一个结构体:

    1
    2
    3
    4
    5
    6
    7
    struct Fsreq_create {
    u_char req_path[MAXPATHLEN];
    int type;
    };

    // 再写一个宏
    #define FSREQ_CREATE 8
  2. 文件系统端, 实现 fs/serv.c/serve_create 函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    void serve_create(u_int envid, struct Fsreq_create *rq)
    {
    writef("serve_create: %s\n", rq->req_path);
    int r;
    char *path = rq->req_path;
    struct File *file;
    // file_create(char *path, struct File **file)
    if((r = file_create(path, &file)) < 0)
    {
    ipc_send(envid, r, 0, 0);
    return;
    }
    // file_create里面没有设置文件,在这里要设置一下
    file->f_type = rq->type;
    // 返回0 表示成功
    ipc_send(envid, 0, 0, 0);
    }
  3. 在 fs/serv.c/serve 中

    1
    2
    3
    case FSREQ_CREATE:
    serve_create(whom, (struct Fsreq_create *)REQVA);
    break;
  4. 用户态部分 实现file.c/create 函数

    1
    2
    3
    4
    int create(const char* path, int type)
    {
    return fsipc_create(path, type);
    }
  5. fsipc.c/fsipc_create

    1
    2
    3
    4
    5
    6
    7
    8
    int fsipc_create(const char* path, int type)
    {
    struct Fsreq_create *req;
    req = (struct Fsreq_create*) fsipcbuf;
    req->type = type;
    strcpy((char*) req->req_path, path);
    return fsipc(FSREQ_CREATE, req, 0, 0);
    }

实现上面的辅助函数之后,就可以整这两个指令了

user/touch.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# include "lib.h"

void umain(int argc, char ** argv)
{
if(argc != 2) // check the number of arg
{
fwritef(1, "usage: touch [filename]\n");
return;
}
int fd = open(argv[1], O_RDONLY);
if(fd >= 0)
{
fwritef(1, "file %s exists\n", argv[1]);
return;
}
if(create(argv[1], FTYPE_REG) < 0)
{
fwritef(1, "failed to create!\n");
}
}

user/mkdir

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# include "lib.h"

void umain(int argc, char ** argv)
{
if(argc != 2) // check the number of arg
{
fwritef(1,"usage: mkdir [filename]\n");
return;
}
int fd = open(argv[1], O_RDONLY);
if(fd >= 0)
{
fwritef(1, "file %s exists\n", argv[1]);
return;
}
if(create(argv[1], FTYPE_DIR) < 0)
{
fwritef(1, "failed to create!\n");
}
}

然后再完成 2. 中的另外两步,make 就ok了

上面这两个程序还有一个小细节,就是输出的时候用的是 fwritef 而不是 writef 。因为如果使用 writef 直接输出的话,是不能重定向的,这就导致这些指令和管道、>等一起使用的时候会失效,所以在这里我们统一用 fwritef

完成!

tree 指令

需要熟悉文件系统,这就是个C语言程序设计题,递归获取文件目录

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
#include "lib.h"

void gettree(int depth, char *path);

void umain(int argc ,char** argv)
{
char *dirname;

if(argc > 2)
{
fwritef(1, "usage: tree [dir]\n");
exit();
}
dirname = (argc == 1) ? "/" : argv[1];
gettree(0, dirname);
}

void print_tab(int num)
{
int i;
for(i = 0; i < num; i++)
{
fwritef(1, " ");
}
}


// ensure that path is a path of dir
void gettree(int depth, char *path)
{
struct Fd* fd;
struct Filefd* fileFd;
int i;

int r = open(path, O_RDONLY);
if(r < 0) return;

fd = (struct Fd*)num2fd(r);
fileFd = (struct FileFd*) fd;
u_int size = fileFd->f_file.f_size;
// print_tab(depth);
fwritef(1, "\x1b[34m%s\x1b[0m\n", fileFd->f_file.f_name);

u_int num = ROUND(size, sizeof(struct File)) / sizeof(struct File);
struct File *file = (struct File *) fd2data(fd);
/*
dir
|---dir1
|---dir2
|---dir3
*/

for(i = 0; i < num; i++)
{
if(file[i].f_name[0] == '\0') continue;
print_tab(depth);
fwritef(1, "|---");
if (file[i].f_type == FTYPE_DIR)
{
char newpath[MAXPATHLEN];
// tree
strcpy(newpath, path);
int len = strlen(path);
if(newpath[len - 1] != '/')
*(newpath + len++) = '/';
strcpy(newpath + len, file[i].f_name);
// writef("npath: %s", newpath);
gettree(depth + 1, newpath);
}
else
{
fwritef(1, "%s\n", file[i].f_name);
}
}
}

(之前上面1b[34后面少了个m,现在加上了,这是导致目录名称输出少一个字符的原因。

测试代码(要实现一行多条命令之后才能用)

1
mkdir qs; mkdir qs/OS;mkdir qs/OO; touch qs/cnx;touch qs/OS/lab6;touch qs/OS/lab7;touch treeres;tree

一行多个命令和 '' ''

runcmd 解析

先来看看指令执行的流程。

在sh.c 中

umain 从控制台读取一行,然后fork,把这一行命令传递给子进程。

子进程执行完毕后退出,父进程调用wait函数等待子进程执行结束被摧毁。

下面是wait函数,父进程调用wait函数传入的是子进程的 envid ,这个while循环直到子进程执行结束才会退出。

1
2
3
4
5
6
7
8
void
wait(u_int envid)
{
struct Env *e;
e = &envs[ENVX(envid)];
while(e->env_id == envid && e->env_status != ENV_FREE)
syscall_yield();
}

再看看子进程执行命令的过程,调用rumcmd函数。

其中用到了一个gettoken函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int gettoken(char *s, char **p1)
{
static int c, nc;
static char *np1, *np2;

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

这个函数用于获取字符串中的一个语义元素,可以看到,里面有两个静态局部变量,s是我们要处理的字符串,p1用来获取返回值。

第一次调用的时候传入s,_gettoken 函数把 np1, np2 设置为第一个语义元素的起始和结束地址,返回零。

下一次调用的时候,参数s的位置要传入0,才能继续处理上一个字符串。返回值是上一个语义元素的标志并且把p1设置为上一个语义元素的开始地址。

上面提到的标志是我乱起的名字,就是 如果是一个字符串,就返回 'w',如果是一些特殊符号 比如 '|',就返回 '|',具体看 _gettoken 函数。

由此分析可知,第一次调用只是为了初始化,并不能从这个函数中返回有用的信息。

下面我们来看看 runcmd 是怎么运行的:

这个函数大体上分为两个部分,前一半(runit之前)用于获取参数,并把它们放在 argv里面;后一半用于spanw进程,运行命令。

首先循环获取语义元素,如果得到的标志是0,说明这个字符串已经读完了,进入下面的runit

遇到字符串就把他放在参数列表里面

遇到'|'就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
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
void runcmd(char *s)
{
char *argv[MAXARGS], *t;
int argc, c, i, r, p[2], fd, rightpipe;
int fdnum;

struct Stat state;
rightpipe = 0;
gettoken(s, 0); // 初始化

again:
argc = 0; // !!
for(;;){
c = gettoken(0, &t);
switch(c){
case 0
goto runit;
case 'w':
if(argc == MAXARGS){
writef("too many arguments\n");
exit();
}
argv[argc++] = t;
break;
case '<':
if(gettoken(0, &t) != 'w'){
writef("syntax error: < not followed by word\n");
exit();
}

r = stat(t, &state); // stat函数用来获取文件的一些状态
if (r < 0) // r < 0 表示文件不能打开
{
writef("cannot open file\n");
exit();
}
if (state.st_isdir != FTYPE_REG)
{
writef("specified path should be file\n");
exit();
}
fdnum = open(t, O_RDONLY);
// dup it onto fd 0, and then close the fd you got.
dup(fdnum, 0);// 本来要从输入到标准输入的东西都输入到了 fdnum
close(fdnum);
break;
case '>':
if (gettoken(0, &t) != 'w')
{
writef("syntax error: > not followed by word\n");
exit();
}
r = stat(t, &state);
if (r >= 0 && state.st_isdir != FTYPE_REG)
{
writef("specified path should be file\n");
exit();
}
fdnum = open(t, O_WRONLY | O_CREAT);
dup(fdnum, 1);
close(fdnum);
break;
case '|':
pipe(p);
rightpipe = fork();
if (rightpipe == 0) // 右边的
{
// reader / 这应该是管道流向的程序, 把标准输入重定向为另一个程序的输入
dup(p[0], 0); // get standard input
close(p[0]);
close(p[1]);
goto again;
}
else // writer
{
dup(p[1], 1); // stahdard out put
close(p[1]);
close(p[0]);
goto runit;
}
user_panic("| not implemented");
break;
}
}

runit:
if(argc == 0) {
if (debug_) writef("EMPTY COMMAND\n");
return;
}
argv[argc] = 0;
if (1) {
writef("[%08x] SPAWN:", env->env_id);
for (i=0; argv[i]; i++)
writef(" %s", argv[i]);
writef("\n");
}

if ((r = spawn(argv[0], argv)) < 0)// argv中第一个参数是指令的名称
writef("spawn %s: %e\n", argv[0], r);
close_all();
if (r >= 0) {
if (debug_) writef("[%08x] WAIT %s %08x\n", env->env_id, argv[0], r);
wait(r);
}
if (rightpipe) { // 是管道左边的程序,也是父进程
if (debug_) writef("[%08x] WAIT right-pipe %08x\n", env->env_id, rightpipe);
wait(rightpipe); // 父进程等待子进程结束之后再
}

exit();
}

再来看 runit 后面的部分

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
runit:
if(argc == 0) {
if (debug_) writef("EMPTY COMMAND\n");
return;
}
argv[argc] = 0;
if (1) {
writef("[%08x] SPAWN:", env->env_id);
for (i=0; argv[i]; i++)
writef(" %s", argv[i]);
writef("\n");
}

if ((r = spawn(argv[0], argv)) < 0)// argv中第一个参数是指令的名称
writef("spawn %s: %e\n", argv[0], r);
close_all();
if (r >= 0) {
if (debug_) writef("[%08x] WAIT %s %08x\n", env->env_id, argv[0], r);
wait(r);
}
if (rightpipe) { // 是管道左边的程序
if (debug_) writef("[%08x] WAIT right-pipe %08x\n", env->env_id, rightpipe);
wait(rightpipe); //
}

exit();

实现思路

处理双引号只需要在 _gettoken 里面,把双引号当成一个特殊的东西就行。(我在处理完空白字符之后处理这个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if(*s == '"')
{
*s = 0;
*p1 = ++s;
while(*s != 0 && *s != '"') // debug
{
s++;
}
if (*s == 0) // debug
{
writef("don't match\n");
return 0;
}
*s++ = 0;//debug
*p2 = s;
return 'w';
}

谢谢,上面有三个bug,已经改正  6-30

处理 ;,首先在 _gettoken 里面像处理|一样把分号当做一个 token(这个原本已经有了,不用改),然后在runcmd 里面,一旦读到分号,就fork ,子进程执行前面的,父进程等子进程执行完前面的在执行后面的,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int forkres;

...

case ';':
forkres = fork();
if(forkres) // father
{
wait(forkres);
goto again;
}
else // child
{
goto runit;
}
break;

& 后台运行

实现

从上面的runcmd解析部分可以知道:

umain 从控制台读取一行,然后fork,把这一行命令传递给子进程。

子进程执行完毕后退出,父进程调用wait函数等待子进程执行结束被摧毁。

我们的目标是:使得 shell 不需要等待此命令执行完毕后再继续执行,而是将此命令置于后台执行,此时可在 shell 继续输入新命令

首先考虑”shell 不需要等待“中的等待对应的代码是在哪里,指令执行过程中有两次等待:

  1. 原本的代码里面,umain结尾长这样

    1
    2
    3
    4
    5
    6
    7
    8
    if ((r = fork()) < 0)
    user_panic("fork: %e", r);
    if (r == 0) {
    runcmd(buf);
    exit();
    return;
    } else
    wait(r); // // <---这里

    这里有一个等待。

  2. 另一个等待是在 runcmd 函数最后面,shell 等待 spawn 出来的进程执行完命令:

    1
    2
    3
    4
    5
    6
    7
    if ((r = spawn(argv[0], argv)) < 0)
    writef("spawn %s: %e\n", argv[0], r);
    close_all();
    if (r >= 0) {
    if (debug_) writef("[%08x] WAIT %s %08x\n", env->env_id, argv[0], r);
    wait(r);// <---这里
    }

这里“剧透”一下,为了方便后面加内部指令(就是不用fork子shell就执行的指令,challenge部分会用到),后面可能会把 umain 函数里面的 fork去掉,不能不管三七二十一就fork。所以这个任务要修改的是上面列出来的第二个 wait 部分,在runcmd里面写一个局部变量hang,用来表示是否需要后台运行

1
2
3
4
5
6
7
8
9
10
11
12
13
// 在switch case语句里面判断
case '&':
hang = 1;
break;

// 修改wait条件
if ((r = spawn(argv[0], argv)) < 0)
writef("spawn %s: %e\n", argv[0], r);
close_all();
if (r >= 0) {
if (debug_) writef("[%08x] WAIT %s %08x\n", env->env_id, argv[0], r);
if (hang) wait(r);// <---这里
}

测试

这个写完了不知道咋测试

Normal 部分

实现用上下键切换历史命令。

这个任务分为这么几个部分

  1. 如何处理上下键的输入?上下键输进去是啥东西?在哪个函数部分判断?
  2. 指令的保存:在什么时候存?
  3. 指令的读取

针对第一个问题:

经过查阅资料,上下左右键在linux中会被编码为

上: 27 '[' 'A'

下: 27 '[' 'B'

左: 27 '[' 'D'

右: 27 '[' 'C'

更多编码见这篇博客:https://blog.csdn.net/manfeel/article/details/13280017

也就是说,你按一下‘上’,实际上相当于输入了三个字符,解析的时候只需要当读到ascii码为 27 的字符的时候特判一下即可。


再来理一下流程:umain函数的循环里面每次用 readline 读一行,然后交给 runcmd 处理。

由于linux指令的回溯是以行为单位的,我们可以在 runcmd 函数开始的时候保存一下这条即将执行的指令。

下面是实现这个功能完整的过程

  1. 像 5-2 课上一样加一个 O_APPEND 选项,用于记录历史指令

  2. 写save_cmd 函数并且在 runcmd最开始调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    int save_cmd(char * cmd)
    {
    struct Stat state;
    int r;
    r = open(".history", O_CREAT | O_WRONLY | O_APPEND);
    if (r < 0)
    {
    fwritef(1, "failed to open .history!");
    return r;
    }
    his_newed = 1; // 勘误
    write(r, cmd, strlen(cmd));
    write(r, "\n", 1);
    return 0;
    }
  3. history 指令(还没实现 history [数字],列出最近多少条历史指令,这个好像有点麻烦,算了

    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
    #include "lib.h"

    void umain(int argc, char ** argv)
    {
    char buf;
    if (argc != 1)
    {
    fwritef(1, "usage: history\n");
    exit();
    }

    int fdnum = open(".history", O_RDONLY);

    if (fdnum < 0)
    {
    fwritef(1, "can't open .history!");
    exit();
    }

    int r;
    int cnt = 1;
    int new = 1;
    while((r = read(fdnum, &buf, 1)) == 1)
    {
    if(new)// newline
    {
    fwritef(1, "%9d ",cnt++);// 序号
    new = 0;
    }
    fwritef(1, "%c", buf);

    if (buf == '\n')
    {
    new = 1;
    }
    }
    }
  4. 上下方向键回溯处理

    先写一个函数用于获取历史指令 get_his

    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
    // return the number of char
    int get_his(int back, char *buf) // 1 means back ; 0 means forth
    {
    static struct Fd *fd;
    static int fdnum = 0;
    static char * va;
    static int offset = 0;
    static int beginva;
    static int endva;
    char* p;
    // writef("his_newed : %d\n", his_newed);
    // his_newed是一个全局变量,每次执行一条新的指令(就是.history文件被刷新)就会将这个变量置为1
    if (!fdnum || his_newed)
    {
    // writef("QAQ");
    his_newed = 0;
    fdnum = open(".history", O_RDONLY);
    if(fdnum < 0)
    {
    fwritef(1, "failed to open .history!\n");
    return fdnum;

    }
    // writef("opened a file(his)\n");
    fd = num2fd(fdnum);
    beginva = fd2data(fd);
    endva = beginva + ((struct Filefd*)fd)->f_file.f_size ; // va point to the end of the file
    va = endva - 1;
    offset = 0;
    }
    if(back)
    {

    while(*va == 0 || *va == '\n') va--;

    while(*va != '\n' && va > beginva) va--;

    int i = 0;
    p = va + ((*va == '\n' ? 1 : 0));
    for(; *p != '\n' && *p != '\0';p++)
    {
    buf[i++] = *p;
    // writef("[%c]",*p);
    }
    buf[i] = 0;
    offset--;
    // writef("in get_his : %s, va:%x\n", buf, va);
    return i;

    }
    // forward
    {
    while(*va == '\n' && va < endva) va++;// jump \n
    while(*va != '\n' && va < endva) va++;// find next \n
    p = va + 1;
    if(p >= endva)
    {
    *buf = 0;
    return 0;
    }
    else
    {
    int i = 0;
    while(*p != '\n')
    {
    buf[i++] = *(p++);
    }
    buf[i] = 0;
    // writef("in get_his : %s, va:%x\n", buf, va);
    return i;
    }
    }
    }

    然后是读取方向键然后做出响应的处理,这个要在 getline 函数里面动手脚:

    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
    else if(buf[i] == '\x1b')
    {
    read(0, &tmp, 1);
    if(tmp != '[')
    user_panic("\\x1b not followed by [\n");
    read(0, &tmp, 1);

    if (tmp == 'A')
    {
    if (i)
    fwritef(1, "\x1b[1B\x1b[%dD\x1b[K", i);// 向下走一格,向后退i格
    else
    fwritef(1, "\x1b[1B");// 向下走一格,抵消输入方向键向上走的
    i = get_his(1, buf);
    fwritef(1,"%s", buf);
    }
    else if (tmp == 'B')
    {
    if (i)
    fwritef(1, "\x1b[%dD\x1b[K", i);
    i = get_his(0, buf);
    fwritef(1,"%s", buf);
    }
    i--;
    }
  5. 注意上面有个叫 his_newed 的全局变量,标志着 .history 文件有没有被更新,这个变量需要在 save_cmd 处更新(反正是要在保存指令的时候置为1,之前的代码有点错误,已修正)

Challenge 环境变量

思路解析

环境变量分为两种

  1. 全局环境变量: 对子shell也可见
  2. 局部环境变量:仅对当前shell可见

emmm其实比较正经的思路是把全局变量放在内核态,用系统调用去访问。但是我没有按照这个思路去做,在这里简单说一下。

在 lib/syscall_all.c 里面写一个用于存储变量的结构体,开一个数组里面放上这种结构体,两种变量都往这存,结构体要保存的信息有 变量名、值、权限、创建这个变量的shell的id。其中“创建这个变量的shell的id”,就是我想给每个shell一个id,因为在指令执行过程中会各种fork,要是用 env_id 去做标识的话,一个 sh fork出来的 sh 是获取不到这个 sh 的局部环境变量的(因为 env_id 不相符),但是我们指令执行的过程中(由于可能用;分割多条指令)难免用 fork 出来的 sh 去执行访问环境变量的指令,这就会出问题。

我的想法是给每个 sh 进程一个标识(sh_id), fork 的时候 sh_id 不变,但是每次使用 sh 指令开启新的 sh 进程的时候这个 sh_id 会改变。具体怎么做呢?就是把 sh_id 作为 sh.c 这个文件里面的一个全局变量,在 sh.c/ umain 的最开始将 sh_id 赋值为当前的 env_id,由于 fork 的时候这个进程的数据段是被duppage给了子进程的,因此 fork 出来的 sh 和原来的 sh 拥有一样的 sh_id。

访问全局变量的时候将 sh_id 作为一个参数传递,如果访问的是全局环境变量,不需要判断;如果访问的是局部环境变量,需要判断这个sh进程的 sh_id 和存变量的结构体里面的 sh_id 是否一致,如果一致则允许访问,如果不一致就当做没有这个变量。

当然还可能有很多细节问题,因为我没有按照这个思路去实现,所以我也不知道。

下面介绍我的离谱思路(做功能嘛,能用就行。算了,看看就行,不建议模仿):

在 bash 中,“子shell 对环境变量的修改是对父进程不可见的“,我觉得这个特征很难不让人怀疑全局变量是放在用户态的,我就进行了这样的尝试。

需要注意的点是

  1. 修改环境变量的指令必须是内置指令,不能由 fork 出来的进程去执行,也就是说,不能在 umain 中 fork ,而是应该在 runcmd 中根据指令的情况进行 fork
  2. 执行 sh.c 指令的时候,不能用 spawn 了,因为用 spawn 生成的shell 看不到之前的全局环境变量(spawn 生成的进程就是一个全新的,像一张白纸一样,当然没有在用户态地址空间的全局变量),改用 fork ,说实话这个有点丑,但是只能这样了。
  3. 指令执行过程中也不能乱 fork 了。

步骤

  1. 写一些用于管理环境变量的函数

    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
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    #include "lib.h"

    typedef struct
    {
    char value[MAXVALUELEN];
    char name[MAXNAMELEN];
    int mode;
    int id__;
    } EnvVar;

    EnvVar env_vars[MAXVARNUM];
    int sh_id; // important!
    int max_num = 0;

    int get_var_by_name(char *name)
    {
    int current_id = sh_id;
    int i;
    for (i = 0; i < MAXVARNUM; i++)
    {
    EnvVar *var = &env_vars[i];
    if(var->name[0] == 0) continue;
    // 注意这里,如果 id 不一致而且不是GLOBAL,就表明这个变量不能用于这个shell
    if((var->id__ != current_id && !((var->mode) & V_GLOBAL)))
    {
    // not valid anymore
    var->name[0] = 0;
    continue;
    }
    if(strcmp(name, var->name) == 0)
    {
    return i;
    }
    }
    return -1;// not found
    }

    int alloc_var()
    {
    int i;
    for(i = 0; i < MAXVARNUM; i++)
    {
    if(env_vars[i].name[0] == 0)
    {
    if(i >= max_num) max_num = i + 1;
    IF_D writef("i:%d,maxnum:%d\n", i, max_num);
    return i;
    }
    }
    IF_D writef("failed to alloc var\n");
    return -1;
    }

    int set_var(char *name, char *value, int mode)
    {
    int current_envid =sh_id;
    int r = get_var_by_name(name);
    if(r < 0)// not exists before
    {
    if((r = alloc_var()) < 0) return r;
    EnvVar *var = &env_vars[r];
    strcpy(var->name, name);
    IF_D writef("copied name:%s", var->name);
    if(value)
    {
    strcpy(var->value, value);
    }
    else var->value[0] = 0;
    var->mode = mode;
    var->id__ = current_envid;
    return 0;
    }
    else
    {
    EnvVar *var = &env_vars[r];
    if(var->mode & V_RDONLY)
    {
    writef("%s is read only\n", name);
    return -1;
    }
    var->mode = mode;
    if (!(mode & V_GLOBAL)) // global -> not global
    {
    var->id__ = current_envid;
    }
    if(value)
    {
    strcpy(var->value, value);
    }
    return 0;
    }
    return -1;
    }

    int get_var(char *name, char *buf)
    {
    int r = get_var_by_name(name);
    if(r < 0) return r;
    strcpy(buf, env_vars[r].value);
    return 0;
    }

    int unset_var(char *name)
    {
    int r = get_var_by_name(name);
    if(r < 0)
    {
    writef("%s is not a var\n", name);
    return r;
    }
    EnvVar *var = &env_vars[r];

    if(var->mode & V_RDONLY)
    {
    writef("%s is RDONLY\n", name);
    return -2;
    }
    var->name[0] = 0;
    return 0;
    }

    int set_global_var(char* name)
    {
    return set_var(name, 0, V_GLOBAL);
    }


    void output_all_var()
    {
    int i;
    int cur_envid = sh_id;

    IF_D writef("maxnum:%d\n", max_num);
    for (i = 0; i < max_num; i++)
    {
    EnvVar *var = &env_vars[i];
    if(var->name[0] == 0) continue;
    if((var->id__ != cur_envid && !((var->mode) & V_GLOBAL)))
    {
    var->name[0] = 0;
    continue;
    }
    fwritef(1, "\x1b[32mname:\x1b[0m %s \t\x1b[32mvalue:\x1b[0m %s \t\x1b[32mmood:\x1b[0m ", var->name, var->value);
    fwritef(1, "%s%s\n", (var->mode & V_RDONLY) ? "r" : "w", (var->mode & V_GLOBAL) ? "x" : "-"); // mood
    }
    }

  2. 用一个函数将执行内建指令的部分封装起来

    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
    int call_inner_instr(int argc, char** argv)
    {
    IF_D writef("inner: %s\n", argv[0]);
    if(strcmp(argv[0], "unset") == 0)
    {
    if(argc != 2) fwritef(1, "usage: unset [varname]\n");
    else unset_var(argv[1]);
    return 0;
    }
    if(strcmp(argv[0], "declare") == 0)
    {
    /*
    usage:
    declare // output all
    declare name // default value is ""
    declare name value
    declare -xr name // set mood
    declare -xr name value
    */
    if(argc == 1)
    {
    output_all_var();
    return 0;
    }
    int have_mood = 0;
    int mood = 0;
    if (argv[1][0] == '-')
    {
    have_mood = 1;
    int i;
    for (i = 1; argv[1][i] != 0; i++)
    {
    if(argv[1][i] == 'r') mood |= V_RDONLY;
    else if (argv[1][i] == 'x') mood |= V_GLOBAL;
    }
    }
    char *name = argv[have_mood + 1];
    // if have_mood + 2 >= argc(actually ==) ,value = 0
    char *value = argv[have_mood + 2];
    set_var(name, value, mood);
    return 0;
    }
    return -1;
    }
  3. 修改 sh.c/umain 让他不 fork ,直接 runcmd

  4. 修改 sh.c/runcmd ,其实这部分是要改的变化比较大的部分,修改 rncmd 的目的有这些:

    1. 防止指令乱 fork
    2. 对于要重定向的指令,必须不能用我们最原始的 sh 进程去执行,不然你把标准输入输出重定向到了文件,之后的输入输出就会出问题。有管道的指令也必须用 fork 出来的进程去执行,这个和 declare 是不兼容的,没什么办法了。
    3. 对于 sh 指令,特判,用fork
    4. 执行完后,如果是最原始的那个 sh 进程 ,用return ,如果是执行过程中产生的进程,用 exit

    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
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    void runcmd(char *s)
    {
    save_cmd(s);
    char *argv[MAXARGS], *t;
    int argc, c, i, r, p[2], fd, rightpipe;
    int hang = 0;
    int fdnum;
    int forkres;
    struct Stat state;
    char buffer[MAXVARNUM][MAXVALUELEN];
    int buffer_len = 0;
    rightpipe = 0;

    gettoken(s, 0);
    again:
    argc = 0;
    for(;;){
    c = gettoken(0, &t);
    switch(c){
    case 0:
    goto runit;
    case '&':
    hang = 1;
    break;
    case 'w':
    if(argc == MAXARGS){
    writef("too many arguments\n");
    exit();
    }
    if(t[0] == '$')
    {
    if(get_var(t + 1, buffer[buffer_len]) >= 0)
    {
    argv[argc++] = buffer[buffer_len++];
    }
    else argv[argc++] = t;
    }
    else argv[argc++] = t;
    break;
    case ';':
    if ((r = call_inner_instr(argc, argv)) == 0) goto again;
    // not a inner instr
    forkres = fork();
    if(forkres) // father
    {
    wait(forkres);
    goto again;
    }
    else // child
    {
    goto runit;
    }
    break;
    case '<':
    if(gettoken(0, &t) != 'w'){
    writef("syntax error: < not followed by word\n");
    exit();
    }
    // Your code here -- open t for reading,

    r = stat(t, &state);
    if (r < 0)
    {
    writef("cannot open file\n");
    exit();
    }
    if (state.st_isdir != FTYPE_REG)
    {
    writef("specified path should be file\n");
    exit();
    }
    fdnum = open(t, O_RDONLY);
    // dup it onto fd 0, and then close the fd you got.
    r = fork();
    if(r)
    {
    wait(r);
    goto exit_or_ret;
    }
    dup(fdnum, 0);
    close(fdnum);
    // user_panic("< redirection not implemented");
    break;
    case '>':
    if (gettoken(0, &t) != 'w')
    {
    writef("syntax error: > not followed by word\n");
    exit();
    }
    // Your code here -- open t for writing,
    // dup it onto fd 1, and then close the fd you got.
    r = stat(t, &state);
    if (r >= 0 && state.st_isdir != FTYPE_REG)
    {
    writef("specified path should be file\n");
    exit();
    }
    // Your code here -- open t for writing,
    fdnum = open(t, O_WRONLY | O_CREAT);
    r = fork();
    if(r)
    {
    wait(r);
    goto exit_or_ret;
    }
    dup(fdnum, 1);
    close(fdnum);
    // dup it onto fd 1, and then close the fd you got.
    // user_panic("> redirection not implemented");
    break;
    case '|':
    r = fork();
    if(r)
    {
    wait(r);
    goto exit_or_ret;
    }

    pipe(p);
    rightpipe = fork();
    if (rightpipe == 0)
    {
    // writer
    dup(p[0], 0); // get standard input
    close(p[0]);
    close(p[1]);
    goto again;
    }
    else // reader
    {
    dup(p[1], 1); // stahdard out put
    close(p[1]);
    close(p[0]);
    goto runit;
    }
    user_panic("| not implemented");
    break;
    }
    }
    runit:
    if(argc == 0) {
    if (debug_) writef("EMPTY COMMAND\n");
    goto exit_or_ret;
    }
    argv[argc] = 0;
    if (1) {
    // IF_D 是用一个宏控制 DEBUG 模式,展开就是 if(DEBUG_)
    IF_D writef("[%08x] SPAWN:", env->env_id);
    IF_D for (i=0; argv[i]; i++)
    writef(" %s", argv[i]);
    IF_D writef("\n");
    }
    if(call_inner_instr(argc, argv) == 0)// 0 means success
    goto exit_or_ret;
    if(strcmp(argv[0], "sh") == 0)
    {
    r = fork();
    if(r == 0)
    {
    sh_id = syscall_getenvid();
    }
    }
    else if ((r = spawn(argv[0], argv)) < 0)
    writef("spawn %s: %e\n", argv[0], r);
    if (r >= 0) {
    if (debug_) writef("[%08x] WAIT %s %08x\n", env->env_id, argv[0], r);
    if (hang == 0) wait(r);
    }
    if (rightpipe) {
    if (debug_) writef("[%08x] WAIT right-pipe %08x\n", env->env_id, rightpipe);
    wait(rightpipe);
    }
    // 如果是执行过程中产生的进程,exit ;如果是最原始的那个进程,return
    exit_or_ret:
    if(sh_id == syscall_getenvid()) return;
    close_all();
    exit();
    }

结果展示

image-20220627002119955