深度探索Linux操作系统 —— 构建内核

简介: 深度探索Linux操作系统 —— 构建内核

前言

    内核的构建系统 kbuild 基于GNU Make,是一套非常复杂的系统。

   对于编译内核而言,一条 make 命令就足够了。因此,构建内核最困难的地方不是编译,而是编译前的配置。配置内核时,通常我们都能找到一些参考。比如,对于桌面系统,可以参考主流发行版的内核配置。但是,这些发行版为了能够在更多的机器上运行,几乎选择了全部的配置选项,编译了全部的驱动,不仅增加了内核的体积,还降低了内核的运行速度。再比如,对于嵌入式系统,BSP(Board Support Package)中通常也提供内核,但他们通常也仅是个可以工作的内核而已。显然,如果要一个占用空间更小、运行更快的内核,就需要开发人员手动配置内核。而且,也确实存在着在某些情况下,我们找不到任何合适的参考,这时我们只能以手动方式从零开始配置。

一、内核映像的组成

1、一级推进系统——setup.bin

    在进行内核初始化时,需要一些信息,如显示信息、内存信息等。曾经,这些信息由工作在实模式下的 setup.bin 通过 BIOS 获取,保存在内核中的变量 boot_params 中,变量 boot_params 是结构体 boot_params 的一个实例。

2、二级推进系统——内核非压缩部分

   内核的保护模式部分是经过压缩的,因此运行前需要解压缩,但是谁来负责内核映像的解压呢?解铃还须系铃人,既然内核在构建时自己压缩了自己,当然解压缩也要由内核映像自己完成。


   内核在压缩的映像外包围了一部分非压缩的代码,Bootloader 在加载内核映像后跳转至外围的这段非压缩部分。这些没有经过解压缩的指令可以直接送给 CPU 执行,由这段 CPU 可执行的指令负责解压内核的压缩部分。


   除了解压以外,非压缩部分还负责内核重定位。内核可以配置为可重定位的(relocatable),所谓可重定位即内核可以被 Bootloader 加载到内存任何位置。但是在链接内核时,链接器需要假定一个加载地址,然后以这个假定地址为参考,为各个符号分配运行时地址。显

然,如果加载地址和链接时假定的地址不同,那么需要对符号的地址进行重新修订,这就是内核重定位。


   内核非压缩部分工作在保护模式下,其占用的内存在完成使命后将会被释放。

3、映像的格式

   在 Linux 作为操作系统的 hosted environment 环境下,二进制文件使用 ELF 格式,操作系统也提供 ELF 文件的加载器。但是,操作系统本身确是工作在 freestanding environment 环境下。操作系统显然不能强制要求 Bootloader 也提供 ELF 加载器。而且,操作系统映像也没有必要使用 ELF 格式来组织,将代码和数据顺次存放即可,即所谓的裸二进制格式。所以,内核映像都采用裸二进制格式进行组织。


   但是,从 Linux 2.6.26 版本开始,内核的压缩部分,即有效载荷部分,采用了 ELF 格式。这样做可以支持 “the Xen domain builder” 的 Bootloader。


   我们知道,在解压内核映像后,将会跳转到解压映像的开头执行。但是,ELF 文件的开头并不是代码段的开始,而是 ELF 文件头,也就是说,并不是 CPU 可执行的机器指令。显然,当内核映像不是裸二进制格式时,我们需要有一个 ELF 加载器来将 ELF 格式的内核映像转化为裸二进制格式。那么谁来充当这个 ELF 加载器呢?


   正所谓“螳螂捕蝉,黄雀在后”。内核的非压缩部分调用函数 decompress 解压内核后,紧接着就调用了函数 parse_elf 来处理ELF格式的内核映像。


   事实上,如果 Bootloader 不是所谓的 “the Xen domain builder” ,我们完全没有必要保留内核的压缩部分为 ELF 格式,并略去启动时进行的 “parse_elf” 。

二、内核映像的构建过程

    在编译内核时,通常我们只需要执行 “make bzImage” ,或者 make 后面不接任何目标。在没有接目标时,构建的内核映像也是 bzImage。

# linux-3.7.4/arch/x86/boot/Makefile:

$(obj)/bzImage: $(obj)/setup.bin $(obj)/vmlinux.bin \
  $(obj)/tools/build FORCE

    根据构建规则可见,bzImage 依赖于 setup.binvmlinux.bin,所以在构建 bzImage 前,make 将自动先去构建它们,以此类推,vmlinux 的构建也是同样的道理。因此,组成内核映像的各个部分的构建顺序如下:

  1. 构建有效载荷 vmlinux,并将其压缩为 vmlinux.bin.gz;
  2. 构建二级推进系统,并将二级推进系统装配到有效载荷上,组成 vmlinux.bin;
  3. 构建一级推进系统,即构建 setup.bin;
  4. 将 setup.bin 和 vmlinux.bin 组合为 bzImage。

三、配置内核

    内核提供了 make menuconfig、make xconfig、make gconfig 等具有图形界面的配置方式。make menuconfig 是图形界面配置方式中最简陋的一种,但是却非常方便易用,依赖也最小。其他如 make xconfig、make gconfig 需要 QT、GTK+ 等库的支持。在本书中,我们使用 make menuconfig 配置内核,其简单地基于终端的图形界面是使用 ncurses 编写的,因此需要安装 libncurses5-dev

apt install libncurses5-dev

1、交叉编译内核设置

    在默认情况下,内核构建系统默认内核是本地编译,即编译的内核是运行在与宿主系统相同的体系架构上。如果是为其他的架构编译内核,即交叉编译,我们需要设置两个变量:ARCHCROSS_COMPILE 。其中:

  • ARCH 指明目标体系架构,即编译好的内核运行在什么平台上,如 x86、arm 或 mips 等。
  • CROSS_COMPILE 指定使用的交叉编译器的前缀。对于我们的交叉工具链来说,其前缀是 i686-none-linux-gnu-

在顶层的 Makefile 中,我们可以看到工具链中的编译器、链接器等均以 $(CROSS_COMPILE) 作为前缀:

# linux-3.7.4/Makefile:

AS  = $(CROSS_COMPILE)as
LD  = $(CROSS_COMPILE)ld
CC  = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR  = $(CROSS_COMPILE)ar
NM  = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump

  可以使用多种方式定义这两个变量,比如通过在环境变量中定义 ARCH、CROSS_COMPILE ;或者每次执行 make 时,通过命名行为这两个变量的赋值,如:

make ARCH=i386 CROSS_COMPILE=i686-none-linux-gnu-

    也可以直接更改顶层 Makefile 。这种方法比较方便,但是要小心,以免破坏 Makefile 文件。本书中我们采用这种方式,将顶层 Makefile 中的如下脚本:

# linux-3.7.4/Makefile:

ARCH      ?= $(SUBARCH)
CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)


更改为:

# linux-3.7.4/Makefile:

ARCH      ?= i386
CROSS_COMPILE ?= i686-none-linux-gnu-

2、基本内核配置

   在很多情况下,我们都会有一个目标系统的老版本内核配置文件,而不必每次都从零开始。在此种情况下,首先将已有的内核配置文件复制到顶层目录下,并命名为 .config;然后运行 make oldconfig,其将会询问用户如何处理变动的内核配置;最后用户可以使用 make menuconfig 进行微调。虽然内核提供 make oldconfig 的方法,但是这些方法并不是完美的,读者需要小心处理新内核中新增或改变的配置项。


   但是也有很多情况,已有配置并不理想,我们需要进行更彻底定制,或者我们根本找不到一个合适的已有配置。难道我们就别无选择,只能从零开始了吗?当然不是,内核构建系统已经为开发者考虑了这些。


   一方面内核为很多平台附带了默认配置文件,保存在 arch/<arch>/configs 目录下,其中 <arch> 对应具体的架构,如 x86、arm 或者 mips 等。比如,对于 x86 架构,内核分别提供了 32 位和 64 位的配置文件,即 i386_defconfig 和 x86_64_defconfig;对于 arm 架构,内核提供了如 NVIDA 的 Tegra 平台的默认配置 tegra_defconfig,Samsung 的 S5PV210 平台的默认配置 s5pv210_defconfig 等。


   如果我们打算使用x86的32位的默认配置,执行下面命令即可:

make i386_defconfig

  如果想使用Samsung的S5PV210平台的默认配置,则使用如下命令:

make ARCH=arm s5pv210_defconfig

  如果对这些内核内置的默认配置依然不满意,kbuild 还提供了创建一个最小配置的方法,从某种意义上讲,这是最彻底的定制方式了,命令如下:

make allnoconfig

3、内核启动的第一程序

    如果用户没有通过内核命令行参数 “init” 指定第一个进程运行的用户空间的程序,则内核依次尝试执行目录 /sbin、/etc、/bin下的 init,最后尝试执行目录 /bin 下的 sh 。因此,我们在目录 /bin 下建立一个指向 bash 的符号链接 sh,而且,这个符号链接也是 FHS 标准要求的。

四、构建基本根文件系统

1、根文件系统的基本目录结构

    Linux 的根文件系统的目录结构不是随意定义的,而是依照 Filesystem Hierarchy Standard Group 制定的 Filesystem Hierarchy Standard(FHS) 标准。从服务器、个人计算机到嵌入式系统,虽达不到完全符合,但大体上还是遵循这个标准的。


  根文件系统中主要有四处存放可执行程序的目录:/bin、/sbin、/usr/bin/usr/sbin 。系统管理员和普通用户都使用的重要命令保存在 /bin 目录下,而仅由系统管理员使用的重要命令则保存在 /sbin 目录下。相应的,不是很重要的命令则分别放置在 /usr/bin/usr/sbin 目录下。

    同样的道理,重要的系统库一般存放在 /lib 目录下,其他的库则存放在 /usr/lib 目录下。

2、安装C库

   几乎所有程序都依赖 C 库,它是整个系统的基础,因此,我们首先安装 C 库到根文件系统。在前面讨论编译构建系统的 C 库时,我们看到,C 库包含函数库、各种工具程序,以及开发所需的头文件等。而这里的文件系统只是个临时系统,所以 C 库中的各种实用工具及 $SYSROOT/usr/share 目录下的数据文件,都不需要安装。而且这个临时根文件系统亦不需要支撑开发,所以凡是开发时所需要的文件,包括头文件、静态库、启动文件等,也不需要安装。因此,最终我们只需要安装 $SYSROOT/lib 目录下的动态库及相应的动态链接/加载器需要的符号链接。


   我们新建一个保存目标系统的根文件系统的 rootfs 目录,并且按照 FHS 标准的规定,将 C 库安装在 rootfs/lib 目录下,命令如下:

mkdir rootfs
mkdir rootfs/lib
cp -d sysroot/lib/* rootfs/lib/

除了 Glibc 中包含的 C 库外,在前面编译 GCC 时,我们也看到,GCC 也将部分底层函数封装到库中,有些程序会使用 GCC 的这些库,因此,我们也将这部分程序安装到 rootfs/lib 目录中。同样,我们也只安装动态库及其对应的运行时符号链接,命令如下:

cp -d cross-tool/i686-none-linux-gnu/lib/lib*.so.*[0-9] rootfs/lib/

3、安装shell

    在安装 C 库后,构建基本的应用程序的基础已经具备了,接下来我们需要为内核准备用户空间的程序了。在 Linux 中,专门负责启动的软件包,如 System V initSystemd 等都提供一个二进制程序作为第一个进程执行的用户空间的程序,但是为简单起见,我们使用 bash shell 。安装 bash 的命令如下:

wget https://ftp.gnu.org/gnu/bash/bash-4.2.tar.gz
tar -xf ../source/bash-4.2.tar.gz
./configure --prefix=/usr --bindir=/bin --without-bash-malloc
make
make install DESTDIR=$SYSROOT
# /vita/cross-tool/bin/ldd:
#!/bin/bash
LIBDIR="${SYSROOT}/lib $(SYSROOT}/usr/lib ${CROSS_TOOL}/${TARGET}/lib"
find() {
  for d in $LIBDIR; do
    found=""
    if [ -f "${d}/$1" ]; then
      found="${d}/$1"
      break
    fi
  done
  
  if [ -n "$found" ]; then
    printf "%8s%s => %s\n" "" $1 $found
  else
    printf "%8s%s => (not found)\n" "" $1
  fi
}

readelf -d $1 | grep NEEDED \
  | sed -r -e 's/.*Shared library:[ ]+\[(.*)\]/\1/;' \
  | while read lib; do
    find $lib
  done
目录
相关文章
|
4天前
|
安全 程序员 Linux
探索操作系统的心脏:内核与用户空间的交互之旅
【9月更文挑战第9天】本文是一次深入操作系统核心的探险,我们不仅会穿越神秘的内核世界和多彩的用户空间,还将揭秘它们之间如何通过系统调用、库函数、API等桥梁进行信息交换。这不仅是一段技术之旅,也是对操作系统设计哲学的思考。我们将用浅显的语言,逐步揭开操作系统的神秘面纱,让读者在轻松愉快的阅读中,获得对操作系统深层次理解的钥匙。
|
3天前
|
物联网 调度 云计算
探索操作系统的心脏:内核与用户空间的交互之旅
【9月更文挑战第10天】在数字世界的海洋中,操作系统扮演着船舶的角色,而它的内核则是这艘船的发动机。本文将揭开操作系统内核的神秘面纱,通过浅显的语言和实际代码示例,带领读者理解内核与用户空间之间的交互机制。我们将从基础概念出发,逐步深入到系统调用、进程管理等核心话题,最后探讨内核在现代计算环境中的作用和面临的挑战。无论你是编程新手还是资深开发者,这篇文章都将为你提供新的视角和深刻的洞见。
|
1天前
|
安全
探索操作系统的心脏:内核与用户模式的交互之旅
【9月更文挑战第12天】在数字世界的海洋中,操作系统扮演着灯塔的角色,指引着每一条数据流的方向。本文将深入探讨操作系统的核心机制——内核与用户模式,揭示它们如何协同工作以保障计算机系统的高效与安全。我们将从基础概念出发,逐步深入到实际代码示例,旨在为读者呈现一幅清晰的操作系统工作原理图景。
|
4天前
|
传感器 安全 算法
操作系统的心脏:内核探秘
在数字世界的每一次跳动中,操作系统内核扮演着至关重要的角色。本文将深入浅出地探索内核的奥秘,从它的定义和功能出发,逐步解析其结构和原理,并探讨内核对系统性能的影响。最后,我们将展望未来内核技术的发展方向,以及它如何适应不断变化的技术需求。让我们一起揭开操作系统内核的神秘面纱,体验技术的魅力。
|
2天前
|
存储 算法 安全
操作系统的心脏:内核深入解析
本文将带您走进计算机的大脑—操作系统内核,探索它如何管理硬件资源、提供系统服务,并确保多任务高效运行。文章以浅显易懂的语言,逐步揭示内核的神秘面纱,从基础概念到实际应用,让您对操作系统的核心组件有更深的理解。
16 4
|
2天前
|
存储 安全 Linux
探索Linux操作系统的心脏:内核
在这篇文章中,我们将深入探讨Linux操作系统的核心—内核。通过简单易懂的语言和比喻,我们会发现内核是如何像心脏一样为系统提供动力,处理数据,并保持一切顺畅运行。从文件系统的管理到进程调度,再到设备驱动,我们将一探究竟,看看内核是怎样支撑起整个操作系统的大厦。无论你是计算机新手还是资深用户,这篇文章都将带你领略Linux内核的魅力,让你对这台复杂机器的内部运作有一个清晰的认识。
12 3
|
2天前
|
存储 安全 Linux
操作系统的心脏:内核探秘
在数字世界的庞大机器中,操作系统扮演着至关重要的角色,而其核心—内核则如同这台机器的心脏。本文将深入浅出地剖析操作系统内核的设计哲学、功能组成以及它如何管理硬件资源和提供系统服务。我们将一同探索进程调度、内存管理、文件系统等关键组件,并通过实例了解它们是如何协同工作以确保系统的高效与稳定。无论你是技术新手还是资深开发者,这篇文章都将为你打开一扇了解操作系统深邃世界的大门。
10 3
|
5天前
|
存储 安全 物联网
探索操作系统的心脏:内核设计与实现
在数字世界的海洋中,操作系统是支撑软件与硬件沟通的桥梁。本文将深入浅出地介绍操作系统的核心——内核的设计原理与实现方法。我们将从内核的定义出发,逐步展开其功能、架构设计以及在实际操作系统中的应用。通过对比不同操作系统内核的特点,揭示内核设计的哲学和挑战。文章旨在为读者提供清晰的内核概念框架,并激发对操作系统深层次理解的兴趣。
|
2天前
|
存储 数据挖掘 Linux
服务器数据恢复—Linux操作系统网站服务器数据恢复案例
服务器数据恢复环境: 一台linux操作系统服务器上跑了几十个网站,服务器上只有一块SATA硬盘。 服务器故障: 服务器突然宕机,尝试再次启动失败。将硬盘拆下检测,发现存在坏扇区
|
3天前
|
存储 资源调度 监控
操作系统的心脏:内核深度解析
在数字世界的庞大机器中,操作系统扮演着至关重要的角色。而作为操作系统核心的内核,其重要性不言而喻。本文将深入浅出地探讨操作系统内核的基本概念、主要功能和工作原理,以及它如何影响计算机的整体性能和稳定性。我们将从内核的设计哲学出发,逐步深入到内核的各个组成部分,包括进程管理、内存管理、文件系统和设备驱动等关键模块。最后,文章还将讨论当前操作系统内核面临的挑战和未来的发展趋势。通过这篇文章,读者将获得对操作系统内核更深层次的理解,从而更好地把握计算机系统的运行机制。
6 1