学习了VFS的基本原理,我们很有必要对这些理论知识进行验证和实践。本文所分析的几个小程序将更具体、直观的展现VFS中一些数据结构之间的逻辑关系。
1.打印超级块和索引结点
通过前面的分析我们知道,系统中所有的超级块都内嵌一个struct list_head类型的字段s_list。通过该字段将系统中所有的超级块链接成一个双联表。因此,如果我们知道这个双联表的头指针以及了解相关便利宏的使用方法,那么我们就可以遍历整个系统中的所有超级块了。
为了解释方便,我们将内嵌的list_head结构体称为内部结构体;将super_block结构体称为外部结构体;
具体的,我们可以通过 list_for_each宏来遍历一个list_head类型的双联表。该宏的定义如下:
364#define list_for_each(pos, head) \ 365 for (pos = (head)->next; prefetch(pos->next), pos != (head); \ 366 pos = pos->next)
使用该宏时,需要向head参数中传递要遍历双联表的头指针;而每次遍历得到的list_head类型的结点地址会保存在pos这个参数中。不过,这个宏只能遍历内嵌于超级块中的那个list_head结构的链表,并不能得到正在被遍历的那个超级块的地址(也就是指向当前正被遍历的超级块的指针)。也就是说,每次遍历时并不能得到超级块中的其他字段。因此,还应该使用 list_entry宏。该宏通过指向list_head结点的地址来得到外部超级块的首地址。
345#define list_entry(ptr, type, member) \ 346 container_of(ptr, type, member)
这个宏的第一个参数是指向内部结构体list_head的指针,第二个参数是外部结构体的类型,第三个参数是list_head类型的变量在外部结构体中的名称。这个宏最后会返回指向当前外部结构体的指针。比如,在super_block结构体中,list_head结构类型的字段名称为s_list,因此可以如下使用该宏:
sb = list_entry(pos, struct super_block, s_list);
对于超级块形成的双联表来说,它的头指针是super_blocks。但是很遗憾,super_blocks这个变量并没有被导出。所谓导出,就是通过EXPORT_SYMBOL将某个函数或者变量对全部内核代码公开。也就是说,使用 EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用 。为了解决这个问题,我们可以在包含super_blocks的这个文件中将这个变量导出,并且重新编译内核。对于我们这里的这个小程序而言,这样做有些不值得。幸好,在/proc/kallsyms文件中,记录了内核中所有符号以及符号的地址。因此,在该文件中查找相应符号就可以得到其地址。
我们使用下述两条命令:
edsionte@edsionte-desktop:~/code/vfs/print_sb$ grep super_blocks /proc/kallsyms c0772a30 D super_blocks edsionte@edsionte-desktop:~/code/vfs/print_sb$ grep " sb_lock" /proc/kallsyms c08c9d60 B sb_lock
就可以得到super_blocks变量的地址。另外,sb_lock超级块对应的自旋锁。
上述都准备好后,我们就可以进行遍历了。关键代码如下:
#define SUPER_BLOCKS_ADDRESS 0xc0772a30 #define SB_LOCK_ADDRESS 0xc08c9d60 static int __init my_init(void) { struct super_block *sb; struct list_head *pos; struct list_head *linode; struct inode *pinode; unsigned long long count = 0; printk("print some fields of super blocks:\n"); spin_lock((spinlock_t *)SB_LOCK_ADDRESS); list_for_each(pos, (struct list_head *)SUPER_BLOCKS_ADDRESS){ sb = list_entry(pos, struct super_block, s_list); printk("dev_t:%d,%d ",MAJOR(sb->s_dev),MINOR(sb->s_dev)); printk("fs_name:%s\n",sb->s_type->name); printk("\n"); } spin_unlock((spinlock_t *)SB_LOCK_ADDRESS); printk("the number of inodes:%llu\n",sizeof(struct inode)*count); return 0; }
另外,需要注意的是,每次重启电脑后,都要重新查找上述两个变量的地址。
对于一个超级块中所有的inode,有专门一个链表将所有的inode链接起来。这个链表的头结点是超级块中的s_inode字段。而inode之间是其内部的i_sb_list字段进行链接的。了解了这些,我们可以在上述程序的基础上,再打印每个超级块中的所有inode:
list_for_each(pos, (struct list_head *)SUPER_BLOCKS_ADDRESS){ sb = list_entry(pos, struct super_block, s_list); printk("dev_t:%d,%d ",MAJOR(sb->s_dev),MINOR(sb->s_dev)); printk("fs_name:%s\n",sb->s_type->name); list_for_each(linode, &sb->s_inodes){ pinode = list_entry(linode, struct inode, i_sb_list); count ++; printk("%lu\t",pinode->i_ino); } printk("\n"); }
在上面代码的基础上,我们再加深一步。一个索引结点可能对应若干个dentry,这些dentry自身通过其内部的d_alias链接在一起;而整个链表的头结点是inode中的i_dentry字段。因此,根据上面的方法,我们可以在遍历每个inode的同时,继续遍历这个inode对应的所有dentry。部分代码如下:
list_for_each(pos, (struct list_head *)SUPER_BLOCKS_ADDRESS){ sb = list_entry(pos, struct super_block, s_list); printk("dev_t:%d,%d ",MAJOR(sb->s_dev),MINOR(sb->s_dev)); printk("fs_name:%s\n",sb->s_type->name); list_for_each(linode, &sb->s_inodes){ pinode = list_entry(linode, struct inode, i_sb_list); count ++; printk("%lu[",pinode->i_ino); list_for_each(ldentry, &pinode->i_dentry){ pdentry = list_entry(ldentry, struct dentry, d_alias); printk("%s->",pdentry->d_name.name); } printk("]\t"); } printk("\n"); }
上出程序的完整的代码在这里。
2.打印文件类型结构体
同样的道理,通过下述的代码可以打印file_system_type结构体。
#define FILE_SYSTEM_ADDRESS 0xc08ca3a4 /* grep file_systems /proc/kallsyms */ #define FILE_SYSTEM_LOCK_ADDRESS 0xc0772de0 /* grep file_systems_lock /proc/kallsyms */ static int __init printfs_init(void) { struct file_system_type **pos; printk("\n\nprint file_system_type:\n"); read_lock((rwlock_t *)FILE_SYSTEM_LOCK_ADDRESS); pos = (struct file_system_type **)FILE_SYSTEM_ADDRESS; while(*pos){ printk("name: %s\n",(*pos)->name); pos = &((*pos)->next); } read_unlock((rwlock_t *)FILE_SYSTEM_LOCK_ADDRESS); return 0; }
更多的打印信息可以按照上述方法继续添加。开始吧!