//fs/serv.h #define PTE_DIRTY 0x0004 // file system block cache is dirty
#define SECT_SIZE 512 /* Bytes per disk sector */ #define SECT2BLK (BLOCK_SIZE / SECT_SIZE) /* sectors to a block */
/* Disk block n, when in memory, is mapped into the file system * server's address space at DISKMAP+(n*BLOCK_SIZE). */ #define DISKMAP 0x10000000
/* Maximum disk size we can handle (1GB) */ #define DISKMAX 0x40000000
//user/include/fs.h #define BLOCK_SIZE PAGE_SIZE ... // Maximum size of a filename (a single path component), including null #define MAXNAMELEN 128 // Maximum size of a complete pathname, including null #define MAXPATHLEN 1024
// Number of (direct) block pointers in a File descriptor #define NDIRECT 10 #define NINDIRECT (BLOCK_SIZE / 4)
init.c: mips_init() is called Memory size: 65536 KiB, number of pages: 16384 to memory 80430000for struct Pages. pmap.c: mips vm init success FS is running superblock is good read_bitmap is good open is good!!! readis good!!! father readis good && father's fd == 0!!! father's fd's offset == 40!!! child readis good && child's fd == 0!!! child's fd's offset == 40!!! [00000800] destroying 00000800 [00000800] free env 00000800 i am killed ... [00001802] destroying 00001802 [00001802] free env 00001802 i am killed ... panic at sched.c:46 (schedule): schedule: no runnable envs
voidide_read(u_int diskno, u_int secno, void *dst, u_int nsecs) { uint8_t temp; //offset代表每个扇区的首地址相对于dst的偏移 //max是读取扇区号的最大值+1(从0开始计) u_int offset = 0, max = nsecs + secno; //由于MOS实验只挂载了0、1号两块磁盘,因此当磁盘号大于1时非法 panic_on(diskno >= 2); // Read the sector in turn while (secno < max) { //等待IDE设备就绪 temp = wait_ide_ready(); // Step 1: Write the number of operating sectors to NSECT register //写入操作扇区数为1 temp = 1; panic_on(syscall_write_dev(&temp, MALTA_IDE_NSECT, 1)); // Step 2: Write the 7:0 bits of sector number to LBAL register //写入扇区号的[7:0]位 temp = secno & 0xff; panic_on(syscall_write_dev(&temp, MALTA_IDE_LBAL, 1)); // Step 3: Write the 15:8 bits of sector number to LBAM register /* Exercise 5.3: Your code here. (1/9) */ //写入[15:8]位 temp = (secno >> 8) & 0xff; panic_on(syscall_write_dev(&temp, MALTA_IDE_LBAM, 1)); // Step 4: Write the 23:16 bits of sector number to LBAH register /* Exercise 5.3: Your code here. (2/9) */ //写入[23:16]位 temp = (secno >> 16) & 0xff; panic_on(syscall_write_dev(&temp, MALTA_IDE_LBAH, 1)); // Step 5: Write the 27:24 bits of sector number, addressing mode // and diskno to DEVICE register //写入[27:24]位 temp = ((secno >> 24) & 0x0f) | MALTA_IDE_LBA | (diskno << 4); panic_on(syscall_write_dev(&temp, MALTA_IDE_DEVICE, 1)); // Step 6: Write the working mode to STATUS register //写入IDE设备的状态为读取状态 temp = MALTA_IDE_CMD_PIO_READ; panic_on(syscall_write_dev(&temp, MALTA_IDE_STATUS, 1)); // Step 7: Wait until the IDE is ready //等待设备就绪 temp = wait_ide_ready(); // Step 8: Read the data from device //进行读取操作 for (int i = 0; i < SECT_SIZE / 4; i++) { //将第diskno号磁盘的第secno号扇区的数据读入dst偏移offset的地址中 panic_on(syscall_read_dev(dst + offset + i * 4, MALTA_IDE_DATA, 4)); } // Step 9: Check IDE status //检查IDE设备状态 panic_on(syscall_read_dev(&temp, MALTA_IDE_STATUS, 1)); //offset增加一个扇区的大小,扇区号自增一,表示进入处理下一个扇区的循环 offset += SECT_SIZE; secno += 1; } }
//大部分操作与ide_read()一致 voidide_write(u_int diskno, u_int secno, void *src, u_int nsecs) { uint8_t temp; u_int offset = 0, max = nsecs + secno; panic_on(diskno >= 2); // Write the sector in turn while (secno < max) { temp = wait_ide_ready(); // Step 1: Write the number of operating sectors to NSECT register /* Exercise 5.3: Your code here. (3/9) */ temp = 1; panic_on(syscall_write_dev(&temp, MALTA_IDE_NSECT, 1)); // Step 2: Write the 7:0 bits of sector number to LBAL register /* Exercise 5.3: Your code here. (4/9) */ temp = secno & 0xff; panic_on(syscall_write_dev(&temp, MALTA_IDE_LBAL, 1)); // Step 3: Write the 15:8 bits of sector number to LBAM register /* Exercise 5.3: Your code here. (5/9) */ temp = (secno >> 8) & 0xff; panic_on(syscall_write_dev(&temp, MALTA_IDE_LBAM, 1)); // Step 4: Write the 23:16 bits of sector number to LBAH register /* Exercise 5.3: Your code here. (6/9) */ temp = (secno >> 16) & 0xff; panic_on(syscall_write_dev(&temp, MALTA_IDE_LBAH, 1)); // Step 5: Write the 27:24 bits of sector number, addressing mode // and diskno to DEVICE register /* Exercise 5.3: Your code here. (7/9) */ temp = ((secno >> 24) & 0x0f) | MALTA_IDE_LBA | (diskno << 4); panic_on(syscall_write_dev(&temp, MALTA_IDE_DEVICE, 1)); // Step 6: Write the working mode to STATUS register /* Exercise 5.3: Your code here. (8/9) */ temp = MALTA_IDE_CMD_PIO_WRITE; panic_on(syscall_write_dev(&temp, MALTA_IDE_STATUS, 1)); // Step 7: Wait until the IDE is ready temp = wait_ide_ready(); // Step 8: Write the data to device for (int i = 0; i < SECT_SIZE / 4; i++) { /* Exercise 5.3: Your code here. (9/9) */ //将src偏移offset地址上的数据写入第diskno号磁盘的第secno号扇区上 panic_on(syscall_write_dev(src + offset + i * 4, MALTA_IDE_DATA, 4)); } // Step 9: Check IDE status panic_on(syscall_read_dev(&temp, MALTA_IDE_STATUS, 1)); offset += SECT_SIZE; secno += 1; } }
Exercise 5.4
题面
1 2 3 4 5 6 7 8 9 10
文件系统需要负责维护磁盘块的申请和释放,在回收一个磁盘块时,需要更改位图中的标志位。如果要将一个磁盘块设置为 free,只需要将位图中对应位的值设置为 1 即可。请完成 fs/fs.c 中的 free_block 函数,实现这一功能。同时思考为什么参数blockno 的值不能为 0? 1// Overview: 2// Mark a block as free in the bitmap. 3voidfree_block(u_int blockno) { 4// You can refer to the function 'block_is_free' above. 5// Step 1: If 'blockno' is invalid (0 or >= the number of blocks in 'super'), return. 6 7// Step 2: Set the flag bit of 'blockno' in 'bitmap'. 8// Hint: Use bit operations to update the bitmap, such as b[n / W] |= 1 << (n % W). 9 }
回答
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12
voidfree_block(u_int blockno) { // You can refer to the function 'block_is_free' above. // Step 1: If 'blockno' is invalid (0 or >= the number of blocks in 'super'), return. /* Exercise 5.4: Your code here. (1/2) */ if (!blockno || blockno >= super->s_nblocks) { return ; } // Step 2: Set the flag bit of 'blockno' in 'bitmap'. // Hint: Use bit operations to update the bitmap, such as b[n / W] |= 1 << (n % W). /* Exercise 5.4: Your code here. (2/2) */ bitmap[blockno / 32] |= 1 << (blockno % 32); }
voidsave_block_link(struct File *f, int nblk, int bno) { assert(nblk < NINDIRECT); // if not, file is too large !
if (nblk < NDIRECT) { f->f_direct[nblk] = bno; } else { if (f->f_indirect == 0) { // create new indirect block. f->f_indirect = next_block(BLOCK_INDEX); } ((uint32_t *)(disk[f->f_indirect].data))[nblk] = bno; } } // Make new block contains link to files in a directory. intmake_link_block(struct File *dirf, int nblk) { int bno = next_block(BLOCK_FILE); save_block_link(dirf, nblk, bno); dirf->f_size += BLOCK_SIZE; return bno; }
struct File *create_file(struct File *dirf) { //获取当前目录一共占用了多少块磁盘块 int nblk = dirf->f_size / BLOCK_SIZE; // Step 1: Iterate through all existing blocks in the directory. for (int i = 0; i < nblk; ++i) { int bno; // the block number // If the block number is in the range of direct pointers (NDIRECT), get the 'bno' // directly from 'f_direct'. Otherwise, access the indirect block on 'disk' and get // the 'bno' at the index. /* Exercise 5.5: Your code here. (1/3) */ //遍历每一个磁盘块寻找空的文件控制块来填入新文件 if (i >= 0 && i < NDIRECT) { //遍历到直接指针处 //直接从目录的文件控制块中获取相应的磁盘块号 bno = dirf->f_direct[i]; } else { //遍历到间接指针处 //从指向的磁盘块的数据中获取相应的磁盘块号,通过解引用的方式 bno = *((uint32_t *)(disk[dirf->f_indirect].data) + i); } // Get the directory block using the block number. //获取对应磁盘块中存储的第一个文件控制块指针 structFile *blk = (struct File *)(disk[bno].data); // Iterate through all 'File's in the directory block. for (struct File *f = blk; f < blk + FILE2BLK; ++f) { // If the first byte of the file name is null, the 'File' is unused. // Return a pointer to the unused 'File'. /* Exercise 5.5: Your code here. (2/3) */ //遍历当前磁盘块中的所有文件控制块指针,若其文件名为空,说明 //该文件控制块为空,可以分配出去,返回相应指针 if (f->f_name[0] == '\0') { return f; } } }
// Step 2: If no unused file is found, allocate a new block using 'make_link_block' function // and return a pointer to the new block on 'disk'. /* Exercise 5.5: Your code here. (3/3) */ //若当前目录占有的所有磁盘块对应文件控制块都满了,则利用make_link_block()新申请一块磁盘块 int bno = make_link_block(dirf, nblk); if (bno >= 0 && bno < NBLOCK) { //新申请的磁盘块号在满足磁盘块号范围的前提下返回该磁盘块的第一个文件控制块指针 //新分配出来的肯定是空闲的啦! return (struct File *)(disk[bno].data); } //否则返回NULL returnNULL; }
intmap_block(u_int blockno) { // Step 1: If the block is already mapped in cache, return 0. // Hint: Use 'block_is_mapped'. /* Exercise 5.7: Your code here. (1/5) */ //利用block_is_mapped函数判断给定的磁盘号是否已经存在与内存的映射关系 if (block_is_mapped(blockno)) { return0; } // Step 2: Alloc a page in permission 'PTE_D' via syscall. // Hint: Use 'disk_addr' for the virtual address. /* Exercise 5.7: Your code here. (2/5) */ //利用syscall_mem_alloc()系统调用获取一块内存 try(syscall_mem_alloc(0, disk_addr(blockno), PTE_D)); return0; }
voidunmap_block(u_int blockno) { // Step 1: Get the mapped address of the cache page of this block using 'block_is_mapped'. void *va; /* Exercise 5.7: Your code here. (3/5) */ //获取建立了映射关系的磁盘对应的虚地址 va = block_is_mapped(blockno); // Step 2: If this block is used (not free) and dirty in cache, write it back to the disk // first. // Hint: Use 'block_is_free', 'block_is_dirty' to check, and 'write_block' to sync. /* Exercise 5.7: Your code here. (4/5) */ //在相应内存块不为free并且被置了脏位的情况下,回写回磁盘 if (!block_is_free(blockno) && block_is_dirty(blockno)) { write_block(blockno); } // Step 3: Unmap the virtual address via syscall. /* Exercise 5.7: Your code here. (5/5) */ //利用syscall_mem_unmap()系统调用解除虚地址与内存的映射关系 //进而解除了与磁盘块的映射关系 //因为磁盘块与虚地址存在映射关系 //而内存通过与虚地址建立映射关系达到磁盘块与内存建立映射关系的效果 panic_on(syscall_mem_unmap(0, va)); user_assert(!block_is_mapped(blockno)); }
intdir_lookup(struct File *dir, char *name, struct File **file) { // Step 1: Calculate the number of blocks in 'dir' via its size. u_int nblock; /* Exercise 5.8: Your code here. (1/3) */ //获得当前目录文件一共占用了多少块磁盘块 nblock = dir->f_size / BLOCK_SIZE; // Step 2: Iterate through all blocks in the directory. for (int i = 0; i < nblock; i++) { // Read the i'th block of 'dir' and get its address in 'blk' using 'file_get_block'. void *blk; /* Exercise 5.8: Your code here. (2/3) */ //利用file_get_block(struct File * f, u_int fileno, void ** blk) //将f文件控制块下占有的第fileno块磁盘块的首地址在不发生错误的情况下写入*blk中 try(file_get_block(dir, i, &blk)); structFile *files = (struct File *)blk;
// Find the target among all 'File's in this block. for (struct File *f = files; f < files + FILE2BLK; ++f) { // Compare the file name against 'name' using 'strcmp'. // If we find the target file, set '*file' to it and set up its 'f_dir' // field. /* Exercise 5.8: Your code here. (3/3) */ //找到同名文件,完成相应的修改f_dir和赋值的要求 //正常返回0 if (!strcmp(name, f->f_name)) { f->f_dir = dir; *file = f; return0; } } } return -E_NOT_FOUND; }
Exercise 5.9
题面
1
请完成 user/lib/file.c 中的 open 函数。(提示:若成功打开文件,则该函数返回文件描述符的编号)。
//user/lib/file.c intopen(constchar *path, int mode) { int r; // Step 1: Alloc a new 'Fd' using 'fd_alloc' in fd.c. // Hint: return the error code if failed. structFd *fd; /* Exercise 5.9: Your code here. (1/5) */ //通过fd_alloc()函数获得一个新的文件描述符,并将其赋给fd变量 try(fd_alloc(&fd)); // Step 2: Prepare the 'fd' using 'fsipc_open' in fsipc.c. /* Exercise 5.9: Your code here. (2/5) */ //尝试使用fsipc_open()函数以mode模式打开path路径下的文件 //其实就是利用ipc通信机制,通过文件系统服务进程进行相应的文件操作 //具体就是文件系统服务进程根据相应的请求号调用请求表中相应的serve_*()函数 //例如:此处调用的就是serve_open()函数 try(fsipc_open(path, mode, fd)); // Step 3: Set 'va' to the address of the page where the 'fd''s data is cached, using // 'fd2data'. Set 'size' and 'fileid' correctly with the value in 'fd' as a 'Filefd'. char *va; structFilefd *ffd; u_int size, fileid; /* Exercise 5.9: Your code here. (3/5) */ //调用fd2data()函数获取文件描述符表中fd对应的数据缓存页地址 //此处的fd只是一个文件信息的虚地址所在地 va = fd2data(fd); //将fd强制类型转换为表征内容更多的(struct Filefd *)类型 ffd = (struct Filefd *)fd; fileid = ffd->f_fileid; size = ffd->f_file.f_size; // Step 4: Map the file content using 'fsipc_map'. for (int i = 0; i < size; i += PTMAP) { /* Exercise 5.9: Your code here. (4/5) */ //遍历文件的所有内容,使用fsipc_map()函数为文件内容分配页面并映射 try(fsipc_map(fileid, i, va + i)); } // Step 5: Return the number of file descriptor using 'fd2num'. /* Exercise 5.9: Your code here. (5/5) */ //返回文件描述符在文件描述符表中的索引 return fd2num(fd); }
if ((fd->fd_omode & O_ACCMODE) == O_RDONLY) { return -E_INVAL; }
r = dev->dev_write(fd, buf, n, fd->fd_offset); if (r > 0) { fd->fd_offset += r; }
return r; }
intread(int fdnum, void *buf, u_int n) { int r; // Similar to the 'write' function below. // Step 1: Get 'fd' and 'dev' using 'fd_lookup' and 'dev_lookup'. structDev *dev; structFd *fd; /* Exercise 5.10: Your code here. (1/4) */ //先使用fd_lookup()和dev_lookup()函数获取文件描述符和设备结构体分别存储在fd和dev中 //若查找失败,返回错误码 try(fd_lookup(fdnum, &fd)); try(dev_lookup(fd->fd_dev_id, &dev)); // Step 2: Check the open mode in 'fd'. // Return -E_INVAL if the file is opened for writing only (O_WRONLY). /* Exercise 5.10: Your code here. (2/4) */ //检查文件描述符的打开模式是否允许读取操作 //也就是不能是O_WRONLY模式 if ((fd->fd_omode & O_ACCMODE) == O_WRONLY) { return -E_INVAL; } // Step 3: Read from 'dev' into 'buf' at the seek position (offset in 'fd'). /* Exercise 5.10: Your code here. (3/4) */ //调用设备的读取函数dev_read()从文件当前的偏移位置fd_offset读取数据到缓冲区中 r = dev->dev_read(fd, buf, n, fd->fd_offset); // Step 4: Update the offset in 'fd' if the read is successful. /* Hint: DO NOT add a null terminator to the end of the buffer! * A character buffer is not a C string. Only the memory within [buf, buf+n) is safe to * use. */ /* Exercise 5.10: Your code here. (4/4) */ //若操作成功,即r>0,修改相应的文件当前的偏移位置 if (r > 0) { fd->fd_offset += r; } //返回读取字节数,若失败则返回错误码 return r; }
//fs/fs.c intfile_remove(char *path) { int r; structFile *f; // Step 1: find the file on the disk. if ((r = walk_path(path, 0, &f, 0)) < 0) { return r; } // Step 2: truncate it's size to zero. file_truncate(f, 0); // Step 3: clear it's name. f->f_name[0] = '\0'; // Step 4: flush the file. file_flush(f); if (f->f_dir) { file_flush(f->f_dir); } return0; }
//fs/serv.c voidserve_remove(u_int envid, struct Fsreq_remove *rq) { // Step 1: Remove the file specified in 'rq' using 'file_remove' and store its return value. int r; /* Exercise 5.11: Your code here. (1/2) */ //调用了fs/fs.c中的file_remove()函数完成文件的删除 r = file_remove(rq->req_path); // Step 2: Respond the return value to the caller 'envid' using 'ipc_send'. /* Exercise 5.11: Your code here. (2/2) */ //使用ipc_send()函数返回用户进程相应的文件操作结果 ipc_send(envid, r, 0, 0); }
intfsipc_remove(constchar *path) { // Step 1: Check the length of 'path' using 'strlen'. // If the length of path is 0 or larger than 'MAXPATHLEN', return -E_BAD_PATH. /* Exercise 5.12: Your code here. (1/3) */ //先判断路径是否为空以及超出最大路径长度 //若不成立,返回错误码说明路径非法 uint32_t len = strlen(path); if (len == 0 || len >= MAXPATHLEN) { return -E_BAD_PATH; } // Step 2: Use 'fsipcbuf' as a 'struct Fsreq_remove'. //将fsipcbuf视为一个Fsreq_remove结构体,代表要发送的remove请求 structFsreq_remove *req = (struct Fsreq_remove *)fsipcbuf; // Step 3: Copy 'path' into the path in 'req' using 'strcpy'. /* Exercise 5.12: Your code here. (2/3) */ //将要删除的路径名复制到结构体对应的字段中 strcpy((char *)req->req_path, path); // Step 4: Send request to the server using 'fsipc'. /* Exercise 5.12: Your code here. (3/3) */ //调用fsipc()函数,通过ipc机制同文件系统服务进程通信实现remove的操作 return fsipc(FSREQ_REMOVE, req, 0, 0); }
structFd { u_int fd_dev_id; //文件对应的设备id u_int fd_offset; //文件的偏移量,在实验中代表读写指针(这里貌似读写指针共用) u_int fd_omode; //文件打开的模式 //共有以下三种形式 /* #define O_RDONLY 0x0000 // open for reading only #define O_WRONLY 0x0001 // open for writing only #define O_RDWR 0x0002 // open for reading and writing */ };
这次的lab函数众多,各种调用链层出不穷,重要的是理解其运作机制,还需要认真掌握每一个函数的用法(至少实验填空涉及到的都要掌握清楚,不然课上怎么考你)。以我为鉴,没有认真理解fs/fs.c中的walk_path()函数,导致函数调用出错,一直报address too low,导致助教一直以为是我后面的访问越界出问题了,以至于两个助教都没看出来,其实walk_path()调用就有问题,也是长教训了,没认真理解函数就动手敲代码是为大忌,其实但凡当时现场认真看应该也不会出错,害,就应该模仿file_open()的对于walk_path()的用法,自作聪明:
// Overview: // Open "path". // // Post-Condition: // On success set *pfile to point at the file and return 0. // On error return < 0. intfile_open(char *path, struct File **file) { return walk_path(path, 0, file, 0); }
// Overview: // Evaluate a path name, starting at the root. // Post-Condition: // On success, set *pfile to the file we found and set *pdir to the directory // the file is in. // If we cannot find the file but find the directory it should be in, set // *pdir and copy the final path element into lastelem. //通俗的来说,就是该函数是用来找path对应的文件的,(目录也是文件,当时我理解错的核心就在于我把目录文件和普通文件区分开来了,认为pdir存相应目录,pfile存普通文件,而题目又保证传入的是目录,我自然而然的以为只需要传入pdir就行,是我没认真看这个函数的实现了) //本质就是pfile是对应的找到的文件,而pdir是pfile所在的目录文件,因此重要的应该是pfile参数,也就是一定要确保这个参数有传入值,不能是NULL //一定要先认真理解函数功能再动手 intwalk_path(char *path, struct File **pdir, struct File **pfile, char *lastelem) { char *p; char name[MAXNAMELEN]; structFile *dir, *file; int r;
// start at the root. path = skip_slash(path); file = &super->s_root; dir = 0; name[0] = 0;
if (pdir) { *pdir = 0; }
*pfile = 0;
// find the target file by name recursively. while (*path != '\0') { dir = file; p = path;
while (*path != '/' && *path != '\0') { path++; }
if (path - p >= MAXNAMELEN) { return -E_BAD_PATH; }