BUAA-OS-Lab6-challengeRepo

OS-lab6挑战性任务实验报告

Posted by Arthuring on Thursday, June 30, 2022

Lab6-Challenge实验报告

一、总述

jI6T56.png

上图是lab6中 实现的shell,在此基础上,进行更改,完成了challenge任务,修改后的shell如下图

jI6oUx.png

其中,蓝色部分对应Easy部分要求的实现后台运行、一行多命令、简单引号支持以及新增外部命令touch/mkdir/tree,黄色部分为Normal部分中支持历史指令回显功能,紫色部分为Challenge部分简单支持环境变量部分,通过在内核中维护环境变量,使用系统调用访问的方式实现。具体实现细节见第二部分。

二、具体实现

2.1 Easy 部分

2.1.1 实现后台运行

用一个变量记录是否需要后台运行,如果需要后台运行,则此进程不等待spawn出的进程直接退出,从而可以获取新的输入

//runcmd
int run_back = 0;/*是否需要后台运行*/
int back_id/*后台运行的进程号*/
/*在处理gettoken的循环中新增一个case &,记录是否需要后台运行*/
for(;;){
    ...
        case '&':
            run_back = 1;
            break;
    ...    
}
// runit:
        if(!run_back){//如果不需要run back, 等待spawn出的进程r结束
            if (debug_) writef("[%08x] WAIT %s %08x\n", env->env_id, argv[0], r);
            wait(r);
        }else{//需要run back
            back_id = fork();//fork 出一个进程等着r
            if(back_id == 0){//输出任务开始和结束的信息
                writef(LIGHT_GREEN(\n[%08x] running\t), r);
                for(i=0; i<argc; i++){
                    writef(LIGHT_GREEN(%s)" ", argv[i]);
                }
                wait(r);
                writef("\n[%08x] done\t", r);
                for(i = 0; i<argc;i++){
                    writef("%s ", argv[i]);
                }
                writef("\n");
                exit();
            }
            //此进程直接退出,从而使等待此进程的主进程umain可以继续获取输入
        }

由于前台进程在readline中获取输入时,最终调用的函数是sys_cgetc,而这个函数在等待输入时是忙等,一直占用CPU,无法被其他进程打断, 因此需要对此进行修改,不然前台进程将一直占用CPUspawn出的进程将无法后台执行,修改后即可实现后台进程运行时继续输入新指令

//lib/getc.S
.set noreorder
LEAF(sys_cgetc)

1:    lb    t0, 0x90000000
    /*这里在不读入字符时死循环,导致忙等*/
//    beqz    t0, 1b
//    nop
    move    v0,t0
    jr    ra
    nop
END(sys_cgetc) 

2.1.2 实现一行多命令

在处理gettoken的循环中新增一个;,仿照管道|的情况,fork出一个新的进程,子进程继续解析;右侧的命令,左侧直接运行 goto runit;

case ';':    
    if(isright = fork() == 0 ){
        goto again;
    }else{
        goto runit;
    }
    break;    
    }

2.1.3 实现引号支持

添加两个变量ignore1 & ignore2分别代表检测到了一个' or "的情况

_gettoken中,添加对/'的判断,在读到这两种符号,也就是ignore1 != 0 || ignore2 != 0时不识别后续的标识符,当取消ignore之后,在合适的位置返回一个word

//in _gettoken(char *s, char **p1, char **p2)
int ignore1 = 0;
int ignore2 = 0;

if(*s == '\''){
        ignore2 = 1-ignore2;
        strcpy(s, s+1);
    }
if(*s == '\"'){
        ignore2 = 1-ignore2;
        strcpy(s, s+1);
    }
//... some code else ...
while(*s && (!strchr(WHITESPACE SYMBOLS, *s) || !(ignore1 == 0 && ignore2 == 0) )) {
        if(*s == '\''){
            ignore1 = 1-ignore1;
            strcpy(s, s+1);
        }
        else if(*s == '\"'){
            ignore2 = 1-ignore2;
            strcpy(s, s+1);
        }else{
            s++;
        }
    }

2.1.4 实现mkdir & touch

2.1.4.1实现user_create

为了更加方便快捷的创建文件和目录,观察到文件系统fs/fs.c中有函数file_create, 但没有供用户使用的用户接口,因此在fs/serv.c中实现serve_create,以及在user/fsipc.c中实现fsipc_create,从而添加文件系统调用接口,在user/file.c中实现user_create,使得用户可以通过调用这个函数新建文件并指定文件类型。

void
serve_create(u_int envid, struct Fsreq_create *rq){
    u_char path[MAXPATHLEN];
    int isdir = rq -> req_isdir;

    struct File *f;
    int fileid;
    int r;

    user_bcopy(rq->req_path, path, MAXPATHLEN);
    path[MAXPATHLEN - 1] = 0;

    if((r = file_create((char *)path, &f)) < 0 ){
        ipc_send(envid, r, 0, 0);
        return;
    }
    f->f_type = isdir;
    ipc_send(envid, 0, 0, 0);
}
int fsipc_create(char* path, int isdir){
    u_int perm;
    struct Fsreq_create *req;

    req = (struct Fsreq_create *)fsipcbuf;

    if (strlen(path) >= MAXPATHLEN ){
        return -E_BAD_PATH;
    }

    strcpy((char *)req->req_path, path);
    req->req_isdir = isdir;
    return fsipc(FSREQ_CREATE, req, 0, &perm);
}
int user_create(char* path, int isdir){
    int r = fsipc_create(path, isdir);
    return r;
}
2.1.4.2 实现 touch & mkdir

只需调用上述用户函数,将isdir分别设置成0和1即可

//user/touch.c
    r = user_create(argv[1], 0);
    if(r < 0){
        writef("\ncannot create file %s: error: %d", argv[1], r );
    }
//user/mkdir.c
    r = user_create(argv[1], 1);
    if(r < 0){
        writef("cannot create directory \'%s\': %d ",argv[1], r );
    }

2.1.5 实现tree

tree指令需要打开指定的路径并显示其中的文件,遇到目录时,只需要递归的调用即可。当一个文件是这个目录中的最后一个时,后续的文件树不显示|,因此需要记录当前文件的各级父文件是否是最后一个文件,从而正确的打印文件树。

int tree(char *path,int deep, int *isEnd){
    int fd;
    struct File f, fnext;
    int r,n;
    int nowEnd = 0;
    char next_path[MAXPATHLEN];
    if((fd = open(path, O_RDONLY)) < 0 ){
    //...
    }
    //...

    if((r = readn( fd, &fnext, sizeof f)) != sizeof f || fnext.f_name[0] == '\0'){
        return;
    }
    while(nowEnd != 1){
        f = fnext;
        if((r = readn( fd, &fnext, sizeof f)) != sizeof f || fnext.f_name[0] == '\0'){
            nowEnd = 1;//当前时最后一个
        }
        if(f.f_name[0] != '\0'){
        //...当前文件不为空,输出文件名
        }
        if(f.f_type == FTYPE_DIR){//当前文件是目录
            isEnd[deep] = nowEnd;
            strcpy(next_path, path);
            strcpy(next_path + strlen(next_path), "/");
            strcpy(next_path + strlen(next_path), f.f_name);
            tree(next_path, deep + 1, isEnd);//递归调用
        }
    }
    close(fd);
}
/*按照当前深度和各级父目录情况输出文件树枝*/
void print_bar(int deep, int* isEnd){
    int i;
    int tot = deep * 3;
    for(i=0 ;i<tot; i++){
        if(i % 3 == 0 && isEnd[i/3] != 1){
            writef("|");
        }else{
            writef(" ");
        }
    }
}

2.2 Normal部分 – 历史命令

Normal部分是需要支持历史指令回显,只需要在每次输入完指令后,将字符串以O_APPEND的方式写入磁盘中的.hestory文件(以O_CREAT模式打开文件,从而在第一次调用时创建文件),并在readline遇到↑ ↓控制符号的时候读.history文件,将特定的历史记录回显到屏幕即可。history指令则是直接输出.history文件的全部内容即可。

2.2.1 支持以 O_CREAT 和 O_APPEND 模式打开文件

//lib.h
#define O_APPEND    0x0004//新增打开模式
//user/file.c/open
/*O_APPEND*/
    if((mode & O_APPEND) != 0){
        ffd->f_fd.fd_offset = ffd->f_file.f_size;
    }
//fs/serv.c/serve_open
/*O_CREATE*/
    if ((r = file_open((char *)path, &f)) < -1) {
        if(r == -E_NOT_FOUND && ((rq->req_omode & O_CREAT) != 0)) {
            if((r = file_create((char*)path, &f)) < 0 ){
                ipc_send(envid, r, 0, 0);
                return;
            }
            f->f_type = FTYPE_REG;

2.2.2 read_history函数和write_history函数

int read_history_cached(char history_cmd[128][128]){
    int index = ((history_cnt - 128) > 0) ? history_cnt-128 : 0;
    int i;
    char buf[128];
    int fd = open("/.history", O_RDONLY);
    if(fd < 0){
        return 0;
    }
    for(i = 0; i< index; i++){
        read_line(fd, buf, 128);//从文件中读入一行,限制最大读入数,返回值为实际读入字符数
    }
    for(i=index; i < history_cnt; i++){
        read_line(fd, buf, 128);
        strcpy(history_cmd[i-index], buf);
    }
    close(fd);
    return history_cnt - index;
}

void write_history(char* buf, int n ){
    static int run_time = 0;
    int fd, r;
    if(run_time == 0){
        user_create("/.history", 0);
    }
    fd = open("/.history", O_WRONLY | O_APPEND );
    r = write(fd, buf, n);
    if(r < n){
        writef("\nerror in write_history\n");
    }
    r = write(fd, "\n", 1);
    if(r < 1){
        writef("\nerror in write_history\n ");
    }
    close(fd);
    history_cnt += 1;
    run_time++;
}

值得注意的是,write_history需要在readline之后立即写入,原样写入输入的命令。

为了防止多次打开关闭文件造成性能影响,read_history一次读取最近的多行历史,通过在readline函数中判断上下箭头控制号

↑:\033[A 若至屏幕顶端则无效

↓:\033[B 若至屏幕底端则无效

从而回显特定的history

        if(i >= 2 && buf[i - 2] == 27 && buf[i-1] == 91 && buf[i] == 65){//上箭头\033[A
            writef("%c%c%c", 27, 91, 66);
            for(i -= 2; i; --i) writef("\b \b");//清空显示
            if(cnt > 0){
                strcpy(buf, historys[--cnt]);//显示历史
            }else{
                strcpy(buf, historys[cnt]);
            }
            writef("%s", buf);
            i =  strlen(buf) -1;
        }
        if(i >= 2 && buf[i - 2] == 27 && buf[i - 1] == 91 && buf[i] == 66){//下箭头\033[B
//            writef("%c%c%c", 27, 91, 65);屏幕底端无效
            if(cnt < sum - 1){
                for(i -= 2; i; --i) writef("\b \b");//清空显示
                strcpy(buf, historys[++cnt]);//显示历史
                writef("%s", buf);
                i = strlen(buf) - 1;
            }//....

2.2.3 history指令

将.history文件中的内容都输出即可

int fd = open("/.history",O_RDONLY);
    char buf[128];
    int r;
    if(fd  < 0){
        writef("\n./history not found\n");
    }
int cnt = 1;
    while((r = read_line(fd, buf, 128)) != 0 ){
        writef(MEG(%d), cnt);
        writef("\t%s\n", buf);
        cnt++;
    }

2.3 Challenge 部分 – 环境变量

2.3.1 环境变量的维护

为了实现不同进程之间声明的局部变量不可见,环境变量可见,选择在内核当中记录变量名以及相应信息。需要记录当前变量是否为环境变量,声明这个变量的进程号,变量值,是否只读。并通过系统调用的方式为用户提供服务。

采用哈希线性探测查找和维护变量表

主要代码为lib/syscall_all.c中的sys_env_var

#define ENVVAR_CLEAR (1 << 7)
#define GET (1 << 6)
#define LIST (1 << 5)
#define UNSET (1 << 4)
#define CREATE (1 << 3)//if set, return value, else set name
#define SET (1 << 2) // recover value, or glob readonly 
#define GLOB (1 << 1)
#define RDONLY (1 << 0)
int sys_env_var(int sysno, char* name, char* value, u_int env_id, u_int option){
    static int cnt = 0;
    static char name_list[NHASH][32] = {0};
    static u_int envid_list[NHASH] = {0};
    static char value_list[NHASH][128];
    static int readonly[NHASH] = {0} ;
    static int glob[NHASH] = {0};
    int i;
    if(option & ENVVAR_CLEAR){
        for(i = 0; i < NHASH; i++ ){
            if(glob[i] != 1 && envid_list[i] == env_id ){
                name_list[i][0] = 0;
                value_list[i][0] = 0;
                envid_list[i] = 0;
                glob[i] = 0;
                readonly[i] = 0;
            }
        }
        return 0;
    }
     /*列出所有env_id进程可见的变量*/
    if(option & LIST){
        for(i=0; i < NHASH ; i++){
            if(name_list[i][0] != 0){
                if(glob[i] == 1 || envid_list[i] == env_id ){
                    printf("%s=%s\n", name_list[i], value_list[i]);
                }
            }
        }
        return 0;
    }
    /*哈希线性探测查找*/
    u_int index = hash(name);
    while(name_list[index][0]){
        if(strcmp(name_list[index], name) == 0 && (env_id == envid_list[index] || glob[index] == 1 )){
            if ((option & CREATE))    {
                printf("Already declaired: %s=%s", name_list[index],value_list[index]);
                return -16;
            }
            break;        
        }else{
            index++;
            if(index == NHASH ) index =0;    
        }
    }
  /*设置变量*/
    if((option & SET)){
        strcpy(name_list[index], name);
        if(!readonly[index]){
            strcpy(value_list[index], value);/*记录值*/
        }else{
            printf("\nfailed,\"%s\" is readonly\n", name);
        }
        if((option & GLOB) && glob[index] == 0 ){/*设置全局*/
            glob[index] = 1;
            envid_list[index] = 0;
        }else if(glob[index] == 0 ) {/*设置非全局*/
            envid_list[index] = env_id;
        }
        readonly[index] |= (option & RDONLY);/*设置只读*/
    }else if(option & CREATE){/*创建变量*/
        strcpy(name_list[index], name);
        value_list[index][0] = 0;
        if((option & GLOB) && glob[index] == 0 ){
            glob[index] = 1;
            envid_list[index] = 0;
        }else if(glob[index] == 0 ) {
            envid_list[index] = env_id;
        }
        readonly[index] |= (option & RDONLY);
    }else if(option == GET){/*查找变量*/
        if(strcmp(name_list[index], name) != 0){
            value[0] = 0;
            return -E_VAR_NOT_FOUND;
        }
        strcpy(value, value_list[index]);
    }else if(option & UNSET){/*销毁变量*/
        if(strcmp(name_list[index], name) != 0){
            printf("\nfailed, \"%s\" is not found", name);
            return -E_VAR_NOT_FOUND;
        }
        if(readonly[index] == 1){
            printf("\nfailed, \"%s\" is readonly", name);
            return -E_VAR_READONLY;
        }
        name_list[index][0] = 0;
        value_list[index][0] = 0;
        glob[index] = 0;
        readonly[index] = 0;
        envid_list[index] = 0;
    }
    return 0;
}

2.3.2 declare & unset命令

使用内建命令,在使用命令的时候不需要spawn单独创建一个进程装载命令的可执行文件。内部命令和shell一起常驻内存。

//sh.c
void declare(int argc, char ** argv, u_int env_id){
    u_int option = 0;
    int i;
    ARGBEGIN/*处理选项*/
    {
        default:
            fwritef(1,  RED(\nusage: declare [-xr] [NAME[=VALUE]]\n));
            return;
        case 'r'://-r readonly
            option |= ENVVAR_RDONLY;
            break;
        case 'x'://-x set environmentaly
            option |=ENVVAR_GLOB;
            break;
    }
    ARGEND    
    char name[32], value[128];
    char *p; 
    int r;
    if(argc > 1){
        fwritef(1, RED(\ntoo many argvs\n));
        return;
    }
    if(argc == 1){
        if((p = strchr(argv[0], '=')) > 0){
            option |= ENVVAR_SET;
            strcpy(name, argv[0]);
            name[p-argv[0]] = 0;
            p++;
            strcpy(value, p);
        }else{
            option |= ENVVAR_CREATE;
            strcpy(name, argv[0]);
        }
        syscall_env_var(name, value, env_id, option);//
    }else{
        option |= ENVVAR_LIST;
        syscall_env_var( 0, 0, env_id, option);
    }
}
void unset(int argc, char **argv, u_int env_id){
    u_int option = 0;
    if(argc > 2){
        fwritef(1, RED(\ntoo many argvs\n));
        return;
    }
    else if(argc < 2){
        fwritef(1, RED(\ntoo few argvs\n));
        return;
    }
    else{
        option |= ENVVAR_UNSET;
        syscall_env_var(argv[1], 0, env_id, option);
    }
}

2.3.3 支持$x变量替换 & 销毁进程同时销毁进程的局部变量

支持如 echo $a变量替换,需要在将命令送入runcmd之前,对所有不在' '内部的变量进行字符串替换。

#define BREAK_SIG " \'\t\r\n$"
void replace_envvar(char *buf, u_int env_id){
    int ignore = 0;
    char* p = buf;
    char name[32] ={0};
    int namelen = 0;
    char value[128] = {0};
    char temp[1024] = {0};
    int i;
    while(*p){
        if(*p == '\''){
            ignore = 1 - ignore;
            p++;
        }else if(*p == '$' && !ignore){
            for(namelen = 1; *(p + namelen) != 0 && !(strchr(BREAK_SIG, *(p+namelen))); namelen++){
                name[namelen-1] = *(p + namelen);
            }
            getvar(name, env_id, value);//通过系统调用获取变量值
            //...省略进行替换的字符串操作
        }else{
            p++;
        }
    }
}

需要通过系统调用获取环境变量的值

void getvar(char *name, u_int env_id, char* value){
    u_int option = 0;
    option |= ENVVAR_GET;
    syscall_env_var(name, value, env_id, option);
}

特别的,当一个shell被销毁时,它声明的局部变量也同时失效

void clear_envvar(u_int env_id, u_int option){
    syscall_env_var(0, 0, env_id, ENVVAR_CLEAR);
}

2.4 其他

2.4.1 支持退格

if(buf[i] == '\b' || buf[i] == 127){
    if(i > 0){
        buf[i] = 0;
        clear_con(buf);
        buf[i-1]=0;
        writef("%s", buf);
        i -= 2;
    }else{
        buf[i] = 0;
        i = -1;
    }
}

2.4.2 支持自动添加.b

strcpy(prog_path, prog);    
    int len = strlen(prog_path);
    if(prog_path[len-2] != '.' || prog_path[len-1] != 'b'){
        strcpy(prog_path+strlen(prog_path), ".b");
    }
    if((r=open(prog_path, O_RDONLY))<0){

2.4.3 支持彩色输出

使用ANSI转义序列

#define LIGHT_BLUE(str)                "\033[0m\033[1;34m" # str "\033[m"
#define BLUE(str)                    "\033[0m\033[34m"    # str "\033[m"
#define DARK_BLUE(str)                "\033[0m\033[2;34m"    # str "\033[m"
#define RED(str)                    "\033[0m\033[31m"    # str "\033[m"
#define LIGHT_RED(str)                "\033[0m\033[1;31m"    # str "\033[m"
#define DARK_RED(str)                "\033[0m\033[2;31m"    # str "\033[m"
#define GREEN(str)                    "\033[0m\033[32m"    # str "\033[m"
#define LIGHT_GREEN(str)            "\033[0m\033[1;32m"    # str "\033[m"
#define DARK_GREEN(str)                "\033[0m\033[2;32m"    # str "\033[m"
#define YELLOW(str)                    "\033[0m\033[33m"    # str "\033[m"
#define LIGHT_YELLOW(str)            "\033[0m\033[1;33m"    # str "\033[m"
#define DARK_YELLOW(str)            "\033[0m\033[2;33m"    # str "\033[m"
#define MEG(str)                    "\033[0m\033[35m"    # str "\033[m"
#define LIGHT_MEG(str)                "\033[0m\033[1;35m"    # str "\033[m"
#define DARK_MEG(str)                "\033[0m\033[2;35m"    # str "\033[m"
#define CIY(str)                    "\033[0m\033[36m"    # str "\033[m"
#define LIGHT_CIY(str)                "\033[0m\033[1;36m"    # str "\033[m"
#define DARK_CIY(str)                "\033[0m\033[2;36m"    # str "\033[m"

三、测试&运行效果

3.1 Easy部分

3.1.1后台运行

测试程序,利用大循环实现长时间后台运行方便观察

#include "lib.h"
void umain(int argc, char **argv){
    int N = (argv[1][0] - '0') * 10000000 , i;
    for(i = 0; i < N; i++ ){
        if(i == N/10){
            writef("\n-%%10-");    
        }        
        if(i == N/10 * 2){
            writef("\n--%%20--");
        }
        if(i == N / 10 *3){
            writef("\n---%%30---");
        }
        if(i == N / 10 *4){
            writef("\n----%%40----");
        }
        if(i == N / 10 *5){
            writef("\n-----%%50-----");
        }
        if(i == N / 10 *6){
            writef("\n------%%60------");
        }
        if(i == N / 10 *7){
            writef("\n--------%%70-------");
        }
        if(i == N / 10 *8){
            writef("\n---------%%80--------");
        }
        if(i == N / 10 *9){
            writef("\n-----------%%90---------");
        }
    }
    writef("\nFINISHED");

可见在后台程序运行时,仍然可以输入指令tree并显示文件树。

jIcSat.png

3.1.2多命令

jI6Lxe.png

3.1.3 tree/mkdir/touch

jIcKiV.png

通过mkdir/touch新建文件,通过tree显示结果。测试命令见附录。

3.2 Normal部分

jIcQRU.png

通过history命令显示历史命令

3.3 Challenge部分

jI6q2D.png

可见,变量定义正常,只读变量无法被修改或取消,子进程只能看到环境变量,无法看到局部变量,echo支持变量带入值,支持' '中原样输出

四、实验困难及解决

4.1 Easy部分中实现后台运行

实现后台运行的难点主要在找出前台进程等待的位置,一开始,我只注意到了runcmdrunit里等待被spawn的进程的问题,在进行测试时,我发现,虽然运行测试程序后,可以立即显示一个新的$提示我继续输入,但我必须进行一些输入后,后台进程才有运行反应,如果我一直不输入,后台进程则一直不会运行。

通过仔细阅读challenge题目提示,我理解了提示中的忙等是指前台进程一直在内核态等待输入死循环,无法被时钟打断,从而一直占有CPU导致其他进程无法被调度,失去执行机会。最终通过找到sys_get_char中的死循环并删除解决。

4.2 Normal部分中关于历史记录条数的维护

在维护历史记录的过程中,我的初次尝试是在write_history中维护一个static变量来记录历史记录条目数,并且在run_cmd中记录历史。

void write_history(char* buf, int n ){
    static int history_cnt = 0;
    int index = ((history_cnt - 128) > 0) ? history_cnt-128 : 0;
void
runcmd(char *s, u_int env_id)
{//...some defination
    rightpipe = 0;
    write_history(s,strlen(s));
    gettoken(s, 0);
again:

我发现这样的写法下,我的history_cnt变量在进入write_history函数后能正常+1,但退出之后,执行下一条命令时,又变回0,无法读取历史命令。

经检查发现,由于runcmd时,主进程已经fork了一个进程并且由子进程runcmd,并且history_cnt所在的页面不是共享页面,因此history_cnt在子进程中的改变无法传递给父进程,因此父进程在下次read时依然认为history_cnt为0

改进后,调整了记录history的位置,改在了fork之前,readline之后,问题迎刃而解。

    readline(buf, sizeof buf);
    write_history(buf, strlen(buf));
    //...some code not important
    if ((r = fork()) < 0)
    user_panic("fork: %e", r);

4.3 Challenge部分中逻辑复杂问题

这部分中由于对环境变量的操作非常多,并且还有选项的组合问题,局部变量和环境变量的问题,使得一开始做起来问题非常复杂。

首先是如何判断查找到的变量是否对于这个进程可见,也就是如何判断命中的问题。思考过后发现这个问题与TLB命中十分类似,TLB中有g(globle)位asid来判断表项是否属于这个进程,那么这里也可以使用一个glob变量和env_id变量判断。当glob为0也就是局部变量时,进行nameenv_id的双关键字比较,当glob为1时,只进行name比较

第二个问题是,-x和-r选项使得declare变得非常灵活,-x-r和declare的组合形式很多,而系统调用的参数传递量最大为5,非常有限。经思考发现这个选项模式很类似于文件打开的O_MODE,选项之间是逻辑或的关系,因此我仿照O_MODE,采用了二进制掩码的方式表示Option,这样只需要传递一个参数即可表达清楚操作内容。

/*ENV VAR OP*/
#define ENVVAR_CLEAR (1 << 7)
#define ENVVAR_GET (1 << 6)
#define ENVVAR_LIST (1 << 5)
#define ENVVAR_UNSET (1 << 4)
#define ENVVAR_CREATE (1 << 3)//if set, return value, else set name
#define ENVVAR_SET (1 << 2) // recover value, or glob readonly 
#define ENVVAR_GLOB (1 << 1)
#define ENVVAR_RDONLY (1 << 0)

附录

mkdir courses
mkdir courses/gradeTwo
mkdir courses/gradeTwoSpring
mkdir courses/blog
touch courses/chapter6.zip
touch courses/data_structure.zip
touch courses/math.c
touch courses/physics.c
mkdir courses/gradeTwo/computer_orgnisim
touch courses/gradeTwo/computer_orgnisim/P1.v
touch courses/gradeTwo/computer_orgnisim/P2.v
touch courses/gradeTwo/computer_orgnisim/P3.v
mkdir courses/gradeTwo/computer_orgnisim/reports
touch courses/gradeTwo/computer_orgnisim/reports/reports1.pdf
touch courses/gradeTwo/computer_orgnisim/reports/reports2.pdf
touch courses/gradeTwo/computer_orgnisim/reports/reports3.pdf
mkdir courses/gradeTwo/physics
touch courses/gradeTwo/physics/chapter6.ppt
mkdir courses/gradeTwoSpring/OO
mkdir courses/gradeTwoSpring/OS
mkdir courses/gradeTwoSpring/OO/Homework1
mkdir courses/gradeTwoSpring/OO/Homework2
mkdir courses/gradeTwoSpring/OO/Homework3
mkdir courses/gradeTwoSpring/OO/Homework4
mkdir courses/gradeTwoSpring/OO/Homework5
mkdir courses/gradeTwoSpring/OO/Homework6
mkdir courses/gradeTwoSpring/OO/Homework7
mkdir courses/gradeTwoSpring/OO/Homework8
mkdir courses/gradeTwoSpring/OO/Homework9
mkdir courses/gradeTwoSpring/OO/Homework1/src
touch courses/gradeTwoSpring/OO/Homework1/src/hw1.jar
mkdir courses/gradeTwoSpring/OO/Homework2/src
touch courses/gradeTwoSpring/OO/Homework2/src/hw2.jar
mkdir courses/gradeTwoSpring/OO/Homework3/src
touch courses/gradeTwoSpring/OO/Homework3/src/hw3.jar
mkdir courses/gradeTwoSpring/OO/Homework4/src
touch courses/gradeTwoSpring/OO/Homework4/src/hw4.jar
mkdir courses/gradeTwoSpring/OO/Homework5/src
touch courses/gradeTwoSpring/OO/Homework5/src/hw5.jar
mkdir courses/gradeTwoSpring/OS/OSLab
mkdir courses/gradeTwoSpring/OS/OSTheory
mkdir courses/gradeTwoSpring/OS/OSLab/reports
touch courses/gradeTwoSpring/OS/OSLab/reports/reports1.pdf
touch courses/gradeTwoSpring/OS/OSLab/reports/reports2.pdf
touch courses/gradeTwoSpring/OS/OSLab/reports/reports3.pdf
touch courses/gradeTwoSpring/OS/OSLab/reports/reports4.pdf
touch courses/gradeTwoSpring/OS/OSLab/reports/reports5.pdf
touch courses/gradeTwoSpring/OS/OSLab/reports/reports6.pdf
touch courses/gradeTwoSpring/OS/OSLab/reports/reports9.pdf
touch courses/gradeTwoSpring/OS/OSLab/reports/reports7.pdf
touch courses/gradeTwoSpring/OS/OSLab/reports/reports8.pdf
touch courses/gradeTwoSpring/OS/OSTheory/PPT1.pptx
touch courses/gradeTwoSpring/OS/OSTheory/PPT2.pptx
touch courses/gradeTwoSpring/OS/reports.zip
-----------------------------------showing--------------------------------------
tree
tree /
tree courses
tree courses/gradeTwoSpring/OO
history
/*show choose history by up & down*/