深度探索Linux操作系统 —— 构建根文件系统1

简介: 深度探索Linux操作系统 —— 构建根文件系统

前言

   在第 3 章中,我们通过手工的方式展示了从零构建根文件系统的过程。在本章中,我们将构建一个相对完善的根文件系统,但是我们不再从零开始,毕竟一旦熟悉了原理后,余下的就是简单的重复了。第 2 章编译工具链时曾通过参数 “–with-sysroot” 指定了目标系统的文件安装的目录,后续所有的为目标系统编译的文件全部安装到了这个目录下。因此,在本章中,我们就基于这个目录下的文件构建运行在真实系统上的根文件系统。


   为了更高效地开发调试,我们首先打通了目标系统的网络,建立了宿主系统与目标系统的桥梁,包括配置内核支持网络协议以及网卡驱动,并安装了用户空间的网络配置工具。如此,我们就可以远程登录到目标系统上进行调试,并且可以动态更新文件(除内核和 initramfs 外)而不必再每次都重启系统。


   几乎所有的现代操作系统都提供图形用户界面,Linux 也不例外。麻省理工的开发者们为 UNIX 系统开发了 X 窗口系统(X Window System,简称 X 或者 X11)作为图形环境。除了 X 外,另外一个需要关注的图形环境是 Wayland 。虽然 Wayland 的目标是替代 X ,并且开源社区也支持 Wayland 向着这个方向发展,但是 Wayland 距广泛使用还有一段路要走。因此,在本章中,我们依然以目前广泛使用的 X 构建基础的图形环境,并安装了 GTK 作为更上层的图形库。事实上,Wayland 更像是 X 的一次整合或者重构,在第 8 章探讨 Linux 的图形原理时,我们会拿出一点篇幅讨论 Wayland ,在那里我们会看到,Wayland 和 X 之间并无本质区别。


一、初始根文件系统

   因为我们使用的是 vita 用户进行编译过程,所以 $SYSROOT 目录下的所有文件的属主和属组都是 vita ,如果对安全问题有顾虑,在最终将其作为根文件系统时,可以将该目录下的所有文件,包括目录的属主和属组,更改为 root 。


   另外,为简单起见,我们也没有考虑文件系统的大小。如果是为一个真实的系统制作根文件系统,那么可以考虑进行一些优化,比如对二进制文件和动态库使用命令 strip 删除一些运行时不需要的信息和符号表等;删除那些只是在编译时使用的头文件和静态库;等等。


   上面讨论的都不是必须的,如果仅作为一个用于测试的系统,完全可以不必理会,下面是必须要做的几件事。


(1)安装GCC库

   在前面编译 GCC 时,我们已经看到,GCC 也将部分底层函数封装到库中,很多程序会使用 GCC 的这些库,因此,我们也将这部分程序安装到根文件系统中。我们只安装运行时使用的动态库及对应的运行时符号链接,当然,系统中并不一定会用到全部这些库,但是简单起见,这里全部安装了:

26eb7faf48314297995a31ed1ce04a47.png



(2)建立相关目录

   在前面讨论从 initramfs 切换到根文件系统时,我们看到,切换程序将最初挂载到文件系统 rootfs 中的 /dev、/run、/proc 和 /sys 目录移动到真正的根文件系统,因此,我们需要在根文件系统上建立这几个目录。另外我们也为 root 用户建立一个属主 root 目录:


ef3a02743e22c2fc49c7e340ef90f4cb.png


(3)构建程序 /sbin/init

   在内核初始化的最后,启动的第一个进程要装载用户空间的程序从而切入用户空间,通常这个程序是 /sbin 目录下的 init,因此我们要准备这个程序。为简单起见,我们也使用 shell 脚本编写:

8a4681a4d1e768134ef93b73764489bd.png


   init 启动了一个交互式的 shell 。其中传递的参数 “-l” 是告诉 bash 以登录方式启动,这样可以使 bash 读取在 /etc/profile、~/.profile 等文件中定义的环境变量。同时要确保 init 程序具有可执行权限:

334c05a288451c907c0df77bafe07046.png


   为了让 shell 提示符看上去友好一些,更重要的是为了后面当从宿主系统远程登录到 vita 系统时,方便区分本地终端和登录到 vita 的终端,我们在全局范围的 profile 文件中定义了环境变量 PS1 来控制 shell 提示符的显示内容和风格:

e1831c55321e0511216a0ed734d847bf.png



   其中,“\u” 告诉 shell 显示当前用户名;“\w” 告诉shell显示完整的工作路径;我们将主机名直接硬编码为 vita,为了便于区分是本地的终端还是登入 vita 的终端;接下来我们给提示符加一点漂亮的颜色,“\e[” 与 “m” 之间的内容表示颜色值,在它们之外包围的 “[” 与 “]” 是保证其内的非打印字符,不占用任何空间。颜色设置的格式为 “[\e[F;B;Cm]” ,其中 F是前景色,B 是背景色,C 是一些表示特殊效果的代码,如下划线、闪烁等。


   具体到我们这个例子,其中 31 表示红色,因此,“用户名@vita” 将以红色显示;35 表示洋红,因此工作路径将以洋红色显示。最后,在提示符结束的位置,我们通过 “\e[0m” 将颜色值设定为零,也就是通知终端将前景、背景重置为它们的默认值,以使后续的文字以非彩色显示。


   接下来将 $SYSROOT 目录整个复制到虚拟机,因为命令 scp 会跟随符号链接,所以我们采用先压缩、再复制的办法。


二、以读写模式重新挂载文件系统

   一般在挂载文件系统之前,将使用工具 fsck 检查文件系统的一致性。如果文件系统中存在错误,则 fsck 会试图修复它们,但是这个过程要求文件系统没有挂载或以只读方式挂载。因此我们在 GRUB 的配置文件 grub.cfg 中经常看到内核的命令行参数中有这么一个字串 “ro” ,其是 “read only” 的简写,目的是告诉内核或 initramfs 最初以只读方式挂载根文件系统。在 Linux 系统进入用户空间、使用工具 fsck 检查文件系统后,然后再以读写方式重新挂载根文件系统。这里我们忽略文件系统检查这一过程,直接以读写模式重新挂载根文件系统。


dc522f73c9388853ab1206e05ad29434.png


   如果读者没有更改根文件系统中文件的属主和属组为 root ,那么更新 vita 系统的 /sbin/init 程序后,重启系统,我们来检查一下根文件系统是否以读写方式成功挂载了。


三、配置内核支持网络

   为了方便宿主系统与目标系统传输文件,并且可以从宿主系统登录到目标系统,目标系统需要支持网络。为此,需要配置目标系统的内核支持 TCP/IP 协议和网卡驱动。


四、启动udev

   前面我们将网卡驱动编译为模块,为了自动加载网卡驱动,需要启动 udev,为此需要修改 init 脚本:

51fb22a5fe9667a5a61c4e4835af8765.png


   这几条命令我们在讨论 initramfs 时已经见过了。事实上,这里启动 udev 服务,不仅是为了加载网卡驱动模块。initramfs 中往往只包含存储介质相关的驱动,而其他大量设备的驱动,大部分还是保存在根文件系统中,所以,在挂载了根文件系统后,需要重新模拟一遍热插拔,从根文件系统中加载相关设备的驱动模块。


五、安装网络配置工具并配置网络

   在用户空间中,我们使用工具 ip 来配置网络,工具 ip 包含在软件包 iproute2 中。所以我们首先来编译安装软件包 iproute2 。

79a5cf4f76ed4bddd0a588a75bf2f07c.png



iproute2编译安装


六、安装并配置ssh服务

   既然网络已经配置好了,一般情况下就不必再通过第三方系统(即虚拟机)更新 vita 系统了,可以直接通过网络和 vita 系统打交道了。当然如果是更新内核、initramfs 或者整个文件系统,还是要通过虚拟机系统的。我们在宿主系统和 vita 系统之间使用 ssh 服务进行通信。因此在这一节,我们为 vita 系统安装并配置 ssh 服务。


   我们使用 ssh 协议的开源实现 openssh ,其依赖 zlib 和 openssl ,因此首先编译安装这两个软件包。


   使用如下命令编译安装 zlib:


4c73cd769367aa116393719bfd750b53.png


使用如下命令编译安装 openssl:


0b7ebce01f99cd7d1dcc6a1d86cc41ed.png


七、安装 procps

   为了方便后面调试,我们在 vita 系统上安装 procps 。该软件包中包含了常用的一些工具,如 ps、kill 等。因为 vita 系统中没有安装 ncurses 库,为简单起见,我们只编译不需要使用 ncurses 库绘制界面的程序,这就是编译 procps 时传递参数 “–without-ncurses” 的目的。

eaeee1fc8415c51647ad2d1665a3ecfe.png



八、安装X窗口系统

   UNIX 系统的主要目标就是多用户、多任务,而且允许多个用户远程登录并发执行任务。这种设计哲学同样被带到了 X 窗口系统中。X 的实现者将 X 设计为客户/服务器的架构,应用程序相当于客户端,它们不需要关心具体的显示和用户输入,而由 X 服务器负责管理显示设备和输入设备。应用程序只需要将请求,比如 “绘制一条直线从点A到点B” ,发送给 X 服务器,而由 X 服务器负责将其绘制到具体的显示设备上。X 服务器也会将用户的输入(包括鼠标、键盘等输入事件),转发给对应的应用。


   X 将协议相关实现封装到了一个库中,开发者将这个库称为 Xlib 。后来因为效率问题,又开发了 xcb 来替代 Xlib 。Xlib 中封装的只是 X 的核心协议,X 使用扩展的方式扩充 X 协议,其他扩展协议可以在单独的库中实现。


   作为类 UNIX 的图形系统的基础,X 的复杂是难以避免的。也恰恰是因为 X 的复杂,很多人提及 X 的安装就会谈虎色变。虽然 X 系统非常庞大,实际上它也是有章可循的。本节笔者就带领读者从头安装一个 X 窗口系统。鉴于 X 的安装过程比较烦琐和复杂,我们提供了一个安装脚本 build-X11.sh 。但是笔者建议读者尽量使用手动的方式安装,这样可以在思考和解决问题中不断提高。遇到自己实在解决不了的问题时再参考这个脚本,从而达到更好的学习效果。


1、安装 M4 宏定义

   X 定义了一些公用的 M4 宏,并将它们放在软件包 util-macros 中。X 的各个组件的配置脚本中将使用 M4 宏,因此我们首先来安装 M4 宏,方法如下:


bbd225e05e6a9808f98fae99af8ac188.png


2、安装X协议和扩展

   X 包含了多种协议和扩展,为简单起见,Vita 系统不必全部安装。比如禁掉了记录事件的扩展 Record,支持扩展屏幕的协议 Xinerama 及用于屏保的 Screensaver,禁掉了已经过时的 DRI1 等。下面是 vita 系统安装的协议,安装这些协议时没有先后顺序要求。如果不要求 X 服务器支持 DRI2,那么可以安装更少的协议,比如去掉 glproto、dri2proto、damageproto等。


(1)核心协议

   Xlib 中的绝大部分编程接口,如 XCreateWindow 、 XMapWindow 、XDrawRectange、XCopyArea 等都是由 X 核心协议定义的。核心协议的定义在软件包 xproto 中。


(2)基本扩展

   X 的基本扩展包括:DOUBLE-BUFFER(DBE)、DPMS、Extended-Visual-Information ( EVI ) 、 Generic Event Extension 、 LBX 、 MIT-SHM 、 MIT-SUNDRY-NONSTANDARD、Multi-Buffering、SECURITY、SHAPE、SYNC、TOG-CUP、XC-APPGROUP、XTEST。它们的定义在软件包 xextproto 中。


(3)键盘扩展

   键盘扩展定义了键盘的模型、布局,如对于不同的键盘模型,某个键值对应的字符。键盘扩展的定义在软件包 kbproto 中。


(4)输入扩展

   输入扩展是为一些特殊的输入设备定义的协议。通过这个扩展,输入设备可以模拟出与鼠标、键盘等核心输入设备相同格式的事件。输入扩展的定义在软件包 inputproto 中。


(5)XCB 协议

   鉴于 Xlib 的效率,开发者们开发了更高效的 XCB 来替代 Xlib。XCB 协议是用于这个库的协议,其以 XML 形式定义,并提供 python 程序将这些 XML 描述文件转换为相应的程序代码。XCB 协议的定义在软件包 xcb-proto 中。


(6)GLX 扩展

   GLX 扩展定义了 OpenGL 和 X 之间通信的协议。该扩展的定义在软件包 glxproto 中。


(7)DRI2 扩展

   DRI2 扩展是 DRI 的第 2 个版本,定义了应用不通过 X 服务器直接使用硬件进行渲染的协议。DRI2 扩展的定义在软件包 dri2proto 中。


(8)XFixes 扩展

   从这个扩展的名字也可以看出,这个扩展其实是为解决 X 核心协议存在的各种限制的。该扩展的定义在软件包 fixesproto 中。


(9)Damage 扩展

   Damage 扩展是 X 服务器用来记录那些离屏的、发生了变化的绘制区域的协议。Damage 扩展的定义在软件包 damageproto 中。


(10)XC-MISC 扩展

   应用可以通过 XC-MISC 扩展获取 X 服务器可用的资源 ID,如 GetXIDRange、GetXIDList 等。该扩展的定义在软件包 xcmiscproto 中。


(11)BIG-REQUESTS 扩展

   BIG-REQUESTS 扩展提供了对大于 262140 字节的请求的支持。该扩展的定义在软件包 bigreqsproto 中。


(12)RANDR 扩展

   RANDR 扩展定义了动态调整屏幕尺寸、旋转屏幕以及镜像屏幕的协议。X 提供的工具 xrandr 就是这个协议的一个典型使用者。该扩展的定义在软件包 randrproto 中。


(13)RENDER 扩展

   RENDER 扩展是 X 使用的较新的渲染模型,用于合成多个绘制区域,相对于原始的通过复制进行合成的模型其更有效率。该扩展的定义在软件包 renderproto 中。


(14)字体扩展

   字体扩展定义了 X 中与字体处理相关的协议。字体扩展的定义在软件包 fontsproto 中。


(15)视频扩展

   视频扩展定义了 X 的视频输出相关的协议。该扩展的定义在软件包 videoproto 中。


(16)复合扩展

   复合扩展是为了 X 支持窗口特效设计的扩展。在没有这个扩展之前,所有的在窗口上的绘制操作都 “实时” 显示在屏幕上。而复合扩展允许窗口可以先在离屏的区域进行绘制。复合扩展的定义在软件包 compositeproto 中。


(17)资源扩展

   资源扩展定义了应用程序查询 X 服务器各种资源使用情况的协议。该扩展的定义在软件包 resourceproto 中。


(18)直接图形访问扩展

   顾名思义,直接图形访问扩展也是为了直接访问图形硬件设计的协议,不过其功能非常有限,目前基本已经停止开发,但 vesa 驱动还在使用这个扩展。该扩展的定义在软件 xf86dgaproto 中。这些协议的配置安装都非常简单,而且安装命令完全相同。以 xproto 为例,安装命令如下:


1e8d874b5d6ba8177ccbffbc74787661.png


3、安装 X 相关库和工具

   在安装 X 服务器前,我们需要安装 X 服务器依赖的库、这些库依赖的库以及 X 服务器使用的工具和相关数据。注意,某些库是有安装顺序要求的,比如,libX11 需要在 libxkbfile 前安装,安装 libXfont 前需要先安装 freetype,libdrm、expat 需要在 Mesa 前安装等。读者按照下面的顺序安装即可。


(1)pixman

   pixman 是一个底层的像素操作的库,提供图形合成及光栅化等功能,是 X 中软件渲染的基础。


(2)xtrans

   xtrans 封装了网络传输的基本功能,从开发角度讲,是 X 服务器和应用程序之间进行通信的基础。X 服务器、libX11 等 X 的相关组件都要用到这个库。


(3)libXau

   libXau 是 X 服务器和应用程序之间认证授权使用的库。


(4)libX11、libxcb 和 libpthread-stubs

   libX11 是为应用程序提供的 X 协议的实现,应用程序使用 libX11 中提供的 API 和 X 服务器进行通信。

   因为 libX11 的效率问题,开发人员又开发了 libxcb 来替换 libX11 。而反过来,libX11 也基于 xcb 进行了改进,所以在安装 libX11 前,需要安装 libxcb。

   libxcb 依赖 libpthread-stubs , 因此在安装 libxcb 前需要先安装 libpthread-stubs。


(5)libxkbfile、xkbcomp 和 xkeyboard-config

   这三个包都与键盘扩展相关。X 服务器根据键盘扩展,确定不同键盘模型的键盘的布局、键值到字符的转换等。键盘相关的数据就包含在 xkeyboard-config 中。

   而开发者将操作这些数据的功能封装在库 libxkbfile 中。

   xkbcomp 包中提供了同名的工具 xkbcomp,该工具根据键盘映射的描述,将键盘映射编译为 X 服务器可以识别的指定格式。


(6)libXfont、libfontenc 和 freetype

   这几个库都是与字体处理相关的。开发者将X使用的与字体相关的功能封装在库 libXfont 中。

   而 libXfont 使用 freetype 进行字体渲染,使用 libfontenc 处理字体编码。所以安装 libXfont 前需要安装 libfontenc 和 freetype 。


(7)pciaccess

   早期版本的GPU的2D驱动,包括X服务器中的一些功能,不通过内核,而是直接访问PCI接口的GPU,这就是这个库的由来。现在虽然GPU驱动都通过内核访问GPU硬件了,但是X服务器中并没有清理得特别干净,还残存着对pciaccess库的依赖。

   库libdrm中也使用了部分pciaccess中的功能。比如通过读取PCI寄存器探测BIOS中给GPU分配的显存大小,libdrm借助的就是库pciaccess中的函数。


(8)libdrm

   用户空间的组件,如GPU的2D驱动和3D驱动、GLX扩展(包括X服务器端和Mesa端的实现部分)等,都需要通过内核的DRM模块访问GPU。为了方便用户空间的组件访问内核DRM模块,开发者开发了库libdrm。


(9)Mesa、expat、libXext、libXdamage和libXfixes

   如果配置X服务器支持DRI2,那么必须要安装Mesa,它是3D应用程序进行直接渲染的基础。

   Mesa中的DRI扩展使用Damage扩展告知X服务器绘制完成,因此需要安装libXdamage。

   Mesa中的DRI2扩展使用XFixes扩展中的如XFixesCreateRegion创建发生了改变的区域,也就是绘制发生的区域,因此也需要安装库libXfixes。

   而在安装扩展前,需要安装库libXext。它是所有扩展的公共库。


   另外,Mesa使用expat解析XML,所以安装Mesa前,还需要安装expat。


   在安装上述相关库之前,在宿主系统上还需安装几个辅助的软件包。一个是 xkeyboard-config 依赖的 intltool 。另外是 Mesa 依赖的 xutils-dev、flex 和 bison,使用如下命令在宿主系统上安装这几个软件包:

482882b4b42abe070135688036e13db5.png



4、安装X服务器

   万事俱备,现在我们开始安装X服务器,配置及安装命令如下:


fde28d2ce1cb75f200c30196c1f51eb0.png


5、安装GPU的2D驱动

如果只是在虚拟机上运行目标系统,安装vesa驱动即可,安装命令如下:

9383303702daabbb839bf36d51db74d2.png



   但是如果是在真实的机器上运行目标系统,最好安装相应GPU的2D驱动。在安装脚本 build-X11.sh 中,包含了安装Intel GPU的2D驱动的方法,读者如果需要,可以参考。PC上使用的GPU一般都符合VESA标准,所以在通常情况下,用vesa也能勉强驱动,但是vesa驱动很多特性不支持,比如硬件加速。

深度探索Linux操作系统 —— 构建根文件系统2:https://developer.aliyun.com/article/1598091

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
目录
相关文章
|
6天前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
43 8
|
8天前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
28 9
|
11天前
|
存储 Linux 文件存储
Linux文件系统
Linux文件系统 一切皆文件 在Linux中,“一切皆文件”的概念意味着系统中的所有资源,包括硬件设备、目录及进程等,均被视为文件。这种设计简化了操作和管理,具体包括: 普通文件:存储数据的常规文件。 目录文件:包含其他文件和子目录的文件。 进程文件:在/proc目录下代表系统中运行的进程。 设备文件:位于/dev目录,代表硬件设备。 网络字节流套接字文件:用于网络通信的数据流。 链接文件:指向另一个文件的符号链接或硬链接。 管道文件:用于进程间通信的文件。
43 7
|
7天前
|
缓存 运维 网络协议
深入Linux内核架构:操作系统的核心奥秘
深入Linux内核架构:操作系统的核心奥秘
24 2
|
9天前
|
存储 安全 大数据
深入浅出操作系统:文件系统的秘密
【10月更文挑战第35天】本文将揭示文件系统背后的奥秘,从其基本概念到复杂的实现机制。我们将一起探索文件系统的结构和原理,并了解它如何影响我们的日常计算体验。通过简单的例子和比喻,文章旨在使读者对文件系统有一个清晰而深刻的理解,就像甘地所言:“你必须成为你希望在世界上看到的改变。”让我们一起成为理解操作系统的先行者。
|
11天前
|
缓存 网络协议 Linux
Linux操作系统内核
Linux操作系统内核 1、进程管理: 进程调度 进程创建与销毁 进程间通信 2、内存管理: 内存分配与回收 虚拟内存管理 缓存管理 3、驱动管理: 设备驱动程序接口 硬件抽象层 中断处理 4、文件和网络管理: 文件系统管理 网络协议栈 网络安全及防火墙管理
34 4
|
10天前
|
安全 网络协议 Linux
Linux操作系统的内核升级与优化策略####
【10月更文挑战第29天】 本文深入探讨了Linux操作系统内核升级的重要性,并详细阐述了一系列优化策略,旨在帮助系统管理员和高级用户提升系统的稳定性、安全性和性能。通过实际案例分析,我们展示了如何安全有效地进行内核升级,以及如何利用调优技术充分发挥Linux系统的潜力。 ####
30 1
|
14天前
|
安全 Linux 数据安全/隐私保护
Vanilla OS:下一代安全 Linux 发行版
【10月更文挑战第30天】
37 0
Vanilla OS:下一代安全 Linux 发行版
|
17天前
|
人工智能 安全 Linux
|
1月前
|
Unix 物联网 大数据
操作系统的演化与比较:从Unix到Linux
本文将探讨操作系统的历史发展,重点关注Unix和Linux两个主要的操作系统分支。通过分析它们的起源、设计哲学、技术特点以及在现代计算中的影响,我们可以更好地理解操作系统在计算机科学中的核心地位及其未来发展趋势。