init进程的详解

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

参考博客

内核源码——C语言阶段的start_kernel函数_天糊土的博客-CSDN博客

分析根文件系统中的/linuxrc文件_天糊土的博客-CSDN博客

一、前言提要

(1)start_kernel函数中的rest_init函数内容如下:

可知其调用kernel_thread函数启动了2个内核线程:kernel_init函数、kthreadd函数。

其中kernel_init函数内容如下,这个进程被称为init进程。

 

(2)注意,init进程和init程序(即linuxrc程序)是有区别的。

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)prepare_namespace函数挂载根文件系统。

 (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本身制作失败的。

2、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。如果以上都不成功,则执行失败。

3、init进程构建了用户交互界面

(1)linux系统中一个进程的创建是通过其父进程创建出来的。根据这个理论只要有一个父进程就能生出一堆子孙进程了。

(2)init进程是其他用户进程的老祖宗。init启动了login进程(用户登录进程)、命令行进程(提供命令行环境)、shell进程(提供命令解释和执行)。

(3)shell进程启动了其他用户进程。命令行和shell一旦工作,用户就可以在命令行下通过./xx的方式来执行其他应用程序,每一个应用程序的运行就是一个进程。


4、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个文件描述符。

 

四、附录

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)

版权声明:本文为oqqHuTu12345678原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。