Linux虚拟文件系统源码分析
数据结构
VFS中关联的数据结构很多,如下图所示。在这里我们先把核心要素说明一下,基本上都是按照面向对象的思路来设计的,而最重要的要素就是超级块、索引节点、文件和目录项。
这里会介绍文件描述符到文件的转换,索引节点和目录项哈希表的基本操作,并以根文件系统为例说明文件系统的注册与挂载操作。此外还包括文件名查询代码的分析,以及文件访问操作等。
超级块
super_block
struct super_block {
struct list_head s_list; // super_blocks & sb_lock
dev_t s_dev; // MKDEV()
struct block_device *s_bdev;
unsigned long s_blocksize; // 512 or 2**n
unsigned char s_blocksize_bits; // log2(s_blocksize)
loff_t s_maxbytes; // MAX_LFS_FILESIZE
struct file_system_type *s_type;
const struct super_operations *s_op;
const struct dquot_operations *dq_op;
const struct quotactl_ops *s_qcop;
const struct export_operations *s_export_op;
unsigned long s_flags;
#define MS_RDONLY 1 // 只读
#define MS_NOSUID 2 // 忽略GID和UID
#define MS_NODEV 4 // 不允许访问设备特殊文件
#define MS_NOEXEC 8 // 不允许执行程序
#define MS_SYNCHRONOUS 16 // 写入立即同步
#define MS_REMOUNT 32 // 更改挂载标志
#define MS_MANDLOCK 64 // 允许对FS强制锁住
#define MS_DIRSYNC 128 // 目录更改立即同步
#define MS_NOATIME 1024 // 不更新访问时间
#define MS_NODIRATIME 2048 // 不更新目录访问时间
// ...
unsigned long s_magic; // 验证磁盘信息
struct dentry *s_root; // root dentry
struct rw_semaphore s_umount; // 读写期间防止umount
int s_count; // get_super()/put_super()
atomic_t s_active; // grab_super(), freeze_super()
// deactivate_super()
const struct xattr_handler **s_xattr;
struct list_head s_inodes; // 所有的索引节点链表
// inode->i_sb_list
// inode_sb_list_lock
struct hlist_bl_head s_anon; // 远程网络文件系统的匿名目录项链表
struct list_head s_mounts; // 所有挂载点
// mount->mnt_instances
// mount_lock
struct backing_dev_info *s_bdi;
struct mtd_info *s_mtd;
struct hlist_node s_instances; // 文件系统实例节点
// file_system_type->fs_supers
// sb_lock
struct quota_info s_dquot;
struct sb_writers s_writers;
char s_id[32]; // 设备名
u8 s_uuid[16];
void *s_fs_info; // 指向具体文件系统的信息
unsigned int s_max_links; // 最大硬链接数
fmode_t s_mode; // 文件操作权限
u32 s_time_gran; // 时间精度ns,最大为1s
// 只有VFS会使用这个互斥锁,具体文件系统的代码不能使用
// 这个锁用于防止把一个目录重命名为它的子目录
struct mutex s_vfs_rename_mutex;
// 子类型,在/proc/mounts显示格式为"type.subtype"
char *s_subtype;
char __rcu *s_options; // 传递给mount()的data
const struct dentry_operations *s_d_op; // dentry的默认操作集
// 缓存池ID,-1表示没有缓存池
// 只有ext3、ext4和btrfs文件系统支持这个特性
int cleancache_poolid;
struct shrinker s_shrink;
// number of inodes with nlink == 0 but still referenced
atomic_long_t s_remove_count;
// being remounted read-only
int s_readonly_remount;
// AIO completions deferred from interrupt context
struct workqueue_struct *s_dio_done_wq;
// dentry的lru链表,节点为dentry->d_lru
struct list_lru s_dentry_lru ____cacheline_aligned_in_smp;
// inode的lru链表,节点为inode->i_lru
struct list_lru s_inode_lru ____cacheline_aligned_in_smp;
struct rcu_head rcu;
};
相关函数:
-
sget
-
构造函数,如果没有从已经挂载的文件系统找到需要的
super_block
就会调用alloc_super
分配一个
put_super
-
析构函数,当引用计数减少到0时才会调用
destroy_super
真正释放
super_operations
主要包括对inode数据结构的操作,注意不是对inode的操作,对inode的操作由inode_operations来完成。如:alloc_inode、destroy_inode、dirty_inode等等。
还包括文件系统挂载和卸载等操作,如:sync_fs、statfs、remount_fs等等。
所有的函数由VFS调用,都在进程上下文调用,所有的函数都可能阻塞。
索引节点
inode
struct inode {
umode_t i_mode; // 访问权限
unsigned short i_opflags; // 用于标识具备那些操作
#define IOP_FASTPERM 0x0001 // 没有permission()
#define IOP_LOOKUP 0x0002 // 具有lookup()
#define IOP_NOFOLLOW 0x0004 // 没有follow_link()
unsigned int i_flags;
#define S_SYNC 1 /* Writes are synced at once */
#define S_NOATIME 2 /* Do not update access times */
#define S_APPEND 4 /* Append-only file */
#define S_IMMUTABLE 8 /* Immutable file */
#define S_DEAD 16 /* removed, but still open directory */
#define S_NOQUOTA 32 /* Inode is not counted to quota */
#define S_DIRSYNC 64 /* Directory modifications are synchronous */
#define S_NOCMTIME 128 /* Do not update file c/mtime */
#define S_SWAPFILE 256 /* Do not truncate: swapon got its bmaps */
#define S_PRIVATE 512 /* Inode is fs-internal */
#define S_IMA 1024 /* Inode has an associated IMA struct */
#define S_AUTOMOUNT 2048 /* Automount/referral quasi-directory */
#define S_NOSEC 4096 /* no suid or xattr security attributes */
// attrs: i_uid, i_gid, i_atime, i_mtime, i_ctime
const inode_operations *i_op;
const file_operations *i_fop;
struct super_block *i_sb;
address_space *i_mapping;
unsigned long i_ino;
dev_t i_rdev;
const unsigned int i_nlink;
loff_t i_size; // 文件大小,字节数
blkcnt_t i_blocks; // 文件大小,块数
unsigned int i_blkbits; // 块的位数,从sb继承
spinlock_t i_lock; // protect i_state
unsigned short i_bytes; // bytes consumed
unsigned long i_state;
#define I_DIRTY_SYNC (1 << 0)
#define I_DIRTY_DATASYNC (1 << 1)
#define I_DIRTY_PAGES (1 << 2)
#define __I_NEW 3
#define I_NEW (1 << __I_NEW)
#define I_WILL_FREE (1 << 4)
#define I_FREEING (1 << 5)
#define I_CLEAR (1 << 6)
#define __I_SYNC 7
#define I_SYNC (1 << __I_SYNC)
#define I_REFERENCED (1 << 8)
#define __I_DIO_WAKEUP 9
#define I_DIO_WAKEUP (1 << I_DIO_WAKEUP)
#define I_LINKABLE (1 << 10)
#define I_DIRTY (I_DIRTY_SYNC | I_DIRTY_DATASYNC | I_DIRTY_PAGES)
struct mutex i_mutex;
unsigned long dirtied_when; // jiffies
struct hlist_node i_hash; // inode_hashtable
struct list_head i_wb_list;
struct list_head i_lru; // sb->s_inode_lru & inode->i_lock
struct list_head i_sb_list; // sb->s_inodes
// inode_sb_list_lock
union {
struct hlist_head i_dentry; // 所有引用该节点的dentry
// dentry->d_alias
struct rcu_head i_rcu;
};
u64 i_version;
atomic_t i_count; // iput()
atomic_t i_dio_count; // direct io count
atomic_t i_writecount; // 有多少个用户对该节点有写权限
struct file_lock *i_flock;
struct address_space i_data;
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
};
__u32 i_generation; // 索引节点版本号
void *i_private; // private pointer
};
-
i_nlink
- 硬链接数,虽然这里显示为const,实际上是可以改变的,设计为union,可以用(set/clear/inc/drop)_nlink() 或inode_(inc/dec)_link_count()修改 i_version
- 用来记录索引节点的改变,例如我们用编辑器打开一个文件,里面的数据缓存在file中,当另外一个程序修改文件以后,编辑器就会提示我们,磁盘上的文件已经被修改,我们可以强制覆盖,也可以从磁盘重新读取。 i_devices
- 之所以用链表而不是单个对象,是因为我们可以为同一个设备创建多个设备节点,用mknod就能做到。另外chroot环境会使得一个设备通过多个设备文件。
相关函数:
-
new_inode
-
构造函数,调用
alloc_inode
从inode_cachep
分配索引节点
inode_operations
对索引节点的操作,包括create、lookup、mkdir、rmdir、link、unlink等等。
文件
文件是和进程息息相关的,和文件相关的结构包括:
-
file
- 文件的表示 fs_struct
- 进程和文件系统的关系 files_struct
-
用于将文件描述符转换为
file
file
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
#define f_dentry f_path.dentry
struct inode *f_inode;
const struct file_operations *f_op;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
u64 f_version;
void *private_data;
spinlock_t f_lock;
struct mutex f_pos_lock;
const struct cred *f_cred;
struct file_ra_state f_ra;
struct address_space *f_mapping;
} __attribute__((aligned(4)));
相关函数:
-
alloc_file
-
调用
get_empty_filp
从filp_cachep
分配一个文件
file_operations
这个操作集包含了对文件的所有操作,如读取、写入、打开和关闭等等。
fs_struct
主要包含两个路径,一个是当前工作目录,一个是工作目录所在文件系统的根目录。主要体现了进程和具体文件系统的关系。
struct fs_struct {
int users;
spinlock_t lock;
seqcount_t seq;
int umask;
int in_exec;
struct path root, pwd;
};
struct path {
struct vfsmount *mnt;
struct dentry *dentry;
};
files_struct
主要就是一个file指针数组,我们通常说的文件描述符是一个整数,而这个整数正好可以作为下标,从而从files_struct中获得file结构。具体查找是通过fdt->fd[fd]来找到对应的file。
struct files_struct {
atomic_t count;
struct fdtable __rcu *fdt;
struct fdtable fdtab;
spinlock_t file_lock ____cacheline_aligned_in_smp;
int next_fd; // 当前fd + 1
unsigned long close_on_exec_init[1];
unsigned long open_fds_init[1];
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};
-
fdt
-
默认是指向fdtab的,当打开的文件数目比较多的时候,就需要重新分配一个fdtable,并增大其fd数组和打开位图,然后将这个fdt指向新分配的fdtable。原来fdt所指向的内存会复制到新的fdtable。
至于如何判断fdt是否指向动态fdtable,也就是最后是否需要释放fdt所指向的内存,可以通过判断fdt和fdtab的地址是否相等来确定。
file_lock
- 保护对file_struct的修改。
从文件描述符转换为file
的关键数据结构就是fdtable
。
struct fdtable {
unsigned int max_fds; // 最大可打开文件数,即fd数组长度
struct file __rcu **fd; // fd数组
unsigned long *close_on_exec; // 位图:带O_CLOEXEC打开标志的fd
unsigned long *open_fds; // 位图:已经打开的fd
struct rcu_head rcu;
};
-
fd
-
如果打开的文件比较少,那么这个fd将指向files_struct的fd_array。如果打开的文件比较多,fdtable本身就是动态分配的,fd也是动态分配。所以是否要释放fd所指空间很好判断,如果要释放fdtable就一定会释放fd。对fd的分配会尝试kmalloc()和vmalloc()两种方法。
另外,close_on_exec与open_fds的行为同fd,如果fdtable是动态分配的,那么他们也必然是动态分配的。
目录项
dentry
struct dentry {
unsigned int d_flags; // 表示支持哪些操作
seqcount_t d_seq;
struct hlist_bl_node d_hash;
struct dentry *d_parent;
struct qstr d_name; // 名字及hash值,
struct inode *d_inode; // 关联inode,NULL表示negative
unsigned char d_iname[DNAME_INLINE_LEN];
struct lockref d_lockref;
const struct dentry_operations *d_op;
struct super_block *d_sb; // 指向超级块
unsigned long d_time; // used by d_revalidate
void *d_fsdata; // fs-specific data
struct list_head d_lru; // sb->s_dentry_lru
union {
struct list_head d_child; // parent的子节点,应叫d_sibling
struct rcu_head d_rcu;
} d_u;
// 这个名字也不恰当,因为它不仅仅包含目录,还包含文件。
struct list_head d_subdirs; // 这才是真正的children
struct hlist_node d_alias; // inode->i_dentry
};
相关函数:
-
d_alloc
-
构造函数,从
dentry_cache
分配一个negative目录项
dput
-
析够函数,当引用计数为0时调用
dentry_kill
释放目录项
dentry_operations
由于dentry主要供VFS使用,所以操作集中的函数一般情况下也不需要具体文件系统去实现。这里的函数是针对dentry的操作,如d_revalidate、d_hash、d_compare、d_delete、 d_release、d_prune等等。
比较容易混淆的是d_delete和d_prune,前者只是判断是否需要delete,如果要delete就会释放dentry,否则会将dentry加入到LRU,而后者是在unhash前的最后一步动作,当然一般也不需要实现。而d_release只会在最后调用,它负责释放内存。
挂载点
struct mount {
struct hlist_node mnt_hash; // mount_hashtable[i]
// mount_lock
struct mount *mnt_parent;
struct dentry *mnt_mountpoint; // 即mnt->mnt_root。
struct vfsmount mnt;
struct rcu_head mnt_rcu;
#ifdef CONFIG_SMP
struct mnt_pcp __percpu *mnt_pcp;
#else
int mnt_count;
int mnt_writers;
#endif
struct list_head mnt_mounts; // mount->mnt_child
struct list_head mnt_child; // mount->mnt_mounts
struct list_head mnt_instance; // sb->s_mounts
const char *mnt_devname; // device name: "/dev/dsk/hda1"
struct list_head mnt_list; // mnt_namespace->list
struct list_head mnt_expire; // link in fs-specific expiry list
struct list_head mnt_share; // circular list of shared mounts
struct list_head mnt_slave_list;// list of slave mounts
struct list_head mnt_slave; // slave list entry
struct mount *mnt_master; // slave is on master->mnt_slave_list
struct mnt_namespace *mnt_ns; // containing namespace
struct mountpoint *mnt_mp; // where is it mounted
int mnt_id; // mount identifier
int mnt_group_id; // peer group identifier
int mnt_expiry_mark;
int mnt_pinned;
struct path mnt_ex_mountpoint;
};
struct vfsmount {
struct dentry *mnt_root; // 挂载目录项
struct super_block *mnt_sb; // 指向super_block
int mnt_flags;
};
struct mountpoint {
struct hlist_node m_hash; // mountpoint_hashtable
struct dentry *m_dentry;
int m_count;
};
文件名
用户空间查询路径的时候实际是传递字符串,内核也可以只保存字符串,但是考虑到内核中查询路径过程比较复杂,单用字符串比较麻烦,就将和文件名相关的信息用一个结构体filename
表示,最关键的还是其名字。
struct filename {
const char *name;
// - 指向用户空间的文件名,如果不是从用户空间传递则指向NULL
const __user char *uptr;
struct audit_names *aname; // 不讨论audit相关代码
bool separate;
};
-
separate
-
filename设计精巧之处在于将长度小于
EMBEDDED_NAME_MAX
的文件名放到filename所在空间后面,这由__getname
保证。当然如果文件名长度很大,就需要单独分配空间来存放文件名。这里的separate
就是为了提供线索,告诉__putname
如何去释放。
相关函数:
-
getname
-
即
getname_flags(filename, 0, NULL)
getname_kernel
-
该函数要求文件名长度小于
EMBEDDED_NAME_MAX
,这由内核代码来保证。
putname
-
释放
filename
基本操作
从描述符获取文件
查找文件的线路图为:current -> files_struct -> fdtable -> file,具体可以参考fget
函数。但是内核在查询的时候引入一些标志,这些标志存放在file指针的低位,为了方便使用引入fd
来分离低位的标志,如下所示。
struct fd {
struct file *file;
unsigned int flags;
};
static inline struct fd fdget(unsigned int fd)
{
return __to_fd(__fdget(fd));
}
static inline struct fd __to_fd(unsigned long v)
{
return (struct fd){(struct file *)(v & ~3), v & 3};
}
索引节点哈希表
哈希表结构
struct hlist_head {
struct hlist_node *first;
};
struct hlist_node {
struct hlist_node *next, **pprev;
};
static __initdata unsigned long ihash_entries;
static unsigned int i_hash_mask __read_mostly;
static unsigned int i_hash_shift __read_mostly;
static struct hlist_head *inode_hashtable __read_mostly;
static __cacheline_aligned_in_smp DEFINE_SPINLOCK(inode_hash_lock);
-
i_hash_mask
-
实际长度位数,即
ilog2(ihash_entries)
i_hash_shift
-
最大索引值,即
(1 << i_hash_mask) - 1
关于hash值的计算使用的是如下函数,参数hashval
实际就是inode的索引号。
static unsigned long hash(struct super_block *sb, unsigned long hashval)
{
unsigned long tmp;
tmp = (hashval * (unsigned long)sb) ^ (GOLDEN_RATIO_PRIME + hashval) /
L1_CACHE_BYTES;
tmp = tmp ^ ((tmp ^ GOLDEN_RATIO_PRIME) >> i_hash_shift);
return tmp & i_hash_mask;
}
-
insert_inode_hash
- 将inode插入哈希表 remove_inode_hash
- 将inode从哈希表删除
目录项哈希表
哈希表结构
struct hlist_bl_head {
struct hlist_bl_node *first;
};
struct hlist_bl_node {
struct hlist_bl_node *next, **pprev;
};
static __initdata unsigned long dhash_entries;
static struct hlist_bl_head *dentry_hashtable __read_mostly;
static unsigned int d_hash_mask __read_mostly;
static unsigned int d_hash_shift __read_mostly;
-
dentry_hashtable
- 在dcache_init()/dcache_init_early()时会对其初始化 d_hash_mask
-
实际长度位数,即
ilog2(dhash_entries)
d_hash_shift
-
最大索引值,即
(1 << d_hash_mask) - 1
对hash值的计算采用的是如下函数,注意d_hash
的参数hash
是将路径名转换出来的一个数字,具体转换方法比较复杂,请参考full_name_hash
。
static inline u32 hash_32(u32 val, unsigned int bits)
{
/* On some cpus multiply is faster, on others gcc will do shifts */
u32 hash = val * GOLDEN_RATIO_PRIME_32;
/* High bits are more random, so use them. */
return hash >> (32 - bits);
}
static inline struct hlist_bl_head *d_hash(const struct dentry *parent,
unsigned int hash)
{
hash += (unsigned long) parent / L1_CACHE_BYTES;
return dentry_hashtable + hash_32(hash, d_hash_shift);
}
-
d_add
- 实例化dentry并加入哈希表,所谓实例化就是和具体的inode关联 d_lookup
-
根据路径名查找目录项,在
__d_lookup
基础上加了一个顺序锁校验
复杂操作
根文件系统
注册文件系统类型
注册rootfs是在init_rootfs()中完成的,主要工作就是注册rootfs_fs_type。
static struct file_system_type rootfs_fs_type = {
.name = "rootfs",
.mount = rootfs_mount,
.kill_sb = kill_litter_super,
};
一旦我们注册好这个文件系统,在初始化rootfs的时候就会调用init_ramfs_fs(),也就是说注册了rootfs会紧接着注册ramfs,而初始化ramfs只不过就是注册ramfs_fs_type。
static struct file_system_type ramfs_fs_type = {
.name = "ramfs",
.mount = ramfs_mount,
.kill_sb = ramfs_kill_sb,
.fs_flags = FS_USERNS_MOUNT,
};
挂载根文件系统
一旦注册好上面两个文件系统,接下来就要开始初始化挂载树:
-
init_mount_tree
-
调用
vfs_kern_mount
,创建名字空间,设置进程pwd/root路径
vfs_kern_mount
-
分配
mount
,调用mount_fs
来执行挂载操作,建立挂载点关系
mount_fs
-
调用具体文件系统提供的
mount
操作,这里为rootfs_mount
rootfs_mount
-
设置
super_block
填充函数为ramfs_fill_super
转交给mount_nodev
mount_nodec
-
调用
sget
分配super_block
,用ramfs_fill_super
初始化,
ramfs_fill_super
-
初始化
super_block
,调用ramfs_get_inode
创建根节点,调用d_make_root
设置根目录项
ramfs_get_inode
-
调用
new_inode
分配一个inode,然后初始化inode_operations
和file_operations
d_make_root
-
调用
__d_alloc
分配一个目录项,再调用d_inistantiate
和根索引节点关联
创建名字空间
在init_mount_tree
完成根目录挂载之后,会为系统init进程准备namespace,其目的就是将mnt和dentry信息记录在进程数据块中。
-
create_mnt_ns
-
调用
alloc_mnt_ns
分配挂载名字空间mnt_namespace
,然后设置挂载点和名字空间关系
alloc_mnt_ns
- 分配名字空间
文件查询
基本概念
- 路径查找就是通过路径名查找dentry。
- 查找从路径名第1个元素开始,要么是ROOT,要么是CWD。
- 然后查找其子目录,称之为路径名的下一个元素。
- 从2.5.62开始使用了新的锁模型,利用RCU来避免使用锁。
- 从2.6.38开始使用RCU实现完全的"store-free"。
- 不需要锁,不用原子操作,不用保存常用dentry到cachelines。
如果路径名地一个字符是'/'那么就从current->fs->root开始查找,不然就从current->fs->pwd查找。
除了查找路径我们还有许多事情要做,对目录的访问权限必须检查,符号链接可能导致循环引用,内核必须考虑这些情况,文件名可能是一个新的文件系统,这时必须要能检测到并跳到新的文件系统。
数据结构
在搜索的过程中需要有一个数据结构保存中间结果,这个nameidata就起着路标的作用。
struct nameidata {
struct path path; // 已解析路径
struct qstr last; // 下一个等待解析的路径元素
struct path root; // 根目录
struct inode *inode; // 已解析节点:path.dentry.d_inode
unsigned int flags; // 以何种方式查询
unsigned seq, m_seq; // 顺序锁
int last_type; // 下一个要解析的路径单元类型
unsigned depth; // 符号链接深度
char *saved_names[MAX_NESTED_LINKS + 1];
};
路径查找
这里先举一个简单例子来加深理解。假设我们要创建一个"/dev"目录,创建目录是由系统调用mkdir()完成的,这个系统调用我们简单来看,包括两个部分。
第一个部分创建dentry:也就是根据文件名来创建dentry,首先要找到父目录,父目录都不用找了,直接可以从current->fs中获取。然后我们需要找一下nameidata指定的目录,看看是不是已经有这个要创建的目录了。然后创建dentry,分配是由lookup_hash()来完成的。
第二个部分是调用inode->i_op->mkdir()来创建节点,因为创建节点要dentry参数,这个参数就是用第一步分配的dentry。注意这里的inode是父目录的inode。
我们以kern_path()作为起始函数进行研究。
int kern_path(const char *name, unsigned int flags, struct path *path)
{
struct nameidata nd;
int res = do_path_lookup(AT_FDCWD, name, flags, &nd);
if (!res)
*path = nd.path;
return res;
}
static int do_path_lookup(int dfd, const char *name,
unsigned int flags, struct nameidata *nd)
{
struct filename filename = { .name = name };
return filename_lookup(dfd, &filename, flags, nd);
}
-
nameidata
- 用于保存查询中间信息 filename
-
仅内核参数,无需
getname()/putname()
,可直接构造
flags
- 查询标志位,即以什么样的方式查询 LOOKUP_FOLLOW
- 跟随符号链接 LOOKUP_DIRECTORY
- 最后一个目录项必须是目录 LOOKUP_AUTOMOUNT
- 当用户试图访问挂载点下面的文件时会自动挂载 LOOKUP_PARENT
- 查找父目录 LOOKUP_REVAL
- 不信任dcache,直接从磁盘查找 LOOKUP_RCU
- 从最近使用的缓存查找,这是首选也是最快的方式 LOOKUP_OPEN
- 试图打开一个文件 LOOKUP_CREATE
- 如果不存在会试图创建一个文件 filename_lookup
-
调用
path_lookupat
,一共有三次调用机会,第一次RCU查找,第二次REF查找,第三次REVAL查找。
path_lookupat
-
这是文件名查找核心函数,首先初始化
nameidata
,这一步转交给path_init
来做,然后分解文件名并逐个查找,这一步转交给link_path_walk
,要注意这个函数只会走到倒数第二个元素,这是为了便于对最后一个元素特殊处理。最后一个元素的查询交给lookup_last
来做。查询完毕由complete_all
来做善后工作。
path_init
-
就是用来初始化
nameidata
,如果是从根目录查找,那么设置nd->path
和nd->inode
为根路径和节点。如果是从相对路径查找,那么nd->path
就设置为当前路径,还有一种情况是从指定文件描述符位置查找,道理都是一样的
link_path_walk
-
这是个重量级函数,从
nd
所在位置开始查询名字。这个函数主要功能在循环中,每次迭代取出一个分量,设置nd
之后交给walk_component
处理。如遇到.
直接跳过,遇到..
跳回到父目录等。如果walk_component
返回1表示要跟踪符号链接,又将问题抛给nested_symlink
。迭代完成就查询完成。
walk_component
-
先尝试
lookup_fast
再尝试lookup_slow
nested_symlink
-
循环调用
follow_link
和walk_component
,如果最后一个分量还是符号连接,就继续循环。而follow_link
本质上就是调用link_path_walk
,这也是为什么link_path_walk
不去查找最后一个分量的原因。
lookup_fast
-
如果RCU方式则调用
__d_lookup_rcu
,当然还要对查询的结果用顺序锁校验,如果不对就返回-ECHILD
,这会导致退回原点用别的方式查询。如果用普通方式就调用__d_lookup
。此外还需要对查询到的目录项校验,即调用d_revalidate
。最后调用follow_managed
。
lookup_slow
-
调用
__lookup_hash
,然后调用follow_managed
。而__lookup_hash
会调用lookup_dcache
,如果lookup_dcache
没找到就调用lookup_real
。其实lookup_dcache
就是调用d_lookup
,然后在找不到的情况下分配一个negative目录项,以供lookup_real
使用。进一步lookup_real
是调用inode_operations
提供的lookup
函数。当然i_op->lookup
也会遵循一些惯例,如找到目录项就调用d_add
加入到dcache。
文件访问
打开文件
-
open
-
系统调用,转调用
do_sys_open
do_sys_open
-
根据参数计算打开标志,调用
getname
获取文件名,调用get_unused_fd_flags
分配文件描述符,调用do_filp_open
分配文件,并和inode关联。
get_unused_fd_flags
-
调用
__alloc_fd
分配文件描述符
__alloc_fd
-
从
fdt
中的位图找第一个0位,如果满足要求就返回。如果找不到就需要扩展fdt
,由expand_files
来扩展。
do_filp_open
-
和
filename_lookup
一样有三次机会,只不过调用path_openat
path_openat
-
调用
get_empty_filp
分配文件,剩下的部分和path_lookupat
很相似,调用path_init
初始化,调用link_path_walk
逐个元素查询,调用do_last
处理最后一步打开操作。
get_empty_filp
-
从
filp_cachep
分配一个文件
do_last
-
最后一步的操作很复杂,状态太多。主要工作是查询最后一个元素,然后调用
follow_managed
,最后调用complete_all
处理查询的善后。 -