ramdisk中只放要运行的电脑上的存储设备驱动,内核中不放存储设备驱动,这样的话如果每次存储设备不同,我们只需要做一个对应的ramdisk,每次更换存储设备只需要做ramdisk,而内核只需要编译一次就行了,这样效率会提高?如果驱动放到内核一起编译,每次换存储设备都要重新编译内核,效率低。编译内核比做一个新的ramdisk效率低。

 

linux 内核启动的目的:

    linux内核启动的目的是为了运行用户态的应用程序,单独启动一个内核其实是没有什么用的。linux内核主要做进程线程调度、内存管理、进程间通信等核心工作,

linux内核有vfs虚拟文件系统的实现,也有常用磁盘文件系统类型的实现。但是文件系统中存放的内容一般并不会放在内核中,例如,应用程序二进制文件、各种外设的驱动。

如果应用程序、驱动也放到内核中,那么每次更新应用都要重新编译内核,这是低效的。

    假设应用程序固定就是一个init进程,但是硬件的千差万别导致不同平台驱动程序也不一样。以驱动为例,如果驱动编译进内核,那么每移植到一个新平台,就要重编一次内核,

驱动和内核紧耦合,效率低下。而内核中只包含一些必要的驱动,平台特有的驱动放到文件系统中。因此,也就引出了根文件系统。

    我们认为一台机器完全启动起来的标志是,所有外设的驱动都得到了正常的安装,成功完成了初始化。应用层至少有一个程序在运行。对于一个最小化系统,这个应用层程序就是init进程.

至于处理具体业务的进程,都是可以按需添加的,不在讨论范围内。

    linux内核一般配置好,相同架构编译一次即可,不同平台的具体差异也就到了根文件系统这里。

    根文件系统里面一般包含bin、sbin、dev(带console、null等设备节点)、以及一些其他的启动脚本。bin、sbin里面是一些常用的命令行命令。

arm平台:

    对于arm平台的根文件系统来讲,一般使用busybox来制作,并最终打成根文件系统磁盘映像,也叫ramdisk。busybox通过一个bin文件实现了很多命令行命令,例如 ls、cp等。

    arm平台的内核映像和根文件系统映像都是通过bootloader加载到内存的(以磁盘块儿的形式读取,这时候还不支持文件系统),在加载之后启动内核时,将根文件系统起始地址和大小以参数(initrd)的形式传给内核,内核启动后会挂载这个根文件系统。也就是在内存中模拟了一块磁盘,并且这块“”磁盘”被格式化成了一定的文件系统,里面也包含了一些提前放进去的文件。

    如果将磁盘的驱动编译到了内核中,这时候bootloader就不需要将根文件系统(initrd)加载到内存了,只加载内核即可。根文件系统留在磁盘boot分区,设置好内核的root启动参数,内核会自己挂载这个根文件系统(嵌入式平台上,磁盘的分区信息,可能直接在内核里面写死了,传入的boot参数,只要能让内核解析出来是第几个分区即可,分区名字不重要。磁盘的boot分区要提前格式化好文件系统,而且内核编译的时候也要支持这种文件系统)。 这时候传给内核的另一个参数是noinitrd,而不再是initrd。同理,如果内核里面编译好了网络的驱动,内核同样可以通过nfs来挂载根文件系统。根文件系统主要保证内核能起来就行,至于更多的工作就交给根文件系统里面的程序去做。

    由于没有格式化分区,或者内核对格式化的分区文件系统不支持而挂载失败的例子如下:

可以看到内核会尝试好几个文件系统类型。

    内核通过 mount_root 挂载完根文件系统后,会走到init_post函数去执行应用程序,在这个函数中首先打开 /dev/console 这个设备节点(这个节点在嵌入式板子上一般对应串口,在打开这个节点之前,内核是通过直接往串口裸写的形式输出信息的,而打开这个节点之后,是以设备文件的形式写的),打开成功后,会把文件描述符复制到1,2上,也就是0,1,2文件句柄都重定向到了这个设备,也就是支持输入输出了,这才具备了交互功能。

    紧接着,内核去解析bootloader传过来的 init= 参数,例如 init=./linuxrc,并去执行里面的程序,如果找到了这个程序,便执行这个程序,如果没有找到或者没有传这个参数,内核便去根文件系统里面依次尝试执行 /sbin/init、/etc/init、/bin/init、/bin/sh,只要有一个成功后面的就不会再被执行了,如果都没有成功,最后就打印一条panic报错消息表示内核启动失败,可以通过擦除根分区来做这个实验。busybox制作的根文件系统里面是存在/sbin/init的,并且这个/sbin/init 也是链接到busybox这个大bin的,和ls、cp等命令一样。内核转到sbin/init运行,不带任何参数,相对应的就是转到busybox源代码的init_main运行,这个函数在busybox源代码的Init.c文件里。下面就转到了busybox的流程里面。

    缺少init文件的报错如下:

 

    init启动的大概流程就是读取配置文件,解析配置文件,执行配置文件里面配置的用户程序。大概的调用过程如下:

1 init_main
2      parse_inittab
3              file = fopen(INITTAB, "r")   // 这个宏对应的配置文件就是/etc/inittab

也就是/sbin/init 写死了先读取 /etc/inittab 配置文件。这个配置文件的格式为:

<id>:<runlevels>:<action><process>

id 表示的是设备id,也就是输入输出和错误信息定向到哪里,如果不配置的话就定向到/dev/null,这也就是根文件系统内为什么需要有 /dev/null 节点的原因,因为如果不配id的话,process可能起不来

runlevels 这个可忽略

action 表示执行时机

process 表示应用程序或者脚本

action 一般有 ctrlaltdel、shutdown、restart、askfirst等

如果没有 /etc/inittab 这个配置文件,busybox 也会有一套默认的流程,会以sysinit时机调用/etc/init.d/rcS,最终会以askfirst的方式启动 /bin/sh。

对不同启动时机的程序处理策略是不同的,如下:

可以看到init进程,最终会走到一个死循环里面,不再返回。在这个循环里面一直监控着子进程的状态,如果有一个子进程退出,那就设置a->pid = 0,然后重新拉起来。这也就是为什么1号进程是系统管理进程的原因。它管理着整个系统的初始化和一些进程的监控。更现代的systemd比busybox里面的init更复杂,功能更强大。

还有一些process的处理时机是在用户按下某些按键时执行的,例如shutdown、ctrlaltdel等,这些process是挂在信号处理函数里面的。

如果 /etc/inittab 里面配上了,::sysinit:/etc/init.d/rcS 初始化脚本,rcS里面是一些驱动装载命令和一些文件系统挂载命令,如果里面配置了mount -a,那么init程序又会去找 /etc/fstab 配置文件,根据里面的配置,依次挂载文件系统。这就是这几个约定俗成的配置文件的处理策略。

    至此,根文件系统就起来了,一个最小的根文件系统需要如下5个部分:

    1、/dev/console   /dev/null

    2、/etc/inittab

    3、配置文件里面指定的应用程序

    4、c库  (busybox动态编译、或者应用程序依赖c库时才需要)

    5、init 本身即busybox

    设备节点创建:根文件系统中必须在/dev下面创建 console、null节点,init进程启动的时候会打开这个console节点,并且将输入输出定向到console,内核中是提前编译好了这个设备的驱动程序的,所以init打开这个设备进行输入输出是可以正常的。如果没有驱动那么就会报错。 如果没有设备节点,init进程启动过程中也会报错。至于这个设备的主设备号和次设备号,应该是约定好的。

busybox的编译:

    busybox编译时可以选择动态或者静态编译,还有tab补全等选项,可以根据需要选择某些功能。编译完成安装的时候要注意安装目录,特别是交叉编译时,安装到宿主机的根目录会导致机器挂掉,

需要设置好环境变量,make CONFIG_PREFIX=/path/from/root install

mdev 机制:

    根据内核中的驱动,自动创建设备节点,不需要手工创建,但是需要提前挂载好sys、tmpf文件系统,这个配以配置在/etc/fstab中,例如:

#device  mount-point    type     options     dump    fsck 
proc          /proc          proc     defaults       0         0
sysfs          /sys          sysfs     defaults       0         0
tmpfs          /dev         tmpfs    defaults       0         0

然后rcS中加入如下内容:

mount  -a
mkdir  /dev/pts
mount  -t  devpts  devpts  /dev/pts
echo  /sbin/mdev  >  /proc/sys/kernel/hotplug
mdev -s

这样设备节点就会自动创建出来了。 用 cat /proc/mounts 也能看到系统挂载的类型。 例如:

rootfs   /    rootfs   rw   0   0
/dev/root   /   yaffs   rw   0   0
proc   /proc   proc   rw   0    0
sysfs    /sys    sysfs   rw   0    0
tmpfs    /dev    tmpfs    rw   0   0
devpts   /dev/pts    devpts    rw   0   0

 

x86平台:

服务器领域硬件定型后一般不会大改,因此,磁盘驱动可以直接编译进内核了,不管是机械硬盘还是固态硬盘。这样内核启动起来之后也就能直接以文件系统接口读取磁盘了,也就可以直接将磁盘挂载到文件系统的根上了,然后直接执行磁盘里面的init程序。 一般服务器上面,常用的命令行命令也都是独立分开的,并不是像busybox一样,一个bin包揽了所有功能。

grub2启动的最后,会根据提前配置好的配置项启动内核,并且也会带上initrd参数。

 

 

initrd 和 initramfs:

initrd是提前做好映像,最后加载到内存,在内存中模拟一块磁盘,并且带有磁盘文件系统,例如ext3、yaffs等。  内核启动的时候先挂载rootfs根文件系统(内存文件系统),同时也会将/dev/ram0挂载到 根目录,然后将内存磁盘中的内容拷贝到rootfs里面,rootfs的文件系统(基于内核中vfs实现)和磁盘文件系统就不一样了,然后再拷贝到/dev/ram0(这个才是ramdisk), 这和磁盘文件系统是一样的。最后将bootload加载的根文件系统那一块内存释放。然后执行init的启动。

initramfs是在编译内核的时候指定好根文件系统目录,根文件系统会被cpio打包后,拷贝到最后的内核镜像文件的一个section中,内核启动的时候,挂载完rootfs根文件系统(内存文件系统)之后,就找到这个section,将它们拷贝到rootfs中,然后执行init的启动。

posted on 2022-10-29 20:49  周伯通789  阅读(277)  评论(0编辑  收藏  举报