武汉大学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:\n");
printf("*** \n");
printf("My student number is:\n");
printf("***\n");
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.\n", argv[1]);
}else {
printf("Failed to create %s, maybe because this file has been existed.\n",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.\n");
return -1;
}
printf("Successfully removed file %s\n", 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\n", msg.pBUF);
// printl("BUF : %s\n", 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\n", 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>\n");
return 1;
}
// 打开或创建文件以写入数据
int fd = open(argv[argc - 1], O_RDWR | O_CREAT);
if (fd < 0) {
printf("Failed to open or create %s\n", 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, "\n", 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>\n");
return 1;
}
// 打开文件
int fd = open(argv[1], O_RDWR);
if (fd < 0) {
printf("Failed to open %s\n", 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>\n");
return 1;
}
// 尝试以打开源文件
int src_fd = open(argv[1], O_RDWR);
if (src_fd < 0) {
printf("Failed to open source file %s\n", 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\n", 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\n");
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等。