日志标签 ‘VFS’

文件操作函数在VFS层的实现

2012年11月23日

虚拟文件系统(Virtual Filesystem Switch,VFS)为各种文件系统提供了一个通用的接口,它使得上层进程在进行与文件系统相关的操作时可以使用同一组系统调用,但是系统调用在内核中可以根据不同的文件系统执行不同的操作。

与文件相关的基本操作函数有:open、read、write和close,本文将结合内核源代码分析这些函数在虚拟文件系统中的实现。

1.open()的实现

open系统调用的作用是打开或创建一个文件,并且返回该文件的文件描述符。在内核中,open系统调用主要完成的工作是为此次打开的文件创建file对象,该对象在fd_array数组中的索引值为返回用户空间的文件描述符。

open系统调用对应的系统调用服务例程为sys_open,不过目前内核已经统一使用SYSCALL_DEFINEn这种方式对系统调用服务例程进行定义。在open系统调用服务例程中又直接调用了do_sys_open函数,它是打开动作的主体函数。

long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{
        char *tmp = getname(filename);
        int fd = PTR_ERR(tmp);

        if (!IS_ERR(tmp)) {
                fd = get_unused_fd_flags(flags);
                if (fd >= 0) {
                        struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);
                        if (IS_ERR(f)) {
                                put_unused_fd(fd);
                                fd = PTR_ERR(f);
                        } else {
                                fsnotify_open(f->f_path.dentry);
                                fd_install(fd, f);
                        }
                }
                putname(tmp);
        }
        return fd;
}

用户进程使用open打开文件时将传递文件路径filename,因此该函数第一步先通过getname函数从用户空间读取文件路径到内核空间,暂存到tmp。通过get_unused_fd_flags函数在当前进程的fd_array数据中找到一个何时的位置,并返回其索引。

接下来通过do_filp_open函数执行打开文件的核心操作:根据系统调用中的标志参数flags和访问模式mode设置相应的局部变量以便后续使用;根据要打开文件的路径tmp寻找其inode节点,如果该inode节点不存在并且设置了O_CREATE标志则在磁盘上创建一个新的磁盘索引节点;分配一个新的文件对象,并根据系统调用传递的标志和访问模式设置文件对象的f_flags和f_mode字段;使用索引节点的i_fop字段初始化文件对象的f_op字段;将该文件对象插入到超级块指向的打开文件链表中;如果文件对象操作函数集中的open函数被定义则调用它;最后返回这个文件对象;

如果这个文件对象创建成功,则通过fd_install函数将该文件对象赋值到fd_array数组的第fd个元素中。

2.read()的实现

读文件系统调用read()的作用是根据文件描述符fd读取指定长度size的数据到缓冲区buf中。该系统调用的实现涉及了内核中对I/O进行处理的各个层次,但是对于VFS层来说实现方法比较清晰。

SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
        struct file *file;
        ssize_t ret = -EBADF;
        int fput_needed;
        
        file = fget_light(fd, &fput_needed);
        if (file) {
                loff_t pos = file_pos_read(file);
                ret = vfs_read(file, buf, count, &pos);
                file_pos_write(file, pos);
                fput_light(file, fput_needed);
        }

        return ret;
}

在read系统调用对应的服务例程中,首先使用fget_light函数通过fd获取对应的文件对象;再通过file_pos_read函数获取读文件的起始偏移量,即文件对象的f_pos字段的值;接着通过vfs_read函数进行读操作;通过file_pos_write函数更新文件当前的偏移量;通过fput_light函数释放文件对象;最终返回vfs_read函数的返回值ret,该值则为实际读取数据的长度。

read系统服务例程中最核心的函数即为vfs_read,它的主要工作是选择一个具体的读操作函数。如果当前文件对象操作函数集中的read钩子函数(file->f_op->read)被实现(通常在驱动程序中实现),则调用它。否则使用内核默认的读函数do_sys_read。

ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
        ssize_t ret;

        if (!(file->f_mode & FMODE_READ))
                return -EBADF;
        if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
                return -EINVAL;
        if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
                return -EFAULT;

        ret = rw_verify_area(READ, file, pos, count);
        if (ret >= 0) {
                count = ret;
                if (file->f_op->read)
                        ret = file->f_op->read(file, buf, count, pos);
                else
                        ret = do_sync_read(file, buf, count, pos);
                if (ret > 0) {
                        fsnotify_access(file->f_path.dentry);
                        add_rchar(current, ret);
                }
                inc_syscr(current);
        }

        return ret;
}

事实上,do_sys_read函数在内部调用钩子函数aio_read(file->f_op->aio_read),该钩子函数一般指向内核实现的通用读函数generic_file_aio_read。这个通用函数已经不属于我们本文所述的VFS层的实现范畴。

3.write函数的实现

write系统调用在VFS层的实现流程与read类似,只不过在出现read的地方将其相应的置换为write。

SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
                size_t, count)
{
        struct file *file;
        ssize_t ret = -EBADF;
        int fput_needed;

        file = fget_light(fd, &fput_needed);
        if (file) {
                loff_t pos = file_pos_read(file);
                ret = vfs_write(file, buf, count, &pos);
                file_pos_write(file, pos);
                fput_light(file, fput_needed);
        }

        return ret;
}

当然最终实现写文件操作的函数也是file->f_op->write或者内核中通用的写操作generic_file_aio_write。

4.close()的实现

close系统调用对应的服务例程中,它首先通过fd在文件对象数组中获取文件对象,接着则将fd处的文件对象清空。接下来的大部分工作都通过filp_close函数完成,它主要的工作是调用flush钩子函数将页高速缓存中的数据全部写回磁盘,释放该文件上的所有锁,通过fput函数释放该文件对象。最后返回0或者一个错误码。

边学边实践:打印VFS中的结构体

2010年11月27日

学习了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;
}

更多的打印信息可以按照上述方法继续添加。开始吧!

VFS中的其他数据结构

2010年11月23日

Last Update:2010/11/27

本文涉及VFS中数据结构有:struct file_system_type;struct vfsmount;struct fs_struct;struct files_struct;struct nameidata;struct qstr;

与文件系统相关的数据结构

VFS中使用file_system_type结构来描述一个具体的文件系统类型。也就是说,Linux支持的所有文件系统类型都分别唯一的对应一个file_system_type结构。如果某个具体文件类型的文件系统被安装到了系统中,那么系统就会创建一个vfsmount结构。这个结构用来描述一种文件系统类型的一个安装实例。

struct file_system_type

该结构位于yoursource/include/linux/fs.h中,下面对该结构中部分字段进行说明:

1736struct file_system_type {
1737        const char *name;
1738        int fs_flags;
1739        int (*get_sb) (struct file_system_type *, int,
1740                       const char *, void *, struct vfsmount *);
1741        void (*kill_sb) (struct super_block *);
1742        struct module *owner;
1743        struct file_system_type * next;
1744        struct list_head fs_supers;
1745
1746        struct lock_class_key s_lock_key;
1747        struct lock_class_key s_umount_key;
1748        struct lock_class_key s_vfs_rename_key;
1749
1750        struct lock_class_key i_lock_key;
1751        struct lock_class_key i_mutex_key;
1752        struct lock_class_key i_mutex_dir_key;
1753        struct lock_class_key i_alloc_sem_key;
1754};

name:文件系统的名字,不能为空;
get_sb:在安装文件系统时,调用此指针所指函数以在磁盘中获取超级块;
kill_sb:卸载文件文件系统时候,调用此指针所指函数以进行一些清理工作;
owner:如果一个文件系统以模块的形式加载到内核,则该字段用来说明哪个模块拥有这个结构。一般为THIS_MODULE;
next:所有的文件系统类型结构形成一个链表,该链表的头指针为全局变量file_systems(struct file_system_type *file_systems)。这个字段指向链表中下一个文件系统类型结构;
fs_supers:同一个文件系统类型下的所有超级块形成一个双联表,这个字段是这个双联表的头结点。超级块之间通过s_instances字段相互链接;

struct vfsmount

在解释这个结构的相关字段之前,我们很有必要了解一些概念。当我们安装linux时,硬盘上已经有了一个分区安装了ext3(或ext4)文件系统,这个文件系统被称之为根文件系统。系统安装完毕后,如果要继续安装其他文件系统,就需要挂载(mount)。具体的,将要安装的文件系统的根目录挂载到根文件系统的某个子目录上。这样,新安装的文件系统的根目录就是父文件系统下的某个子目录,我们将这个子目录称为安装点(mount point)。

该结构存储在yoursource/include/linux/mount.h中,下面对该结构的部分字段进行说明:

  49struct vfsmount {
  50        struct list_head mnt_hash;
  51        struct vfsmount *mnt_parent;    /* fs we are mounted on */
  52        struct dentry *mnt_mountpoint;  /* dentry of mountpoint */
  53        struct dentry *mnt_root;        /* root of the mounted tree */
  54        struct super_block *mnt_sb;     /* pointer to superblock */
  55        struct list_head mnt_mounts;    /* list of children, anchored here */
  56        struct list_head mnt_child;     /* and going through their mnt_child */
  57        int mnt_flags;
  63        const char *mnt_devname;        /* Name of device e.g. /dev/dsk/hda1 */
  64        struct list_head mnt_list;
…… ……
  71        int mnt_id;                     /* mount identifier */
  72        int mnt_group_id;               /* peer group identifier */
  78        atomic_t mnt_count;
  79        int mnt_expiry_mark;            /* true if marked for expiry */
  80        int mnt_pinned;
  81        int mnt_ghosts;
…… ……
  87};

mnt_hash:内核通过哈希表对vfsmount进行管理,当前vfsmount结构通过该字段链入相应哈希值对应的链表当中;
mnt_parent:指向父文件系统对应的vfsmount结构;
mnt_mountpoint:指向该文件系统安装点对应的dentry;
mnt_root:该文件系统对应的设备根目录的dentry;(两者是否指向同一个dentry?mnt_root是指向该文件系统设备根目录的dentry,具体这个dentry是什么?)

mnt_sb:指向该文件系统对应的超级块;
mnt_child:同一个父文件系统中的所有子文件系统通过该字段链接成双联表;
mnt_mounts:该字段是上述子文件系统形成的链表的头结点;
mnt_list:所有已安装的文件系统的vfsmount结构通过该字段链接在一起;

与路径查找有关的辅助结构

我们在使用open系统调用时,给该函数的第一个参数传递的是文件路径名。open函数对文件的操作最终会转化为对该文件inode的操作。VFS为了识别目标文件,会沿着路径逐层查找。因此,VFS中引入了nameidata结构。nameidata结构用于在路径查找过程中记录中间信息和查找结果。该结构体定义在yoursource/include/linux/namei.h中。

  18struct nameidata {
  19        struct path     path;
  20        struct qstr     last;
  21        struct path     root;
  22        unsigned int    flags;
  23        int             last_type;
  24        unsigned        depth;
  25        char *saved_names[MAX_NESTED_LINKS + 1];
  26
  27        /* Intent data */
  28        union {
  29                struct open_intent open;
  30        } intent;
  31};
33struct qstr {
34        unsigned int hash;
35        unsigned int len;
36        const unsigned char *name;
37};

与进程有关的数据结构

struct fs_struct

每个进程都有自己的根目录和当前的工作目录,内核使用struct fs_struct来记录这些信息,进程描述符中的fs字段便是指向该进程的fs_struct结构。该结构定义于yoursource/include/linux/fs_struct.h结构中。

   6struct fs_struct {
   7        int users;
   8        spinlock_t lock;
   9        int umask;
  10        int in_exec;
  11        struct path root, pwd;
  12};

users:用户数量;
lock:保护该结构的自旋锁;
umask:打开文件时设置的文件访问权限;
paroot:进程的根目录;
pwd:进程的当前工作目录;

struct files_struct

一个进程可能打开多个文件。所有被某个进程已打开的文件使用struct files_struct来记录。进程描述符的files字段便指向该进程的files_struct结构。该结构定义于yoursource/include/linux/fdtable.h中。

  44struct files_struct {
  48        atomic_t count;
  49        struct fdtable *fdt;
  50        struct fdtable fdtab;
  54        spinlock_t file_lock ____cacheline_aligned_in_smp;
  55        int next_fd;
  56        struct embedded_fd_set close_on_exec_init;
  57        struct embedded_fd_set open_fds_init;
  58        struct file * fd_array[NR_OPEN_DEFAULT];
  59};

count:引用计数;
fdtab:内核专门使用fdtable结构(该结构也称为文件描述符表)来描述文件描述符。该字段为初始的文件描述符表;
fdt:指向fdtab描述符表;
next_fd:最近关闭的文件描述符中数值最小的下一个可用的文件描述符;
close_on_exec_init:执行exec()时需要关闭的文件描述符;
open_fds_init:当前已经打开的文件描述符;
fd_array[NR_OPEN_DEFAULT]:文件对象的初始化数组;

我们都知道open函数正确执行后会返回一个整形的文件描述符,其实这个整数便是fd_arrary数组的下标。我们所说的标准输入文件、标准输出文件和标准错误文件分别是这个数组的0、1和2号元素。

通常在x86体系架构中NR_OPEN_DEFAULT的大小为32,难道一个进程最多只可以打开32个文件吗?当然不是,我们在上述的字段描述中,对fdtab和fd_array都用“初始化”来修饰。也就是说,当一个进程打开的文件数目超过32的时候,内核就分配一个更大的文件对象指针数组(fd_array中的文件对象指针也会被“复制”新的数组中),以便可以存放更多打开文件所对应的file结构体的指针。

上述字段已经说过,内核通过专门的struct fdtable来描述文件描述符表。该结构定义在yoursource/include/linux/file.h中。

  32struct fdtable {
  33        unsigned int max_fds;
  34        struct file ** fd;      /* current fd array */
  35        fd_set *close_on_exec;
  36        fd_set *open_fds;
  37        struct rcu_head rcu;
  38        struct fdtable *next;
  39};

max_fds:当前文件对象数组中元素的个数;
fd:指向当前文件对象数组,初始指向fd_arrary;
close_on_exec:执行exec时要关闭的文件描述符,初始指向close_on_exec_init;
open_fds:当前已经打开的文件描述符,初始指向open_fds_init;

VFS中的基本关系图

2010年11月21日

分析了VFS中几个基本的数据结构后,根据自己的理解,我画了一个关系图。如下(点击看大图):

关于此图的说明:

1. 图中两个结构之间的虚线箭头表示这两者之间仍有有若干个类似结点相连接;

2. 图中阴影部分所示的进程并不是以实际的关系来表示的;

3. 图中彩色线条示意的场景:三个进程分别打开同一个文件。进程1和进程2打开同一路径的文件,因此两者的打开文件对应同一个目录项;而进程3打开的是一个硬链接文件,因此对应的目录项与前两者不同;

4.索引结点中i_sb_list链表是链接一个文件系统中所有inode的链表,因此相邻的inode之间均会由此链表链接;而i_list链接的是处于同一个状态的所有inode。所以,相邻inode之间并不一定链接在一起。

VFS中的基本数据结构

2010年11月16日

本文涉及VFS中的数据结构有:struct super_block;struct inode;struct dentry;struct file;

Linux中的VFS(关于VFS更加全面的解说可以看这里)以一组通用的数据结构来描述各种文件系统。这些数据结构分别是超级块、索引结点、目录项和文件。下面将分别对这些结构进行说明。

超级块结构体

超级块结构代表一个已经安装了的文件系统,其存储该文件系统的有关信息。对于一个基于磁盘的文件系统来说,这类对象存放于磁盘的特定扇区中;对于非基于磁盘的文件系统,它会在该文件系统的使用现场创建超级块结构并存放在内存中。
在yoursource/include/linux/fs.h中有这个结构体的定义。下面对该结构的部分字段进行说明:

1318struct super_block {
1319        struct list_head        s_list;         /* Keep this first */
1320        dev_t                   s_dev;          /* search index; _not_ kdev_t */
1321        unsigned char           s_dirt;
1322        unsigned char           s_blocksize_bits;
1323        unsigned long           s_blocksize;
1324        loff_t                  s_maxbytes;     /* Max file size */
1325        struct file_system_type *s_type;
1326        const struct super_operations   *s_op;
…… ……
1330        unsigned long           s_flags;
1332        struct dentry           *s_root;
1335        int                     s_count;
1342        struct list_head        s_inodes;       /* all inodes */
1347        struct list_head        s_files;
1353        struct block_device     *s_bdev;
1357        struct quota_info       s_dquot;        /* Diskquota specific options */
1362        char s_id[32];                          /* Informational name */
…… ……
1388};

s_list:所以的超级块形成一个双联表,s_list.prev和s_list.next分别指向与当前超级块相邻的前一个元素和后一个元素。通常我们通过list_entry宏来获取s_list所在超级块结构体的地址。超级块链表的头结点是变量super_blocks;
s_dev:超级块所描述的文件系统所在设备的设备号。比如,ext2文件系统所在设备为磁盘,则该设备号即为该磁盘在系统中的设备号;
s_dirt:超级块在内存中被修改后,该标志为1,则修改后的超级块必须写回磁盘。
s_dirty:所有脏inode链接在一起所形成的指针;
s_blocksize:以字节为单位表示块的大小。系统对文件的存取操作是以块为单位的,该值即代表这个块的具体大小;
s_blocksize_bits:以位来表示块的大小。比如,一个块的大小为1024字节,则该值为10;
s_maxbytes:该超级块所属文件系统中文件的最大长度;
s_type:指向具体的文件系统类型;
s_op:指向对超级块操作的函数指针结构体;
s_flags:安装文件系统时的标志,记录比如只读或可擦写等这样的标志;
s_root:该文件系统根目录的目录项结构指针。利用该根目录项,可以访问到这个文件系统中的任何一个文件;
s_count:对该超级块的引用计数;
s_inodes:该文件系统中所有的索引结点形成一个双联表,该字段存放这个链表的头结点;
s_files:该文件系统中所有已被打开的文件形成一个双联表,该字段存放这个链表的头结点;
s_instances:某个具体文件系统中所有超级块会组成一个双联表。这个链表的头结点为super_block,头结点定义在该文件系统对应的file_system_type结构体中;

s_id[32]:文件系统的名称。比如ext3文件系统,该值为“ext3”;

超级块操作结构体

从上面的字段说明可以知道,超级块中有一个s_op字段,它指向与该操作块相关的操作。该字段的类型为struct super_operations,在yoursource/include/linux/fs.h中有这个结构体的定义。下面对该结构的部分字段进行说明:

1560struct super_operations {
1561        struct inode *(*alloc_inode)(struct super_block *sb);
1562        void (*destroy_inode)(struct inode *);
…… ……
1564        void (*dirty_inode) (struct inode *);
1565        int (*write_inode) (struct inode *, struct writeback_control *wbc);
1568        void (*put_super) (struct super_block *);
1569        void (*write_super) (struct super_block *);
…… ……
1584};

alloc_inode:创建和初始化一个新的索引结点;
destroy_inode:释放指定的索引结点;
put_super:释放指定的超级块,文件系统被卸载时使用;
write_super:如果该超级块被修改,即s_dirt为1时,则要将超级块写回磁盘,同时还要将s_dirt重设为0;
write_inode:将指定的inode写回磁盘,用于指定inode的更新;
drop_inode:释放指定的inode,与write_inode成对出现;

当文件系统需要对其所对应的超级块进行操作时,就应该使用超级块操作类中的具体函数。比如,定义sb为指向某个超级块的指针,如果该超级块需要将自己写回磁盘,则应该这么调用:

sb->s_op->write_super(sb);

可以看到,虽然write_super函数是由sb所指的超级块所调用的,但是仍然将sb传递给write_super函数。

索引结点结构体

索引结点结构体用来描述存放在磁盘上的文件信息。每当内核对磁盘上的文件进行操作时,就会将该文件的信息填充到一个索引结点可以代表一个普通的文件,也可以代表管道或者设备文件等这样的特殊文件。因此,在索引结点结构中,会包含针对这些特殊文件的一些属性。该结构体定义于在yoursource/include/linux/fs.h中有这个结构体的定义。下面对该结构的部分字段进行说明:

 725struct inode {
 726        struct hlist_node       i_hash;
 727        struct list_head        i_list;         /* backing dev IO list */
 729        struct list_head        i_dentry;
 730        unsigned long           i_ino;
 731        atomic_t                i_count;
 732        unsigned int            i_nlink;
 733        uid_t                   i_uid;
 734        gid_t                   i_gid;
 735        dev_t                   i_rdev;
 736        unsigned int            i_blkbits;
 737        u64                     i_version;
 738        loff_t                  i_size;
…… ……
 742        struct timespec         i_atime;
 743        struct timespec         i_mtime;
 744        struct timespec         i_ctime;
 745        blkcnt_t                i_blocks;
 746        unsigned short          i_bytes;
 747        umode_t                 i_mode;
 …… ……
 751        const struct inode_operations   *i_op;
 752        const struct file_operations    *i_fop; /* former ->i_op->default_file_ops */
 753        struct super_block      *i_sb;
 760        struct list_head        i_devices;
 761        union {
 762                struct pipe_inode_info  *i_pipe;
 763                struct block_device     *i_bdev;
 764                struct cdev             *i_cdev;
 765        };
 …… ……
 788};

i_hash:为了提高查找正在被使用的inode的效率,每一个inode都会有一个hash值,所有hash值相同的inode形成一个双链表。该字段包含prev和next两个指针,分别指向上述链表的前一个元素和后一个元素;
i_list:VFS中使用四个链表来管理不同状态的inode结点。inode_unused将当前未使用的inode链接起来,inode_in_use将当前正在被使用的inode链接起来,超级块中的s_dirty将所有脏inode链接起来,i_hash将所有hash值相同的inode链接起来。i_list中包含prev和next两个指针,分别指向与当前inode处于同一个状态链表的前后两个元素。
i_sb_list:每个文件系统中的inode都会形成一个双联表,这个双链表的头结点存放在超级块的s_inodes中。而该字段中的prev和next指针分别指向在双链表中与其相邻的前后两个元素;
i_dentry:所有引用该inode的目录项将形成一个双联表,该字段即为这个双联表的头结点;
i_ino:索引结点号。通过ls -l命令可以查看文件的索引节点号;
i_count:引用计数;
i_nlink:硬链接数。当该inode描述一个目录时,这个值至少为2,代表.和..的数目;
i_uid:inode所属文件的拥有者的id,通过ls -n可查看拥有者id;
i_gid:inode所属文件所在组的id,通过ls -n可查看组id;
i_rdev:如果该inode描述的是一个设备文件,此值为设备号;
i_blkbits:以位为单位的块大小;
i_atime:文件最近一次被访问的时间。通过ls -lu可查看该时间;
i_mtime:文件最近一次被修改的时间,这里的修改只文件内容被修改。通过ls -l可查看该时间;
i_ctime:文件最近一次被修改的时间,这里的修改除了指文件内容被修改外,更强调的是文件的属性被修改。通过ls -lc可查看该时间;
i_blocks:文件使用块的个数,通过ls -s可以查看该某个文件的块使用数目;
i_mode:文件的访问权限;
i_op:指向索引结点操作结构体的指针;
i_fop:指向文件操作结构体的指针,这个字段用来初始化文件结构体(struct file)中的f_op字段;
i_sb:指向inode所属文件系统的超级块的指针;
i_pipe:如果inode所代表的文件是一个管道,则使用该字段;
i_bdev:如果inode所代表的文件是一个块设备,则使用该字段;
i_cdev:如果inode所代表的文件是一个字符设备,则使用该字段;

索引结点操作结构体

在inode结构体中,有一个i_op字段,该字段指向与索引结点相关的操作。这个字段的类型为struct inode_operations,在yoursource/include/linux/fs.h中有这个结构体的定义。下面只列出部分字段的说明。

1516struct inode_operations {
1517        int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
1518        struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
…… ……
1519        int (*link) (struct dentry *,struct inode *,struct dentry *);
1520        int (*unlink) (struct inode *,struct dentry *);
1521        int (*symlink) (struct inode *,struct dentry *,const char *);
1522        int (*mkdir) (struct inode *,struct dentry *,int);
1523        int (*rmdir) (struct inode *,struct dentry *);
1524        int (*mknod) (struct inode *,struct dentry *,int,dev_t);
…… ……
1544};

create:如果该inode描述一个目录文件,那么当在该目录下创建或打开一个文件时,内核必须为这个文件创建一个inode。VFS通过调用该inode的i_op->create()函数来完成上述新inode的创建。该函数的第一个参数为该目录的inode,第二个参数为要打开新文件的dentry,第三个参数是对该文件的访问权限。如果该inode描述的是一个普通文件,那么该inode永远都不会调用这个create函数;
lookup:查找指定文件的dentry;
link:用于在指定目录下创建一个硬链接。这个link函数最终会被系统调用link()调用。该函数的第一个参数是原始文件的dentry,第二个参数即为上述指定目录的inode,第三个参数是链接文件的dentry。
unlink:在某个目录下删除指定的硬链接。这个unlink函数最终会被系统调用unlink()调用。 第一个参数即为上述硬链接所在目录的inode,第二个参数为要删除文件的dentry。
symlink:在某个目录下新建
mkdir:在指定的目录下创建一个子目录,当前目录的inode会调用i_op->mkdir()。该函数会被系统调用mkdir()调用。第一个参数即为指定目录的inode,第二个参数为子目录的dentry,第三个参数为子目录权限;
rmdir:从inode所描述的目录中删除一个指定的子目录时,该函数会被系统调用rmdir()最终调用;
mknod:在指定的目录下创建一个特殊文件,比如管道、设备文件或套接字等。

目录项结构体

为了方便对目标文件的快速查找,VFS引入了目录项。目标文件路径中的每一项都代表一个目录项,比如/home/test.c中,/,home,test.c都分别是一个目录项。这些目录项都属于路径的一部分,并且每个目录项都与其对应的inode相联系。如果VFS得到了某个dentry,那么也就随之得到了这个目录项所对应文件的inode,这样就可以对这个inode所对应的文件进行相应操作。所以,依次沿着目标文件路径中各部分的目录项进行搜索,最终则可找到目标文件的inode。

与超级块和索引结点不同的是,目录项在磁盘上并没有对应的实体文件,它会在需要时候现场被创建。因此,在目录项结构体中并没有脏数据字段,因为目录项并不会涉及重写到磁盘。

目录项由struct dentry描述,在yoursource/include/linux/dcache.h中有这个结构的定义。下面只对部分字段进行说明。

 89struct dentry {
  90        atomic_t d_count;
  91        unsigned int d_flags;           /* protected by d_lock */
  92        spinlock_t d_lock;              /* per dentry lock */
  93        int d_mounted;
  94        struct inode *d_inode;          /* Where the name belongs to - NULL is
  …… ……
 100        struct hlist_node d_hash;       /* lookup hash list */
 101        struct dentry *d_parent;        /* parent directory */
 102        struct qstr d_name;
 103
 104        struct list_head d_lru;         /* LRU list */
 108        union {
 109                struct list_head d_child;       /* child of parent list */
 110                struct rcu_head d_rcu;
 111        } d_u;
 112        struct list_head d_subdirs;     /* our children */
 113        struct list_head d_alias;       /* inode alias list */
 115        const struct dentry_operations *d_op;
 116        struct super_block *d_sb;       /* The root of the dentry tree */
…… ……
 120};

d_count:引用计数;
d_inode:与该目录项相关联的索引结点;
d_hash:内核使用哈希表对所有dentry进行管理,该字段使得当前dentry处于哈希表的某个冲突链表当中;
d_parent:指向父目录的目录项;
d_name:目录项的名称;
d_subdirs:如果当前目录项是一个目录,那么该目录下所有的子目录形成一个链表。该字段是这个链表的表头;
d_child:如果当前目录项是一个目录,那么该目录项通过这个字段加入到父目录的d_subdirs链表当中。这个字段中的next和prev指针分别指向父目录中的另外两个子目录;
d_alias:一个inode可能对应多个目录项,所有的目录项形成一个链表。inode结构中的i_dentry即为这个链表的头结点。当前目录项以这个字段处于i_dentry链表中。该字段中的prev和next指针分别指向与该目录项同inode的其他两个(如果有的话)目录项;
d_op:指向目录项操作结构体的指针;
d_sb:指向该目录项所属的文件系统对应的超级块;

目录项操作结构体

 134struct dentry_operations {
 135        int (*d_revalidate)(struct dentry *, struct nameidata *);
 136        int (*d_hash) (struct dentry *, struct qstr *);
 137        int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
 138        int (*d_delete)(struct dentry *);
 139        void (*d_release)(struct dentry *);
 140        void (*d_iput)(struct dentry *, struct inode *);
 141        char *(*d_dname)(struct dentry *, char *, int);
 142};

文件结构体

VFS使用struct file来描述一个被进程已经打开的文件。与上述三个结构体不同,文件结构体是进程直接处理的对象。因此,在该结构体中你可以看到我们熟悉的一些文件属性信息。文件对象是已打开文件在内存中的表示,因此,它在磁盘上并没有与之对应的数据。也就是说,文件对象只存在于内存中,所以这个结构也就不涉及脏数据字段和是否需要写回磁盘。

有上述可知,每当一个进程打开一个文件时,内存中有会有相应的file结构体。因此,当一个文件被多个进程打开时,这个文件就会有多个对应的文件结构体。但是,这些文件结构体对应的索引结点和目录项却是唯一的。在yoursource/include/linux/fs.h中有这个结构体的定义。下面只列出部分字段的说明。

 909struct file {
…… ……
 914        union {
 915                struct list_head        fu_list;
 916                struct rcu_head         fu_rcuhead;
 917        } f_u;
 918        struct path             f_path;
 919#define f_dentry        f_path.dentry
 920#define f_vfsmnt        f_path.mnt
 921        const struct file_operations    *f_op;
 922        spinlock_t              f_lock;  /* f_ep_links, f_flags, no IRQ */
 923#ifdef CONFIG_SMP
 924        int                     f_sb_list_cpu;
 925#endif
 926        atomic_long_t           f_count;
 927        unsigned int            f_flags;
 928        fmode_t                 f_mode;
 929        loff_t                  f_pos;
 930        struct fown_struct      f_owner;
 935#ifdef CONFIG_SECURITY
 936        void                    *f_security;
 937#endif
 938        /* needed for tty driver, and maybe others */
 939        void                    *private_data;
 940
…… ……
 949};

fu_list:每个文件系统中以被打开的文件都会形成一个双联表,这个双联表的头结点存放在超级块的s_files字段中。该字段的prev和next指针分别指向在链表中与当前文件结构体相邻的前后两个元素;
f_dentry:与该文件对应的dentry;
f_vfsmnt:该文件所在文系统的安装点,与f_dentry相结合可以得到该文件的绝对路径;
f_op:指向与该文件相关的操作的结构体;
f_count:该文件的引用计数;
f_flags:进程打开该文件时候的标志,比如以只读,可读写等方式打开该文件;
f_mode:该文件的访问权限;
f_pos:当前该文件的偏移量,读写操作均从该偏移量开始;
f_security:指向文件安全数据结构struct file_security_struct的指针;

文件操作结构体

VFS使用struct file_operations来描述与文件相关的操作集合,文件结构体中的f_op字段就指向这种结构体类型。在这个操作结构体中,有许多我们所熟悉的函数指针,大多数与文件相关的系统调用最终会调用这里的函数。当一个进程打开某个文件时,该文件结构体中的f_op字段是通过该文件inode对象中的i_fop字段来初始化的。在yoursource/include/linux/fs.h中有这个结构体的定义。下面只列出部分字段的说明。

1488struct file_operations {
1489        struct module *owner;
1490        loff_t (*llseek) (struct file *, loff_t, int);
1491        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
1492        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
…… ……
1499        int (*mmap) (struct file *, struct vm_area_struct *);
1500        int (*open) (struct inode *, struct file *);
1502        int (*release) (struct inode *, struct file *);
…… ……
1514};

owner:用于指定拥有这个文件操作结构体的模块,通常取THIS_MODULE;
llseek:用于设置文件的偏移量。第一个参数指明要操作的文件,第二个参数为偏移量,第三个参数为开始偏移的位置(可取SEEK_SET,SEEK_CUR和SEEK_END之一)。
read:从文件中读数据。第一个参数为源文件,第二个参数为目的字符串,第三个参数指明欲读数据的总字节数,第四个参数指明从源文件的某个偏移量处开始读数据。由系统调用read()调用;
write:往文件里写数据。第一个参数为目的文件,第二个参数源字符串,第三个参数指明欲写数据的总字节数,第四个参数指明从目的文件的某个偏移量出开始写数据。由系统调用write()调用;
mmap:将指定文件映射到指定的地址空间上。由系统调用mmap()调用;
open:打开指定文件,并且将这个文件和指定的索引结点关联起来。由系统调用open()调用;
release:释放以打开的文件,当打开文件的引用计数(f_count)为0时,该函数被调用;

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key