在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的垃圾信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。
1 什么是文件系统
什么是文件系统?文件系统的作用是什么?没有文件系统可不可以?操作系统和文件系统有什么关系?
文件系统就是文件管理系统。从字面意思理解就是管理文件的系统。嵌入式设备需要管理的硬件资源主要有以下几类:CPU 、RAM 、ROM 、IO 。用来管理CPU资源的就是调度系统;用来管理RAM资源的就是内存管理系统;用来管理ROM资源的就是文件系统;用来管理IO资源的就是输入输出系统。这些系统都属于操作系统旗下的子系统,这些子系统都是用来管理硬件资源的,这也是操作系统存在(或者说被创造)的最大的价值。
那什么又叫做 管理 呢?管理就是将硬件资源合理高效地利用起来,并为上层软件提供标准的接口以便上层软件更简单地使用底层的硬件资源。所以,操作系统以及其子系统最重要的 目的 就是改善硬件使用的 效率 和 便捷性 。明确这个目的很重要,因为系统中的大部分设计最终都指向该目标。
文件是依赖文件系统而存在的,没有了文件系统也就没有了文件的概念。其实在没有文件系统和文件的情况,嵌入式软件想要使用ROM就按照字节或者结构体去读写。但这种方式有很大的缺陷,比如存储的内容要求地址必须连续,多个线程同时访问时会产生冲突等等。为了解决这些问题,文件系统才应运而生。
文件是基于 面向对象 思想进行设计的。文件其实就是一个对象,有着自己的存储结构和相应的操作方法,对象类型就是文件系统类型。也可以从 协议 的角度来理解文件和文件系统,文件的存储结构和相应的操作方法就是一种对二进制数据编码和解析的一组协议。
至于什么是根文件系统,这里埋个伏笔,大家先跟着后面的文章一步步去应用、去创建、去阅读源码,等到本系列的最后我们再来剖析这个问题。
2 从根文件系统启动第一个应用程序
应用程序存放在文件系统里,或者说应用程序以文件的格式存放在ROM中。
为什么要这样设计呢?因为从逻辑和使用习惯上要求需要将文件系统和Linux内核代码分割开来编译和存放。内核负责为应用程序提供启动前和启动后的各项服务,文件系统用来存放应用程序。由于应用程序的种类和数目非常多,而且还有可能根据用户的需求动态调整(比如朋友推荐了一款好用的软件,你肯定也想着在自己的电脑或手机上装一个),所以将这些 经常变化 的东西和 不经常变化 的内核分割开来存放是最好的选择。
内核通过init进程启动第一个应用程序标志着用户态的开启。init进程前一半是运行在内核态的,后一半是运行在用户态的,并在用户态驻扎下来变成守护进程,监视其他用户进程的运行。所以,init进程(关于Linux进程和线程的深入分析,我们放在后续的CPU调度专题分析,防止一入门就劝退~)是系统中所有其他用户进程的祖先进程。
下面,我们主要看一下内核中第一个应用程序是谁,在何时启动的。
第一个应用程序是由init进程调用的,我们从 kernel_init() 函数看起:
/* /init/main.c */
static int __init kernel_init(void * unused)
{
...
/*
* Ok, we have completed the initial bootup, and
* we're essentially up and running(此时,已经进入怠速状态).
* Get rid of the initmem segments and start the user-mode stuff..
* (从这里开始进入用户态)
*/
init_post();
return 0;
}
接下来展开 init_post() 函数分析。该函数主要做了两件事。
一件是创建0、1、2号文件映射到控制台设备,分别对应着标准输入、标准输出和标准错误,以便应用程序可以使用scanf()、printf()和err()函数。
另一件就是启动第一个应用程序。启动应用程序使用的是 run_init_process() 函数,直到启动成功一个为止。看来init进程也是一个不折不扣的花心大萝卜了,居然有这么多备胎~~ 有兴趣的TX可以将这几个应用程序都试一试,我自己只把execute_command破坏了,试了一下/sbin/init,也是可以正常启动的。
/* This is a non __init function. Force it to be noinline otherwise gcc
* makes it inline to init() and it becomes part of init.text section
* (该函数使用noinline属性就是为了防止该函数被编译到init.text,后续在
* CPU调度专题中会深入介绍这个操作的效果,因为一开始就一头扎进去很容易劝退~)
*/
static int noinline init_post(void)
{
free_initmem();
unlock_kernel();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
/*第0个文件:标准输入,映射到/dev/console设备*/
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
(void) sys_dup(0); /*第1个文件:标准输出,映射到/dev/console设备*/
(void) sys_dup(0); /*第2个文件:标准错误,映射到/dev/console设备*/
if (ramdisk_execute_command) { /*该参数为空*/
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
/*该参数是从uboot传递过来的,为/Linuxrc*/
if (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
panic("No init found. Try passing init= option to kernel.");
}
恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~