该实验可以分为下面几个步骤:
XV6 的文件系统中对磁盘的划分类似 EXT2 底层划分,从底层到调用,XV6 的文件系统分 6 层实现。
| 层 | 抽象接口 |
|---|---|
| 5 | 路径名 |
| 4 | 目录 |
| 3 | 索引节点 |
| 2 | 日志层 |
| 1 | 缓存层 |
| 0 | 磁盘 |
本节主要利用文件系统知识给 XV6 添加一个原生磁盘并实现简单的读写。
首先我们查看 XV6 是如何添加磁盘的,先看看 xv6.img 的生成,打开 Makefile,相关语句如下:
xv6.img: bootblock kernel
dd if=/dev/zero of=xv6.img count=10000
dd if=bootblock of=xv6.img conv=notrunc
dd if=kernel of=xv6.img seek=1 conv=notrunc
可知 xv6.img 生成需要两个文件:bootblock 和 kernel。
第二行的 dd 指令中以 /dev/zero 为输入文件(input file),xv6.img 为输出文件(output file),拷贝 10000 块,一块默认为 512 字节,故生成的 xv6.img 总大小为 5120000 B,也就是 5000 KB。
第三行将 bootblock 的内容写入到 xv6.img 文件中。conv 是指定的参数转换文件(conversion),notrunc 表示不截短文件。
第四行将 kernel 文件复制到 xv6.img 中,seek 为偏移位,即从输出文件开头跳过 1 个块(512 字节)后再开始复制(第一个 512 字节给了 bootblock),同样是不截短文件的形式写入。
我们模仿 xv6.img 的生成,在 Makefile 中添加如下指令,手动生成一个 4096000 B 的空磁盘,每个盘块为 512 字节(8 个盘块等于 1 块物理页帧)。
rawdisk.img:
dd if=/dev/zero of=rawdisk.img bs=512 count=8000
dd if=README.md of=rawdisk.img conv=notrunc
如果这里没有加参数 notrunc,那么读入 README.md 后,rawdisk.img 的大小将变为 README.md 的大小。添加后,命令行下执行如下命令,即可得到新磁盘信息,大小为。
# make
# du -sh -b rawdisk.img
4096000 rawdisk.img
我们先看看 xv6.img 和 fs.img 这两个设备是如何挂载到操作系统上的,打开 Makefile,查找相关信息。可用看到 Makefile 的 QEMU 仿真命令选项 QEMUOPTS 将这两个文件挂载, 其中关于 fs.img 的命令如下
-drive file=fs.img,index=1,media=disk,format=raw
index 是设备号,media 是媒介类型,format 是格式。
QEMUOPTS 中有一个变量是 QEMUEXTRA,应该是用来扩展功能的,我们在 QEMUOPTS 的上面添加如下一行完成挂载。
QEMUEXTRA = -drive file=rawdisk.img,index=2,media=disk,format=raw
在 clean 命令下添加 rawdisk.img,可以使用 make clean 清理生成的 rawdisk.img 文件。
xv6 的 main 会调用 ideinit() 对磁盘进行初始化。只有最后一个 CPU 打开了磁盘设备中断 IRQ_IDE。
缓存块 buf 的信息在 buf.h,磁盘的读写都依赖于缓存块。
磁盘读写函数是 iderw()。
提供系统调用对 rawdisk 的读写,通过复用系统读超级块的代码,完成裸磁盘的读写。
首先我们得学习一下 XV6 读文件的过程,然后再来进行模仿和创新。其中关于文件的函数有:
open(name, perm); // 打开或创建文件
write(fd, buf, len); // 将 buf 中 len 个字节写入文件
close(fd) // 关闭文件符,相当于进程不使用该文件
read(fd, buf, len); // 将文件中的 len 字节读到 buf 中
unlink(name); // 若硬链接为 1,unlink 操作表示删除文件
fstat(fd, stat); // 将文件 fd 的状态信息读取到 stat 中
因此我们可用编写一个程序来读取 README.md 的数据以及文件信息,并显示出来。函数如下:
char buf[512] = "hello world!"; // 全局变量
void readme() {
int fd = open("README.md", O_RDONLY); // fd >= 0 表示创建成功
struct stat st;
fstat(fd, &st); // 读取文件信息
printf(1, "dev: %d\n", st.dev);
printf(1, "ino: %d\n", st.ino);
printf(1, "nlink: %d\n", st.nlink);
printf(1, "type: %d\n", st.type);
printf(1, "size: %d\n", st.size);
read(fd, buf, 512);
printf(1, "content is below:\n%s", buf);
close(fd);
}
编写应用程序检验读写结果,检查 rawdisk.img 内容是否符合所写结果。为了忽略同步等繁琐的问题,我们在内核区定义一个全局变量,在 main 函数初始化时将磁盘的数据读到全局变量中,之后就可以用系统调用将数据读取出来。首先用 more 命令查看 rawdisk.img 的数据如下:
# more rawdisk.img
read rawdisk succeed!