590 likes | 864 Vues
Linux 操作系统分析与实践 第五讲:文件管理. 《 Linux 操作系统分析与实践 》 课程建设小组 北京大学 二零零八年春季 *致谢:感谢 Intel 对本课程项目的资助. 本讲主要内容. 虚拟文件系统 VFS Ext2 文件系统. POSIX 文件 API. 设备 API. POSIX 文件 API. 文件 API. 文件管理器. 文件管理器. 设备独立转换器. 设备驱动. 设备驱动. 磁盘. 磁盘. 传统文件系统. Linux 文件系统. 虚拟文件系统.
E N D
Linux操作系统分析与实践第五讲:文件管理 《Linux操作系统分析与实践》课程建设小组 北京大学 二零零八年春季 *致谢:感谢Intel对本课程项目的资助
本讲主要内容 • 虚拟文件系统VFS • Ext2文件系统
POSIX 文件 API 设备API POSIX文件API 文件API 文件管理器 文件管理器 设备独立转换器 设备驱动 设备驱动 磁盘 磁盘 传统文件系统 Linux文件系统
虚拟文件系统 • 内核软件层,在内核中提供一个文件系统框架(接口函数集、管理用的数据结构、各种缓存机制) • 为各种文件系统提供通用接口
Linux文件管理系统调用接口 VFS开关 Ext2 文件系统 VFAT 文件系统 NFS 文件系统 Proc 文件系统
虚拟文件系统(续) • 支持的文件系统可分为三类 • 基于磁盘的文件系统 • e.g VFAT、NTFS、ISO9660 CDROM… • 网络文件系统 • e.g NFS、Coda… • 特殊文件系统 • 不管理磁盘空间,e.g /proc • 所有的文件系统都可以安装到根系统的子目录中
通用文件模型 • 用于表示所有支持的文件系统,由以下对象类型组成 • 超级块对象:存放已安装文件系统信息 • 索引节点对象:存放文件信息,每个索引节点对象的索引节点号唯一地标识了文件系统中的文件 • 文件对象:存放打开文件与进程间交互的信息 • 目录项对象:存放目录项与文件进行链接的信息 • 同时VFS还使用了磁盘高速缓存(软件机制),将常用的目录项对象放在目录项高速缓存中
通用文件模型(续) • 这个模型是对要支持的文件系统的一种抽象,对于UNIX系列的,直接就可以很好地支持;对于没有目录文件的文件系统,比如FAT系列,Linux需要能够快速建立对应于目录的文件 • 可以将VFS看成一种通用文件系统,它位于应用程序和具体文件系统之间,提供了一层通用的接口,它在必要时依赖具体的文件系统
VFS数据结构 • 描述各对象的数据结构 • 超级块对象super_block • 索引节点对象inode • 文件对象file • 目录项对象dentry
超级块对象 • 超级块对象 struct super_block { struct list_head s_list; /* Keep this first */ kdev_t s_dev; unsigned long s_blocksize; unsigned char s_blocksize_bits; unsigned char s_dirt; unsigned long long s_maxbytes; /* Max file size */ struct file_system_type *s_type; struct super_operations *s_op; struct dquot_operations *dq_op; ... struct list_head s_files /*分配给超级块的文件对象链表*/ }; • 所有超级块对象由循环双链表组成,首元素s_list • 超级块操作在s_op中
超级块对象(续) struct super_operations { void (*read_inode) (struct inode *); void (*read_inode2) (struct inode *, void *) ; void (*dirty_inode) (struct inode *); void (*write_inode) (struct inode *, int); void (*put_inode) (struct inode *); void (*delete_inode) (struct inode *); void (*put_super) (struct super_block *); void (*write_super) (struct super_block *); void (*write_super_lockfs) (struct super_block *); void (*unlockfs) (struct super_block *); int (*statfs) (struct super_block *, struct statfs *); int (*remount_fs) (struct super_block *, int *, char *); void (*clear_inode) (struct inode *); void (*umount_begin) (struct super_block *); struct dentry * (*fh_to_dentry)(struct super_block *sb, __u32 *fh, int len, int fhtype, int parent); int (*dentry_to_fh)(struct dentry *, __u32 *fh, int *lenp, int need_parent); int (*show_options)(struct seq_file *, struct vfsmount *); }; 主要的操作对象:i节点、超级块、文件系统
索引节点对象 struct inode { struct list_head i_hash; struct list_head i_list; struct list_head i_dentry; ... unsigned long i_ino; /*索引节点号*/ atomic_t i_count; /*引用计数器*/ … unsigned long i_nrpages /*包含文件数据的页数*/ ... struct inode_operations *i_op; /*索引节点的操作*/ wait_queue_head_t i_wait; /*索引节点等待队列*/ struct file_lock *i_flock; /*指向文件锁链表的指针*/ struct address_space *i_mapping; /*共享内存中使用的address_space指针*/ struct address_space i_data; /*块设备的address_space对象*/ ... struct pipe_inode_info *i_pipe; ... struct block_device *i_bdev; /*指向块设备驱动程序*/ struct char_device *i_cdev; /*指向字符设备驱动程序*/ ... unsigned char i_sock; /*文件是否是套接字*/ … };
索引节点对象(续) • 分为三类,每一类均组织成双向循环链表,由inode.i_list连接 • 未使用的索引节点 • 正在使用的索引节点 • 脏索引节点 • 索引节点对象组织成inode_hashtable散列表,冲突的节点组成链表,由inode.i_hash连接
索引节点对象(续) struct inode_operations { int (*create) (struct inode *,struct dentry *,int); struct dentry * (*lookup) (struct inode *,struct dentry *); int (*link) (struct dentry *,struct inode *,struct dentry *); int (*unlink) (struct inode *,struct dentry *); int (*symlink) (struct inode *,struct dentry *,const char *); int (*mkdir) (struct inode *,struct dentry *,int); int (*rmdir) (struct inode *,struct dentry *); int (*mknod) (struct inode *,struct dentry *,int,int); int (*rename) (struct inode *, struct dentry *,struct inode *, struct dentry *); int (*readlink) (struct dentry *, char *,int); int (*follow_link) (struct dentry *, struct nameidata *); void (*truncate) (struct inode *); int (*permission) (struct inode *, int); int (*revalidate) (struct dentry *); int (*setattr) (struct dentry *, struct iattr *); int (*getattr) (struct dentry *, struct iattr *); int (*setxattr) (struct dentry *, const char *, void *, size_t, int); ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t); ssize_t (*listxattr) (struct dentry *, char *, size_t); int (*removexattr) (struct dentry *, const char *); }; 目录项对象相关的文件的索引节点的创建、查找、修改、删除
文件对象 • 文件对象在磁盘上没有映像,在文件被打开时创建 • 文件对象也分为三类,每类是双向循环链表,由file.f_list连接 • 未使用的文件对象,首元素free_list • 还未分配给超级块的在使用的文件对象,首元素anon_list • 分配给超级块的在使用的文件对象,首元素为super_block.s_files
文件对象(续) struct file { struct list_head f_list; /*Pointers for generic file object list*/ struct dentry *f_dentry; /*dentry object associated with the file*/ struct vfsmount *f_vfsmnt; /*Mounted filesystem containing the file*/ struct file_operations *f_op; /*Pointer to file operation table*/ atomic_t f_count; /*File object's usage counter*/ ... loff_t f_pos; /*Current file offset (file pointer)*/ ... void *private_data; /*Needed for tty driver*/ ... struct kiobuf *f_iobuf; /*Descriptor for direct access buffer*/ long f_iobuf_lock; /*Lock for direct I/O transfer*/ };
文件对象(续) struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); /*Sends a command to an underlying hardware device */ int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); };
目录项对象 • 目录项用于建立与索引节点的联系,在磁盘上没有映像 • 目录项对象的四种状态 • 空闲:未关联索引节点 • 未使用:关联索引节点,但未被内核使用 • 正在使用:关联索引节点,且正被使用 • negative:关联的索引节点不存在
目录项对象(续) struct dentry { atomic_t d_count; unsigned int d_flags; struct inode * d_inode; /* Where the name belongs to - NULL is negative */ struct dentry * d_parent; /* parent directory */ struct list_head d_hash; /* lookup hash list */ struct list_head d_lru; /* d_count = 0 LRU list */ struct list_head d_child; /* child of parent list */ struct list_head d_subdirs; /* our children */ struct list_head d_alias; /* inode alias list */ int d_mounted; struct qstr d_name; unsigned long d_time; /* used by d_revalidate */ struct dentry_operations *d_op; struct super_block * d_sb; /* The root of the dentry tree */ unsigned long d_vfs_flags; void * d_fsdata; /* fs-specific data */ unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */ };
目录项对象(续) struct dentry_operations { int (*d_revalidate)(struct dentry *, int); int (*d_hash) (struct dentry *, struct qstr *); int (*d_compare) (struct dentry *, struct qstr *,struct qstr *); int (*d_delete)(struct dentry *); void (*d_release)(struct dentry *); void (*d_iput)(struct dentry *, struct inode *); }; 目录项对象的管理、目录项高速缓存的维护
目录项高速缓存 • 包括: • 在使用、未使用和negative状态的目录项对象,组织成最近最少使用的双向链表 • 在使用:dentry.d_alias • 未使用:dentry.d_lru • negative:内核维护 • 散列表dentry_hashtable,文件名和目录名到目录项对象的hash表,各元素指向冲突元素链表 • 目录项对象通过dentry.d_hash连接各冲突元素
与进程相关的文件 struct task_struct { ... struct fs_struct *fs; /*文件系统信息*/ struct files_struct *files; /*当前打开的文件信息*/ ... }; struct files_struct { … struct file ** fd; /* 文件对象数组,索引就是文件描述符*/ … struct file * fd_array[NR_OPEN_DEFAULT]; … };
特殊文件系统 • 给用户提供一种容易的方式操作内核数据结构并实现操作系统的特殊特征
文件系统注册 • 用file_system_type结构表示文件系统类型 • 链表,首元素为file_systems • 读/写自旋锁为file_system_lock • 相关的操作 • register_filesystem() • unregister_filesystem() • get_fs_type()
文件系统注册 file_systems name “ext2” fa_flags read_super next name “proc” fa_flags read_super next name “iso9660” fa_flags read_super next
文件系统注册(续) struct file_system_type { const char *name; int fs_flags; struct super_block *(*read_super) (structsuper_block *, void *, int); struct module *owner; struct file_system_type * next; struct list_head fs_supers; //link list head of super blocks };
文件系统安装 • 文件系统可以安装在系统的任意目录下 • e.g mount –t type dev dir • 多个文件系统可以安装到同一个目录 • 结构类型为vfsmount的数据结构描述每个已安装文件描述符,内核中是一个双向链表,首指针为mnt_list • 可使用的操作 • alloc_vfsmnt():分配并初始化已安装文件系统描述符 • free_vfsmnt():释放指定的已安装文件系统描述符 • lookup_mnt():在散列表中查找描述符并返回它的地址
文件系统安装(续) • 根文件系统的安装: 1、安装特殊文件系统rootfs,用来提供作为初始安装点的空目录,主要由函数init_mount_tree()完成 2、在空目录上安装一个真正的根目录,由函数mount_root()完成 why rootfs? Linux允许根文件系统存放在很多不同的地方,因此内核在启动时可能会安装和卸载多个根文件系统,使用rootfs可以很容易改变实际的根目录
文件系统安装(续) • 安装一般文件系统,系统调用mount()->sys_mount()->do_mount(): 1、查找要安装到的目录的目录项 2、优先针对三种安装标志参数值执行相应的操作 MS_REMOUNT:do_remount()重新安装改变了安装标志的文件系统 MS_BIND:调用do_loopback()完成“绑定安装”,使系统目录树中的另一个目录能看见 MS_MOVE:调用do_move_mount()改变已安装文件系统的目录 3、否则,调用do_add_mount()安装普通系统
文件系统安装(续) • 卸载文件系统,系统调用umount()->sys_umount(): 1、查找文件系统目录的目录项对象 2、以下情况返回出错: • 传入的目录参数不是文件系统的目录 • 文件系统没有安装在目录下 • 用户不具有卸载文件系统的特权 3、调用do_umount()卸载文件系统
文件加锁 • 多个进程访问同一个文件时必须要解决同步问题 read()、write()等操作:原子性的 • 文件加锁机制允许对整个文件或它的某一部分进行加锁,同一个文件不同部分可以保持多个锁
文件加锁(续) • Linux中的文件锁分类: • POSIX标准规定的两种锁: • 劝告锁(建议锁) • 只有当其他进程显式检查文件锁时,这个文件锁才起作用 • 强制锁 • 每次调用open()、read()、write()系统调用时,内核要检查确保这些系统调用不违背在所访问文件上加的强制锁 • 强制锁的两种变种: • 共享模式强制锁:进程打开文件的时候不能锁设置的访问模式相冲突 • 租借锁:打开受租借锁保护的文件的进程必须被阻塞,直到在一定时间间隔内拥有锁的进程更新文件并释放锁,否则,由内核自动删除
文件加锁(续) • FL_POSIX与进程和索引节点相关联,可指定具体区段 • FL_LOCK与文件对象相关联,锁整个文件
文件加锁(续) 描述文件锁的数据结构: struct file_lock { … wait_queue_head_t fl_wait; /*processes blocked at this lock*/ struct file *fl_file; unsigned char fl_flags; unsigned char fl_type; loff_t fl_start; loff_t fl_end; void (*fl_notify)(struct file_lock *); /* unblock callback */ void (*fl_insert)(struct file_lock *); /* lock insertion callback */ void (*fl_remove)(struct file_lock *); /* lock removal callback */ … }; • 所有的file_lock组织成双链表,首元素为file_lock_list • 同一文件的file_lock结构也是一个链表,首元素为索引节点对象中的i_flock字段
路径名查找 • 要考虑的问题: 1、进程是否有权限读取目录的内容 2、目录是否为符号链接 3、符号链接导致的循环引用问题 4、文件名是否是已安装文件系统的安装点
VFS的系统调用 • open()、read()、write()、close() • 填相应的数据结构 • 需要注意的地方: 1、读写的时候注意检查文件锁,读写都是调用具体注册的文件系统的操作来实现 2、关闭的时候注意释放文件锁、调flush()同步
Ext2文件系统 • ext2 1993 年创建,是ext的改进版本,具有如下的特点: • 支持UNIX所有标准的文件特征 • 可管理大硬盘,一个分区的容量最大可达4TB • 它使用变长的目录项 ,支持255个字符的长文件名,可扩展到1012个字符 • 使用位图来管理数据块和节点的使用情况 • 使用了块组的概念,从而使数据的读和写更快,更有效,也使系统变得更安全可靠
引导块 块组0 块组1 块组2 块组3 超级块 组描述符 块位图 索引节点位图 索引节点表 数据块 Ext2:磁盘数据结构 • Ext2将磁盘分区看成是由块组组成,每个块组包含一个或多个块 • 每个块组大小相同,且顺序存放
引导块 块组0 块组1 块组2 块组3 超级块 组描述符 块位图 索引节点位图 索引节点表 数据块 struct ext2_super_block { __u32 s_inodes_count; /* 索引节点总数 */ __u32 s_blocks_count; /* 文件系统的块数 */ __u32 s_r_blocks_count; /* 保留给内核使用的块数 */ __u32 s_free_blocks_count; /* 空闲块计数器 */ __u32 s_free_inodes_count; /* 空闲索引节点计数器 */ __u32 s_first_data_block; /* 第一个数据块的块号 */ __u32 s_log_block_size; /* 块大小 */ ... __u32 s_blocks_per_group; /* # 每组的块数 */ ... __u32 s_inodes_per_group; /* # 每组的节点数 */ ... };
引导块 块组0 块组1 块组2 块组3 超级块 组描述符 块位图 索引节点位图 索引节点表 数据块 struct ext2_group_desc { __u32 bg_block_bitmap; /* 块位图的块号 */ __u32 bg_inode_bitmap; /* 索引节点位图的块号 */ __u32 bg_inode_table; /* 第一个索引节点表块的块号 */ __u16 bg_free_blocks_count; /* 组中空闲块的个数 */ __u16 bg_free_inodes_count; /* 组中索引点的个数 */ __u16 bg_used_dirs_count; /* 组中目录的个数 */ … }; 注:各位图存放在一个单独的块中
引导块 块组0 块组1 块组2 块组3 超级块 组描述符 块位图 索引节点位图 索引节点表 数据块 1、索引节点表由多个块组成, 每个块存放多个大小相同的索引节点 2、ext2_super_block. bg_inode_table指向第一个块的块号 struct ext2_inode { __u16 i_mode; /* 描述的是什么及用户应具有的权限 */ ... __u32 i_size; /* 文件的有效长度 */ ... __u32 i_blocks; /* 已分配给文件的数据块数 */ ... __u32 i_block[EXT2_N_BLOCKS]; /* 指针数组,各元素指向分配给文件的数据块 */ ... }; i_size最高位没有使用,因此文件大小限制为2GB 块组和索引节点表的使用,可以快速得到索引节点的磁盘地址
引导块 块组0 块组1 块组2 块组3 超级块 组描述符 块位图 索引节点位图 索引节点表 数据块
引导块 块组0 块组1 块组2 块组3 超级块 组描述符 块位图 索引节点位图 索引节点表 数据块 文件类型为目录时,数据块存放目录项数据结构: struct ext2_dir_entry { __u32 inode; /* 索引节点号 */ __u16 rec_len; /* 目录项长度 */ __u8 name_len; /* 文件名长度 */ __u8 name_len; /* 文件名类型 */ char name[EXT2_NAME_LEN]; /* 文件名 */ }; 删除目录项:inode置0,并增长前一个有效目录项的rec_len长度,这样在计算下一个有效目录项时,就可以跳过该已删除的目录项
文件类型 • 普通文件 • 目录 • 字符设备特殊文件 • 块设备特殊文件 • 命名管道 • Socket • 符号连接
文件查找 • /usr/include/stdio.h • 从当前进程的根目录项中(current→fs → root)找到它的根目录的inode编号,根据该编号和超级块的s_inodes_per_group,计算出该inode所在的块组,查找该块组中的inode 表,找到描述根目录文件的inode,根据该inode的描述,读取其数据块(得到目录项列表)
文件查找(续) • 搜索名为“usr”的目录项,得到对应的inode编号 • 按照该编号,以及超级块的s_inodes_per_group,计算出该inode所在的块组,查找块组的inode表,找到目录/usr对应的inode,根据该inode的描述,读取其数据块(得到目录项列表) • 在/usr的目录项列表中找出名为“include”的目录项,得到对应的inode编号
文件查找(续) • 按照该编号,以及超级块的s_inodes_per_group,计算出该inode所在的块组,查找块组的inode表,找到目录/usr对应的inode,根据该inode的描述,读取其数据块(又得到目录项列表) • 在/usr/include的目录项列表中找出名为“stdio.h”的目录项,得到对应的inode编号