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

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

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

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(“.”);

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 中国程序员的悲哀

    中国程序员的悲哀
    中国程序员有个很悲哀的地方,大多数程序都对微软崇拜有加,奉若神明;然而大多数人都用着盗版的微软操作系统,盗版的visualstudio,然后还牛逼哄哄的出个什么微软vs使用心得。在他们眼里软件本身并不是商品,软件衍生出来的服务才能赚钱。
     
    这就好比几个小偷偷了别人的手机,然后交流用什么方法销赃才能最赚钱,你会觉得小偷太无耻了。但是如果满大街都是小偷,那你就会习以为常了。这么一想,发觉中国的程序员是抛开道德观念的,一心研究技术的。
     
    但是这不能怪程序员

  • ssh用法及命令

    ssh用法及命令http://blog.csdn.net/pipisorry/article/details/52269785什么是SSH?简单说,SSH是一种网络协议,用于计算机之间的加密登录。如果一个用户从本地计算机,使用SSH协议登录另一台远程计算机,我们就可以认为,这种登录是安全的,即使被中途截获,密码也不会泄露。最早的时候,互联网通信都是明文通信,一旦被截获,内容就暴露无疑。1995年,芬兰学者…

  • HotSpot源码分析之类模型

    HotSpot源码分析之类模型

    2020年11月20日
  • 创建选区快捷键是什么_Photoshop选区操作的快捷键

    创建选区快捷键是什么_Photoshop选区操作的快捷键1.使用快捷键快速操作.F1-帮助F2-剪切F3-拷贝F4-粘贴F5-隐藏/显示画笔面板F6-隐藏/显示颜色面板F7-隐藏/显示图层面板F8-隐藏/显示信息面板F9-隐藏/显示动作面板F12-恢复Shift+f5-填充Shift+f6-羽化Shift+f7-选择→反选ctrl+h-隐藏选定区域ctrl+d-取消选定区域ctrl+w-关闭文件ctrl+Q-退出PHOTOSHOPEsc-取消操作…

  • 什么是窗口句柄

    什么是窗口句柄什么是窗口句柄举个例子:你有你自己的身份证号,一报身份证号,你应该知道是你了你也有名字,当然名字复杂点,并且不是唯一,没有数字来得方便,所以,窗口句柄就相当于身份证号,每个窗口都有一个编号

  • 加密Excel解密

    加密Excel解密excel文件进行加密,能够保护excel文件的内容,但是有时候我们自己设置的密码,时间久了可能会忘记,或者在网上下载的excel文件或者同事之间转发的excel文件也有加密,这对于我们来说都不是很方便了。想要解密excel文件的加密,需要用到奥凯丰EXCEL解密大师excel加密有两种,它们的解密方法也是不一样的。激活成功教程打开密码,激活成功教程它的方法目前只有通过软件找到正确密码才能进行解密,所以点击进入【找回密码】,选择一种找回方法进行激活成功教程(如果对自己设置的密码还有一些印象,可以使用组合破击..

发表回复

您的电子邮箱地址不会被公开。

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