- 论坛徽章:
- 0
|
3. 内核的数据结构和处理方法(只限于FAT文件系统,这里提到的代码为2.6.20.15版内核)
3.1 super block
VFS 中有一个叫super_block的struct,它定义在include/linux/fs.h中。就像之前说的那样VFS是一个通用的文件系统模型,所以super_block具有一般性,它所包含的内容是所有文件系统的super_block一般都具有的内容,具体的内容这里就不讨论了,有兴趣的同学可用仔细研究一下。光有一般的信息当然不够,不同的文件系统总会有它独到之处,为了能反应这一点 super_block有一个成员变量void *s_fs_info,它指向>一个特定文件系统定义的superblock数据结构。对FAT文件系统来说,这个结构就是 structmsdos_sb_info它定义在include/linux/msdos_fs.h中,这个数据结构就基本包含了所有磁盘中FAT文件系统的super block的信息。
(VFS)
+-------------+
| super_block |
| | s_fs_info (FAT)
| ---+---------->+---------------+
+-------------+ | msdos_sb_info |
| |
+---------------+
那么内核又是怎样组织所有的superblock的呢?所有的SB都串在一个双向链表中,而链表的头是一个叫super_blocks的变量,它定义在 fs/super.c中LIST_HEAD(super_blocks); 而在super_block中的s_list把所有的SB链接起来:
+------------+ +-----------+ +-----------+ +-----------+
|super_blocks |<--->|s_list |<--->|s_list |<--->|s_list |
+------------+ +-----------+ +-----------+ +-----------+
|FAT | |EXT2 | |EXT2 |
|super_block | |super_block | |super_block |
+-----------+ +-----------+ +-----------+
相同的文件系统在不同的设备上面有不同的super block。
mount是内核对磁盘super block的第一个操作。mount做了很多事情,而其中一个比较重要的动作就是读磁盘的super block,获得关于这个磁盘的信息。在mount的过程中函数vfs_kern_mount()会读取设备中的super block:
- struct vfsmount *
- vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
- {
- ...
- error = type->get_sb(type, flags, name, data, mnt);
- ...
- }
复制代码
因为不同的文件系统格式不同,会有不同的方法来读取super blcok,所以type(文件系统的种类)决定了这个方法。对应FAT文件系统get_sb实际上是msdos_get_sb()。
- static int msdos_get_sb(struct file_system_type *fs_type,
- int flags, const char *dev_name,
- void *data, struct vfsmount *mnt)
- {
- return get_sb_bdev(fs_type, flags, dev_name, data, msdos_fill_super,
- mnt);
- }
复制代码
大部分文件系统都是通过get_sb_bdev()来获取superblcok内容的,不同的是调用它时的第五个参数msdos_fill_super一个函数指针,它才是真正读磁盘,解析superblock的地方。而msdos_fill_super实际上又是调用fat_fill_super来完成工作的,下面简要分析一下fat_fill_super所做的事:
- int fat_fill_super(struct super_block *sb, void *data, int silent,
- struct inode_operations *fs_dir_inode_ops, int isvfat)
- {
- ...
- //在进这个函数之前VFS的super_block已经分配好并挂在super_blocks链
- //上。msdos_sb_info在这里分配。
- sbi = kzalloc(sizeof(struct msdos_sb_info), GFP_KERNEL);
- if (!sbi)
- return -ENOMEM;
- //把这个msdos_sb_info挂在VFS的super_block的s_fs_info上
- sb->s_fs_info = sbi;
- ...
- //解析和处理用户挂载一个磁盘时mount的参数
- error = parse_options(data, isvfat, silent, &debug, &sbi->options);
- ...
- //进行一次实际的读磁盘操作,目的是把super block读到内存中,sb_bread的第二个
- //参数是指出要读的block在那个位置,super block当然是第0个了。
- bh = sb_bread(sb, 0);
- if (bh == NULL) {
- printk(KERN_ERR "FAT: unable to read boot sector\n");
- goto out_fail;
- }
- //bh->b_data就是从磁盘中读上来的内容。
- //fat_boot_sector是一个定义在include/linux/msdos_fs.h中的结构体。
- //它的内容结构和磁盘中的super block完全一样。所有可用直接赋值,这样变量b
- //就有了这个设备的SB信息。
- b = (struct fat_boot_sector *) bh->b_data;
- ...
- //接着就是根据b内容给msdos_sb_info的成员赋值
- sbi->sec_per_clus = b->sec_per_clus;//每个cluster占多少个sector
- ...
- sbi->cluster_size = sb->s_blocksize * sbi->sec_per_clus;
- sbi->cluster_bits = ffs(sbi->cluster_size) - 1;
- sbi->fats = b->fats;//FAT表的个数
- sbi->fat_bits = 0; /* Don't know yet */
- sbi->fat_start = le16_to_cpu(b->reserved);//FAT表的开始位置
- sbi->fat_length = le16_to_cpu(b->fat_length);//FAT表占的sector数
- sbi->root_cluster = 0;
- sbi->free_clusters = -1; /* Don't know yet */
- sbi->prev_free = FAT_START_ENT;
- ...
- //每个block占多少个目录文件
- sbi->dir_per_block = sb->s_blocksize / sizeof(struct msdos_dir_entry);
- sbi->dir_per_block_bits = ffs(sbi->dir_per_block) - 1;
- //根目录区域的起始地址
- sbi->dir_start = sbi->fat_start + sbi->fats * sbi->fat_length;
- //每个目录文件有多少个目录项
- sbi->dir_entries =
- le16_to_cpu(get_unaligned((__le16 *)&b->dir_entries));
- ...
- //给根目建一个inode
- root_inode = new_inode(sb);
- ...
- }
复制代码
这些super block的信息会一直留在内存中到设备被umount。当然super block的信息也会在用户使用过程中被修改
3.2 inode
与super block一样,inode也是由VFS的inode和FAT本身的inode组成:
(FAT)
+------------------+
| msdos_inode_info |
| |
| | vfs_inode (VFS)
| --+---------->+-------+
+------------------+ | inode |
| |
+-------+
不过这次是FAT的inode数据结构包着VFS的inode数据结构。至于inode是怎样在内核中联系起来,由于与FAT关系不太大,这里就不讨论了,同学可以通过阅读关于VFS的资料来获取更多信息。(msdos_sb_info 定义在include/linux/msdos_fs.h中)
但是FAT文件系统不像EXT2/3文件系统,自己本身是没有inode这个概念的,为了与VFS这个通用的模型接合起来,内核是怎样做的呢?
先看一下目录文件的目录项在内核中用struct msdos_dir_entry(定义在 include/linux/msdos_fs.h中)来表示,它也是通过sb_bread函数>读取磁盘信息然后给msdos_sb_info赋值:
- struct msdos_dir_entry {
- __u8 name[8],ext[3]; /* name and extension */
- __u8 attr; /* attribute bits */
- __u8 lcase; /* Case for base and extension */
- __u8 ctime_cs; /* Creation time, centiseconds (0-199) */
- __le16 ctime; /* Creation time */
- __le16 cdate; /* Creation date */
- __le16 adate; /* Last access date */
- __le16 starthi; /* High 16 bits of cluster in FAT32 */
- __le16 time,date,start;/* time, date and first cluster */
- __le32 size; /* file size (in bytes) */
- };
复制代码
可见这个数据结构与磁盘中的目录项基本一致,在的到了一个目录项之后,内核调用fat_fill_inode来给FAT的inode赋值:
- static int fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de)
- {
- ...
- //在这个函数之前,VFS和FAT的inode数据结构都已经分配好了。
- //一方面一些inode信息可用通过SB里的信息得到,如下:
- MSDOS_I(inode)->i_pos = 0;
- inode->i_uid = sbi->options.fs_uid;
- inode->i_gid = sbi->options.fs_gid;
- inode->i_version++;
- ...
- //而其他的信息就通过这个文件的目录项来得到
- //这是个目录
- if ((de->attr & ATTR_DIR) && !IS_FREE(de->name)) {
- ...
- //目录的属性
- inode->i_mode = MSDOS_MKMODE(de->attr,
- S_IRWXUGO & ~sbi->options.fs_dmask) | S_IFDIR;
- ...
- //真正的数据在数据区的那个cluster中保存
- MSDOS_I(inode)->i_start = le16_to_cpu(de->start);
- if (sbi->fat_bits == 32)
- MSDOS_I(inode)->i_start |= (le16_to_cpu(de->starthi) << 16);
- ...
- //这是个文件
- } else {
- inode->i_mode = MSDOS_MKMODE(de->attr,
- ((sbi->options.showexec &&
- !is_exec(de->ext))
- ? S_IRUGO|S_IWUGO : S_IRWXUGO)
- & ~sbi->options.fs_fmask) | S_IFREG;
- MSDOS_I(inode)->i_start = le16_to_cpu(de->start);
- if (sbi->fat_bits == 32)
- MSDOS_I(inode)->i_start |= (le16_to_cpu(de->starthi) << 16);
- ...
- }
- ...
- //关于时间的信息
- if (sbi->options.isvfat) {
- int secs = de->ctime_cs / 100;
- int csecs = de->ctime_cs % 100;
- inode->i_ctime.tv_sec =
- date_dos2unix(le16_to_cpu(de->ctime),
- le16_to_cpu(de->cdate)) + secs;
- inode->i_ctime.tv_nsec = csecs * 10000000;
- inode->i_atime.tv_sec =
- date_dos2unix(0, le16_to_cpu(de->adate));
- inode->i_atime.tv_nsec = 0;
- } else
- inode->i_ctime = inode->i_atime = inode->i_mtime;
- ...
- }
复制代码
FAT文件系统中没有inode,而最能反应文件各种属性的就是SB和目录项,当然还有FAT表,内核就是通过它们来建立一个虚构的inode。
inode被修改了,当然要向磁盘写了,fat_write_inode(定义在fs/fat/inode.c中)完成了这个工作。从构建inode的过程可用想到,写inode其实主要也是写inode对应的文件在父目录中的目录项。这里就不多说了。
3.3 FAT表
大家也应该想到,VFS中不可能有FAT表,因为它并不是传统的UNIX文件系统应有的东西。但是从FAT表的结构可用看到,如果要访问一个文>件的某部分内容,举个例子,要访问一个文件的第3个cluster的内容,那么我们还是要从FAT表中文件对应的第一个cluster的表项找到第二个 >,再从第二个表项的内容得知第三个表项的下表从而判断出第三个cluster的具体位置。
+----------------+
3| 4 |-----------+
+----------------+ |
4| 5 | <---------+
+----------------+ |
5| 0xFFFF | <---------+
+----------------+
6| 0 |
+----------------+
内核并没有把整个FAT表读到内存中,为了避免频繁读磁盘,它通过一个链表cache_lru来加快查找速度。cache_lru是 msdos_inode_info的>一个成员变量,它记录最近访问过的FAT表表项,每个inode有一个这样的链表。每个链表元素为 fat_cache(定义在fs/fat/cache.c):
- struct fat_cache {
- struct list_head cache_list;
- int nr_contig; /* number of contiguous clusters */
- int fcluster; /* cluster number in the file. */
- int dcluster; /* cluster number on disk. */
- };
复制代码
这样一个结构就记录了相对于文件开头第fcluster个cluster对应于磁盘中的位置为第dcluster个cluster。有了这个链表,如果要访问文件的第3个cluster,而我们又曾经访问过第二个cluster,那么我们就可用从第二个开始去读FAT表,从而知道第三个cluster的磁盘位置,同时这个新的cluster的信息又会被加到这个链表中。nr_contig这个变量是标明这个文件从fcluster这个cluster开始,后面有nr_contig个cluster在磁盘中是连续的,这种结构更有利于节省空间。当一个文件不再被使用,inode被删除时,这个链表也会被删除。
|
|