目录
第1章 Linux内核启动应用程序的方式
1.1 什么是Linux 0号进程:idle进程
0号进程, 通常也被称为idle进程,是内核无事可做时执行的进程。是空闲进程,也就是死循环。
struct task_struct init_task = INIT_TASK(init_task);
set_task_stack_end_magic(&init_task);
使用全局变量来设置进程全局环境。
作用:
1 初始化各个模块 (MM/FS/VFS/sched)
2 创建1号进程 2号进程
1号进程 kernel_thread(kernel_init, NULL, CLONE_FS);
2号进程 kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
3 把自己变成idle。(当任务链没有任务,会执行idle进程)
cpu_startup_entry
do_idle();
1.2 什么是Linux 1号进程:init进程
run_init_process(execute_command);
do_execve
try_to_run_init_process("/sbin/init")
try_to_run_init_process("/etc/init")
try_to_run_init_process("/bin/init")
try_to_run_init_process("/bin/sh")
主要用户加载根文件系统init进程,然后回到应用态,它是所用应用程序进程的祖先进程
1.3 Linux 1号进程的作用
(1)负责在用户空间完成对Linux系统进一步的初始化,mount文件系统、动态插入内核模块等。
(2)它是所有用户空间进程的祖先进程,负责fork系统中所有的其他用户进程。
(3)负责用户空间应用程序的初始化。
(4)负责监控和管理用户空间的进程。这是1号进程一项重要的职责。
1.4 Linux 1号进程启动应用程序的两种机制
从上图可以看出:根文件系统是由Linux内核进行加载的,根文件系统是挂载在虚拟文件系统之下。当内核加载或挂载完根文件系统之后,Linux就会读取根文件系统中的可执行文件,执行后续的应用程序的初始化。
Linux提供了两种不同机制启动和管理根文件系统中的程序。
(1)内核版本2.6以及之前的版本,内核是通过init进程和inittab来加载根文件系统中的程序。
(2)内核版本3.10以及以后的版本,内核是通过SystemD进程和xxx.target文件来加载根文件系统中的程序。
根文件系统中的程序包括:
- Linux的系统程序
- 内核模块ko
- 用户程序
1.5 公司的组织架构与1号进程
如果说,Linux内核是董事会的话,总经理就1号进程。
董事会只需要启动总经理,再由总经理组建和运营公司的其他子系统。
Linux内核只需要启动1号进程,再由1号进程组建和运营公司的整个系统的其他进程。
第2章 1号进程的演进之路
2.1 从 init 系统说起
Linux 操作系统的启动首先从 BIOS 开始,接下来进入 boot loader,由 bootloader 载入内核,进行内核初始化。内核初始化的最后一步就是启动 PID 为 1 的 init 进程。这个进程是系统的第一个进程。它负责产生其他所有的用户进程。init 进程以守护进程(也就是服务)的方式存在,是所有其他进程的祖先。init 进程非常独特,能够完成其他进程无法完成的任务。
init 系统能够定义、管理和控制 init 进程的行为。它负责组织和运行许多独立的或相关的初始化工作(因此被称为 init 系统),从而让计算机系统进入某种用户预定义的运行模式,比如命令行模式或图形界面模式 。
对于一个操作系统而言,仅仅将内核运行起来是毫无实际用途的,必须由 init 系统将操作系统初始化为可操作的状态。比如启动 shell 后,便有了人机交互,这样就可以让计算机执行一些程序完成有实际意义的任务。或者启动 X 图形系统以便提供更佳的人机界面,更加高效的完成任务。这里,字符界面的 shell 或者 X 系统都是一种预设的运行模式。
随着计算机系统软硬件的发展,init 系统也在不断的发展变化之中。大体上的演进路线为 sysvinit -> upstart -> systemd。
2.2 sysvinit
sysvinit 就是 System V 风格的 init 系统,顾名思义,它源于 System V 系列的 UNIX。最初的 linux 发行版几乎都是采用 sysvinit 作为 init 系统。sysvinit 用术语 runlevel 来定义 "预订的运行模式"。比如 runlevel 3 是命令行模式,runlevel 5 是图形界面模式,runlevel 0 是关机,runlevel 6 是重启。sysvinit 会按照下面的顺序按部就班的初始化系统:
- 激活 udev 和 selinux (SELinux(Security-Enhanced Linux))
- 设置定义在 /etc/sysctl.conf 中的内核参数
- 设置系统时钟
- 加载 keymaps
- 启用交换分区
- 设置主机名(hostname)
- 根分区检查和 remount
- 激活 RAID 和 LVM 设备
- 开启磁盘配额
- 检查并挂载所有文件系统,包括proc文件系统
- 清除过期的 locks 和 PID 文件
- 最后找到指定 runlevel 下的脚本并执行,其实就是启动应用服务。
除了负责初始化系统,sysvinit 还要负责关闭系统,主要是在系统关闭是为了保证数据的一致性,需要小心地按照顺序进行任务的结束和清理工作。
另外,sysvinit 还提供了很多管理和控制系统的命令,比如 halt、init、mesg、shutdown、reboot 等等。
sysvinit 的优点是概念简单。特别是服务(service)的配置,只需要把启动/停止服务的脚本链接接到合适的目录就可以了。
sysvinit 的另一个重要优点是确定的执行顺序,脚本严格按照顺序执行(sysvinit 靠脚本来初始化系统),一个执行完毕再执行下一个,这非常有益于错误排查。
同时,完全顺序执行任务也是 sysvinit 最致命的缺陷。如果 linux 系统只用于服务器系统,那么漫长的启动过程可能并不是什么问题,毕竟我们是不会经常重启服务器的。但是现在 linux 被越来越多的用在了桌面系统中,漫长的启动过程对桌面用户来说是不能接受的。Linux还被广泛应用于嵌入式系统,启动时间对于嵌入式系统也是一个重要的性能指标。除了启动慢,sysvinit 还有一些其它的缺陷,比如不能很好的处理即插即用的设备,对网络共享磁盘的挂载也存在一定的问题,于是 init 系统开始了它的进化之旅。
2.3 upstart
由于 sysvinit 系统的种种弊端,Ubuntu 的开发人员决定重新设计和开发一个全新的 init 系统,即 upstart 。upstart 是第一个被广泛应用的新一代 init 系统。
upstart 基于事件机制,比如 U 盘插入 USB 接口后,udev 得到内核通知,发现该设备,这就是一个新的事件。upstart 在感知到该事件之后触发相应的等待任务,比如处理 /etc/fstab 中存在的挂载点。采用这种事件驱动的模式,upstart 完美地解决了即插即用设备带来的新问题。采用事件驱动机制也带来了一些其它有益的变化,比如加快了系统启动时间。sysvinit 运行时是同步阻塞的。一个脚本运行的时候,后续脚本必须等待。这意味着所有的初始化步骤都是串行执行的,而实际上很多服务彼此并不相关,完全可以并行启动,从而减小系统的启动时间。
upstart 的特点
upstart 解决了之前提到的 sysvinit 的缺点。采用事件驱动模型的 upstart 可以:
- 更快地启动系统
- 当新硬件被发现时动态启动服务
- 硬件被拔除时动态停止服务
这些特点使得 upstart 可以很好地应用在桌面或者便携式系统中,处理这些系统中的动态硬件插拔特性。
2.3 主角 systemd 登场
systemd 是 linux 系统中最新的初始化系统(init),它主要的设计目标是克服 sysvinit 固有的缺点,提高系统的启动速度。systemd 和 ubuntu 的 upstart 是竞争对手,但是时至今日 ubuntu 也采用了 systemd,所以 systemd 在竞争中胜出,大有一统天下的趋势。其实,systemd 的很多概念都来源于苹果 Mac OS 操作系统上的 launchd。
systemd 的优点是功能强大,使用方便,缺点是体系庞大,非常复杂。
systemd 能够在与 upstart 的竞争中胜出自然有很多过人之处,接下来让我们介绍一些 systemd 的主要优点。
兼容性
systemd 提供了和 sysvinit 兼容的特性。系统中已经存在的服务和进程无需修改。这降低了系统向 systemd 迁移的成本,使得 systemd 替换现有初始化系统成为可能。
启动速度
systemd 提供了比 upstart 更激进的并行启动能力,采用了 socket / D-Bus activation 等技术启动服务。一个显而易见的结果就是:更快的启动速度。为了减少系统启动时间,systemd 的目标是:
- 尽可能启动更少的进程
- 尽可能将更多进程并行启动
同样地,upstart 也试图实现这两个目标。下图展示了 upstart 相对于 sysvinit 在并发启动这个方面的改进(此图来自互联网):
upstart 增加了系统启动的并行性,从而提高了系统启动速度。但是在 upstart 中,有依赖关系的服务还是必须先后启动。比如任务 A,B,(C,D)因为存在依赖关系,所以在这个局部,还是串行执行。
systemd 能够更进一步提高并发性,即便对于那些 upstart 认为存在相互依赖而必须串行的服务,比如 Avahi 和 D-Bus 也可以并发启动。从而实现如下图所示的并发启动过程(此图来自互联网):
在 systemd 中,所有的任务都同时并发执行,总的启动时间被进一步降低为 T1。可见 systemd 比 upstart 更进一步提高了并行启动能力,极大地加速了系统启动时间。
systemd 提供按需启动能力
当 sysvinit 系统初始化的时候,它会将所有可能用到的后台服务进程全部启动运行。并且系统必须等待所有的服务都启动就绪之后,才允许用户登录(顺序执行)。这种做法有两个缺点:首先是启动时间过长,其次是系统资源浪费。
某些服务很可能在很长一段时间内,甚至整个服务器运行期间都没有被使用过。比如 CUPS,打印服务在多数服务器上很少被真正使用到。您可能没有想到,在很多服务器上 SSHD 也是很少被真正访问到的。花费在启动这些服务上的时间是不必要的;同样,花费在这些服务上的系统资源也是一种浪费。
systemd 可以提供按需启动的能力,只有在某个服务被真正请求的时候才启动它。当该服务结束,systemd 可以关闭它,等待下次需要时再次启动它。
这有点类似于以前系统中的 inetd,并且有很多文章介绍如何把过去 inetd 管理的服务迁移到 systemd。
采用 linux 的 cgroups 跟踪和管理进程的生命周期
systemd 利用了 Linux 内核的特性即 cgroups 来完成跟踪的任务。当停止服务时,通过查询 cgroups ,systemd 可以确保找到所有的相关进程,从而干净地停止服务。
cgroups 已经出现了很久,它主要用来实现系统资源配额管理。cgroups 提供了类似文件系统的接口,使用方便。当进程创建子进程时,子进程会继承父进程的 cgroups 。因此无论服务如何启动新的子进程,所有的这些相关进程都会属于同一个 cgroups ,systemd 只需要简单地遍历指定的 cgroups 即可正确地找到所有的相关进程,将它们一一停止即可。
启动挂载点和自动挂载的管理
传统的 linux 系统中,用户可以用 /etc/fstab 文件来维护固定的文件系统挂载点。这些挂载点在系统启动过程中被自动挂载,一旦启动过程结束,这些挂载点就会确保存在。这些挂载点都是对系统运行至关重要的文件系统,比如 HOME 目录。和 sysvinit 一样,Systemd 管理这些挂载点,以便能够在系统启动时自动挂载它们。systemd 还兼容 /etc/fstab 文件,您可以继续使用该文件管理挂载点。
有时候用户还需要动态挂载点,比如打算访问 DVD 或者 NFS 共享的内容时,才临时执行挂载以便访问其中的内容,而不访问光盘时该挂载点被取消(umount),以便节约资源。传统地,人们依赖 autofs 服务来实现这种功能。
systemd 内建了自动挂载服务,无需另外安装 autofs 服务,可以直接使用 systemd 提供的自动挂载管理能力来实现 autofs 的功能。
实现事务性依赖关系管理
系统启动过程是由很多的独立工作共同组成的,这些工作之间可能存在依赖关系,比如挂载一个 NFS 文件系统必须依赖网络能够正常工作。systemd 虽然能够最大限度地并发执行很多有依赖关系的工作,但是类似"挂载 NFS"和"启动网络"这样的工作还是存在天生的先后依赖关系,无法并发执行。对于这些任务,systemd 维护一个"事务一致性"的概念,保证所有相关的服务都可以正常启动而不会出现互相依赖,以至于死锁的情况。
日志服务
systemd 自带日志服务 journald,该日志服务的设计初衷是克服现有的 syslog 服务的缺点。比如:
- syslog 不安全,消息的内容无法验证。每一个本地进程都可以声称自己是 Apache PID 4711,而 syslog 也就相信并保存到磁盘上。
- 数据没有严格的格式,非常随意。自动化的日志分析器需要分析人类语言字符串来识别消息。一方面此类分析困难低效;此外日志格式的变化会导致分析代码需要更新甚至重写。
systemd journal 用二进制格式保存所有日志信息,用户使用 journalctl 命令来查看日志信息。无需自己编写复杂脆弱的字符串分析处理程序。
systemd journal 的优点如下:
简单性:代码少,依赖少,抽象开销最小。
零维护:日志是除错和监控系统的核心功能,因此它自己不能再产生问题。举例说,自动管理磁盘空间,避免由于日志的不断产生而将磁盘空间耗尽。
移植性:日志文件应该在所有类型的 Linux 系统上可用,无论它使用的何种 CPU 或者字节序。
性能:添加和浏览日志非常快。
最小资源占用:日志数据文件需要较小。
统一化:各种不同的日志存储技术应该统一起来,将所有的可记录事件保存在同一个数据存储中。所以日志内容的全局上下文都会被保存并且可供日后查询。例如一条固件记录后通常会跟随一条内核记录,最终还会有一条用户态记录。重要的是当保存到硬盘上时这三者之间的关系不会丢失。syslog 将不同的信息保存到不同的文件中,分析的时候很难确定哪些条目是相关的。
扩展性:日志的适用范围很广,从嵌入式设备到超级计算机集群都可以满足需求。
安全性:日志文件是可以验证的,让无法检测的修改不再可能。
第3章 init进程机制与systemD机制的比较
3.1 出现的时间不同
- Centos6以前用的都是init来启动服务;
- Centos7开始默认使用了systemd管理服务;
3.2 串行与并行的方式不同
- init串行启动所有服务;启动时间长。init进程是串行启动,只有前一个进程启动完,才会启动下一个进程。
- systemd并行启动所有服务,充分利用多内核架构。启动快速,启动速度是init进程的10倍以上。
3.3 组织服务的方式不同
- init启动的服务分两类:独立启动模式(stand alone)和超级守护进程(super daemon);
- systemd将所有服务定义为不同类型的服务单位(unit),类型有:service、socket、target、path、snapshot、timer等
3.4 管理线程的技术手段不同
- init下管理程序使用纯脚本/etc/init.d/daemon可自定义参数;启动脚本复杂。init进程只是执行启动脚本,不管其他事情。脚本需要自己处理各种情况,这往往使得脚本变得很长。
- systemd使用systemctl命令来管理,不可自定义参数,有语法限制。
3.5 功能复杂性不同
- init没法自动启动当前服务所依赖的其他服务,必须手动处理,init对进程管理的方式比较单一。
- systemd可以检查并启动当前所启动的服务所依赖的其他服务,systemd对进程管理的方式强大。
备注:
在较新的linux系统上,都使用systemd 取代了init,成为系统的第一个进程(PID 等于 1),其他进程都是它的子进程。systemd为系统启动和管理提供了完整的解决方案
不是通过systemctl启动的服务则没法通过systemd来管理。
3.6 运行级别不同
- init有7个运行级别
- systemd只有1、3、5有相对应的target类型。