【linux kernel】挂载根文件系统之rootfs

2023-11-17

挂载根文件系统之rootfs

一、开篇

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

​ 根文件系统的概念这里从内核的角度和用户的角度来看,先从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内核参数指定根文件系统的类型,从而挂载用户的根文件系统。在源码中,具体的实现由prepare_namespace()函数来完成,这部分内容见该篇文章:《【linux kernel】linux内核如何挂载根文件系统

四、结尾

该篇文章内容的源码出自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);
}


小生由于知识和精力有限,如若文章存在有不妥的地方,欢迎批评指正或与我讨论,哈哈!!


搜索关注【嵌入式小生】wx公众号获取更多精彩内容>>>>
请添加图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【linux kernel】挂载根文件系统之rootfs 的相关文章

  • 静态 OpenCV 库中未定义的引用

    我有一个使用 OpenCV 3 1 的 C 项目 并且使用共享库可以正常工作 但现在我想使用静态库 位于项目目录中的文件夹中 来编译它 因为我希望能够在未安装 OpenCV 的情况下导出它 如果需要还可以编辑和重新编译 这次我重新编译了 O
  • 如何在 ASP.NET Core 6.0 Web API 项目中启用 cors?

    在我的 ASP NET Core 6 0 Web API 项目中配置了 CORS 但预检请求收到 http 405 错误 换句话说 不允许使用 HTTP OPTION 看起来 cors 没有启用 我见过的例子config EnableCor
  • c 使用 lseek 以相反顺序复制文件

    我已经知道如何从一开始就将一个文件复制到另一个文件 但是我如何修改程序以按相反的顺序复制它 源文件应具有读取访问权限 目标文件应具有读写执行权限 我必须使用文件控制库 例如 FILE A File B should be ABCDEF FE
  • C# 实体框架我们应该使用 POCO.Id 还是仅使用 POCO 设置关系?

    我在服务方法中遇到一种情况 将 POCO 分配为另一个 POCO 的子对象无法按预期工作 我正在使用实体框架 4 public void ChangeOrderCurrency Currency currency order Currenc
  • 为什么我在 WinForms 列表框中得到“System.Data.DataRowView”而不是实际值?

    每当我运行代码并尝试查看highscore我在列表框中得到的只是System Data DataRowView 谁能明白为什么吗 Code MySqlConnection myConn new MySqlConnection connStr
  • AcceptSocket 超时?

    是否有可能AcceptSocket on a TcpListener具有超时的对象 以便它偶尔被中断 TcpListener server new TcpListener localIP port server Start while sh
  • C#生成的csv文件通过电子邮件发送嵌入到Lotus Note中电子邮件的底部

    我遇到了一个奇怪的问题 即使用 NET SmtpClient 通过电子邮件发送的 CSV 附件出现在电子邮件底部 而不是 Lotus Note 中的附件 我只是不知道如何解决这个问题 而且我无法访问客户端计算机 这使得调试非常困难 我可以采
  • 从二进制文件读取字节到 long int

    我有两个问题 我有二进制文件的数据 我想使用 read 函数读取前 8 个字节以签署 long int 但我不能 你知道我该怎么做吗 如何直接读取一块数据到字符串中 我可以像所示那样阅读吗 前任 ifstream is is open te
  • 主构造函数不再在 VS2015 中编译

    直到今天 我可以使用主构造函数 例如 public class Test string text private string mText text 为了能够做到这一点 在以前的 Visual Studio CTP 中 我必须将其添加到 c
  • 重定向 std::cout

    我需要一个类 在其对象的生命周期内将一个 ostream 重定向到另一个 ostream 经过一番修补后 我想出了这个 include
  • 这些工作队列标志意味着什么?

    在研究工作队列时 我遇到了内核中定义的工作队列标志和常量 我有以下我无法理解的疑问 这里的排水和救援到底是什么意思 WQ DRAINING 1 lt lt 6 internal workqueue is draining WQ RESCUE
  • 禁用实体框架的默认值生成(Code First)

    我数据库中有一个列不能为空 我想将其设置为默认值在数据库中 问题是实体框架似乎自己创建了一个默认值 例如 int gt 0 并且完全忽略了数据库中的默认值约束 有没有办法禁用实体框架的默认值 我发现您可以使用以下属性来装饰您的字段 Data
  • List 或其他类型上的 string.Join

    我想将整数数组或列表转换为逗号分隔的字符串 如下所示 string myFunction List
  • 在用户程序中使用 或在驱动程序模块代码中使用 ...这有关系吗?

    我正在开发一个设备驱动程序模块和关联的用户库来处理ioctl 来电 该库获取相关信息并将其放入一个结构中 该结构被传递到驱动程序模块中并在那里解压 然后进行处理 我省略了很多步骤 但这就是总体思路 一些数据通过结构体传递ioctl is u
  • 按 Enter 继续

    这不起作用 string temp cout lt lt Press Enter to Continue cin gt gt temp cout lt lt Press Enter to Continue cin ignore 或更好 in
  • 使用方法的状态模式

    我正在尝试使用方法作为状态而不是类来基于状态模式的修改版本来实现一个简单的状态机 如下所示 private Action
  • C# 和断点 - 这里有魔术师吗?

    我有这个 public static void ByLinkText string text for var i 0 i lt 50 i try Setup Driver FindElement By LinkText text Click
  • 如何获取运行或段落的高度

    我找到了Run or Paragraph in FlowDocument现在我需要知道HEIGHT of it i e while navigator CompareTo flowDocViewer Document ContentEnd
  • boost::spirit::qi::语法和可变参数模板

    我在使用可变参数模板定义语法时面临一个问题 我首先定义一些包含在某些结构中的简单语法 例如纬度 经度 如下所示 include
  • 查找并删除超过 x 天的文件或文件夹

    我想删除超过 7 天的文件和文件夹 所以我尝试了 17 07 14 email protected cdn cgi l email protection find tmp mindepth 1 maxdepth 1 ctime 7 exec

随机推荐

  • Redis Cluster集群主从切换踩坑记

    因为项目的原因采用了Redis Cluster 3主3从 每台主机1主1从 集群信息如下 10 135 255 72 20011 gt cluster nodes 7b662b36489a6240aa21d1cf7b04b84019254b
  • STL源码剖析之一:空间适配器(allocator)

    空间适配器是 所有组件的核心 每个操作系统都有自己的内存分配器 他承担着内存分配 管理 释放 作为模版参数传递到每个容器去 allocate函数分配一片连续的未被构造的空间备用 deallocate 函数释放空间 construct函数调用
  • nodejs 中 token 的使用

    前言 token 验证 在设计登录注册和一些权限接口时发挥作用 以nodejs为例 谈一谈jsonwebtoken的使用 正文 一 安装 npm i jsonwebtoken 二 使用 首先 需要提供一个密匙 也就是一个字符串 用于toke
  • FPGA零基础学习之Vivado-ROM使用教程

    FPGA零基础学习之Vivado ROM使用教程 本系列将带来FPGA的系统性学习 从最基本的数字电路基础开始 最详细操作步骤 最直白的言语描述 手把手的 傻瓜式 讲解 让电子 信息 通信类专业学生 初入职场小白及打算进阶提升的职业开发者都
  • 云原生之使用Docker部署Magma导航页

    云原生之使用Docker部署Magma导航页 一 Magma导航页介绍 1 1 Magma导航页简介 1 2Magma导航页特点 二 本地环境介绍 2 1 本地环境规划 2 2 本次实践介绍 三 本地环境检查 3 1 检查Docker服务状
  • 扛住阿里双十一高并发流量,Sentinel是怎么做到的?

    Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景 本文介绍阿里开源限流熔断方案 Sentinel 功能 原理 架构 快速入门以及相关框架比较 基本介绍 1 名词解释 服务限流 当系统资源不够 不足以应对大量请求 对系统
  • # winform实现一个服务端和多个客户端进行通信

    参看此链接 http www cnblogs com longwu archive 2011 08 25 2153636 html 在上述代码的基础上进行了修改 包括一些捕获异常以及按钮的应用 扩充了一个listbox确保服务端可以选择和不
  • linux echo输出转义换行回车引号

    echo 输出引号的正确格式 echo 123 echo 123 echo 输出回车换行 制表符的正确格式 echo e n123 echo e n123 echo e t123 echo e t123 输出结果
  • springboot使用pagehelper进行分页

    上次的博客项目 使用到了分页 这里总结一下 1 项目环境 IDE IDEA 语言 java 框架 springboot 模板引擎 thymeleaf 2 效果 3 pom xml
  • 贪吃蛇视频教程

    http gameinstitute qq com lore catalog 10017
  • nvm切换node版本

    nvm是一个node的版本管理工具 可以简单操作node版本的切换 安装 查看等等 与npm不同的是 npm是依赖包的管理工具 nvm 主要为了解决 node js 各种版本存在不兼容现象 1 下载 可去github上下载相关版本 链接地址
  • cmd命令解密Bitlocker

    解锁 manage bde unlock C Recovery 加锁 manage bde lock C 解密 manage bde off C 加密 manage bde on C C表示解锁的盘符 解密需要一定时间 可以用manage
  • 利用python拼接图片代码_Python实现图片拼接的代码

    具体代码如下所示 import os from PIL import Image UNIT SIZE 220 the size of image save path root group dia zxb Code lip CycleGAN
  • python PriorityQueue遍历

    要写一段遍历PriorityQueue中每个元素的代码 去网上找到的都是for循环 get 但是这样会把PriorityQueue中的元素取出来 得 问了chatGPT 没想到真有用 from queue import PriorityQu
  • Oracle 中 decode 函数用法

    Oracle 中 decode 函数用法 含义解释 decode 条件 值1 返回值1 值2 返回值2 值n 返回值n 缺省值 该函数的含义如下 IF 条件 值1 THEN RETURN 翻译值1 ELSIF 条件 值2 THEN RETU
  • 最新QQ强制搜索Api接口

    强制搜索QQ接口 QQ隐藏搜索不到的把他QQ放在 后面然后直接搜索链接就可以搜索到了 QQ设置了隐藏无法搜索使用这个隐藏都不管用的 进入官网 https apis hackeus cn 找到强制搜索接口点进去 后面输入QQ号即可
  • 用户账户控制(无法截图/退出全屏/使用窗口模式)

    用户账户控制提示框无法截图 这是我遇到的问题 如下 就是这种对话框 一般是程序请求管理员权限运行 就会弹出 默认是全屏状态 无法截图 试过什么PrintScreen等均不行 这里提供一个办法 把该提示框改变为窗口模式 而非全屏 就可以使用截
  • 数据结构--二叉堆与优先队列

    堆的一些性质 1 堆是一颗完全二叉树 2 堆的顶端一定是 最大 最小 的 但是要注意一个点 这里的大和小并不是传统意义下的大和小 它是相对于优先级而言的 3 堆一般有两种样子 小根堆和大根堆 分别对应第二个性质中的 堆顶最大 堆顶最小 对于
  • 毕业设计 - 基于云平台的火灾报警器 - stm32 物联网 单片机 OneNET云平台

    文章目录 0 简介 1 项目简介 2 开发环境 3 火焰传感器 4 连接OneNET云平台 5 演示效果 6 最后 0 简介 Hi 大家好 学长今天向大家介绍一个 单片机项目 基于云平台的火灾报警器 stm32 物联网 单片机 OneNET
  • 【linux kernel】挂载根文件系统之rootfs

    挂载根文件系统之rootfs 文章目录 挂载根文件系统之rootfs 一 开篇 二 rootfs根文件系统 2 1 初始化rootfs 2 2 挂载rootfs文件系统 2 3 创建简单的rootfs根文件系统目录和文件 2 4 打开0 1