Linux并非操作系统,而是一个内核,我记不清在网上看过这句话多少次,但那些总复述它的人却鲜少愿意详加解释,这篇文章将沿着进程树回溯至首个进程,精确理清这条分界线。
问题的答案竟藏在用户进程中,这点或许会让很多人感到意外,首要任务是理解进程的创建机制。Windows系统中的进程可通过系统调用请求创建新进程,系统会直接执行。而Linux类UNIX系统要求进程先申请自我克隆, 克隆产生的进程再请求替换自身程序,两种系统管理进程创建的方式存在哲学层面的差异,但核心理念始终一致。用户进程皆由其它用户进程创建。 请思考一下,如果所有用户进程都由其它用户进程创建,那就意味着每个进程都有创建它的父进程,但由于这些父进程本身也是用户进程,它们必然也被其它用户进程创建,以此类推。这意味着,如果我们向上 追溯,最终会发现一个作为所有用户进程始祖的单一进程。在LINUX系统中,你实际上可以通过运行pstree命令来可视化这颗树状结构,正如下图所示:

树状结构根部确实存在一个其它所有进程的始祖进程 ,但这个根进程引出了一个疑问,它是由谁创建的? 它不可能是由其它进程创建的,否则就不会是树状结构的根。
答案就在这里,因为在某种意义上,该进程之前发生的所有事情,都被视为内核的一部分, 既然如此,我们就来定义何为内核。 计算机系统的一端是硬件设备,另一端则是以运行多个应用软件为目的的用户。处理硬件实际上非常繁琐且存在风险,为防止应用程序因漏洞或劣质代码导致故障,在用户应用与硬件之间存在非常复杂的中间层, 这一层最底部的组件,也就是直接与硬件交互的部分,我们称之为内核,这种设计的核心思想是,每当用户程序需要操作硬件时,内核都代表它来执行硬件交互。用户进程仍试图自行操作,因此需要额外的保护机制 为此,CPU被设计为两种运行模式,受限模式与特权模式,当CPU处于特权模式时,可执行指令集中的所有指令,包括配置硬件,通过页表管理内存,控制中断,以及与GPU,网卡,存储设备等输入输出外设通信,在受限模式下,CPU仅能执行安全指令子集,无法直接访问硬件及关键系统资源,该模式下处理器可执行计算,比较,循环等常规程序逻辑。但无法运行特权指令,由于特权模式完全掌控处理器和系统资源 ,运行于此模式的软件决定着受限模式代码的权限范围。所有用户程序都在受限模式下运行,而内核则处于特权模式。因此我们对应使用两个术语,内核空间和用户空间。
当用户程序需要访问硬件,例如屏幕显示文字,或通过网络发送消息时,只能通过系统调用请求内核代其执行,现代系统正是通过这种方式确保普通用户程序无法直接访问硬件,既保障正常运行,又维护系统安全 ,不过该隔离层并非单一组件,内核整体运行在特权模式下,但其内部存在一系列子系统,各自管理特定资源,或提供特定服务,没有内核,用户应用程序就无法存在,因为它们需要内核来完成受限模式下无法 执行的操作,系统调用接口是内核最接近用户程序的部分,因此,当我们启动计算机时,在从用户角度执行任何有效操作前,必须通过引导启动过程将内核可执行代码加载到内存中,当所有关键组件加载就绪后 内核会创建唯一一个用户进程,这就是INIT进程,在UNIX,LINUX系统中,PID为1的 INIT进程是唯一不由其它用户进程创建的用户进程,在内核代码深处,存在一段硬编码例程,他会按优先级顺序在文件系统中 寻找名为INIT的可执行文件,查看LINUX内核源码中的main.c文件,找到名为kernel_init的函数,会初始化大量资源,最终在启动过程中,会尝试从指定路径加载INIT 程序,该函数会调用run_init_process函数,而后者 内部会调用execlp,注意,这并非用户程序使用的传统excep系统调用,由于这段代码本身就在内核模式下执行,无需系统调用,故使用名为kernel_excep的变体,直接执行新进程加载例程。 还需注意,如果所有路径都找不到INIT文件,内核将在启动过程中触发PANIC,此刻你可能疑惑,这些内容对解决本篇开头提出的问题有何帮助,因为内核开发者对用户空间软件的关注仅止于此。开发者直接将启动 首个用户空间的例程硬编码在内核中,但对该进程后续的具体行为毫不关心,实际上,LINUX内核源码中根本找不到这个INIT程序,它不属于内核组成部分.
shell,显示服务器,会话管理器,网络管理器,软件包管理器,用于远程访问的SSH服务管理器,如果系统支持图形界面,那么桌面环境及其附带组件,终端模拟器,文件浏览器,任务管理器,设置管理器也在此列。这些都我们通常认为是操作系统组成部分的东西。然而,它们都不属于内核。
所以,操作系统并非全部运行于内核空间,我们通常所称的操作系统,其实大部分是用户空间进程的集合。理解这点最好的例子就是桌面环境,KDE,GNOME,那些停靠栏,任务栏,窗口平铺系统,以及我们联想到桌面体验的抽象概念,它们都运行在用户模式, 桌面环境并非内核的一部分,因此不直接操纵硬件,它通过另一个用户空间进程显示服务器与内核交互。后者利用图新个相关的系统调用将渲染好的缓冲区内容呈现到屏幕。内核并不理解窗口的概念,也不知道按钮,控件和深色模式是什么,屏幕上显示的内容是用户空间软件将像素渲染至缓冲区后,请求内核通知GPU进行显示的结果。对内核而言,这只是内存访问和硬件操作。
因此,更准确的定义是,操作系统是由软件构成的集合。其中被称为内核的组件运行在特权模式,其余部分则运行在受限模式,这正是两者的分界线。这里有个微妙但关键的要点,所有用户进程并非凭空出现,它们必须由另一个进程创建。这时就轮到INIT进程登场了。在引导启动阶段,当内核完成关键子系统初始化并准备切换到用户空间时,会执行前文提到的硬编码例程来定位并启动INIT进程。
这么设计的原因是,仅靠这个单一用户空间INIT进程,便足以繁衍出前面看到的用户空间进程树。从此刻开始,操作系统的其余部分并非直接原自内核,而是由构建应用程序所依赖全套服务基础设施的用户空间代码生成。比如在UBUNTU上运行进程树命令时,你看到的进程树共同塑造了UBUNTU不同于其他发行版(例如fedora)的独特体验。根进程INIT通过启动各类应用程序,最终塑造出你喜爱的LINUX发行版的独特行为与个性。
这也解释了,为何不存在所谓标准版LINUX,LINUX开发者仅提供内核。讽刺的是,如果没有用户空间程序运行其上,这个内核本身完全无法发挥作用。内核需要这些用户空间程序才能真正提供功能。
如果你曾好奇LINUX发行版的诞生过程,这就是答案,它们共享相同内核,但基于该内核构建的服务基础设施会因发行版创建者的设计理念而异。事实上,进程树中显示的根进程也会有不同名称,因为INIT有多种实现方案。从传统的sysvinit,busybox,到更复杂的 systemd,应有尽有。如前所述,内核并不关心这个进程的具体运作方式,正因为如此,不同项目对INIT进程的行为规范和职责划分形成了不同理念。
内核只负责启动首个用户空间进程。该进程的本质及其后续行为完全不属于内核的管辖范围。不过内核确实赋予了这个用户进程若干特殊属性。其一是固定获得进程ID 1,普通程序的进程ID很难保证,但是INIT进程必然获得ID 1。这也使得到它在任务管理器等工具中易于识别。第二条与其说是特权,不如说是责任,INIT进程永远不应终止。原因其实非常简单,内核不喜欢孤儿进程。进程不能用于已经不存在的父进程。INIT必须常驻运行,因为内核需要它来收养孤儿进程。所以尽管内核并不关心INIT进程的具体行为,但确实要求它保持长生不老。即使发送KILL -9信号,也无法终止它。内核坚持该进程永不终止的原则,直接拒绝传递终止信号。


正因为永不终止的特性,许多INIT实现还被设计为故障恢复机制,如果关键服务崩溃,比如图形界面,INIT进程可能会重启它。如果INIT进程本身因漏洞崩溃,甚至是无错退出,内核都会发生PANIC。尽管内核自身并无故障。因此,INIT进程确实拥有某些特权,但是它并不运行在内核空间。
真正的起点是进程0------swapper/idle进程。这是内核在启动时"凭空"创建的,没有父进程,也从来不在用户空间运行。进程1(init)是进程0通过fork()创建的第一个用户态进程。进程1(init)是进程0通过fork()创建的第一个用户态进程。
-
进程0:由内核构造,永远运行在内核空间。
-
进程1:由内核"助产",但之后便运行在用户空间,负责启动所有其他用户进程。
区分内核与操作系统其它部分的分界线相对容易定义,所有以特权模式运行的组件都属于内核,但其它部分呢,如果操作系统所有这些组件都是普通用户空间进程,那我们该如何划分操作系统与用户应用程序的边界。操作系统通常预装软件, 比如,几乎所有现代操作系统都会内置网页浏览器,LINUX世界通常是firefox,这是否意味着浏览器属于操作系统?多数人会反对,因为浏览器并系统运行必须的,但同样的论点也适用于图形界面GUI,文件资源管理器,任务管理器,文本编辑器,系统监视器, 乃至桌面环境本身,毕竟这些程序的功能理论上都可以通过终端和命令行界面实现。这是否意味着它们只是用户应用程序,而非操作系统的一部分?多数人并不这么认为,桌面环境及其应用才是让操作系统独具特色的关键,当然这个观点见仁见智, 但是想想MAC OS,WINDOWS,ANDROID等图形界面操作系统,它们的设计理念就是用户完全不需要借助命令来操作电脑,这类系统既没有SHELL,也没有终端,所有电脑操作都必须通过GUI完成,在这种环境下,文件资源管理器,任务管理器和设置面板等工具, 虽然只是用户空间进程,但却是与系统交互的唯一途径,因此变得必不可少,所以,操作系统与用户应用程序的边界更难划定,在大部分程序员看来,这条界限有些模糊,因为我们要如何定义操作系统的用户空间部分,是所有预装在操作系统中的程序么? 还是没有底层替代方案的程序? 可能最接近的定义是这个,所有INIT进程自动启动的程序,因为理论上它们都属于核心服务,但是这个定义本身也存在问题,它可能不是必要的,某些基础工具,并非由INIT进程启动,因为它们是用户主动调用时才需要的, 比如终端或者BUSYBOX集,这些几乎预装载所有LINUX发行版中的工具集合,提供了你在SHELL中使用的各种小而关键的指令。这些程序大多不是由INIT进程启动的,尽管我们大多数都会承认它们是用户进程,但却很难不把它们看成是操作系统的一部分。试想一下 终端里没有任何指令的情形,那样的LINUX就失去了灵魂。
所以,没有一条单一的、普适的线能完美区分"操作系统"和"应用程序"。在讨论时必须先明确语境,是在讨论内核态与用户态的技术划分,还是在讨论一个发行版/平台的完整交付物。
总结
-
内核只负责"启动第一个用户进程"这个动作,至于这个进程是 systemd、SysV init、还是你自己的一个简单 shell,内核完全不在意。
-
init 进程本身不是内核的一部分,但它的存在是内核"刻意制造的边界"。
-
Unix的设计者(尤其是Ken Thompson和Dennis Ritchie)秉持一个信念:系统调用应该是最小、最原子的操作,复杂的场景由用户空间的组合来实现。
-
哲学边界:内核是所有进程的"最后授权者"和"初始创造者"。第一个用户进程诞生之前的所有代码都属于内核;第一个用户进程诞生之后,内核退居幕后,成为"服务的提供者"而非"执行的参与者"
-
内核是唯一被允许在裸硬件上"信任执行"的软件,其它所有程序都必须通过它来间接接触硬件。内核空间是被"信任"的代码,用户空间是被"隔离"的代码。
Linux是内核,不是操作系统。