最新消息:XAMPP默认安装之后是很不安全的,我们只需要点击左方菜单的 "安全"选项,按照向导操作即可完成安全设置。

扒开linux内核根文件系统的“外衣”

XAMPP相关 admin 429浏览 0评论

一、开篇

对于linux内核,文件系统可以说是给内核增添了无尽的“乐趣”。在linux运行情况下,对于一个文件系统来说,只有挂载到内存中目录树的一个目录下,文件系统才会被linux内核所访问。linux内核中很多地方都运用“父—子”概念。在文件系统部分,也同样使用了该概念。对于linux内核中第一个文件系统,不能通过mount命令或者系统调用来挂载。这时候内核是通过以下两种机制来挂载根文件系统。

根文件系统的概念这里从linux内核的角度和用户的角度来看,先从linux内核角度来看,根文件系统是rootfs;从用户的角度来看,根文件系统是用户指定的根文件系统,在linux引导时通过内核参数root=指定。二者的关系是:在linux内核启动流程的后续会把用户指定的根文件系统挂载到rootfs文件系统的根目录下。

二、rootfs根文件系统

在linux内核启动过程中,最先挂载的根文件系统是rootfs文件系统,该文件系统是一个内存文件系统,即是基于内存的,而且对用户隐藏。该文件系统非常重要,每个进程所使用的标准输入、标准输出和标准错误,对应文件描述符0、1和2,这3个文件描述符都对应rootfs文件系统中的字符设备文件”/dev/console”。下面将从源码的角度来看看rootfs

(2-1)初始化rootfs

初始化rootfs文件系统由init_rootfs()函数完成,定义如下:

int __init init_rootfs(void)
{
 int err = register_filesystem(&rootfs_fs_type);

 if (err)
  return err;

 if (IS_ENABLED(CONFIG_TMPFS) && !saved_root_name[0] &&
  (!root_fs_names || strstr(root_fs_names, "tmpfs"))) {
  err = shmem_init();
  is_tmpfs = true;
 } else {
  err = init_ramfs_fs();
 }

 if (err)
  unregister_filesystem(&rootfs_fs_type);

 return err;
}

在以上代码中,会调用register_filesystem()向linux内核注册rootfs文件系统类型rootfs_fs_type。定义如下:

static struct dentry *rootfs_mount(struct file_system_type *fs_type,
 int flags, const char *dev_name, void *data)
{
 static unsigned long once;
 void *fill = ramfs_fill_super;

 if (test_and_set_bit(0, &once))
  return ERR_PTR(-ENODEV);

 if (IS_ENABLED(CONFIG_TMPFS) && is_tmpfs)
  fill = shmem_fill_super;

 return mount_nodev(fs_type, flags, data, fill);
}

static struct file_system_type rootfs_fs_type = {
 .name  = "rootfs",
 .mount  = rootfs_mount,
 .kill_sb = kill_litter_super,
};

(2-2)挂载rootfs文件系统

挂载rootfs文件系统由init_mount_tree()函数完成,从该函数名可形象的知道该函数的功能是:初始化挂载树。函数定义如下:

static void __init init_mount_tree(void)
{
 struct vfsmount *mnt;
 struct mnt_namespace *ns;
 struct path root;
 struct file_system_type *type;

 type = get_fs_type("rootfs");
 if (!type)
  panic("Can't find rootfs type");
 mnt = vfs_kern_mount(type, 0, "rootfs", NULL);
 put_filesystem(type);
 if (IS_ERR(mnt))
  panic("Can't create rootfs");

 ns = create_mnt_ns(mnt);
 if (IS_ERR(ns))
  panic("Can't allocate initial namespace");

 init_task.nsproxy->mnt_ns = ns;
 get_mnt_ns(ns);

 root.mnt = mnt;
 root.dentry = mnt->mnt_root;
 mnt->mnt_flags |= MNT_LOCKED;

 set_fs_pwd(current->fs, &root);
 set_fs_root(current->fs, &root);
}

上述第11行代码,使用vfs_kern_mount()挂载rootfs文件系统。

第16行代码,使用create_mnt_ns(mnt)函数创建第一个挂载命令空间。

上述第20行代码,将设置0号线程的挂载命名空间。

第27和28行代码,将0号线程的当前工作目录和根目录设置为rootfs文件系统的根目录。

(2-3)创建rootfs根文件系统的目录和文件

在内核后续的启动过程中,default_rootfs()函数会在rootfs文件系统中创建必须的目录和文件节点。如下代码所示(/init/noinitramfs.c):

static int __init default_rootfs(void)
{
 int err;

 err = ksys_mkdir((const char __user __force *) "/dev", 0755);
 if (err < 0)
  goto out;

 err = ksys_mknod((const char __user __force *) "/dev/console",
   S_IFCHR | S_IRUSR | S_IWUSR,
   new_encode_dev(MKDEV(5, 1)));
 if (err < 0)
  goto out;

 err = ksys_mkdir((const char __user __force *) "/root", 0700);
 if (err < 0)
  goto out;

 return 0;

out:
 printk(KERN_WARNING "Failed to create a rootfs\n");
 return err;
}
rootfs_initcall(default_rootfs);

从以上代码可知,default_rootfs()将创建/dev目录;创建控制台的字符设备文件/dev/console,主设备号是5,从设备号是1.

还将创建/root目录。最后会使用rootfs_initcall宏将default_rootfs函数加入到初始化节段中。

【特别注意】对于default_rootfs函数的编译将在没有设置initrd/initramfs的条件下进行。如果设置了【Initial RAM filesystem and RAMdisk(initramfs/initrd)support】选项。那么default_rootfs()函数将不会编译进linux内核!

(2-4)打开0、1、2文件描述符

在linux内核1号线程的线程函数中,会调用kernel_init_freeable()函数,在该函数中会打开控制台的字符设备文件/dev/console,得到文件描述符0,然后两次复制文件描述符0,得到文件描述符1和2。如下代码片段:

 /* Open the /dev/console on the rootfs, this should never fail */
 if (ksys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
  pr_err("Warning: unable to open an initial console.\n");

 (void) ksys_dup(0);
 (void) ksys_dup(0);

linux内核的1号线程会使用try_to_run_init_process()试图加载用户空间的程序,从而使内核向用户空间转换,在这个过程中,1号线程将作为其他线程的父进程,从而子线程将继承打开的文件表,从而也将继承文件描述符0、1、2,这就是每个进程所使用的标准输入、标准输出和标准错误具有统一性的原因。

三、挂载用户指定的根文件系统

在引导linux内核时,可以使用内核参数root来指定存储设备的名称,使用rootfstype内核参数指定根文件系统的类型,从而挂载用户的根文件系统。在linux内核源码中,具体实现由prepare_namespace()函数来完成。

四、结尾

该篇文章内容的源码出自linux内核版本:4.19.4,小生对比了linux内核2.6版本的源码,发现init_rootfs()init_mount_tree()这两个函数内容没有太大变化,可见linux内核设计的深厚功力!!下面附上这两个函数的定义:


2.6版本的linux内核(/fs/ramfs/inode.c)init_rootfs函数定义:

int __init init_rootfs(void)
{
 int err;

 err = bdi_init(&ramfs_backing_dev_info);
 if (err)
  return err;

 err = register_filesystem(&rootfs_fs_type);
 if (err)
  bdi_destroy(&ramfs_backing_dev_info);

 return err;
}

2.6版本的linux内核(/fs/namespace.c)init_mount_tree函数定义:

static void __init init_mount_tree(void)
{
 struct vfsmount *mnt;
 struct mnt_namespace *ns;
 struct path root;

 mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
 if (IS_ERR(mnt))
  panic("Can't create rootfs");

 ns = create_mnt_ns(mnt);
 if (IS_ERR(ns))
  panic("Can't allocate initial namespace");

 init_task.nsproxy->mnt_ns = ns;
 get_mnt_ns(ns);

 root.mnt = ns->root;
 root.dentry = ns->root->mnt_root;

 set_fs_pwd(current->fs, &root);
 set_fs_root(current->fs, &root);
}

转载请注明:XAMPP中文组官网 » 扒开linux内核根文件系统的“外衣”

您必须 登录 才能发表评论!