操作系统大作业——基于OrangeOS的改写(2)

武汉大学21级网安操作系统大作业 任务二

 1. 问题介绍与思路摘要

 1.1问题介绍

 本任务是 OS 期末实验的第二个基本任务,主要目标是理清代码结构并扩展shell,提供一些自制的指令方便后续实验。具体要求如下:

? 利用当前OrangeS所提供的系统调用和API,编写2个以上可执行程序(功能自定),并编译生成存储在文件系统中

? 分析教材的Shell代码,画出Shell的流程图,在Shell中调入你所编写的可执行程序,启动并执行进程,注意使用教材中所提供的系统调用来实现

? 进程结束后返回Shell

 1.2 解决方案摘要

在chapter11/a的基础上,我首先实现了一个基本的测试指令test,其可以输出我的学号姓名;然后实现了linux系统中常见的文件操作指令touch(创造文件)和rm(删除文件),以及常用的目录操作指令ls(显示当前目录下的所有文件),又改写了echo指令(写入内容到文件),添加了cat指令(查看文件内容)、cp指令(复制文件)。

 2. 具体思路及其实现

这部分实验原理实际上是较为简单的。在第十章书上实现了“简单的shell”过后,oranges操作系统已经具备了将command中的.c文件根据makefile的指令进行编译、链接、并装载入操作系统中的能力。之后,在bochs中输入命令名称就可以调用这些指令了。 因此,要扩展shell,编写更多的可执行指令,只需要在command 中编写其代码即可。 

下面是教材中shell的流程图: (不知道为啥这么糊)

 

接着,我将讨论我实现的指令及其思路。 

 2.1 test指令

 test指令是我用来测试的,显而易见,就是输出我的姓名拼音和学号。

int main(int argc, char * argv[])  
{  
    printf("hello, my name is:
");  
    printf("*** 
");  
    printf("My student number is:
");  
    printf("***
");  
    return 0;  
}  

然后,在Makefile中添加编译信息,仿照着其他指令来: 

test.o: test.c ../include/type.h ../include/stdio.h  
    $(CC) $(CFLAGS) -o $@ $<  
  
test : test.o start.o $(LIB)  
    $(LD) $(LDFLAGS) -o $@ $?  
BIN     = echo pwd test

先在根目录make image,再进入command文件夹make、make install,最后退出来make image。 

摁住shift+F2后输入test,得到如下: 

 

 所以test指令添加成功。

 2.2 torch和rm指令

 实现linux系统中常见的文件操作指令touch(创造文件)和rm(删除文件)。这部分的主要思路是利用open和unlink两个函数。

 touch.c
利用了调用open(file_name, O_CREAT) 时,若file_name不存在则会自动创建的特征。使用~判断返回值是否为-1(出错)。如果出错则报错并提示用户,最常见的可能性是在尝试创建已经存在的文件。

#include "stdio.h"  
  
int main(int args, char * argv[])  
{  
    if(~open(argv[1], O_CREAT)) {  
        printf("Successfully created %s.
", argv[1]);  
    }else {  
        printf("Failed to create %s, maybe because this file has been existed.
",argv[1]);  
    }  
    return 0;  
}  

rm.c 
rm指令的实现方式类似,采用的是unlink函数,unlink从文件系统中中删除一个名字,若这个名字是指向这个文件的最后一个链接,并且没有进程处于打开这个文件的状态,则删除这个文件,释放这个文件占用的空间。调用成功返回0,不成功返回-1。

#include "stdio.h"  
  
int main(int args, char * argv[])  
{  
    if (~unlink(argv[1])) {  
        printf("rm file failed, please try again later or check your authority.
");  
        return -1;  
    }  
    printf("Successfully removed file %s
", argv[1]);  
    return 0;  
}  

编写完上述指令后,在Makefile文件中修改相应指令,然后再make、make install、make image将可执行文件装载入软盘即可在bochs中执行,步骤和上述一样。执行结果如下: 

现在的验证看起来还较为苍白和没有说服力,之后我们将使用ls指令来验证我们是否真的创建和删除了这些文件。 

 2.3 ls指令

 接下来实现的是linux文件系统中另一个常用的指令ls,调用其将打印出文件系统中,当前路径下的所有文件的名称。真正的ls还带有不少其余功能,比如用参数-a显示隐藏文件,-d显示目录名等等,这里我们只实现最简单的、原始的功能。

ls指令的实现思路如下:在fs/misc.c中我们已经有了实现的search_file,所以我们可以依葫芦画瓢,模拟一个进程间的通信,仿造原有的search_file指令写一个search_dir函数,在本目录下扫描文件名并写入一条message中,之后返回这个message即可。 

具体来说,我们首先在fs文件夹下的main.c中添加ls的case具体为首先找到 task_fs,然后添加如下case: 

case SEARCH:  
    fs_msg.BUF = do_search_dir();

 下面的switch(msgtype)也要添加case:

case SEARCH:

 意思是之后的message type如果是search就调用search_dir函数,并将返回的结果存储在缓冲区BUF中。

 添加完这部分调用过后,我们需要在lib文件夹下添加一个发消息的接口。接下来,我们模仿lib文件夹下的代码,写一个发消息的文件

search_dir.c 

#include "type.h"  
#include "stdio.h"  
#include "const.h"  
#include "protect.h"  
#include "string.h"  
#include "fs.h"  
#include "proc.h"  
#include "tty.h"  
#include "console.h"  
#include "global.h"  
#include "keyboard.h"  
#include "proto.h"  
  
PUBLIC char* search_dir(char* path) {  
    MESSAGE msg;  
    msg.type = SEARCH;  
    memcpy(msg.pBUF, path, strlen(path));  
    // printl("msg.pBug address is %d
", msg.pBUF);  
    // printl("BUF : %s
", msg.pBUF);  
    send_recv(BOTH, TASK_FS, &msg);  
    return msg.pBUF;  
}  

此处是给message.type枚举体增加了一个属性SEARCH,用来之后确认是否是ls指令。 

 然后在type.h中添加一个Path数组,用来存储未来搜索到的结果。Path数组同样需要在message定义处增加定义。

typedef struct {  
    int source;  
    int type;  
    char Path[200];  
    union {  
        struct mess1 m1;  
        struct mess2 m2;  
        struct mess3 m3;  
    } u;  
} MESSAGE;  

最后,就是在fs文件夹下的misc.c中写一个do_search_dir()函数,搜索当前路径下的文件,将文件名存储到buf中然后返回。具体来说,我们添加一个函数,基本上就是照着search_file写,只不过最后一步将地址拷贝到缓冲区中: 

 已有的search_file()

PUBLIC int search_file(char * path)  
{  
    int i, j;  
  
    char filename[MAX_PATH];  
    memset(filename, 0, MAX_FILENAME_LEN);  
    struct inode * dir_inode;  
    if (strip_path(filename, path, &dir_inode) != 0)  
        return 0;  
  
    if (filename[0] == 0)   /* path: "/" */  
        return dir_inode->i_num;  
  
    /**  
     * Search the dir for the file.  
     */  
    int dir_blk0_nr = dir_inode->i_start_sect;  
    int nr_dir_blks = (dir_inode->i_size + SECTOR_SIZE - 1) / SECTOR_SIZE;  
    int nr_dir_entries =  
      dir_inode->i_size / DIR_ENTRY_SIZE; /**  
                           * including unused slots  
                           * (the file has been deleted  
                           * but the slot is still there)  
                           */  
    int m = 0;  
    struct dir_entry * pde;  
    for (i = 0; i < nr_dir_blks; i++) {  
        RD_SECT(dir_inode->i_dev, dir_blk0_nr + i);  
        pde = (struct dir_entry *)fsbuf;  
        for (j = 0; j < SECTOR_SIZE / DIR_ENTRY_SIZE; j++,pde++) {  
            if (memcmp(filename, pde->name, MAX_FILENAME_LEN) == 0)  
                return pde->inode_nr;  
            if (++m > nr_dir_entries)  
                break;  
        }  
        if (m > nr_dir_entries) /* all entries have been iterated */  
            break;  
    }  
  
    /* file not found */  
    return 0;  
}  

添加的do_search_dir() 

PUBLIC int do_search_dir() {  
    char* dir = fs_msg.Path;  
    int pointer = 0;  
  
    int i, j;  
  
    char filename[MAX_PATH];  
    memset(filename, 0, MAX_FILENAME_LEN);  
    struct inode* dir_inode;  
    if (strip_path(filename, dir, &dir_inode)) {  
        return 0;  
    }  
  
    // if (filename[0] == 0) {  
    // return dir_inode->i_num;  
    // }  
    /**  
     * Search the dir for the file.  
     */  
    int dir_blk0_nr = dir_inode->i_start_sect;  
    int nr_dir_blks = (dir_inode->i_size + SECTOR_SIZE - 1) / SECTOR_SIZE;  
    int nr_dir_entries = dir_inode->i_size / DIR_ENTRY_SIZE; /**  
                                     * including unused slots  
                                     * (the file has been deleted  
                                     * but the slot is still there)  
                                                            */  
    struct dir_entry* pde;  
    for (i = 0; i < nr_dir_blks; i++) {  
        RD_SECT(dir_inode->i_dev, dir_blk0_nr + i);  
        pde = (struct dir_entry*)fsbuf;  
  
        for (j = 0; j < SECTOR_SIZE / DIR_ENTRY_SIZE; j++, pde++) {  
            dir[pointer] = ' ';  
        pointer += 1;  
        memcpy(dir + pointer, pde->name, strlen(pde->name));  
        pointer += strlen(pde->name);  
        }  
    }  
    return (void*)0;  
 }  

然后在proto.h中声明, Makefile中也添加相应的

完成过后,就可以在函数里调用 search_dir() 这个函数来获取地址了。之后我们只需要在 command文件夹下编写这部分代码即可。 

ls.c 

#include "stdio.h"  
#include "const.h"  
#include "string.h"  
#include "fs.h"  
  
int main (int args, char* argv[])  
{  
    char* result;  
    result = search_dir("/");  
    printf("%s
", result);  
    return 0;  
}  

 编译运行

最后测试是否能成功调用命令。有了 ls 命令,我们还能一并测试刚刚写的 touch、rm 命令是否真的成功创建、删除了文件。我们测试的结果如下: 

 

可以看到,在touch后ls,确实出现了刚刚我们创建的文件。同理,rm也确实能删除文件,这说明我们创建的这三个命令都是正常工作的。 

 2.4 echo指令

原有的echo指令功能有些简陋,只能够实现打印出所有通过命令行传入的参数,比如echo 123,就打印出“123”。我将其功能进行了改写,即通过命令行传入的文本参数写入到指定的文件中,比如echo dp dp.txt,就能够将dp写入dp.txt文件中。 

 改写的echo思路如下:

先看命令格式是否正确,即echo <text> <filename>。正确就调用open打开或创建文件,然后调用write写入文件,最后关闭文件。思路很简单,代码如下:

echo.c 

#include "stdio.h"  
#include "string.h"  
  
int main(int argc, char *argv[]) {  
    if (argc < 3) {  
        printf("Usage: echo <text> <filename>
");  
        return 1;  
    }  
  
    // 打开或创建文件以写入数据  
    int fd = open(argv[argc - 1], O_RDWR | O_CREAT);  
    if (fd < 0) {  
        printf("Failed to open or create %s
", argv[argc - 1]);  
        return 1;  
    }  
  
    int i;  
    // 遍历所有参数并写入文件,除了最后一个参数(文件名)  
    for (i = 1; i < argc - 1; i++) {  
        write(fd, argv[i], strlen(argv[i]));  
        if (i < argc - 2) {  
            write(fd, " ", 1);  // 在单词之间添加空格  
        }  
    }  
    write(fd, "
", 1);  // 添加换行符  
  
    // 关闭文件  
    close(fd);  
    return 0;  
}  

因为是改写,Makefile中已经存在,所以不必修改。编译执行效果如下: 

显然,生成了一个dp.txt文件,但是我们不知道hello,dp是否写入该文件,所以我又添加了一条指令,即cat指令,来帮助我们验证。 

 2.5 cat指令

cat代码基本思路如下:

先看命令格式是否正确,即cat <filename>。正确就打开文件,然后读取并输出,最后关闭文件。代码如下:

cat.c 

#include "stdio.h"  
#include "string.h"  
  
#define BUFFER_SIZE 256  
  
int main(int argc, char *argv[]) {  
    if (argc != 2) {  
        printf("Usage: cat <filename>
");  
        return 1;  
    }  
  
    // 打开文件  
    int fd = open(argv[1], O_RDWR);  
    if (fd < 0) {  
        printf("Failed to open %s
", argv[1]);  
        return 1;  
    }  
  
    char buf[BUFFER_SIZE];  
    int n;  
  
    //读取并输出   
    while ((n = read(fd, buf, BUFFER_SIZE)) > 0) {  
        write(1, buf, n);  // 1 是标准输出文件描述符  
    }  
  
    // 关闭文件  
    close(fd);  
    return 0;  
}  

Makefile改写和编译过程跟之前一样,最终代码效果如下: 

 可以看到文件内容,成功。

 2.6 cp指令

做完cat指令,我仔细思考,发现还可以加一个指令即,cp指令。因为思路是一脉相承的。 

我的思路是这样的:

依旧是先看命令行格式是否正确,即cp <source> <destination>,source是源文件,destination是复制到的文件。然后分别打开源文件和目标文件,目标文件若不存在就创建。之后,复制文件内容,程序使用 read 函数从源文件读取数据到缓冲区,并用 write 函数将数据写入目标文件,这个过程持续进行,直到源文件的所有内容都被读取和写入。最后关闭文件。

cp.c 

#include "stdio.h"  
#include "string.h"  
  
#define BUFFER_SIZE 256  
  
int main(int argc, char* argv[]) {  
    if (argc != 3) {  
        printf("Usage: cp <source> <destination>
");  
        return 1;  
    }  
  
    // 尝试以打开源文件  
    int src_fd = open(argv[1], O_RDWR);  
    if (src_fd < 0) {  
        printf("Failed to open source file %s
", argv[1]);  
        return 1;  
    }  
  
    // 尝试打开目标文件,如果不存在则创建  
    int dest_fd = open(argv[2], O_RDWR | O_CREAT);  
    if (dest_fd < 0) {  
        printf("Failed to open or create destination file %s
", argv[2]);  
        close(src_fd);  
        return 1;  
    }  
  
    char buf[BUFFER_SIZE];  
    int n;  
  
    while ((n = read(src_fd, buf, BUFFER_SIZE)) > 0) {  
        if (write(dest_fd, buf, n) != n) {  
            printf("Failed to write to destination file
");  
            close(src_fd);  
            close(dest_fd);  
            return 1;  
        }  
    }  
  
    close(src_fd);  
    close(dest_fd);  
    return 0;  
}  

Makefile改写和编译过程跟之前一样,最终代码效果如下: 

 

文件复制成功,内容一致,成功。 

 3. 工作局限性和改进方向

 3.1 工作局限性

 尽管我们已经实现了linux文件系统中最核心的几个指令,即touch、rm、ls、echo、cat、cp,也通过充分的实验验证了所提议方法的正确性,但我们的工作仍然并非十全十美,主要的局限性有:

? 正如前文所说,我所实现的指令都只实现了其最基本的功能。但是,在真实的linux系统中,命令会有更多的参数以完成其他功能。

? rm指令在删除文件的时候存在一些小问题。具体来说,在上次会话中创建的文件保留了下来,在本次会话中,我想用rm删除的时候却发生了assert报错。但是删除本次会话中创建的文件却不会发送报错,不知道为什么。

? echo功能并不完善比如对于已有的文件dp.txt,echo 123 dp.txt,应当把123继续放到文件中中,而不是输出报错。

尽管如此,我们有理由相信在完成了这些命令的基础上,要补充携带参数的功能或打印帮助信息等并非难事,只需要添加case来判断出参数即可。 

3.2 改进方向 

 根据上面所说的局限性,这部分工作未来可以有以下几个改进方向:

? 为指令添加上解析携带参数的功能;

? 实现更多常用指令,如mkdir、grep等。