Linux内核源码分析-安装虚拟根文件系统-init_rootfs- init_mount_tree
本文主要参考《深入理解Linux内核》,结合2.6.11.1版的内核代码,分析内核文件子系统中的安装根文件系统函数。
注意:
1、不描述内核同步、错误处理、参数合法性验证相关的内容
2、源码摘自Linux内核2.6.11.1版
3、阅读本文请结合《深入理解Linux内核》第三版相关章节
4、本文会不定时更新
函数调用层次结构:
1、init_rootfs
函数功能:
注册rootfs文件系统
函数源码:
该源码包含在文件:fs/ramfs/inode.c
-
nt __init init_rootfs(void)
-
{
-
return register_filesystem(&rootfs_fs_type);
-
}
-
static struct file_system_type rootfs_fs_type = {
-
.name = "rootfs",
-
.get_sb = rootfs_get_sb,
-
.kill_sb = kill_litter_super,
-
};
2、init_mount_tree
函数功能:
安装rootfs文件系统
函数源码:
-
static void __init init_mount_tree(void)
-
{
-
struct vfsmount *mnt;
-
struct namespace *namespace;
-
struct task_struct *g, *p;
-
-
mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
-
if (IS_ERR(mnt))
-
panic("Can't create rootfs");
-
namespace = kmalloc(sizeof(*namespace), GFP_KERNEL);
-
if (!namespace)
-
panic("Can't allocate initial namespace");
-
atomic_set(&namespace->count, 1); //引用计数器
-
INIT_LIST_HEAD(&namespace->list); //命名空间中已安装文件系统描述链表头
-
init_rwsem(&namespace->sem);//保护namespace的读写信号量
-
list_add(&mnt->mnt_list, &namespace->list);//把rootfs文件系统加入命名空间中已安装文件系统描述链表
-
namespace->root = mnt; //命名空间根目录的已安装文件系统
-
mnt->mnt_namespace = namespace; //rootfs文件系统的命名空间
-
-
init_task.namespace = namespace; //init进程的命名空间
-
read_lock(&tasklist_lock);
-
/*初始化系统中所有进程的地址空间为默认地址空间*/
-
do_each_thread(g, p) {
-
get_namespace(namespace);
-
p->namespace = namespace;
-
} while_each_thread(g, p);
-
read_unlock(&tasklist_lock);
-
-
set_fs_pwd(current->fs, namespace->root, namespace->root->mnt_root);
-
set_fs_root(current->fs, namespace->root, namespace->root->mnt_root);
-
}
函数处理流程:
1、
调用函数do_kern_mount进行实际安装操作,返回新安装文件系统描述符的地址
2、
调用kmalloc分配一个namespace对象,对象地址存放在局部变量namespace中,并初始化namespace、mnt相关数据结构,具体参见注释
3、
初始化系统中所有进程的地址空间为默认地址空间
4、
调用函数set_fs_pwd和set_fs_root初始化当前进程的当前工作目录和根目录
3、do_kern_mount
函数源码:
-
struct vfsmount *
-
do_kern_mount(const char *fstype, int flags, const char *name, void *data)
-
{
-
struct file_system_type *type = get_fs_type(fstype);
-
struct super_block *sb = ERR_PTR(-ENOMEM);
-
struct vfsmount *mnt;
-
int error;
-
char *secdata = NULL;
-
-
if (!type)
-
return ERR_PTR(-ENODEV);
-
-
mnt = alloc_vfsmnt(name);
-
if (!mnt)
-
goto out;
-
-
if (data) {
-
secdata = alloc_secdata();
-
if (!secdata) {
-
sb = ERR_PTR(-ENOMEM);
-
goto out_mnt;
-
}
-
-
error = security_sb_copy_data(type, data, secdata);
-
if (error) {
-
sb = ERR_PTR(error);
-
goto out_free_secdata;
-
}
-
}
-
-
sb = type->get_sb(type, flags, name, data);
-
if (IS_ERR(sb))
-
goto out_free_secdata;
-
error = security_sb_kern_mount(sb, secdata);
-
if (error)
-
goto out_sb;
-
mnt->mnt_sb = sb; //超级块
-
mnt->mnt_root = dget(sb->s_root); //根文件系统
-
mnt->mnt_mountpoint = sb->s_root; //挂载点
-
mnt->mnt_parent = mnt; //父文件系统
-
mnt->mnt_namespace = current->namespace; //命名空间
-
up_write(&sb->s_umount);
-
put_filesystem(type);
-
return mnt;
-
out_sb:
-
up_write(&sb->s_umount);
-
deactivate_super(sb);
-
sb = ERR_PTR(error);
-
out_free_secdata:
-
free_secdata(secdata);
-
out_mnt:
-
free_vfsmnt(mnt);
-
out:
-
put_filesystem(type);
-
return (struct vfsmount *)sb;
-
}
函数处理流程:
1、根据文件系统类型名称,调用函数get_fs_type获得类型为file_system_type的文件系统类型对象的地址,存入局部变量type中
2、调用alloc_vfsmnt从mnt_cache slab高速缓存中分配一个新的文件系统对象,地址存入局部变量mnt中
3、调用依赖于文件系统的type->get_sb函数分配并初始化一个超级块
4、初始化mnt相关字段,具体参见注释
4、rootfs_get_sb
函数功能:
是get_sb_nodev的封装函数
函数源码:
-
static struct super_block *rootfs_get_sb(struct file_system_type *fs_type,
-
int flags, const char *dev_name, void *data)
-
{
-
return get_sb_nodev(fs_type, flags|MS_NOUSER, data, ramfs_fill_super);
-
}
5、get_sb_nodev
函数源码:
-
struct super_block *get_sb_nodev(struct file_system_type *fs_type,
-
int flags, void *data,
-
int (*fill_super)(struct super_block *, void *, int))
-
{
-
int error;
-
struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL);
-
-
if (IS_ERR(s))
-
return s;
-
-
s->s_flags = flags;
-
-
error = fill_super(s, data, flags & MS_VERBOSE ? 1 : 0);
-
if (error) {
-
up_write(&s->s_umount);
-
deactivate_super(s);
-
return ERR_PTR(error);
-
}
-
s->s_flags |= MS_ACTIVE;
-
return s;
-
}
函数处理流程:
1、调用sget函数分配并初始化超级块对象
2、调用函数fill_super,即ramfs_fill_super初始化超级块对象
6、ramfs_fill_super
函数功能:
初始化相关字段,并分配根索引节点对象和根目录项对象
函数源码:
-
static int ramfs_fill_super(struct super_block * sb, void * data, int silent)
-
{
-
struct inode * inode;
-
struct dentry * root;
-
-
sb->s_maxbytes = MAX_LFS_FILESIZE;
-
sb->s_blocksize = PAGE_CACHE_SIZE;
-
sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
-
sb->s_magic = RAMFS_MAGIC;
-
sb->s_op = &ramfs_ops;
-
sb->s_time_gran = 1;
-
inode = ramfs_get_inode(sb, S_IFDIR | 0755, 0);
-
if (!inode)
-
return -ENOMEM;
-
-
root = d_alloc_root(inode);
-
if (!root) {
-
iput(inode);
-
return -ENOMEM;
-
}
-
sb->s_root = root;
-
return 0;
-
}
7、sget
函数源码:
-
/**
-
* sget - find or create a superblock
-
* @type: filesystem type superblock should belong to
-
* @test: comparison callback
-
* @set: setup callback
-
* @data: argument to each of them
-
*/
-
struct super_block *sget(struct file_system_type *type,
-
int (*test)(struct super_block *,void *),
-
int (*set)(struct super_block *,void *),
-
void *data)
-
{
-
struct super_block *s = NULL;
-
struct list_head *p;
-
int err;
-
-
retry:
-
spin_lock(&sb_lock);
-
if (test) list_for_each(p, &type->fs_supers) {
-
struct super_block *old;
-
old = list_entry(p, struct super_block, s_instances);
-
if (!test(old, data))
-
continue;
-
if (!grab_super(old))
-
goto retry;
-
if (s)
-
destroy_super(s);
-
return old;
-
}
-
if (!s) {
-
spin_unlock(&sb_lock);
-
s = alloc_super();
-
if (!s)
-
return ERR_PTR(-ENOMEM);
-
goto retry;
-
}
-
-
err = set(s, data);
-
if (err) {
-
spin_unlock(&sb_lock);
-
destroy_super(s);
-
return ERR_PTR(err);
-
}
-
s->s_type = type;
-
strlcpy(s->s_id, type->name, sizeof(s->s_id));
-
list_add_tail(&s->s_list, &super_blocks);
-
list_add(&s->s_instances, &type->fs_supers);
-
spin_unlock(&sb_lock);
-
get_filesystem(type);
-
return s;
-
}
函数处理流程:
1、如果定义了test函数,用该函数为对比函数,在文件系统类型的超级块链表中查找super_block对象,找到则返回
2、未找到则调用alloc_super函数分配一个super_block对象,并把对象地址存入局部变量s中
3、调用set函数,即set_anon_super函数初始化超级块
8、set_anon_super
函数功能:
初始化特殊文件系统的超级块,分配一个主设备号为0、次设备号任意(每个特殊文件系统有不同的值)的设备标识符,用该标识符初始化超级块的s_dev字段。
函数源码:
int set_anon_super(struct super_block
*s, void *data)
{
int
dev;
int
error;
retry:
if
(idr_pre_get(&unnamed_dev_idr, GFP_ATOMIC) == 0)
return
-ENOMEM;
spin_lock(&unnamed_dev_lock);
error
= idr_get_new(&unnamed_dev_idr, NULL, &dev);
spin_unlock(&unnamed_dev_lock);
if
(error == -EAGAIN)
/*
We raced and lost with another CPU. */
goto
retry;
else
if (error)
return
-EAGAIN;
if
((dev & MAX_ID_MASK) == (1 << MINORBITS)) {
spin_lock(&unnamed_dev_lock);
idr_remove(&unnamed_dev_idr,
dev);
spin_unlock(&unnamed_dev_lock);
return
-EMFILE;
}
s->s_dev
= MKDEV(0, dev & MINORMASK);
return
0;
}
注:idr相关处理函数解释摘自“参考文章1”,摘部分内容如下:
idr在linux内核中指的就是整数ID管理机制,从本质上来说,这就是一种将整数ID号和特定指针关联在一起的机制。这个机制最早是在2003年2月加入内核的,当时是作为POSIX定时器的一个补丁。现在,在内核的很多地方都可以找到idr的身影。
idr机制适用在那些需要把某个整数和特定指针关联在一起的地方。举个例子,在I2C总线中,每个设备都有自己的地址,要想在总线上找到特定的设备,就必须要先发送该设备的地址。如果我们的PC是一个I2C总线上的主节点,那么要访问总线上的其他设备,首先要知道他们的ID号,同时要在pc的驱动程序中建立一个用于描述该设备的结构体。
此时,问题来了,我们怎么才能将这个设备的ID号和他的设备结构体联系起来呢?最简单的方法当然是通过数组进行索引,但如果ID号的范围很大(比如32位的ID号),则用数组索引显然不可能;第二种方法是用链表,但如果网络中实际存在的设备较多,则链表的查询效率会很低。遇到这种清况,我们就可以采用idr机制,该机制内部采用radix树实现,可以很方便地将整数和指针关联起来,并且具有很高的搜索效率。
(1)获得idr
要在代码中使用idr,首先要包括。接下来,我们要在代码中分配idr结构体,并初始化:
void idr_init(struct idr *idp);
其中idr定义如下:
struct idr {
struct idr_layer *top;
struct idr_layer *id_free;
int
layers;
int
id_free_cnt;
spinlock_t lock;
};
/* idr是idr机制的核心结构体 */
(2)为idr分配内存
int idr_pre_get(struct idr *idp, unsigned int gfp_mask);
每次通过idr获得ID号之前,需要先分配内存。
返回0表示错误,非零值代表正常
(3)分配ID号并将ID号和指针关联
int idr_get_new(struct idr *idp, void *ptr, int *id);
int idr_get_new_above(struct idr *idp, void *ptr, int start_id, int *id);
idp: 之前通过idr_init初始化的idr指针
id: 由内核自动分配的ID号
ptr: 和ID号相关联的指针
start_id: 起始ID号。内核在分配ID号时,会从start_id开始。如果为I2C节点分配ID号,可以将设备地址作为start_id
函数调用正常返回0,如果没有ID可以分配,则返回-ENOSPC
在实际中,上述函数常常采用如下方式使用:
again:
if (idr_pre_get(&my_idr, GFP_KERNEL) == 0) {
/* No memory, give up entirely */
}
spin_lock(&my_lock);
result = idr_get_new(&my_idr, &target, &id);
if (result == -EAGAIN) {
sigh();
spin_unlock(&my_lock);
goto again;
}
(4)通过ID号搜索对应的指针
void *idr_find(struct idr *idp, int id);
返回值是和给定id相关联的指针,如果没有,则返回NULL
(5)删除ID
要删除一个ID,使用:
void idr_remove(struct idr *idp, int id);
通过上面这些方法,内核代码可以为子设备,
inode生成对应的
ID号。这些函数都定义在
中
9、alloc_super
函数源码:
-
/**
-
* alloc_super - create new superblock
-
*
-
* Allocates and initializes a new &struct super_block. alloc_super()
-
* returns a pointer new superblock or %NULL if allocation had failed.
-
*/
-
static struct super_block *alloc_super(void)
-
{
-
struct super_block *s = kmalloc(sizeof(struct super_block), GFP_USER);
-
static struct super_operations default_op;
-
-
if (s) {
-
memset(s, 0, sizeof(struct super_block));
-
if (security_sb_alloc(s)) {
-
kfree(s);
-
s = NULL;
-
goto out;
-
}
-
INIT_LIST_HEAD(&s->s_dirty);//脏的索引节点链表头
-
INIT_LIST_HEAD(&s->s_io); //正在被写入磁盘的索引节点链表头
-
INIT_LIST_HEAD(&s->s_files);//文件对象的链表头
-
INIT_LIST_HEAD(&s->s_instances); //文件系统类型对象的超级块链表指针
-
INIT_HLIST_HEAD(&s->s_anon); //用于处理远程网络文件系统的匿名目录项链表头
-
INIT_LIST_HEAD(&s->s_inodes);//索引索引节点链表头
-
init_rwsem(&s->s_umount); //卸载使用的读写信号量
-
sema_init(&s->s_lock, 1); //超级块信号量
-
down_write(&s->s_umount); //获取卸载写信号量
-
s->s_count = S_BIAS; //引用计数器
-
atomic_set(&s->s_active, 1);//次级引用计数器
-
sema_init(&s->s_vfs_rename_sem,1); //vfs重命名信号量
-
sema_init(&s->s_dquot.dqio_sem, 1); //磁盘限额相关
-
sema_init(&s->s_dquot.dqonoff_sem, 1); //磁盘限额相关
-
init_rwsem(&s->s_dquot.dqptr_sem); //磁盘限额相关
-
init_waitqueue_head(&s->s_wait_unfrozen); //进程挂起的等待队列,直到文件系统被解冻
-
s->s_maxbytes = MAX_NON_LFS; //文件的最长长度,#define MAX_NON_LFS ((1UL<<31) - 1)
-
s->dq_op = sb_dquot_ops; //磁盘限额处理方法
-
s->s_qcop = sb_quotactl_ops;//磁盘限额管理方法
-
s->s_op = &default_op; //超级块方法
-
s->s_time_gran = 1000000000; //时间戳粒度,纳秒级,默认值1秒
-
}
-
out:
-
return s;
-
}
函数处理流程:
1、调用kmalloc函数分配超级块对象,存入局部变量s中
2、分配成功,初始化s中的相关字段,见代码注释
参考文章:
1、浅析linux内核中的idr机制
http://blog.csdn.net/ganggexiongqi/article/details/6737389\