武汉大学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等。