Chinaunix首页 | 论坛 | 博客
  • 博客访问: 44748
  • 博文数量: 9
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 25
  • 用 户 组: 普通用户
  • 注册时间: 2016-10-14 18:13
个人简介

淡泊明志、宁静致远

文章存档

2016年(9)

我的朋友

分类: LINUX

2016-10-25 16:20:01

Linux内核源码分析-安装虚拟根文件系统-init_rootfs- init_mount_tree

本文主要参考《深入理解Linux内核》,结合2.6.11.1版的内核代码,分析内核文件子系统中的安装根文件系统函数。

注意:

1、不描述内核同步、错误处理、参数合法性验证相关的内容

2、源码摘自Linux内核2.6.11.1

3、阅读本文请结合《深入理解Linux内核》第三版相关章节

4、本文会不定时更新

函数调用层次结构:


1init_rootfs

函数功能:

注册rootfs文件系统

函数源码:

该源码包含在文件:fs/ramfs/inode.c

点击(此处)折叠或打开

  1. nt __init init_rootfs(void)
  2. {
  3.     return register_filesystem(&rootfs_fs_type);
  4. }
  5. static struct file_system_type rootfs_fs_type = {
  6.     .name        = "rootfs",
  7.     .get_sb        = rootfs_get_sb,
  8.     .kill_sb    = kill_litter_super,
  9. };

2init_mount_tree

函数功能:

安装rootfs文件系统

函数源码:


点击(此处)折叠或打开

  1. static void __init init_mount_tree(void)
  2. {
  3.     struct vfsmount *mnt;
  4.     struct namespace *namespace;
  5.     struct task_struct *g, *p;

  6.     mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
  7.     if (IS_ERR(mnt))
  8.         panic("Can't create rootfs");
  9.     namespace = kmalloc(sizeof(*namespace), GFP_KERNEL);
  10.     if (!namespace)
  11.         panic("Can't allocate initial namespace");
  12.     atomic_set(&namespace->count, 1); //引用计数器
  13.     INIT_LIST_HEAD(&namespace->list); //命名空间中已安装文件系统描述链表头
  14.     init_rwsem(&namespace->sem);//保护namespace的读写信号量
  15.     list_add(&mnt->mnt_list, &namespace->list);//把rootfs文件系统加入命名空间中已安装文件系统描述链表
  16.     namespace->root = mnt; //命名空间根目录的已安装文件系统
  17.     mnt->mnt_namespace = namespace; //rootfs文件系统的命名空间

  18.     init_task.namespace = namespace; //init进程的命名空间
  19.     read_lock(&tasklist_lock);
  20.    /*初始化系统中所有进程的地址空间为默认地址空间*/
  21.     do_each_thread(g, p) {
  22.         get_namespace(namespace);
  23.         p->namespace = namespace;
  24.     } while_each_thread(g, p);
  25.     read_unlock(&tasklist_lock);

  26.     set_fs_pwd(current->fs, namespace->root, namespace->root->mnt_root);
  27.     set_fs_root(current->fs, namespace->root, namespace->root->mnt_root);
  28. }

函数处理流程:

1、     调用函数do_kern_mount进行实际安装操作,返回新安装文件系统描述符的地址

2、     调用kmalloc分配一个namespace对象,对象地址存放在局部变量namespace中,并初始化namespacemnt相关数据结构,具体参见注释

3、     初始化系统中所有进程的地址空间为默认地址空间

4、     调用函数set_fs_pwdset_fs_root初始化当前进程的当前工作目录和根目录

3do_kern_mount

函数源码:


点击(此处)折叠或打开

  1. struct vfsmount *
  2. do_kern_mount(const char *fstype, int flags, const char *name, void *data)
  3. {
  4.     struct file_system_type *type = get_fs_type(fstype);
  5.     struct super_block *sb = ERR_PTR(-ENOMEM);
  6.     struct vfsmount *mnt;
  7.     int error;
  8.     char *secdata = NULL;

  9.     if (!type)
  10.         return ERR_PTR(-ENODEV);

  11.     mnt = alloc_vfsmnt(name);
  12.     if (!mnt)
  13.         goto out;

  14.     if (data) {
  15.         secdata = alloc_secdata();
  16.         if (!secdata) {
  17.             sb = ERR_PTR(-ENOMEM);
  18.             goto out_mnt;
  19.         }

  20.         error = security_sb_copy_data(type, data, secdata);
  21.         if (error) {
  22.             sb = ERR_PTR(error);
  23.             goto out_free_secdata;
  24.         }
  25.     }

  26.     sb = type->get_sb(type, flags, name, data);
  27.     if (IS_ERR(sb))
  28.         goto out_free_secdata;
  29.      error = security_sb_kern_mount(sb, secdata);
  30.      if (error)
  31.          goto out_sb;
  32.     mnt->mnt_sb = sb; //超级块
  33.     mnt->mnt_root = dget(sb->s_root); //根文件系统
  34.     mnt->mnt_mountpoint = sb->s_root; //挂载点
  35.     mnt->mnt_parent = mnt; //父文件系统
  36.     mnt->mnt_namespace = current->namespace; //命名空间
  37.     up_write(&sb->s_umount);
  38.     put_filesystem(type);
  39.     return mnt;
  40. out_sb:
  41.     up_write(&sb->s_umount);
  42.     deactivate_super(sb);
  43.     sb = ERR_PTR(error);
  44. out_free_secdata:
  45.     free_secdata(secdata);
  46. out_mnt:
  47.     free_vfsmnt(mnt);
  48. out:
  49.     put_filesystem(type);
  50.     return (struct vfsmount *)sb;
  51. }

函数处理流程:

1、根据文件系统类型名称,调用函数get_fs_type获得类型为file_system_type的文件系统类型对象的地址,存入局部变量type

2、调用alloc_vfsmntmnt_cache slab高速缓存中分配一个新的文件系统对象,地址存入局部变量mnt

3、调用依赖于文件系统的type->get_sb函数分配并初始化一个超级块

4、初始化mnt相关字段,具体参见注释

4rootfs_get_sb

函数功能:

get_sb_nodev的封装函数

函数源码:

点击(此处)折叠或打开

  1. static struct super_block *rootfs_get_sb(struct file_system_type *fs_type,
  2.     int flags, const char *dev_name, void *data)
  3. {
  4.     return get_sb_nodev(fs_type, flags|MS_NOUSER, data, ramfs_fill_super);
  5. }

5get_sb_nodev

函数源码:


点击(此处)折叠或打开

  1. struct super_block *get_sb_nodev(struct file_system_type *fs_type,
  2.     int flags, void *data,
  3.     int (*fill_super)(struct super_block *, void *, int))
  4. {
  5.     int error;
  6.     struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL);

  7.     if (IS_ERR(s))
  8.         return s;

  9.     s->s_flags = flags;

  10.     error = fill_super(s, data, flags & MS_VERBOSE ? 1 : 0);
  11.     if (error) {
  12.         up_write(&s->s_umount);
  13.         deactivate_super(s);
  14.         return ERR_PTR(error);
  15.     }
  16.     s->s_flags |= MS_ACTIVE;
  17.     return s;
  18. }

函数处理流程:

1、调用sget函数分配并初始化超级块对象

2、调用函数fill_super,即ramfs_fill_super初始化超级块对象

6ramfs_fill_super

函数功能:

初始化相关字段,并分配根索引节点对象和根目录项对象

函数源码:


点击(此处)折叠或打开

  1. static int ramfs_fill_super(struct super_block * sb, void * data, int silent)
  2. {
  3.     struct inode * inode;
  4.     struct dentry * root;

  5.     sb->s_maxbytes = MAX_LFS_FILESIZE;
  6.     sb->s_blocksize = PAGE_CACHE_SIZE;
  7.     sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
  8.     sb->s_magic = RAMFS_MAGIC;
  9.     sb->s_op = &ramfs_ops;
  10.     sb->s_time_gran = 1;
  11.     inode = ramfs_get_inode(sb, S_IFDIR | 0755, 0);
  12.     if (!inode)
  13.         return -ENOMEM;

  14.     root = d_alloc_root(inode);
  15.     if (!root) {
  16.         iput(inode);
  17.         return -ENOMEM;
  18.     }
  19.     sb->s_root = root;
  20.     return 0;
  21. }

7sget

函数源码:


点击(此处)折叠或打开

  1. /**
  2.  *    sget    -    find or create a superblock
  3.  *    @type:    filesystem type superblock should belong to
  4.  *    @test:    comparison callback
  5.  *    @set:    setup callback
  6.  *    @data:    argument to each of them
  7.  */
  8. struct super_block *sget(struct file_system_type *type,
  9.             int (*test)(struct super_block *,void *),
  10.             int (*set)(struct super_block *,void *),
  11.             void *data)
  12. {
  13.     struct super_block *s = NULL;
  14.     struct list_head *p;
  15.     int err;

  16. retry:
  17.     spin_lock(&sb_lock);
  18.     if (test) list_for_each(p, &type->fs_supers) {
  19.         struct super_block *old;
  20.         old = list_entry(p, struct super_block, s_instances);
  21.         if (!test(old, data))
  22.             continue;
  23.         if (!grab_super(old))
  24.             goto retry;
  25.         if (s)
  26.             destroy_super(s);
  27.         return old;
  28.     }
  29.     if (!s) {
  30.         spin_unlock(&sb_lock);
  31.         s = alloc_super();
  32.         if (!s)
  33.             return ERR_PTR(-ENOMEM);
  34.         goto retry;
  35.     }
  36.         
  37.     err = set(s, data);
  38.     if (err) {
  39.         spin_unlock(&sb_lock);
  40.         destroy_super(s);
  41.         return ERR_PTR(err);
  42.     }
  43.     s->s_type = type;
  44.     strlcpy(s->s_id, type->name, sizeof(s->s_id));
  45.     list_add_tail(&s->s_list, &super_blocks);
  46.     list_add(&s->s_instances, &type->fs_supers);
  47.     spin_unlock(&sb_lock);
  48.     get_filesystem(type);
  49.     return s;
  50. }

函数处理流程:

1、如果定义了test函数,用该函数为对比函数,在文件系统类型的超级块链表中查找super_block对象,找到则返回

2、未找到则调用alloc_super函数分配一个super_block对象,并把对象地址存入局部变量s

3、调用set函数,即set_anon_super函数初始化超级块

8set_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号和特定指针关联在一起的机制。这个机制最早是在20032月加入内核的,当时是作为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号。这些函数都定义在

9alloc_super

函数源码:

点击(此处)折叠或打开

  1. /**
  2.  *    alloc_super    -    create new superblock
  3.  *
  4.  *    Allocates and initializes a new &struct super_block. alloc_super()
  5.  *    returns a pointer new superblock or %NULL if allocation had failed.
  6.  */
  7. static struct super_block *alloc_super(void)
  8. {
  9.     struct super_block *s = kmalloc(sizeof(struct super_block), GFP_USER);
  10.     static struct super_operations default_op;

  11.     if (s) {
  12.         memset(s, 0, sizeof(struct super_block));
  13.         if (security_sb_alloc(s)) {
  14.             kfree(s);
  15.             s = NULL;
  16.             goto out;
  17.         }
  18.         INIT_LIST_HEAD(&s->s_dirty);//脏的索引节点链表头
  19.         INIT_LIST_HEAD(&s->s_io); //正在被写入磁盘的索引节点链表头
  20.         INIT_LIST_HEAD(&s->s_files);//文件对象的链表头
  21.         INIT_LIST_HEAD(&s->s_instances); //文件系统类型对象的超级块链表指针
  22.         INIT_HLIST_HEAD(&s->s_anon); //用于处理远程网络文件系统的匿名目录项链表头
  23.         INIT_LIST_HEAD(&s->s_inodes);//索引索引节点链表头
  24.         init_rwsem(&s->s_umount); //卸载使用的读写信号量
  25.         sema_init(&s->s_lock, 1); //超级块信号量
  26.         down_write(&s->s_umount); //获取卸载写信号量
  27.         s->s_count = S_BIAS; //引用计数器
  28.         atomic_set(&s->s_active, 1);//次级引用计数器
  29.         sema_init(&s->s_vfs_rename_sem,1); //vfs重命名信号量
  30.         sema_init(&s->s_dquot.dqio_sem, 1); //磁盘限额相关
  31.         sema_init(&s->s_dquot.dqonoff_sem, 1); //磁盘限额相关
  32.         init_rwsem(&s->s_dquot.dqptr_sem); //磁盘限额相关
  33.         init_waitqueue_head(&s->s_wait_unfrozen); //进程挂起的等待队列,直到文件系统被解冻
  34.         s->s_maxbytes = MAX_NON_LFS; //文件的最长长度,#define    MAX_NON_LFS    ((1UL<<31) - 1)
  35.         s->dq_op = sb_dquot_ops; //磁盘限额处理方法
  36.         s->s_qcop = sb_quotactl_ops;//磁盘限额管理方法
  37.         s->s_op = &default_op; //超级块方法
  38.         s->s_time_gran = 1000000000; //时间戳粒度,纳秒级,默认值1秒
  39.     }
  40. out:
  41.     return s;
  42. }

函数处理流程:

1、调用kmalloc函数分配超级块对象,存入局部变量s

2、分配成功,初始化s中的相关字段,见代码注释

 

参考文章:

1、浅析linux内核中的idr机制

http://blog.csdn.net/ganggexiongqi/article/details/6737389\

阅读(2383) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~