Linux1.0虚拟文件系统(VFS)
在linux1.0操作系统当中文件系统是非常重要的一环,Linux操作系统当中支持的文件系统众多,如ext,ext2,minix,iosfs等等。当我们在使用操作系统提供的文件读写api来操作系统中的文件的时候,操作系统内部是怎么去完成我们想要的功能呢?在操作系统当中设计了一种虚拟文件系统,就是将所有的文件系统都给抽象出来,形成不同的文件系统使用相同的api来完成相同功能。Linux1.0中的文件系统结构可以用下面的结构来表示:
在实现这种VFS结构当中,操作系统当中定义了几个非常重要的结构:
struct file {
mode_t f_mode;
dev_t f_rdev; /* needed for /dev/tty */
off_t f_pos;
unsigned short f_flags;
unsigned short f_count; /*文件的引用计数*/
unsigned short f_reada;
struct file *f_next, *f_prev;
struct inode * f_inode;
struct file_operations * f_op;
};
struct file_operations {
int (*lseek) (struct inode *, struct file *, off_t, int);
int (*read) (struct inode *, struct file *, char *, int);
int (*write) (struct inode *, struct file *, char *, int);
int (*readdir) (struct inode *, struct file *, struct dirent *, int);
int (*select) (struct inode *, struct file *, int, select_table *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct inode *, struct file *, unsigned long, size_t, int, unsigned long);
int (*open) (struct inode *, struct file *);
void (*release) (struct inode *, struct file *);
int (*fsync) (struct inode *, struct file *);
};
struct inode {
dev_t i_dev; /* 设备号 */
unsigned long i_ino; /* i节点号 */
umode_t i_mode;
nlink_t i_nlink;
uid_t i_uid; /*创建文件的用户ID*/
gid_t i_gid; /*创建文件的用户组ID*/
dev_t i_rdev;
off_t i_size;
time_t i_atime;
time_t i_mtime;
time_t i_ctime;
unsigned long i_blksize;
unsigned long i_blocks;
struct semaphore i_sem;
struct inode_operations * i_op;
struct super_block * i_sb; /*对应的超级块*/
struct wait_queue * i_wait;
struct file_lock * i_flock;
struct vm_area_struct * i_mmap; /* 该文件映射到的虚拟地址段的地址 */
struct inode * i_next, * i_prev; /*空闲双向链表*/
struct inode * i_hash_next, * i_hash_prev; /*hash双向链表*/
struct inode * i_bound_to, * i_bound_by;
struct inode * i_mount;
struct socket * i_socket; /*如果是网络inode,则指向网络数据*/
unsigned short i_count;
unsigned short i_flags;
unsigned char i_lock;
unsigned char i_dirt;
unsigned char i_pipe;
unsigned char i_seek;
unsigned char i_update;
/* 因为Linux采用的时VFS文件系统,上面数据是所有文件系统都需要使用的数据
* 下面的数据是相应文件系统对应的特定inode的信息
*/
union {
struct pipe_inode_info pipe_i;
struct minix_inode_info minix_i;
struct ext_inode_info ext_i;
struct ext2_inode_info ext2_i;
struct hpfs_inode_info hpfs_i;
struct msdos_inode_info msdos_i;
struct iso_inode_info isofs_i;
struct nfs_inode_info nfs_i;
struct xiafs_inode_info xiafs_i;
struct sysv_inode_info sysv_i;
} u;
};
struct inode_operations {
struct file_operations * default_file_ops;
int (*create) (struct inode *,const char *,int,int,struct inode **);
int (*lookup) (struct inode *,const char *,int,struct inode **);
int (*link) (struct inode *,struct inode *,const char *,int);
int (*unlink) (struct inode *,const char *,int);
int (*symlink) (struct inode *,const char *,int,const char *);
int (*mkdir) (struct inode *,const char *,int,int);
int (*rmdir) (struct inode *,const char *,int);
int (*mknod) (struct inode *,const char *,int,int,int);
int (*rename) (struct inode *,const char *,int,struct inode *,const char *,int);
int (*readlink) (struct inode *,char *,int);
int (*follow_link) (struct inode *,struct inode *,int,int,struct inode **);
int (*bmap) (struct inode *,int);
void (*truncate) (struct inode *);
int (*permission) (struct inode *, int);
};
就是以上四种结构体 struct file、struct file_operations、struct inode、struct inode_operations。struct file结构体是系统中对所有文件系统的文件的描述,包含文件的基本信息,但是虽然包含了文件的基本信息,但是你需要对文件进行操作啊,所以这里就是问题的重点来了。所有的文件系统都需要有open、close、read、write操作,但是不同的文件系统的具体实现是不一样的,也就是同样打开一个文件,不同的文件系统中具有不同的操作,在VFS中将这些需要去操作具体文件系统的函数给抽象出来,形成一个struct file_operations,此时你回来去看看struct file_operations这个结构体,你会发现这个结构体当中有lseek、read、wirte、open等函数,当你调用一个open系统调用时,最后其实是struct file_operations中的open函数去完成的。同样的道理struct inode是描述文件在磁盘上信息的一个结构体,里面信息非常多。在struct inode中除了一些inode的基本信息之外,还有一个很特别的联合体,该联合体是用来描述不同文件系统的特定inode的信息。有兴趣的话,可以自己去看看,我这里就不贴上代码了。struct inode记录了文件磁盘的基本信息,同样的一个问题就是这些信息也是需要被操作和修改的啊,所以也有一个struct inode_operations结构,你可以回头去看看这些结构的信息。
下面就以系统当中open函数为例来说明VFS的一个基本实现过程
int do_open(const char * filename,int flags,int mode)
{
struct inode * inode;
struct file * f;
int flag,error,fd;
for(fd=0 ; fd<NR_OPEN ; fd++)
if (!current->filp[fd])
break;
if (fd>=NR_OPEN)
return -EMFILE;
FD_CLR(fd,¤t->close_on_exec);
f = get_empty_filp();
if (!f)
return -ENFILE;
current->filp[fd] = f;
f->f_flags = flag = flags;
f->f_mode = (flag+1) & O_ACCMODE;
if (f->f_mode)
flag++;
if (flag & (O_TRUNC | O_CREAT))
flag |= 2;
/* 通过一个路径filename来打开一个文件,并获取文件的inode
*/
error = open_namei(filename,flag,mode,&inode,NULL);
if (error) {
current->filp[fd]=NULL;
f->f_count--;
return error;
}
/* 在open函数当中,先通过open_namei函数获取对应路径文件的inode
* 在获取的inode当中会有f_op的操作符,Linux的文件系统是根据所要
* 操作文件的类型来确定文件操作的f_op和i_op,因为Linux支持的文件系统众多
* 每种文件系统的f_op和i_op都有特定的实现,文件系统具体函数的实现在相应的
* 文件夹当中,如ext2文件系统的实现在/fs/ext2/当中
*/
f->f_inode = inode;
f->f_pos = 0;
f->f_reada = 0;
f->f_op = NULL;
if (inode->i_op)
f->f_op = inode->i_op->default_file_ops;
if (f->f_op && f->f_op->open) {
error = f->f_op->open(inode,f);
if (error) {
iput(inode);
f->f_count--;
current->filp[fd]=NULL;
return error;
}
}
f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
return (fd);
}
/* flag为打开文件方式,如可读,可写,可读写,
* 如果文件没有则会去创建一个新的文件
* mode中有在创建新的文件时候才起作用,也就是确定新建文件的权限
*/
asmlinkage int sys_open(const char * filename,int flags,int mode)
{
char * tmp;
int error;
error = getname(filename, &tmp);
if (error)
return error;
error = do_open(tmp,flags,mode);
putname(tmp);
return error;
}
在sys_open函数当中先是做了数据的拷贝,这个现在可以不用管,然后就直接调用了do_open函数,在do_open函数当中首先给分配了一个struct file结构,然后根据函数当中给定的路径来获取相应文件的struct inode,open_namei这个函数我这里来简单介绍一下它的基本过程,首先它会根据路径来查找相应文件的struct inode,依次处理目录中的每一个struct inode,直到找到最后路径给出的那个文件的struct inode,如现在给出下面一个路径/etc/demo.txt,首先会获取/路径的struct inode,然后再该inode下面查找一个叫做etc目录的struct inode,找到了之后再去查找etc这个目录的inode下面的一个叫做demo.txt的struct inode,注意在这个查找的过程当中有一个非常重要的动作,就是给struct inode的i_op赋值,因为在查找的过程当中就知道demo.txt是一个什么文件系统的什么类型的文件了,这个读取inode的函数如下:
void ext2_read_inode (struct inode * inode)
{
struct buffer_head * bh;
struct ext2_inode * raw_inode;
unsigned long block_group;
unsigned long group_desc;
unsigned long desc;
unsigned long block;
struct ext2_group_desc * gdp;
if ((inode->i_ino != EXT2_ROOT_INO && inode->i_ino != EXT2_ACL_IDX_INO &&
inode->i_ino != EXT2_ACL_DATA_INO && inode->i_ino < EXT2_FIRST_INO) ||
inode->i_ino > inode->i_sb->u.ext2_sb.s_es->s_inodes_count) {
ext2_error (inode->i_sb, "ext2_read_inode",
"bad inode number: %lu", inode->i_ino);
return;
}
block_group = (inode->i_ino - 1) / EXT2_INODES_PER_GROUP(inode->i_sb);
if (block_group >= inode->i_sb->u.ext2_sb.s_groups_count)
ext2_panic (inode->i_sb, "ext2_read_inode",
"group >= groups count");
group_desc = block_group / EXT2_DESC_PER_BLOCK(inode->i_sb);
desc = block_group % EXT2_DESC_PER_BLOCK(inode->i_sb);
bh = inode->i_sb->u.ext2_sb.s_group_desc[group_desc];
if (!bh)
ext2_panic (inode->i_sb, "ext2_read_inode",
"Descriptor not loaded");
gdp = (struct ext2_group_desc *) bh->b_data;
block = gdp[desc].bg_inode_table +
(((inode->i_ino - 1) % EXT2_INODES_PER_GROUP(inode->i_sb))
/ EXT2_INODES_PER_BLOCK(inode->i_sb));
if (!(bh = bread (inode->i_dev, block, inode->i_sb->s_blocksize)))
ext2_panic (inode->i_sb, "ext2_read_inode",
"unable to read i-node block\n"
"inode=%lu, block=%lu", inode->i_ino, block);
raw_inode = ((struct ext2_inode *) bh->b_data) +
(inode->i_ino - 1) % EXT2_INODES_PER_BLOCK(inode->i_sb);
/* 浠庣鐩樹腑璇诲彇鐨別xt2鏂囦欢绯荤粺涓枃浠剁殑inode鐨勬暟鎹?
* 骞惰祴鍊?
*/
inode->i_mode = raw_inode->i_mode;
inode->i_uid = raw_inode->i_uid;
inode->i_gid = raw_inode->i_gid;
inode->i_nlink = raw_inode->i_links_count;
inode->i_size = raw_inode->i_size;
inode->i_atime = raw_inode->i_atime;
inode->i_ctime = raw_inode->i_ctime;
inode->i_mtime = raw_inode->i_mtime;
inode->u.ext2_i.i_dtime = raw_inode->i_dtime;
inode->i_blksize = inode->i_sb->s_blocksize;
inode->i_blocks = raw_inode->i_blocks;
inode->u.ext2_i.i_flags = raw_inode->i_flags;
inode->u.ext2_i.i_faddr = raw_inode->i_faddr;
inode->u.ext2_i.i_frag = raw_inode->i_frag;
inode->u.ext2_i.i_fsize = raw_inode->i_fsize;
inode->u.ext2_i.i_file_acl = raw_inode->i_file_acl;
inode->u.ext2_i.i_dir_acl = raw_inode->i_dir_acl;
inode->u.ext2_i.i_version = raw_inode->i_version;
inode->u.ext2_i.i_block_group = block_group;
inode->u.ext2_i.i_next_alloc_block = 0;
inode->u.ext2_i.i_next_alloc_goal = 0;
if (inode->u.ext2_i.i_prealloc_count)
ext2_error (inode->i_sb, "ext2_read_inode",
"New inode has non-zero prealloc count!");
if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode))
inode->i_rdev = raw_inode->i_block[0];
else for (block = 0; block < EXT2_N_BLOCKS; block++)
inode->u.ext2_i.i_data[block] = raw_inode->i_block[block];
brelse (bh);
inode->i_op = NULL;
/* 閫氳繃鍒ゆ柇鏂囦欢绫诲瀷鏉ヨ缃枃浠剁殑璇诲啓鍑芥暟
*/
if (inode->i_ino == EXT2_ACL_IDX_INO ||
inode->i_ino == EXT2_ACL_DATA_INO)
/* Nothing to do */ ;
else if (S_ISREG(inode->i_mode))
inode->i_op = &ext2_file_inode_operations;
else if (S_ISDIR(inode->i_mode))
inode->i_op = &ext2_dir_inode_operations;
else if (S_ISLNK(inode->i_mode))
inode->i_op = &ext2_symlink_inode_operations;
else if (S_ISCHR(inode->i_mode))
inode->i_op = &chrdev_inode_operations;
else if (S_ISBLK(inode->i_mode))
inode->i_op = &blkdev_inode_operations;
else if (S_ISFIFO(inode->i_mode))
init_fifo(inode);
if (inode->u.ext2_i.i_flags & EXT2_SYNC_FL)
inode->i_flags |= MS_SYNC;
}
在知道了struct file的f_op和struct inode的i_op之后,当你需要去调用某个系统调用的时候,其实最后的关键操作都交给相应的f_op和i_op去操作了。