/* Overview: * 从 va 处读数据,并写入指定外设的地址 * * Post-Condition: * Data within [va, va+len) is copied to the physical address 'pa'. * Return 0 on success. * Return -E_INVAL on bad address. * * Hint: 使用 kseg1 段虚拟地址访问外设. * Hint: 使用 'is_illegal_va_range' 检测 va 有效性. * Hint: You MUST use 'memcpy' to copy data after checking the validity. * * All valid device and their physical address ranges: * * ---------------------------------* * | device | start addr | length | * * -----------+------------+--------* * | console | 0x10000000 | 0x20 | (dev_cons.h) * | IDE disk | 0x13000000 | 0x4200 | (dev_disk.h) * | rtc | 0x15000000 | 0x200 | (dev_rtc.h) * * ---------------------------------* */ intsys_write_dev(u_int va, u_int pa, u_int len) { /* Exercise 5.1: Your code here. (1/2) */ if (is_illegal_va_range(va, len)) { return -E_INVAL; }
if (pa < 0x10000000 || pa > 0x15000200) { return -E_INVAL; }
// TEST 5-1: Forget to think about the illegal_pa in this part if ((pa >= 0x10000021 && pa < 0x13000000) || (pa >= 0x13004201 && pa < 0x15000000)) { return -E_INVAL; }
memcpy((void *) (KSEG1 + pa), (constvoid *) va, len);
return0; }
/* Overview: * 从外设指定地址中读取数据 * * Hint: You MUST use 'memcpy' to copy data after checking the validity. */ intsys_read_dev(u_int va, u_int pa, u_int len) { /* Exercise 5.1: Your code here. (2/2) */ if (is_illegal_va_range(va, len)) { return -E_INVAL; }
if (pa < 0x10000000 || pa > 0x15000200) { return -E_INVAL; }
if ((pa >= 0x10000021 && pa < 0x13000000) || (pa >= 0x13004201 && pa < 0x15000000)) { return -E_INVAL; }
memcpy((void *) va, (constvoid *) (KSEG1 + pa), len);
return0; }
Exercise 5.2 是实现在用户态中的 msyscall 接口,实现方式与其他系统调用类似。
IDE 磁盘与读写
在 MOS 系统中,GXemul 为我们提供了模拟的 IDE 磁盘,每次读写的单位是一个扇区(512 Byte)。和其他的外设一样,在指定位置读写可以实现和磁盘的交互,地址表如下:
偏移
效果
位宽
0x0000
写:下一次读写操作的偏移字节数
4 Byte
0x0008
写:设置高 32 位偏移
4 Byte
0x0010
写:下一次读写的磁盘号
4 Byte
0x0020
写:开始操作(0读/1写)
4 Byte
0x0030
读:上一次操作的返回值(0成功/非0失败)
4 Byte
0x4000 - 0x414f
读/写:512 Byte 的一个扇区
none
读操作后需要从缓冲区取出数据,写操作前需要事先写入缓冲区内
内核态实现
课程组在指导书中给出了内核态的访问实现,但实际上我们的 MOS 中并不需要这一函数去实现驱动,因为我们采用微内核架构,将访问磁盘的操作交给用户态完成,这里的代码只起到示例作用。
// 定义 - C externintread_sector(int diskno, int offset); // 实现 - MIPS LEAF(read_sector) sw a0, 0xB3000010# choose the IDE id, must 0 in our MOS sw a1, 0xB3000000# offset in the disk li t0, 0 sw t0, 0xB3000020# launch a 'read' action lw v0, 0xB3000030# get the result of the action nop jr ra nop END(read_sector)
用户态实现 - Exercise 5.3
在 MOS 中,实际完成对磁盘读写操作的是 fs/ide.c 中的两个函数,他们可在用户态使用,充当磁盘驱动。在这里我们就相当于复刻了前面的内核态驱动,使用 C 语言替代 MIPS,并使用系统调用来完成写入地址的操作。
// Overview: // read data from IDE disk. // // Parameters: // diskno: disk number. // secno: start sector number. // dst: destination for data read from IDE disk. // nsecs: the number of sectors to read. // // Hint: Use syscalls to access device registers and buffers. // Sample: ide_read(0, blockno * SECT2BLK, va, SECT2BLK);
voidide_read(u_int diskno, u_int secno, void *dst, u_int nsecs) { u_int begin = secno * BY2SECT; u_int end = begin + nsecs * BY2SECT;
//sample: ide_read(0, blockno * SECT2BLK, va, SECT2BLK);
for (u_int off = 0; begin + off < end; off += BY2SECT) { uint32_t temp = diskno; /* Exercise 5.3: Your code here. (1/2) */ panic_on(-E_INVAL == syscall_write_dev(&temp, (DEV_DISK_ADDRESS | DEV_DISK_ID), sizeof(temp))); //select disk_id temp = begin + off; panic_on(-E_INVAL == syscall_write_dev(&temp, (DEV_DISK_ADDRESS | DEV_DISK_OFFSET), sizeof(temp))); // select reading offset_address temp = DEV_DISK_OPERATION_READ; panic_on(-E_INVAL == syscall_write_dev(&temp, (DEV_DISK_ADDRESS | DEV_DISK_START_OPERATION), sizeof(temp))); // select operation panic_on(-E_INVAL == syscall_read_dev(&temp, (DEV_DISK_ADDRESS | DEV_DISK_STATUS), sizeof(temp))); // get reading result if (temp == 0) { panic_on(temp == 0); } else { panic_on(-E_INVAL == syscall_read_dev((void *) ((u_int) dst + off), (DEV_DISK_ADDRESS | DEV_DISK_BUFFER), DEV_DISK_BUFFER_LEN)); // pull from buffer } } }
ide_write 同理,不再添加注释
// Overview: // write data to IDE disk. voidide_write(u_int diskno, u_int secno, void *src, u_int nsecs) { u_int begin = secno * BY2SECT; u_int end = begin + nsecs * BY2SECT;
structSuper { u_int s_magic; // FS_MAGIC u_int s_nblocks; // number of blocks = 1024 structFiles_root;// root directory of the disk };
其中 s_root 的类型为 FTYPE_DIR,f_name 为 "/"
在 MOS 中,我们通过设置结构体 Block 的数组 disk 来对磁盘进行表示,每个 Block 就代表一个磁盘块,data是具体的空间,type是磁盘块用途/状态的标记。
磁盘状态与更改
tools/fsformat.c
在实验中的 IDE 磁盘中,我们占用了一些磁盘块用来充当标记是否已分配磁盘块的位图数组,通过读取数组的状态来得知磁盘块的分配情况,这点与内存管理中我们使用的链表不同。
通常情况下这部分磁盘块紧跟磁盘的超级块
磁盘文件的创建
我们使用 tools/fsformat.c 的 main 函数来在 Linux 中创建一个可供我们 MOS 使用的磁盘镜像文件。Usage: fsformat <img-file> [files or directories]... 意味着函数的 argv[1] 代表着生成镜像的路径,后面的每个参数都代表准备写入的文件/目录路径。
// Write file to disk under specified dir. voidwrite_file(struct File *dirf, constchar *path) { int iblk = 0, r = 0, n = sizeof(disk[0].data); structFile *target = create_file(dirf);
/* in case `create_file` is't filled */ if (target == NULL) { return; }
int fd = open(path, O_RDONLY);
// Get file name with no path prefix. constchar *fname = strrchr(path, '/'); if (fname) { fname++; } else { fname = path; } strcpy(target->f_name, fname);
// Start reading file. 从 Linux 的环境中读到 disk 中存储 lseek(fd, 0, SEEK_SET); while ((r = read(fd, disk[nextbno].data, n)) > 0) { save_block_link(target, iblk++, next_block(BLOCK_DATA)); } close(fd); // Close file descriptor. }
其中也同样调用了一些本文件的函数,做一些基本介绍
// Overview: // Allocate an unused 'struct File' under the specified directory. // Hint: // Use 'make_link_block' to allocate a new block for the directory if there are no existing unused // 'File's. struct File *create_file(struct File *dirf) { int nblk = dirf->f_size / BY2BLK;
// 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 < NDIRECT) { bno = dirf->f_direct[i]; } else { bno = ((u_int *)(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) */ int bno = make_link_block(dirf, dirf->f_size / BY2BLK); structFile *blk = (struct File *)(disk[bno].data); return blk;