init系统简介:
Linux 操作系统的启动首先从 BIOS 开始,接下来进入 boot loader,由 bootloader 载入内核,进行内核初始化。内核初始化的最后一步就是启动 pid 为 1 的 init 进程,这个进程是系统的第一个进程,它负责产生其他所有用户进程。
init 的一些特点
- init是Linux系统操作中不可缺少的程序之一。
- 所谓的init进程,它是由内核启动的第一个用户级进程。内核自行启动(已被装入内存,开始运行,并已初始化所有的设备驱动程序和数据结构等)之后,就通过启动一个用户级程序init的方式,完成引导进程。所以,init始终是第一个进程(其进程编号始终为1)。
- 仅仅将内核运行起来是毫无实际用途的,必须由 init 系统将系统代入可操作状态。比如启动外壳 shell 后,便有了人机交互,这样就可以让计算机执行一些预订程序完成有实际意义的任务(这其实就是就是后面所说的内核态到用户态的转变)。内核会在过去曾使用过init的几个地方查找它,它的正确位置(对Linux系统来说)是/sbin/init。如果内核找不到init,它就会试着运行/bin/sh,如果运行失败,系统的启动也会失败。
Linux下的三个特殊进程
Linux下有三个特殊的进程idle进程(PID=0),init进程(PID=1),和kthreadd(PID=2)
- idle进程由系统自动创建,运行在内核态。idle进程其pid=0,其前身是系统创建的第一个进程,也是唯一一个没有通过fork或者kernel_thread产生的进程。完成加载系统后,演变为进程调度、交换。
- kthreadd进程由idle通过kernel_thread创建,并始终运行在内核空间,负责所有内核进程的调度和管理。它的任务就是管理和调度其他内核线程kernel_thread, 会循环执行一个kthread的函数,该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程 。
- init进程由idle通过kernel_thread创建,在内核空间完成初始化后,加载init程序。在这里我们就主要了解下init进程,init进程由0进程创建,完成系统的初始化,是系统中所有其他用户进程的祖先进程
Linux中的所有进程都是由init进程创建并运行的。首先Linux内核启动,然后在用户空间中启动init进程,再启动其他系统进程。在系统启动完成后,init将变成为守护进程监视系统其他进程。(内核态转变为用户态)
大致过程为:0号进程->1号内核进程->1号用户进程(init进程)->getty进程->shell进程
init进程完成从内核态向用户态的转变
一个进程先后两种状态
init进程刚开始运行的时候是内核态,它属于一个内核线程,然后运行一个用户态下面的程序后(如/sbin/init),把自己转成用户态(后面的进程需要工作在用户态下)。
init进程完成了从内核态到用户态的过渡,因此后续的其他进程都可以工作在用户态。
init进程在内核态下的工作内容
主要是挂载根文件系统,并试图找到用户态下的那个init程序。(这句话看出,init进程是早于init程序运行的。)
init进程要把自己转成用户态就必须运行一个用户态的应用程序,要运行这个应用程序就必须得找到这个应用程序,要找到这个应用程序就必须得挂载根文件系统,因为所有的应用程序都在文件系统中。
内核源代码中的所有函数都处于内核态,执行其中任何一个都不能脱离内核态。而应用程序必须不属于内核源代码,这样才能保证应用程序处于用户态。这里执行的init程序和内核不在一起,由根文件系统另外提供。
init进程在用户态下的工作内容
init进程大部分有意义的工作都是在用户态下进行的。init进程对操作系统的意义在于,其他所有的用户进程都直接或者间接派生自init进程。
init进程如何从内核态跳跃到用户态 ?还能回来不?
init进程处于内核态时,通过函数do_execve来执行一个用户空间编译链接的应用程序就跳跃到用户态了。
- 跳跃过程中进程号没有改变,一直是进程1。
- 跳跃过程是单向的,一旦执行init程序转到用户态,整个操作系统就算真正运转起来了,以后只能在用户态下工作,用户态下想要进入内核态只能通过调用API。
1、init进程挂载了根文件系统
(1)prepare_namespace函数挂载根文件系统。
(2)根文件系统在哪里?根文件系统的文件系统类型是什么?
uboot通过传参来告诉内核这些信息。
uboot传参中的root=/dev/mmcblk0p2 rw 这一句就是告诉内核根文件系统在哪里。
uboot传参中的rootfstype=ext3这一句就是告诉内核rootfs的类型。
(3)挂载结果
如果内核挂载根文件系统成功,则会打印出: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)一旦挂载rootfs成功,则进入rootfs中寻找应用程序的init程序(在init_post()函数中),找到后用run_init_process去执行。
(2)如果确定init程序是谁?
先从uboot传参cmdline中看有没有指定,如果有指定先执行cmdline中指定的程序。比如init=/linuxrc表示rootfs的根目录下的linuxrc程序就是init程序。
如果uboot传参cmdline中没有init=xx或者cmdline中指定的这个xx执行失败,还有备用方案。第一备用:/sbin/init,第二备用:/etc/init,第三备用:/bin/init,第四备用:/bin/sh。如果以上都不成功,则没有办法了。
init=/linuxrc一般指向busybox。
3、init进程构建了用户交互界面
(1)init进程是其他用户进程的祖先。
linux系统中一个进程的创建是通过其父进程创建出来的。根据这个理论只要有一个父进程就能生出一堆子孙进程了。
(2)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个文件描述符。
运行级别
简单的说,运行级就是操作系统当前正在运行的功能级别。这个级别有多种,以centos为例,6和7版本的设置方法不同,具体对应关系如下。
init level (centos 6 /etc/inittab) | systemctl target(centos 7) | 说明 |
---|---|---|
0 | poweroff.target | 停机(千万不能把initdefault 设置为0 ) |
1 | rescure.target | 单用户(救援)模式 |
2 | multi-user.target | 多用户,没有 NFS |
3 | multi-user.target | 完整的多用户文本模式级别,登录后进入到控制台命令行模式 |
4 | 未使用 | |
5 | graphical.target | X11 (xwindow,能够正常切换的前提是系统支持) |
6 | reboot.target | 重新启动 |
[root@k8s-m1 ~]# ll /usr/lib/systemd/system/runlevel*target
lrwxrwxrwx 1 root root 15 Feb 27 16:14 /usr/lib/systemd/system/runlevel0.target -> poweroff.target
lrwxrwxrwx 1 root root 13 Feb 27 16:14 /usr/lib/systemd/system/runlevel1.target -> rescue.target
lrwxrwxrwx 1 root root 17 Feb 27 16:14 /usr/lib/systemd/system/runlevel2.target -> multi-user.target
lrwxrwxrwx 1 root root 17 Feb 27 16:14 /usr/lib/systemd/system/runlevel3.target -> multi-user.target
lrwxrwxrwx 1 root root 17 Feb 27 16:14 /usr/lib/systemd/system/runlevel4.target -> multi-user.target
lrwxrwxrwx 1 root root 16 Feb 27 16:14 /usr/lib/systemd/system/runlevel5.target -> graphical.target
lrwxrwxrwx 1 root root 13 Feb 27 16:14 /usr/lib/systemd/system/runlevel6.target -> reboot.target
运行级别的配置
centos6设置默认的启动级别
# 查看运行级别: centos7 也可以使用该命令。
[root@k8s-m1 ~]# runlevel
N 3
# 修改运行级别:
临时修改: init level(对应的级别即可)
永久修改:需要修改 /etc/inittab 文件。
在最下面的一行中的语句 id:5:initdefault 中的数字5改成需要的启动程度就可以了,一般是命令行模式 3
centos7设置默认的启动级别
#centos7修改设置默认的系统级别
[root@k8s-m1 ~]# systemctl set-default multi-user.target
Removed symlink /etc/systemd/system/default.target.
Created symlink from /etc/systemd/system/default.target to /usr/lib/systemd/system/multi-user.target.
#centos7查看默认的系统级别
[root@k8s-m1 ~]# systemctl get-default
multi-user.target
参考:https://blog.csdn.net/m0_45406092/article/details/130660743
更多关于Linux的知识请前往博客主页查看。