《Linux启动过程分析》内核挂载根文件系统

原文:https://blog.csdn.net/tankai19880619/article/details/12093239

Linux内核中current指针作为全局变量,使用非常广泛;例如:进程上下文中获取当前进程ID、任务调度,以及open等文件系统调用中路径搜索等;首先介绍下current结构体:

各个平台、各个内核版本中current的实现可能不同;但原理是一样的。该指针一般定义在具体平台的current.h头文件中,类型为struct task_struct:

#define current (get_current())
static inline struct task_struct *get_current(void)

2.文件系统挂载vfsmount(struct vfsmount):

本质上,mount操作的过程就是新建一个vfsmount结构,然后将此结构和挂载点(目录项对象)关联。关联之后,目录查找时就能沿着vfsmount挂载点一级级向下查找文件了。
对于每一个mount的文件系统,都由一个vfsmount实例来表示。
kernel/include/linux/mount.h

struct vfsmount {
  struct list_head mnt_hash; //内核通过哈希表对vfsmount进行管理
  struct vfsmount *mnt_parent;	//指向父文件系统对应的vfsmount
  struct dentry *mnt_mountpoint; //指向该文件系统挂载点对应的目录项对象dentry
  struct dentry *mnt_root; //该文件系统对应的设备根目录dentry
  struct super_block *mnt_sb; //指向该文件系统对应的超级块
  struct list_head mnt_mounts; 
  struct list_head mnt_child;  //同一个父文件系统中的所有子文件系统通过该字段链接成双联表
  int mnt_flags;
  /* 4 bytes hole on 64bits arches */
  const char *mnt_devname;	/* Name of device e.g. /dev/dsk/hda1 */
  struct list_head mnt_list;  //所有已挂载文件系统的vfsmount结构通过该字段链接在一起
  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 vfsmount *mnt_master;	/* slave is on master->mnt_slave_list */
  struct mnt_namespace *mnt_ns;	/* containing namespace */
  int mnt_id;			/* mount identifier */
  int mnt_group_id;		/* peer group identifier */
  /*
  * We put mnt_count & mnt_expiry_mark at the end of struct vfsmount
  * to let these frequently modified fields in a separate cache line
  * (so that reads of mnt_flags wont ping-pong on SMP machines)
  */
  atomic_t mnt_count;
  int mnt_expiry_mark;		/* true if marked for expiry */
  int mnt_pinned;
  int mnt_ghosts;
  /*
  * This value is not stable unless all of the mnt_writers[] spinlocks
  * are held, and all mnt_writer[]s on this mount have 0 as their ->count
  */
  atomic_t __mnt_writers;
};

三、注册/创建、安装/挂载rootfs,并调用set_fs_root设置系统current的根文件系统为rootfs
过程:

第一步:建立rootfs文件系统;

第二步:调用其get_sb函数(对于rootfs这种内存/伪文件系统是get_sb_nodev,实际文件系统比如ext2等是get_sb_bdev)、建立超级块(包含目录项和i节点);

第三步:挂载该文件系统(该文件系统的挂载点指向该文件系统超级块的根目录项);

第四步:将系统current的根文件系统和根目录设置为rootfs和其根目录。

kernel/init/main.c

扫描二维码关注公众号,回复: 6236528 查看本文章
asmlinkage void __init start_kernel(void)
{
  setup_arch(&command_line);//解析uboot命令行,实际文件系统挂载需要
  parse_args("Booting kernel", static_command_line, __start___param,
		   __stop___param - __start___param,
		   &unknown_bootoption);
  vfs_caches_init(num_physpages);
}

kernel/fs/dcache.c

void __init vfs_caches_init(unsigned long mempages)
{
  mnt_init();
  bdev_cache_init(); //块设备文件创建
  chrdev_init();//字符设备文件创建
}

kernel/fs/namespace.c

void __init mnt_init(void)
{
  init_rootfs(); //向内核注册rootfs
  init_mount_tree();//重要!!!rootfs根目录的建立以及rootfs文件系统的挂载;设置系统current根目录和根文件系统为rootfs
}

下边分两步:

1.向内核注册rootfs虚拟文件系统init_rootfs
kernel/fs/ramfs/inode.c
如果内核为高版本的话:
kernel/init/do_mounts.c

int __init init_rootfs(void)
{
  err = 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,
};

文件系统注册 kernel/include/include/fs.h 结构体:

struct file_system_type {
  const char *name; //文件系统名字;如:rootfs及ext3等
  int fs_flags;
  int (*get_sb) (struct file_system_type *, int, const char *, void *, struct vfsmount *);
  //安装/挂载文件系统时,会调用;获取超级块。
  void (*kill_sb) (struct super_block *);
  //卸载文件系统时会调用。
  struct module *owner;
  struct file_system_type * next;
  //指向下一个文件系统类型。
  struct list_head fs_supers;
  //同一个文件系统类型中所有超级块组成双向链表。
  struct lock_class_key s_lock_key;
  struct lock_class_key s_umount_key;
 
  struct lock_class_key i_lock_key;
  struct lock_class_key i_mutex_key;
  struct lock_class_key i_mutex_dir_key;
  struct lock_class_key i_alloc_sem_key;
};

2.建立rootfs的根目录,并将rootfs挂载到自己的根目录;设置系统current根目录和根文件系统
kernel/fs/namespace.c

static void __init init_mount_tree(void)
{
  struct vfsmount *mnt;
  struct mnt_namespace *ns;
  struct path root;
  //创建rootfs的vfsmount结构,建立rootfs的超级块、并将rootfs挂载到自己的根目录。
  /*
  mnt->mnt_mountpoint = mnt->mnt_root = dget(sb->s_root),而该mnt和自己的sb是关联的;
  所以,是把rootfs文件系统挂载到了自己对应的超级块的根目录上。
  这里也是实现的关键:一般文件系统的挂载是调用do_mount->do_new_mount而该函数中首先调用do_kern_mount,这时mnt->mnt_mountpoint = mnt->mnt_root;但后边
  它还会调用do_add_mount->graft_tree->attach_recursive_mnt如下代码mnt_set_mountpoint(dest_mnt, dest_dentry, source_mnt)改变了其挂载点!!!
  */
  mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
  list_add(&mnt->mnt_list, &ns->list);
  ns->root = mnt; //将创建好的mnt加入系统当前
  mnt->mnt_ns = ns;
 
  init_task.nsproxy->mnt_ns = ns; //设置进程的命名空间
  get_mnt_ns(ns);
 
  root.mnt = ns->root; //文件系统为rootfs,相当与root.mnt = mnt;
  root.dentry = ns->root->mnt_root;//目录项为根目录项,相当与root.dentry = mnt->mnt_root;
 
  //设置系统current的pwd目录和文件系统
  set_fs_pwd(current->fs, &root);
  //设置系统current根目录,根文件系统。这个是关键!!!整个内核代码最多只有两处调用
  set_fs_root(current->fs, &root);  
}

以下着重分析do_kern_mount函数,它实现了rootfs在自己根目录上的挂载:
kernel/fs/super.c

struct vfsmount *
do_kern_mount(const char *fstype, int flags, const char *name, void *data)
{
  mnt = vfs_kern_mount(type, flags, name, data);
  return mnt;
}

kernel/fs/super.c

struct vfsmount *
vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
{
  mnt = alloc_vfsmnt(name); //建立并填充vfsmount
  error = type->get_sb(type, flags, name, data, mnt);//为文件系统建立并填充超级块(主要是其dentry和inode),建立rootfs根目录
  mnt->mnt_mountpoint = mnt->mnt_root; //文件系统挂载点目录,其实就是刚才建立的”/”目录。挂载点就是自己!!!!
  mnt->mnt_parent = mnt; //父对象是自己!!!!
  return mnt;
}

kernel/fs/ramfs/inode.c

static int rootfs_get_sb(struct file_system_type *fs_type,
	int flags, const char *dev_name, void *data, struct vfsmount *mnt)
{
  return get_sb_nodev(fs_type, flags|MS_NOUSER, data, ramfs_fill_super,
			    mnt);
}

kernel/fs/super.c

int get_sb_nodev(struct file_system_type *fs_type,
	int flags, void *data,
	int (*fill_super)(struct super_block *, void *, int),
	struct vfsmount *mnt)
{
  struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL);
  //在内存中分配一个超级块
  error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);
  //执行回调,填充超级块,并建立根目录项及对应i节点
  /*
  kernel/fs/ramfs/inode.c
  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;
    //static const struct super_operations ramfs_ops;
    sb->s_time_gran = 1;
    //建立根目录索引节点,我们最终的目标是要找到目录项对象关联的索引节点。
    //根目录索引节点会有自己的ops。
    inode = ramfs_get_inode(sb, S_IFDIR | 0755, 0); 
    //ramfs_get_inode
    kernel/fs/ramfs/inode.c
    struct inode *ramfs_get_inode(struct super_block *sb, int mode, dev_t dev)
    {
      struct inode * inode = new_inode(sb);
      switch (mode & S_IFMT) {  //判断文件类型
        default:
	  init_special_inode(inode, mode, dev);
	  //init_special_inode
          void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
          {
	    inode->i_mode = mode;
	    if (S_ISCHR(mode)) {//字符设备文件
	      inode->i_fop = &def_chr_fops;
	      inode->i_rdev = rdev;
	    } else if (S_ISBLK(mode)) {//块设备文件
	      inode->i_fop = &def_blk_fops;
	      inode->i_rdev = rdev;
	    } else if (S_ISFIFO(mode))
	      inode->i_fop = &def_fifo_fops;
	    else if (S_ISSOCK(mode)) //网络设备文件
	      inode->i_fop = &bad_sock_fops;
	    else
	      printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"
				  " inode %s:%lu\n", mode, inode->i_sb->s_id,
				  inode->i_ino);
           }
          //init_special_inode end
          break;
        case S_IFREG: //普通文件
          inode->i_op = &ramfs_file_inode_operations;  //索引节点的操作方法
          inode->i_fop = &ramfs_file_operations;  //缺省普通文件的操作方法
          break;
        case S_IFDIR:  //目录文件
          inode->i_op = &ramfs_dir_inode_operations;
          //ramfs_dir_inode_operations
          static const struct inode_operations ramfs_dir_inode_operations;
          kernel/include/linux/fs.h
          struct inode_operations {
            int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
            int (*mkdir) (struct inode *,struct dentry *,int);
	    int (*rmdir) (struct inode *,struct dentry *);
	    int (*mknod) (struct inode *,struct dentry *,int,dev_t);
          }
          //ramfs_dir_inode_operations end
          inode->i_fop = &simple_dir_operations;  //目录文件的操作方法
          inc_nlink(inode);
          break;
      }
    }
    //ramfs_get_inode end
    //建立根目录目录对象,目录项对象的存在主要是为了我们进行路径的查找。
    root = d_alloc_root(inode);   
    //d_alloc_root
    kernel/fs/dcache.c
    struct dentry * d_alloc_root(struct inode * root_inode)
    {
      struct dentry *res = NULL;
      static const struct qstr name = { .name = "/", .len = 1 };
      res = d_alloc(NULL, &name);
      res->d_sb = root_inode->i_sb; //指向该文件系统的超级块
      res->d_parent = res;  //根目录的父亲是它自己
      d_instantiate(res, root_inode); //关联 dentry 和 inode
    }
    //d_alloc_root end
    sb->s_root = root;  //超级块的s_root指向刚建立的根目录对象。
  }
  */
  return simple_set_mnt(mnt, s); //关联超级块(包含目录项dentry和i节点inode)和vfsmount
}

kernel/fs/namespace.c

int simple_set_mnt(struct vfsmount *mnt, struct super_block *sb)
{
  printk("TK-------_>>>>>>>namespace.c>>>>simple_set_mnt\n");//add by tankai
  mnt->mnt_sb = sb;  //对 mnt_sb超级块指针附值
  mnt->mnt_root = dget(sb->s_root); //对mnt_root指向的根目录赋值
  return 0;
}

至此,rootfs文件系统建立、并且挂载于自己超级块(包括目录项dentry和i节点inod)对应的目录项,设置了系统current根目录和根文件系统、pwd的目录和文件系统。

释放Initramfs到rootfs;如果Initramfs中有init,这种情况比较特殊、rootfs就是最后系统使用的根文件系统。

猜你喜欢

转载自blog.csdn.net/weixin_43836778/article/details/90205373