实验目的
- 掌握文件系统的工作机理,通过一个简单内核源码理解文件系统的主要数据结构。
- 学习较为复杂的 Linux 系统下的编程
- 了解 EXT2 文件系统的结构
步骤
以下操作都是在 root 用户下进行的,所以不用加 sudo
-
解压 filesystem2.tar.gz
文件,得到源文件如下图所示 ![]()
-
直接运行 make
,发现报错 ![]()
-
开始解决问题,先在系统中寻找 3.10.0-327.el7.x86_64/build
的链接。 ![]()
-
它指向 /usr/src/kernels/3.10.0-327.el7.x86_64
,再去这个路径里面看看。
-
这里什么都没有。说明我的红帽系统缺少相应的内核开发包。
-
上网搜索相应的 kernel-devel 包,下载,并传送到虚拟机上。
-
使用 rpm2cpio
安装该包。 ![]()
-
再尝试 make
,这次成功。 ![]()
-
使用 insmod aufs.ko
加载内核模块,发现无法安装。再 dmesg | tail
查看内核 debug 信息。 ![]()
-
方知内核版本不支持,需要重新使用正确的内核版本编译才能通过。内核所需的 rhelversion
为 7.5,而我的系统为 7.2,显然不匹配。
-
我选择使用一台新的虚拟机来完成本实验。当然,系统安装结束后,要装载安装光盘镜像,从 Packages
目录中安装内核相关包,gcc
和 g++
也要顺手安装。
-
再次 make
。警告不影响编译通过。 ![]()
-
再次尝试加载模块,这次就没有错误出现了。![]()
内核信息也可以看到加载内核的日志消息。super.c
中 static int __init aufs_init(void)
得以执行
![]()
-
直接使用 mount -o loop -t aufs ./image ./dir
挂载该模块,系统会重启。但是在重启后并没有找到刚才挂载上的目录。查阅资料后发现 mount
挂载是临时的,需要修改 /etc/fstab
文件,开机的时候,系统就是根据这个分区来挂载系统的。
我没有选择修改 /etc/fstab
文件,因为这个操作容易使系统崩溃
-
格式化分区(关键的一步)
然后再 mount
,再次挂载系统就不会重启了。
1 2
| insmod aufs.ko mount -o loop -t aufs ./image ./dir
|
此时 dmesg
的输出如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| [ 2082.575699] loop: module loaded [ 2093.191691] create inode cache success [ 2093.191698] register filesystem success [ 2093.191700] aufs module loaded [ 2094.725812] Buffer I/O error on dev loop0, logical block 0, async page read [ 2094.725832] Buffer I/O error on dev loop0, logical block 0, async page read [ 2094.725839] Buffer I/O error on dev loop0, logical block 0, async page read [ 2094.725846] Buffer I/O error on dev loop0, logical block 0, async page read [ 2094.725852] Buffer I/O error on dev loop0, logical block 0, async page read [ 2094.725859] Buffer I/O error on dev loop0, logical block 0, async page read [ 2094.725874] Buffer I/O error on dev loop0, logical block 3, async page read [ 2094.756612] aufs_super_block_fill 320017171 [ 2094.756615] now magic number 320017171 [ 2094.756617] aufs super block info: magic = 320017171 inode blocks = 1 block size = 4096 root inode = 1 inodes in block = 128 [ 2094.756621] now device block size 4096 [ 2094.756633] aufs reads inode 1 from 3 block with offset 32 [ 2094.756642] aufs inode 1 info: size = 0 block = 4 blocks = 1 uid = 0 gid = 0 mode = 40755 [ 2094.756778] aufs_inode_get success [ 2094.756782] d_make_root success [ 2094.756783] aufs mounted
|
从 debug 输出看,aufs 文件系统注册时(insmod 操作时),经历了加载 inode,注册文件系统两个过程。在 mount 的过程中获得了 inode 信息,建立了文件系统根目录,其中为 super block 超级块分配了一个魔法数,super block 大小为 4096,它含有 128 块 inode。第一块 inode 的大小为 0,有 4 块 block。Device 的块大小为 4096,偏移值为 32,从第一块 inode 的 block3 开始读的。Mode 的含义是,这是一个目录(0x40000),所有者拥有全部权限(0x700),用户组可读取,可写入(0x40|0x10),其他用户可读取,可写入(0x40|0x10)。
可以用 lsmod
命令查看加载的模块列表,刚才的 aufs
在其中。
-
刚刚挂载上去的文件系统在桌面上是这样显示的 ![]()
-
卸载模块:rmmod xxx.ko
Super.c
中 static void __exit aufs_fini(void)
得以执行
实验关键里程碑数据与结果
内核源代码结构分析
文件系统各种结构的定义,比如 block、inode、entry
aufs.h1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| struct aufs_disk_super_block { __be32 dsb_magic; __be32 dsb_block_size; __be32 dsb_root_inode; __be32 dsb_inode_blocks; };
struct aufs_disk_inode { __be32 di_first; __be32 di_blocks; __be32 di_size; __be32 di_gid; __be32 di_uid; __be32 di_mode; __be64 di_ctime; }; struct aufs_disk_dir_entry { char dde_name[AUFS_DDE_MAX_NAME_LEN]; __be32 dde_inode; };
struct aufs_super_block { unsigned long asb_magic; unsigned long asb_inode_blocks; unsigned long asb_block_size; unsigned long asb_root_inode; unsigned long asb_inodes_in_block; }; ......
|
还定义了 inode
的获取、分配和删除操作。
aufs 模块定义,内核版本定义等
aufs.mod.cMODULE_INFO(srcversion, "7E6AB09FC99FF944E10E236"); MODULE_INFO(rhelversion, "7.5");
|
定义设备操作 device_open
、device_release
、device_read
、device_write
及其实现
实验难点与收获
内核的安装比普通软件的安装要更加困难,它需要系统内核版本的匹配和更多的命令。这个实验给了一个 7.5 的红帽版本的 ko 内核,一开始并没有意识到版本问题,编译、挂载总是失败。后来解决了版本问题,但是挂载后系统会自动重启,而 Linux 重启后所有的挂载均会消失,很坑。
一步关键的动作是使用 mkfs.aufs 来格式化所给定的 image 文件。
观看内核源代码,发现它十分复杂,很难一次读通,对我来说是个极大的挑战。
实验思考
能不能照着这个给定的模块结构,自己写一个 Linux 内核模块呢?