2022北航操作系统lab5-2 课上测试解题思路分享
2022北航操作系统lab5-2 课上测试解题思路分享
在此仅给出我做题时的一些想法和理解,希望对大家有所帮助,如有表述不当之处还望多多指正。
exam
主要是让我们为open
函数新增三种打开模式。
O_APPEND
当以O_APPEND
选项打开文件时,写的内容会添加在文件最后(即偏移指针为文件大小)。
题目中已经给出了提示,只需要修改user/file.c 中的 open 函数,那我们就想open函数中哪里有涉及到文件指针偏移。
在lab5-2的课下学习中,应当对文件系统中涉及的几个结构体有所了解,而文件指针偏移就是在Fd
结构体中定义的。
struct Fd
{
u_int fd_dev_id;
u_int fd_offset;
u_int fd_omode;
};
所以只需要在适当的位置改这个结构体中的fd_offset
就可以了,那什么位置是适当的位置呢,首先我们得知道文件大小吧,文件大小是在这条语句中获取的:
size = ffd->f_file.f_size;
所以只需要在这条语句之后对fd进行修改就可以了,加上如下条件判断,第一小题就解决了。
if ((mode & O_APPEND) != 0) {
fd->fd_offset = size;
}
O_ALONE
以O_ALONE
选项打开文件,使得打开文件的父进程与子进程不再共享这一文件的文件描述符的偏移指针。
题目中给出的提示是”修改文件描述符页面进行映射时的PTE_LIBRARY
标志位“。我们还是从open函数开始找哪个地方涉及到了PTE_LIBRARY
标志位,那我们就聚焦于open函数中调用的方法。fsipc_open
无疑是最核心的一个函数,进入这个方法,我们发现这个方法其实只是封装好了一个请求,然后由fsipc
方法发送给文件服务进程,最终我们在fs/serv.c中找到了open的最终实现函数serve_open
。而这个函数的最后一行ipc_send
正好涉及到了标志位,所以我们只需要加一个判断条件,来决定是否需要PTE_LIBRARY
标志:
if (rq->req_alone == 1)
ipc_send(envid, 0, (u_int)o->o_ff, PTE_V | PTE_R)
else
ipc_send(envid, 0, (u_int)o->o_ff, PTE_V | PTE_R | PTE_LIBRARY);
当然上述只是最核心的思路,如何将这个O_ALONE
的条件一层层传到这里也是要解决的问题,我的方法是修改传输数据的结构体Fsreq_open
,在里面加上了一个属性req_alone
,相关的函数及其定义也需要做一定的修改。
可能有些同学像我一样也注意到了open函数中的fsipc_map
函数,因为它对应的文件服务进程中的函数serve_map
也有对权限位的相关操作。但是后来想一想,fsipc_map
是在映射文件对应的页,而我们想要修改的只是对偏移指针的共享,所以无需修改serve_map
。
O_CREAT
以O_CREAT
选项打开文件,使得文件不存在时会自动创建一个空文件,并正常打开它。
我们还是和上一个小题一样顺藤摸瓜,看哪里在判断文件是否存在。最后在文件服务进程的serve_open
函数中找到了file_open
,这里判断如果文件不存在就直接返回错误码。
if ((r = open_alloc(&o)) < 0) {
user_panic("open_alloc failed: %d, invalid path: %s", r, path);
ipc_send(envid, r, 0, 0);
}
那我们就在当文件不存在时创建一个就好了,提示中也告诉我们用file_create
函数。那么修改后的代码如下:
if ((r = file_open((char *)path, &f)) < 0) {
// 如果以O_CREAT打开,并且错误类型为E_NOT_FOUND
if (rq->req_create == 1 && r == -E_NOT_FOUND) {
r = file_create((char*)path, &f); // 新建一个文件
if (r < 0) {
writef("O_CREAT create error!");
ipc_send(envid, r, 0, 0);
return;
}
} else { // 不以O_CREAT打开还是会返回错误
ipc_send(envid, r, 0, 0);
return;
}
}
extra
方法一
extra相当于让我们实现一个ls
指令,只不过我们只需要返回当前目录下的普通文件,目录文件可以不管。
那么按照exam的思路,我们应该这么做:在user/file.c中实现list_dir
函数,在user/fsipc.c中实现fsipc_ls
函数,新建一个Fsreq_ls
结构体用于传输请求,在fs/serv.c中实现serve_ls
,在fs/fs.c中实现最底层的dir_ls
函数(和dir_lookup
平级)。
整个实现的调用次序为:list_dir
调用fsipc_ls
向文件服务进程发送Fsreq_ls
请求,由服务进程中的相关函数serve_ls
来处理该请求,serve_ls
则通过调用底层的dir_ls
来实现。
那么我们的重点就是实现dir_ls
方法,和dir_lookup
的实现很类似(因此可以复制过来直接修改):
int dir_ls(struct File *dir, char* ans)
{
int r;
u_int i, j, k, nblock;
void *blk;
struct File *f;
int p = 0; // ans数组的长度
nblock = ROUND(dir->f_size, BY2BLK) / BY2BLK;
for (i = 0; i < nblock; i++) {
r = file_get_block(dir, i, &blk);
if (r < 0) return r;
for (j = 0; j < FILE2BLK; j++) {
f = ((struct File *)blk) + j;
// 实现的重点,如果是普通文件,则将文件名拼接到ans(注意MOS中没有strcat函数,所以只能自己写)
if (f->f_type == FTYPE_REG) {
for (k = 0; k < strlen(f->f_name); k++, p++) {
ans[p] = f->f_name[k];
}
ans[p] = ' ';
p++;
}
}
}
if (p > 0) p--;
ans[p] = '\0';
return 0;
}
当然这样写确实可以获得最终结果,但是问题是我们最终要传出去的是一个字符串,而不是直接在最开始传入的*ans
上直接改(这样值是写不进去的)。所以我们返回这个字符串的时候就必须重新分配一页空间,题中也提示到了”如果用户进程接受IPC映射的 dst 页面已经被映射,需要调用 syscall_mem_unmap 解除映射关系“。最终我们把这一页中的结果再复制给*ans
就可以了。
方法二
上一种方法可以做,但反复这么折腾确实挺麻烦,所以想简化的话可以不写那么多函数,直接在顶层的list_dir
中写具体的操作(当然这样肯定违背了这道题考查的意图)。
int list_dir(const char *path, char* ans)
{
int r, va;
struct Fd* fd;
struct File* f;
// 直接打开目录文件,看是否存在
r = open(path, O_RDWR);
if (r < 0) return -1;
r = fd_lookup(r, &fd);
if (r < 0) return -1;
// 同open
va = fd2data(fd);
ffd = (struct Filefd*)fd;
fileid = ffd->f_fileid;
size = ffd->f_file.f_size;
f = (struct File*)va; // 获得文件指针
// 接下来的操作和方法一的dir_ls函数差不多,就不再赘述了
}