以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
参考博客
内核源码——C语言阶段的start_kernel函数_天糊土的博客-CSDN博客
分析根文件系统中的/linuxrc文件_天糊土的博客-CSDN博客
linux内核sys_mount()分析_kai_ding的博客-CSDN博客_sys_mount
【linux kernel】linux内核如何挂载根文件系统_iriczhao的博客-CSDN博客
一、前言提要
(1)start_kernel函数中的rest_init函数内容如下:
可知其调用kernel_thread函数启动了2个内核线程:kernel_init函数、kthreadd函数。
其中kernel_init函数内容如下,这个进程被称为init进程。
(2)注意,init进程和init程序(即linuxrc程序)是有区别的。
init进程一开始就有(是在rest_init函数中创建的),它运行于内核态,属于一个内核线程。后来init进程挂载根文件系统,并运行应用程序init程序后,init进程才从内核态进程转变为用户态进程。因为转变过程中进程号没有变,还是进程1,所以有人会把init程序(linuxrc程序)当做进程1。但其实init进程除了后面init程序的内容外,还包括内核态下挂载根文件系统等操作。
二、init进程从内核态向用户态的转变
1、一个进程先后两种状态
- init进程刚开始运行的时候是内核态,它属于一个内核线程,然后运行一个用户态下面的程序后,把自己强行转成用户态(因为后面的进程需要工作在用户态下)。
- init进程完成了从内核态到用户态的过渡,因此后续的其他进程都可以工作在用户态。
2、init进程在内核态下的工作内容
- 主要是挂载根文件系统,并试图找到用户态下的那个init程序。(这句话看出,init进程是早于init程序运行的。)
- init进程要把自己转成用户态就必须运行一个用户态的应用程序,要运行这个应用程序就必须得找到这个应用程序,要找到这个应用程序就必须得挂载根文件系统,因为所有的应用程序都在文件系统中。
- 内核源代码中的所有函数都处于内核态,执行其中任何一个都不能脱离内核态。应用程序必须不属于内核源代码,这样才能保证应用程序处于用户态。这里执行的init程序和内核不在一起,由根文件系统另外提供。
3、init进程在用户态下的工作内容
- init进程大部分有意义的工作都是在用户态下进行的。
- init进程对操作系统的意义在于,其他所有的用户进程都直接或者间接派生自init进程。
4、init进程如何从内核态跳跃到用户态
- init进程处于内核态时,通过函数kernel_execve(见下文)来执行一个用户空间编译链接的应用程序就跳跃到用户态了。
- 跳跃过程中进程号没有改变,一直是进程1。
- 跳跃过程是单向的,一旦执行init程序转到用户态,整个操作系统就算真正运转起来了,以后只能在用户态下工作,用户态下想要进入内核态只能通过调用API。
三、init进程的工作内容
1、init进程打开了控制台
(1)linux系统中每个进程都有一个文件描述符表,表中存储的是本进程打开的文件。
(2)linux系统中一切皆是文件,因此设备也是以文件的方式来访问的。要访问一个设备,就要打开此设备对应的文件描述符。比如/dev/fb0这个设备文件就代表LCD显示器设备,/dev/buzzer代表蜂鸣器设备,/dev/console代表控制台设备。
(3)这里打开了/dev/console文件,并且复制了2次文件描述符,一共得到了3个文件描述符,分别是0、1、2,就是所谓的标准输入、标准输出、标准错误这3个文件描述符。
(4)进程1打开了这3个文件描述符,因此进程1衍生出来的所有的进程默认都具有这3个文件描述符。
2、init进程挂载了根文件系统
(1)prepare_namespace函数挂载根文件系统。
关于这个函数如何挂载根文件系统的,见博客linux内核如何挂载根文件系统 。
(2)uboot通过传参告知内核根文件系统的位置、根文件系统的文件系统类型等信息。
- 比如uboot传参中的“root=/dev/mmcblk0p2 rw”这一句就是告诉内核根文件系统在哪里。
- 比如uboot传参中的“rootfstype=ext3”这一句就是告诉内核rootfs的类型。
(3)挂载结果(下面的描述是以inand启动为例,不是以tftp、NFS方式启动)
- 如果内核挂载根文件系统成功,则会打印出:VFS: Mounted root (ext3 filesystem) on device 179:2。(也可能其他数字)
- 如果挂载根文件系统失败,则会打印:No filesystem could mount root, tried: yaffs2
(4)如果内核启动时挂载rootfs失败,则后面无法执行。
- 内核中设置了启动失败后5s自动重启的机制,因此会看到反复重启的情况。
(5)如果挂载rootfs失败,可能的原因有
- 最常见的错误就是uboot的bootargs设置不对。
- rootfs烧录失败(fastboot烧录不容易出错)。
- rootfs本身制作失败的。
3、init进程执行init程序(完成内核态到用户态的转变)
(1)执行prepare_namespace函数成功挂载rootfs后,接着执行init_post()函数。此函数进入rootfs中寻找应用程序的init程序,找到后调用run_init_process函数去执行。
(2)如何确定init程序是哪个文件?
- 先判断uboot的传参cmdline中是否指定,如果指定则先执行cmdline中指定的程序。比如“init=/linuxrc”表示rootfs的根目录下的linuxrc程序就是init程序。下面代码就是执行/linuxrc的部分,其中变量execute_command的赋值过程见附录。它肯定是将cmdline解析后,从中得到 “init=/linuxrc” 这个项目。
- init=/linuxrc,这个/linuxrc一般是软连接,指向busybox,如下所示。
- 如果uboot的传参cmdline中没有init=xx,或者cmdline中指定的这个xx执行失败,则执行备用方案:第一备用是/sbin/init,第二备用是/etc/init,第三备用是/bin/init,第四备用是/bin/sh。如果以上都不成功,则执行失败。
4、init进程构建了用户交互界面
(1)linux系统中一个进程的创建是通过其父进程创建出来的。根据这个理论只要有一个父进程就能生出一堆子孙进程了。
(2)init进程是其他用户进程的老祖宗。init启动了login进程(用户登录进程)、命令行进程(提供命令行环境)、shell进程(提供命令解释和执行)。
(3)shell进程启动了其他用户进程。命令行和shell一旦工作,用户就可以在命令行下通过./xx的方式来执行其他应用程序,每一个应用程序的运行就是一个进程。
四、附录
1、变量execute_command的赋值过程
在/init/main.c文件中有如下内容:
在/include/linux/init.h文件中,有如下定义:
/** Only for really core code. See moduleparam.h for the normal way.** Force the alignment so the compiler doesn't space elements of the* obs_kernel_param "array" too far apart in .init.setup.*/ #define __setup_param(str, unique_id, fn, early) \static const char __setup_str_##unique_id[] __initconst \__aligned(1) = str; \static struct obs_kernel_param __setup_##unique_id \__used __section(.init.setup) \__attribute__((aligned((sizeof(long))))) \= { __setup_str_##unique_id, fn, early }#define __setup(str, fn) __setup_param(str, fn, fn, 0)