小前言
开写之前,插个题外话。内核相关内容写了几篇文章了,每篇文章篇幅都不少,特别是贴了不少代码,因为要看内核的具体实现,源码是必不可少的。为了方便阅读源码,添加了部分中文注释,也保留了原来的的英文注释,所以篇幅比较长。写这一段话,希望保持住啃内核的热情,不要轻易放弃。
进入正题。在linux操作系统当中,一切皆是文件,除了通所说狭义的文件(文本文件和二进制文件)以外,目录、套接字、设备及管道等都是文件。为了处理与文件系统相关的所有调用,表现为能够给各种文件系统提供一个通用的接口,使上层的应用程序能够使用通用的接口访问不同文件系统,同时也为不同文件系统的通信提供媒介,linux中设计了虚拟文件系统。
文件系统
先介绍不虚拟的文件系统。
文件系统在不同的上下文中有不同的含义:
- 在存储设备缓存文件的方法,包括数据结构和访问方法。
- 按照某种文件系统类型格式化的一块存储介质。
- 内核中负责管理和存储文件的模块,即文件系统模块。
在linux中,可以通过命令mount -t fstype device dir
把文件系统挂载到某个目录下,其中fstype是文件系统相关类型,device是挂载设备,dir是设备所对应的目录,这个命令底层是调用内核mount()函数。卸载挂载在某个目录上的文件系统,执行命令umount dir
。这两个命令还可以有其他参数,这里略掉。关于文件系统的命令,还有open、close、write、read、lseek、fsync、fdatasync等。
fsync与fdatasync是磁盘同步命令。linux写文件时,内核的文件系统模块会把数据保存在缓存页中,不会立即写入存储设备,需要使用fsync命令把修改的文件属性和数据立即写入存储设备,或使用fdatasync把文件中修改的数据立即写入存储设备。
两者的区别,fdatasync函数类似于fsync,但它只影响文件的数据部分。而除数据外,fsync还会同步更新文件的属性。
再多说一点,内核中fwrite()函数也是同样的原理,可以使用fflush()冲刷流,把写到缓冲区的数据立即写到内核中。
linux文件系统的架构,分为用户空间、内核空间和硬件 3 个层面。看图
下面针对这三个层面,一个个介绍。
用户空间
应用程序可以直接使用内核提供的系统调用访问文件:
- 一个存储设备上的文件系统,只有挂载到内存中目录树的某个目录下,进程才能访问这个文件系统。
- 系统调用 umount 用来卸载某个目录下挂载的文件系统 。 可 以 执 行 命 令
umount dir
来 卸 载 文 件 系 统 ,umount 命令调用系统调用 umount()函数。
应用程序可以使用 glibc 库封装标准 I/O 流函数访问文件,标准 I/O 流提供缓冲区,目的是尽可能减少调用 read/write 次 数 , 提 高 性 能 。 标 准 I/O 流 函数:fopen/fclose/fread/fwrite(fflush)/fseek。
硬件层面
外部存储设备分为块设备、闪存和 NVDIMM(非易失性内存)设备 3 类。
块设备主要有 2 种类型:机械硬盘和闪存类块设备。机械硬盘读写单位为扇区,访问首先沿着半径备方向移动磁头寻找磁道,然后转动盘片找到扇区。闪存作为存储设备,里面的控制器运行固化驱动程序,驱动程序的功能是内存转换层,把闪存转换为块设备,对外表现为块设备。Solid state drives,SSD。手机/平板嵌入式存储卡 eMMC(embedded multi media card)/通用闪存存储 UFS(Universal flash storage)
这里再介绍一下DIMM(双列直插式存储模块)。设备把NAND闪存、内存、超级电容集成到一起,做到访问速度和内存一样快,且断电不丢失,因为瞬间断电时会有超级电容供电,内存中的数据会转移到NAND。
硬件不是重点,就介绍这么多。
内核空间
在内核的目录 fs 下可以看到,内核支持多种文件系统类型。为了对用户程序提供统一的文件操作接口,为了使不同的文件系统实现能够共享,内核实现一个抽象层,称为虚拟文件系统(Virtual File System,VFS),也称为虚拟文件系统切换(Virtual Filesystem Switch,VFS)。
文件系统分为:块设备文件系统(存储设备是机械硬盘和 SSD 等块,如EXT2/3/4)、闪存文件系统(存储设备 NOR 闪存、NAND 闪存,常用的闪存文件系统有目录型闪存文件系统2JFFS2、无序区块镜像文件系统UBIFS)、内存文件系统(文件在内存中,如tmpfs)、伪文件系统。
为什么需要针对闪存专门设计文件系统?一是因为软件需要识别并跳过NAND的存储坏块,二是因为需要实现损耗均匀,所有擦除块的擦除次数要做到均匀,避免一部分擦除块先损坏。
伪文件系统是为了使用vfs的编程接口而设计的假的文件系统,常用的有文件系统有:
- proc:把内核的信息导出到用户空间。
- sockfs:文件系统使用套接字,可以使用read()接收报文write()发送报文。
- sysfs:把内核设备的所有信息导出到用户空间,挂载目录是/sys。
- cgroup:控制组用来控制进程的资源,能让管理员可以写成文件的方式来配置。
虚拟文件系统数据结构
虽然不同文件系统类型的物理结构不同,但是虚拟文件系统定义一套统一的数据结构。下面介绍一些,又到了贴源码的环节……
超级块
文件系统的第一块是超级块,用来描述文件系统的总体信息。当我们把文件系统挂载到内存中目录树的一个目录下时,就会读取文件系统的超级块,在内存中创建超级块的副本。数据结构:
struct super_block {
//用来把所有超级块实例链接到全局链表super_blocks
struct list_head s_list; /* Keep this first */
//保存文件系统所有的块设备,保存设备号
dev_t s_dev; /* search index; _not_ kdev_t */
//块长度,值为s_blocksize所占位数,即s_blocksize_bits=log2(s_blocksize)向上取整
unsigned char s_blocksize_bits;
//块长度,单位字节。
unsigned long s_blocksize;
//文件系统支持的最大长度
loff_t s_maxbytes; /* Max file size */
//指向文件系统类型
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;
unsigned long s_iflags; /* internal SB_I_* flags */
//文件系统类型的魔幻数,每种文件系统类型分配一个唯一数
unsigned long s_magic;
//指向根目录的结构体struct dentry
struct dentry *s_root;
struct rw_semaphore s_umount;
int s_count;
atomic_t s_active;
#ifdef CONFIG_SECURITY
void *s_security;
#endif
const struct xattr_handler **s_xattr;
const struct fscrypt_operations *s_cop;
struct hlist_bl_head s_anon; /* anonymous dentries for (nfs) exporting */
struct list_head s_mounts; /* list of mounts; _not_ for fs use */
struct block_device *s_bdev;
struct backing_dev_info *s_bdi;
struct mtd_info *s_mtd;
struct hlist_node s_instances;
unsigned int s_quota_types; /* Bitmask of supported quota types */
struct quota_info s_dquot; /* Diskquota specific options */
struct sb_writers s_writers;
char s_id[32]; /* Informational name */
u8 s_uuid[16]; /* UUID */
void *s_fs_info; /* Filesystem private info */
unsigned int s_max_links;
fmode_t s_mode;
/* Granularity of c/m/atime in ns.
Cannot be worse than a second */
u32 s_time_gran;
/*
* The next field is for VFS *only*. No filesystems have any business
* even looking at it. You had been warned.
*/
struct mutex s_vfs_rename_mutex; /* Kludge */
/*
* Filesystem subtype. If non-empty the filesystem type field
* in /proc/mounts will be "type.subtype"
*/
char *s_subtype;
/*
* Saved mount options for lazy filesystems using
* generic_show_options()
*/
char __rcu *s_options;
const struct dentry_operations *s_d_op; /* default d_op for dentries */
/*
* Saved pool identifier for cleancache (-1 means none)
*/
int cleancache_poolid;
struct shrinker s_shrink; /* per-sb shrinker handle */
/* 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;
struct hlist_head s_pins;
/*
* Owning user namespace and default context in which to
* interpret filesystem uids, gids, quotas, device nodes,
* xattrs and security labels.
*/
struct user_namespace *s_user_ns;
/*
* Keep the lru lists last in the structure so they always sit on their
* own individual cachelines.
*/
struct list_lru s_dentry_lru ____cacheline_aligned_in_smp;
struct list_lru s_inode_lru ____cacheline_aligned_in_smp;
struct rcu_head rcu;
struct work_struct destroy_work;
struct mutex s_sync_lock; /* sync serialisation lock */
/*
* Indicates how deep in a filesystem stack this SB is
*/
int s_stack_depth;
/* s_inode_list_lock protects s_inodes */
spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp;
struct list_head s_inodes; /* all inodes */
spinlock_t s_inode_wblist_lock;
struct list_head s_inodes_wb; /* writeback inodes */
};
超级块操作集合
struct super_operations {
//用来为一个索引节点分配内存并初始化
struct inode *(*alloc_inode)(struct super_block *sb);
//用来释放内存中的索引节点
void (*destroy_inode)(struct inode *);
//用来把索引节点标记为脏
void (*dirty_inode) (struct inode *, int flags);
//用来把一个索引节点写到存储设备
int (*write_inode) (struct inode *, struct writeback_control *wbc);
//用来在索引节点的引用计数减到0时调用
int (*drop_inode) (struct inode *);
//从存储设备上的文件系统删除一个索引节点
void (*evict_inode) (struct inode *);
//用来释放超级块
void (*put_super) (struct super_block *);
int (*sync_fs)(struct super_block *sb, int wait);
int (*freeze_super) (struct super_block *);
int (*freeze_fs) (struct super_block *);
int (*thaw_super) (struct super_block *);
int (*unfreeze_fs) (struct super_block *);
int (*statfs) (struct dentry *, struct kstatfs *);
int (*remount_fs) (struct super_block *, int *, char *);
void (*umount_begin) (struct super_block *);
int (*show_options)(struct seq_file *, struct dentry *);
int (*show_devname)(struct seq_file *, struct dentry *);
int (*show_path)(struct seq_file *, struct dentry *);
int (*show_stats)(struct seq_file *, struct dentry *);
#ifdef CONFIG_QUOTA
ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
struct dquot **(*get_dquots)(struct inode *);
#endif
int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
long (*nr_cached_objects)(struct super_block *,
struct shrink_control *);
long (*free_cached_objects)(struct super_block *,
struct shrink_control *);
};
挂载描述符
一个文件系统,只有挂载到内存中目录树的一个目录下,进程才能访问这个文件系统。每次挂载文件系统,虚拟文件系统就会创建一个挂载描述符:mount结构体。挂载描述符用来描述文件系统的一个挂载实例,同一个存储设备上的文件系统可以多次挂载,每次挂载到不同的目录下。
/*
为了方便分析,给一个案例。案例分析如下:
把文件系统2挂载到目录"/x"下,目录x属于文件系统1,目录x称为挂载点
文件系统2的mount实例是文件系统1的mount实例的孩子
文件系统1的mount实例是文件系统2的mount实例的父亲
这里假设下面是文件系统2的挂载描述符
*/
struct mount {
//用来把挂载描述符加入全局散列表mnt_hashtable,关键字是(父挂载描述符,挂载点)
struct hlist_node mnt_hash;
//指向父亲,即文件系统1的mount实例
struct mount *mnt_parent;
//指向作为挂载点的目录,即文件系统1的目录x
struct dentry *mnt_mountpoint;
//文件系统2的挂载信息,下面贴代码
struct vfsmount mnt;
union {
struct rcu_head mnt_rcu;
struct llist_node mnt_llist;
};
#ifdef CONFIG_SMP
struct mnt_pcp __percpu *mnt_pcp;
#else
int mnt_count;
int mnt_writers;
#endif
//孩子链表的头节点
struct list_head mnt_mounts; /* list of children, anchored here */
//用来加入父亲的孩子链表
struct list_head mnt_child; /* and going through their mnt_child */
//用来把挂载描述符添加到超级块的挂载实例链表中
//同一个存储设备上的文件系统,可以多次挂载,每次挂载到不同的目录
struct list_head mnt_instance; /* mount instance on sb->s_mounts */
//指向存储设备的名称,例如"/dev/dsk/hda1"
const char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
struct list_head mnt_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 */
struct hlist_node mnt_mp_list; /* list mounts with the same mountpoint */
struct list_head mnt_umounting; /* list entry for umount propagation */
#ifdef CONFIG_FSNOTIFY
struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
__u32 mnt_fsnotify_mask;
#endif
int mnt_id; /* mount identifier */
int mnt_group_id; /* peer group identifier */
int mnt_expiry_mark; /* true if marked for expiry */
struct hlist_head mnt_pins;
struct fs_pin mnt_umount;
struct dentry *mnt_ex_mountpoint;
};
挂载信息
struct vfsmount {
//指向文件系统2的根目录
struct dentry *mnt_root; /* root of the mounted tree */
//指向文件系统2的超级块
struct super_block *mnt_sb; /* pointer to superblock */
int mnt_flags;
};
挂载点
struct mountpoint {
struct hlist_node m_hash;
//指向作为挂载点的目录
struct dentry *m_dentry;
//用来把同一个挂载点下的所有挂载描述符链接起来
struct hlist_head m_list;
int m_count;
};
文件系统类型
因为每种文件系统类型的超级块的格式不同,所以每种文件系统需要向虚拟文件系统注册文件系统类型 file_system_type,并且实现 mount 方法用来读取和解析超级块。
struct file_system_type {
//文件系统类型的名称
const char *name;
int fs_flags;
#define FS_REQUIRES_DEV 1
#define FS_BINARY_MOUNTDATA 2
#define FS_HAS_SUBTYPE 4
#define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
#define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */
//用来在挂载文件系统的时候读取并解析超级块
struct dentry *(*mount) (struct file_system_type *, int,
const char *, void *);
//用来在卸载文件系统的时候释放超级块
void (*kill_sb) (struct super_block *);
//指向实现文件系统的模块的指针
struct module *owner;
//指向文件系统类型链表中下一个元素的指针
struct file_system_type * next;
//多个存储设备上的文件系统的类型可能相同,此成员用来把相同文件系统类型的超级块链接起来
struct hlist_head fs_supers;
struct lock_class_key s_lock_key;
struct lock_class_key s_umount_key;
struct lock_class_key s_vfs_rename_key;
struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];
struct lock_class_key i_lock_key;
struct lock_class_key i_mutex_key;
struct lock_class_key i_mutex_dir_key;
};
索引节点
在文件系统中,每个文件对应一个索引节点,索引节点描述两类信息。
- 文件的属性,也称为元数据(metadata)。
- 文件数据的存储位置,每个索引节点有一个唯一的编号。当内核访问存储设备上的一个文件时,会在内存中创建索引节点的一个副本。
看代码
struct inode {
//文件类型和访问权限
umode_t i_mode;
unsigned short i_opflags;
//创建文件的用户的标识符
kuid_t i_uid;
//创建文件的用户所属的组标识符
kgid_t i_gid;
unsigned int i_flags;
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *i_acl;
struct posix_acl *i_default_acl;
#endif
const struct inode_operations *i_op;
//指向文件所属的文件系统的超级块
struct super_block *i_sb;
//指向文件的地址空间
struct address_space *i_mapping;
#ifdef CONFIG_SECURITY
void *i_security;
#endif
/* Stat data, not accessed from path walking */
unsigned long i_ino; //索引节点编号
/*
* Filesystems may only read i_nlink directly. They shall use the
* following functions for modification:
*
* (set|clear|inc|drop)_nlink
* inode_(inc|dec)_link_count
*/
union {
const unsigned int i_nlink; //硬链接计数
unsigned int __i_nlink;
};
dev_t i_rdev; //设备号
loff_t i_size; //文件长度
//下三者区分在后面介绍
struct timespec i_atime; //上一次访问文件的时间
struct timespec i_mtime; //上一次修改文件数据的时间
struct timespec i_ctime; // 上一次修改文件节点的时间
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
unsigned short i_bytes; //文件长度除以块长度的余数
//inode块长度位数,即长度是 2 的 i_blkbits 次幂字节
unsigned int i_blkbits;
blkcnt_t i_blocks; //文件的块数
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
/* Misc */
unsigned long i_state;
struct rw_semaphore i_rwsem;
unsigned long dirtied_when; /* jiffies of first dirtying */
unsigned long dirtied_time_when;
struct hlist_node i_hash;
struct list_head i_io_list; /* backing dev IO list */
#ifdef CONFIG_CGROUP_WRITEBACK
struct bdi_writeback *i_wb; /* the associated cgroup wb */
/* foreign inode detection, see wbc_detach_inode() */
int i_wb_frn_winner;
u16 i_wb_frn_avg_time;
u16 i_wb_frn_history;
#endif
struct list_head i_lru; /* inode LRU list */
struct list_head i_sb_list;
struct list_head i_wb_list; /* backing dev writeback list */
union {
struct hlist_head i_dentry;
struct rcu_head i_rcu;
};
u64 i_version;
atomic_t i_count;
atomic_t i_dio_count;
atomic_t i_writecount;
#ifdef CONFIG_IMA
atomic_t i_readcount; /* struct files open RO */
#endif
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct file_lock_context *i_flctx;
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; //指向字符设备
char *i_link;
unsigned i_dir_seq;
};
__u32 i_generation;
#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct fsnotify_mark_connector __rcu *i_fsnotify_marks;
#endif
#if IS_ENABLED(CONFIG_FS_ENCRYPTION)
struct fscrypt_info *i_crypt_info;
#endif
void *i_private; /* fs or device private pointer */
};
区分一下三个时间。
- i_atime:文件最后访问时间,即是文件最后的读取时间,例如:用命令
cat filename
,此时间修改,其他两个时间不修改。 - i_ctime:结点最后修改时间,即是修改inode结构的时间,例如:用命令
ln filename1 filename2
,此时间修改,其他两个时间不修改。 - i_mtime:文件最后修改时间,即是对文件内容的修改时间,例如:用命令
echo aaa >filename
,以上三个时间都修改。
目录项
文件系统把目录当作文件,这种文件的数据是由目录项结构组成的,每个目录项存储一个子目录或文件的名称以及对应的索引节点号。当内核访问存储设备上的一个目录项时,会在内核中创建目录项的一个副本。
struct dentry {
/* RCU lookup touched fields */
unsigned int d_flags; /* protected by d_lock */
seqcount_t d_seq; /* per dentry seqlock */
//用来把目录项加入散列表
struct hlist_bl_node d_hash; /* lookup hash list */
//指向父目录
struct dentry *d_parent; /* parent directory */
//存储文件名称
struct qstr d_name;
//指向对应的文件节点
struct inode *d_inode; /* Where the name belongs to - NULL is
* negative */
unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */
/* Ref lookup also touches following */
struct lockref d_lockref; /* per-dentry lock and refcount */
const struct dentry_operations *d_op;
struct super_block *d_sb; /* The root of the dentry tree */
unsigned long d_time; /* used by d_revalidate */
void *d_fsdata; /* fs-specific data */
union {
struct list_head d_lru; /* LRU list */
wait_queue_head_t *d_wait; /* in-lookup ones only */
};
struct list_head d_child; /* child of parent list */
struct list_head d_subdirs; /* our children */
/*
* d_alias and d_rcu can share memory
*/
union {
struct hlist_node d_alias; /* inode alias list */
struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */
struct rcu_head d_rcu;
} d_u;
};
目录项与索引节点的对应关系
文件打开实例及打开文件表
当进程打开一个文件的时候,虚拟文件系统就会创建一个打开实例:file 结构体。
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
//存储文件在目录树中的位置,下面贴代码
struct path f_path;
//指向文件的索引节点
struct inode *f_inode; /* cached value */
//指向文件操作命令
const struct file_operations *f_op;
/*
* Protects f_ep_links, f_flags.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode; //访问模式
struct mutex f_pos_lock;
loff_t f_pos; //文件偏移,进程当前正在访问的位置
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping; //指向文件的地址空间
} __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
struct file_handle {
__u32 handle_bytes;
int handle_type;
/* file identifier */
unsigned char f_handle[0];
};
解释一下为什么有private_data成员。由于挂载目录下可能会有多个文件,而read/write回调是针对文件系统的操作,如果对不同文件进行读写操作,那么这些不同的文件数据就无法区分。解决措施就是在inode成员里加入一个成员,因为inode是每个文件都有一个实例,所以这个成员可以实现私有空间的功能。
path结构体
struct path {
struct vfsmount *mnt; // 指向文件所属文件系统的挂载描述符的成员 mnt
struct dentry *dentry; // 文件对应的目录项
};
文件系统信息结构
struct fs_struct {
int users;
spinlock_t lock;
seqcount_t seq;
int umask;
int in_exec;
//root存储进程的根目录,pwd存储进程当前的工作目录
struct path root, pwd;
};
打开文件表真正记录哪些文件描述符被使用了,哪些是空闲的,实际是一个文件描述符位图,每1bit表示了一个文件描述符。
struct fdtable {
unsigned int max_fds; /*fdtable能管理的打开文件最大数量,由位图大小决定*/
struct file __rcu **fd; /*指向file指针数组的指针*/
unsigned long *close_on_exec; /*执行execve()系统调用时关闭文件的位图*/
unsigned long *open_fds; /*进程打开文件位图*/
unsigned long *full_fds_bits;
struct rcu_head rcu;
};
这些数据结构之间的关系
注册文件系统类型
因为每种文件系统的超级块的格式不同,所以每种文件系统需要向虚拟文件系统注册文件系统类型file_system_type,实现 mount 方法来读取和解析超级块。
函数 register_filesystem 用来注册文件系统类型,看代码
/**
* register_filesystem - register a new filesystem
* @fs: the file system structure
*
* Adds the file system passed to the list of file systems the kernel
* is aware of for mount and other syscalls. Returns 0 on success,
* or a negative errno code on an error.
*
* The &struct file_system_type that is passed is linked into the kernel
* structures and must not be freed until the file system has been
* unregistered.
*/
int register_filesystem(struct file_system_type * fs)
{
int res = 0;
struct file_system_type ** p;
BUG_ON(strchr(fs->name, '.'));
if (fs->next)
return -EBUSY;
write_lock(&file_systems_lock);
p = find_filesystem(fs->name, strlen(fs->name)); //这里遍历的链表头是全局变量
if (*p)
res = -EBUSY;
else
*p = fs;
write_unlock(&file_systems_lock);
return res;
}
EXPORT_SYMBOL(register_filesystem);
可以执行命令cat /proc/filesystems
查看已经注册的文件系统类型。