Linux内核有没有rootfs,Linux内核rootfs的初始化过程[通俗易懂]

Linux内核有没有rootfs,Linux内核rootfs的初始化过程[通俗易懂]由于在下水平相当有限,不当之处,还望大家批评指正^_^在Linuxshell中执行mount命令,通常可以看到某个做了文件系统的磁盘分区或flash分区或内存文件系统做为所谓的根文件系统被mount到了挂载点/处。实际上内核中最初始的根文件系统,并不是来自内核外部,他是由内核自己构建出来的。为了说明这个过程,我们先说说mount的过程。系统调用sys_mount是在fs/namespace.c…


Jetbrains全系列IDE稳定放心使用

由于在下水平相当有限,不当之处,还望大家批评指正^_^

在Linux  shell中执行mount命令,通常可以看到某个做了文件系统的磁盘分区或flash分区或内存文件系统做为所谓的根文件系统被mount到了挂载点/处。

实际上内核中最初始的根文件系统,并不是来自内核外部,他是由内核自己构建出来的。

为了说明这个过程,我们先说说mount的过程。

系统调用sys_mount是在fs/namespace.c中实现的,主要工作则由do_mount完成。

一个常规的mount操作大致包含两个动作:

(1)将一个文件系统加载到内核中

注意,这里仅仅是加载。

该动作是由vfs_kern_mount完成的。

每一个文件系统被加载到内核后,内核中都会产生如下几个结构:

一个struct mount结构

一个struct super_block结构

一个struct dentry结构,他是此文件系统的根目录的目录顶,名称为/。注意仅仅是此文件系统的根目录。

struct mount结构中有指针分别指向super_block结构和此文件系统的根目录顶结构。

super_block结构和此文件系统的根目录顶结构也有指针相互指向对方。

另外,struct mount结构中的mnt_devname记录了所mount的块设备文件。

(2)将上一步加载的文件系统,挂接到以/为起点的目录树中的某个位置处。

此工作由do_add_mount完成。

其实就是将此mount结构串接到某个已有的文件系统内的某个目录项上(类型需要为目录)。

实质性的操作函数如下: void mnt_set_mountpoint(struct mount *mnt,

struct mountpoint *mp,

struct mount *child_mnt)

{

mp->m_count++;

mnt_add_count(mnt, 1);

/* essentially, that’s mntget */

child_mnt->mnt_mountpoint = dget(mp->m_dentry);

child_mnt->mnt_parent = mnt;

child_mnt->mnt_mp = mp;

}

这里child_mnt为被挂载的文件系统的mount结构。

mp->m_dentry是挂载点路径中最后一个目录对应的目录项。

mnt为mp->m_dentry所在文件系统的mount结构。

另外,mp->m_dentry->d_flags还会被置上 DCACHE_MOUNTED标记,

这个动作完成后,外界就可以访问到这个文件系统了。

这个过程感觉挺复杂,在下对其代码实现理解得也很有限^_^

不过,可以通过open系统调用的实现,看到内核遍历路径的过程中,是如何转向被挂载的文件系统内部的。

下面列出了sys_open的函数调用链(从上到下),

其中最后的函数__lookup_mnt展示了由挂载点目录项查找被挂载的文件系统对应的struct mount结构的过程。

sys_open

do_sys_open

do_filp_open

path_openat

path_openat

link_path_walk

walk_component

lookup_fast

__follow_mount_rcu

__lookup_mnt

前面说了,一个普通的mount包含上述两个步骤。

然而,内核中最初始的根文件系统,由于其特殊性(没有地方可以挂接),所以只执行了上述两步中的第一步。

这里先贴一下相关函数吧^_^

fs/namespace.c

mnt_init

init_mount_tree

核心是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;

set_fs_pwd(current->fs, &root);

set_fs_root(current->fs, &root);

}

可见内核通过如下方式调用vfs_kern_mount加载了一个文件系统到内核中。

vfs_kern_mount(type, 0, “rootfs”, NULL);

特殊之处:一般的mount,设备文件为/dev/path/to/block_dev_file,而这里的设备文件为rootfs。

所以,相应的mnt_devname就是rootfs了。注意,只有这个最早的rootfs对应的块设备文件为rootfs.

文件系统类型type(即名叫rootfs文件系统类型)的实现在哪里呢?

答案是在fs/ramfs/inode.c中。如下即是。

static struct file_system_type rootfs_fs_type = {

.name= “rootfs”,

.mount = rootfs_mount,

.kill_sb = kill_litter_super,

};

而此文件正是ramfs文件系统的实现文件,其结构定义如下。

static struct file_system_type ramfs_fs_type = {

.name= “ramfs”,

.mount = ramfs_mount,

.kill_sb = ramfs_kill_sb,

.fs_flags = FS_USERNS_MOUNT,

};

可见初始的rootfs文件系统类型,基本上就是ramfs,超级块的填充、文件系统的操作,与ramfs都是共用同一份代码。

而rootfs包装一个自己的mount函数rootfs_mount,只是为了传个MS_NOUSER标记而已。

对于设备名rootfs,rootfs_mount压根就没用到,而实际上也不存在这个设备。

那么这里的vfs_kern_mount完成后,达成了什么效果呢。

同前面介绍的一样,会产生一个mount结构,一个super_block结构,一个文件系统内的根目录对应的名称为/的目录项。

那么这个文件系统往哪挂呢?没地方挂 ^_^

接下来可以看到,内核的主要操作如下:

ns = create_mnt_ns(mnt);init_task.nsproxy->mnt_ns = ns;get_mnt_ns(ns);root.mnt = mnt;root.dentry = mnt->mnt_root;set_fs_pwd(current->fs, &root);set_fs_root(current->fs, &root);

创建一个mnt_namespace(mount命名空间),将rootfs的mount结构做为其root。

然后将init_task的mount命名空间指定为此命名空间。

然后,将此初始rootfs的根目录设置为当前任务的pwd及root。

好了,至此目录树的起点/算是有了。但是目前rootfs里面还没有内容呢。

接下来start_kernel的流程会顺着rest_init -) kernel_init -) kernel_init_freeable往下走。

那接下来顺着kernel_init_freeable往下看rootfs相关内容。

先是走到do_pre_smp_initcalls,从而调用到了由rootfs_initcall(populate_rootfs);定义的初始化函数populate_rootfs。

如果系统配置了使用initramfs,那么后面populate_rootfs会将内存中的根文件系统压缩包解压到rootfs中。

压缩包起始于__initramfs_start地址处,大小为__initramfs_size。

注意,这只是向初始的rootfs中增加内容,并没有更换rootfs。

具体过程,就是解压压缩包,根据解压出的内容,在初始的根文件系统中创建目录、文件,然后将解压出的文件的内容部分write到创建的文件中。由于已经有根文件系统了,因此相关代码就是通过调用通用的文件操作方面的系统调用,来完成上述任务的。

具体函数有do_name、do_copy、do_symlink。

对于压缩包压缩算法的识别,是由decompress_method函数完成的。

相关代码如下:

struct compress_format {

unsigned char magic[2];

const char *name;

decompress_fn decompressor;

};

static const struct compress_format compressed_formats[] __initconst = {

{ {037, 0213}, “gzip”, gunzip },

{ {037, 0236}, “gzip”, gunzip },

{ {0x42, 0x5a}, “bzip2”, bunzip2 },

{ {0x5d, 0x00}, “lzma”, unlzma },

{ {0xfd, 0x37}, “xz”, unxz },

{ {0x89, 0x4c}, “lzo”, unlzo },

{ {0, 0}, NULL, NULL }

};

decompress_fn __init decompress_method(const unsigned char *inbuf, int len,

const char **name)

{

const struct compress_format *cf;

if (len < 2)

return NULL; /* Need at least this much… */

for (cf = compressed_formats; cf->name; cf++) {

if (!memcmp(inbuf, cf->magic, 2))

break;

}

if (name)

*name = cf->name;

return cf->decompressor;

}

实际上,这里识别的前两个字节,应该就是相应算法的压缩文件的前两个字节。

例如xz文件的前两个字节内容如下(前两个字节正是上面的xz对应的magic数字):

[root@localhost ~]# hexdump -n 2 -C  test.tar.xz

00000000  fd 37                                             |.7|

00000002

[root@localhost ~]#

initramfs环节完成了,继续顺着kernel_init_freeable往下看。

如果ramdisk_execute_command指向的init程序不可访问,

就进入prepare_namespace,但是这个过程涉及到内核命令行参数中与rootfs有关的内容。

例如,noinitrd、initrd=adrress,size、 root=/dev/ram、root=/dev/mmcblk0p1、root=/dev/nfs等。

这一步做完,初始的根文件系统基本上就被替换掉了。

最终的根文件系统可能是内存文件系统、可能是flash存储介质上的一块区域,也可能是nfs,就看用户的系统是如何定制的了。

如果是内存文件系统,这里应该会创建/dev/ram或/dev/root块设备文件节点,并将之mount为新的rootfs。

实际的过程为:

sys_mount(block_dev_file, “/root”, fs, flags, data);

sys_chdir(“/root”);

sys_mount(“.”, “/”, NULL, MS_MOVE, NULL);

sys_chroot(“.”);

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/188286.html原文链接:https://javaforall.net

赞 (0)
全栈程序员-站长的头像全栈程序员-站长

相关推荐

  • goland2022.01.4激活【最新永久激活】2022.01.17

    (goland2022.01.4激活)这是一篇idea技术相关文章,由全栈君为大家提供,主要知识点是关于2021JetBrains全家桶永久激活码的内容IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.html0HKLM1UCCY-eyJsaWN…

    2022年3月31日
  • GT注册大全-终结动态修订版「建议收藏」

    GT注册大全-终结动态修订版「建议收藏」本次‘GT注册大全-终结动态修订版’的特点:1.仅中文名称共享软件范围来说为国内乃至世界范围最新最全,通用注册信息有效率最高;2.较之前版本删除了N多已完全失效的注册信息,增加了N多新旧有效注册信息;3.保持了GT注册大全原有的按软件发布时间排列,软件按同系列或同一制作公司及个人软件作者来合并,提供软件相关下载页面或直接下载链接,非通用注册信息一律标明,必要时附带说明等特点;4.进一步完善了‘GT

    2022年5月20日
  • 博弈论基础mooc答案_博弈论考试题及答案

    博弈论基础mooc答案_博弈论考试题及答案1、“博弈的本意是什么?A、摔跤B、下棋C、赌博D、游戏参考答案:B2、古时“弈”字,就是指A、跳棋B、象棋C、五子棋D、围棋参考答案:D3、按照博弈方是否达成有约束力的协议,可以分为()A、理性博弈和非理性博弈B、完全信息博弈和不完全信息博弈C、动态博弈和静态博弈D、合作博弈与非合作博弈参考答案:D4、囚徒困境的例子属于()的典型A、非合作博弈B、合作博弈C、理性博弈D、动态博弈参考答案:A5、“石头剪刀布游戏,属于()。A、贯序博弈B、动态博弈…

    2022年10月15日
  • java graphics2d 乱码_Graphics2D 中文乱码[通俗易懂]

    今天遇到了一个乱码问题,合成的小票图片上的中文全部变成了口口口,后来在网上查了资料,发现是Graphics2D用了宋体字,而linux服务器上没有对应的字体库。把本地的字体库上传上去就解决了。本地字体库位置:(winXP宋体字)C:\WINDOWS\Fonts\simsun.ttcsimsun.ttc改名为simsun.ttfrz命令(需提前装,其它方式也行)复制到linux:/usr/ja…

    2022年4月13日
  • JAVA中字符串和数组做参数传递的情况

    JAVA中字符串和数组做参数传递的情况首先明确的一点就是在java中只有值传递!只有值传递!理论依据来自《thinkinjava》。接下来就是具体说明为何java只有值传递。因为java中有基本类型和引用类型两种数据类型,再加上String这个特殊的类型,所以主要从三个方面就行解释。1.基本数据类型先看代码publicclassDemo01{publicvoidchange(inta){System.out.println(“副本a的初始…

    2022年5月6日
  • Nginx 502 Bad Gateway 的错误的解决方案

    Nginx 502 Bad Gateway 的错误的解决方案我用的是nginx反向代理Apache,直接用Apache不会有任何问题,加上nginx就会有部分ajax请求502的错误,下面是我收集到的解决方案。一、fastcgi缓冲区设置过小出现错误,首先要查找nginx的日志文件,目录为/var/log/nginx,在日志中发现了如下错误2013/01/1713:33:47[error]15421#0:*16upstr…

    2022年6月26日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号