学习 Linux 内核的第一步就是下载 Linux 内核代码, 然后进行编译与运转.

下载内核源代码

内核源码除了 Linus 的官方 github.com/torvalds/li… git, 还有各家发布版的 Linux 内核. 最好去发布版的内核去下载, 这样编译进程中不容易犯错.

我的 Linux 机器是 Ubuntu 22.04 TLS, 所以根据官方文档: wiki.ubuntu.com/Kernel/Sour…, 能够 clone git 下载:

$ git clone --depth 1 -b master git://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/jammy

这儿为了减少下载文件的数量和大小, 挑选只下载最新的代码, 只挑选了master分支. 下载到 jammy 文件夹.

编译装备

编译之前先要装置编译内核需求的各种装置包:

$ sudo apt update
$ sudo apt upgrade -y
$ sudo apt install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex bison libelf-dev -y 

装备并编译

$ cd jammy
# 此刻并没有 .config 文件, 履行下面的指令会让你挑选装备, 然后保存.
$ make menuconfig
# 生成 .config 文件
$ ls -lah .config
-rw-rw-r-- 1 sre sre 256K Jun  2 20:58 .config

咱们能够看到 .config 文件里边的各种装备项目, 比方:

CONFIG_VERSION_SIGNATURE="Ubuntu 5.15.0-72.79-generic 5.15.98"
CONFIG_HAVE_KERNEL_GZIP=y
CONFIG_TSL2583=m

有些装备项表明一个纯文本的装备, 比方上面的版本号. 别的一些后面是 =y, =m 或许=n.

  1. =y:当选项被设置为=y时,表明该选项将作为内核的一部分编译进内核映像(静态编译)。这意味着相关的功用将一向可用,并包括在生成的内核映像中。当体系发动时,这些功用将立即可用,无需加载额定的模块。挑选=y是在构建内核时将特定功用编译到内核中的一种方式。
  2. =m:当选项被设置为=m时,表明该选项将作为可加载模块编译(动态编译)。这意味着相关的功用将编译为独立的模块文件(一般是以.ko为扩展名),并在需求时由内核加载。运用=m选项能够将特定功用作为模块构建,以便在运转时根据需求加载和卸载。

挑选=y=m取决于您对体系需求的权衡。假如您确认某个功用一向需求在内核运转时可用,而且不期望依靠额定的模块加载进程,则挑选=y。假如您期望能够根据需求加载和卸载某个功用,而且不会一向运用该功用,则挑选=m。

请注意,对于某些选项,或许还有其他设置,例如=n,表明将完全排除该功用的编译。这意味着相关的功用将在内核映像和模块中都不可用。挑选特定的设置取决于您的需求和体系装备。

文字界面装备如下, 最终挑选 Save 到 .config 文件.

Linux 内核的编译, 启动和制作ISO镜像

编译:

$ make all -j 4 # 运用 4个线程编译, 或许要等很久, 最终生成内核文件 arch/x86/boot/bzImage
...
$ ls -lah arch/x86/boot/bzImage
-rw-r--r-- 1 root root 11M Jun  5 08:59 arch/x86/boot/bzImage
Kernel: arch/x86/boot/bzImage is ready  (#1)
$ make help # 检查更多指令

假如遇到下面的犯错:

sre@sre:~/work/exp/jammy$ make all -j 8
  ...
make[1]: *** No rule to make target 'debian/canonical-certs.pem', needed by 'certs/x509_certificate_list'.  Stop.
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:1900: certs] Error 2

能够参看 askubuntu.com/questions/1… 去掉里边证书的部分:

# 能够看到当时的装备, 改成=“”
sre@sre:~/work/exp/jammy$ cat .config | grep CONFIG_SYSTEM_TRUSTED_KEYS
CONFIG_SYSTEM_TRUSTED_KEYS="debian/canonical-certs.pem"

测验发动内核

首先装置 qemu, 然后发动内核:

$ sudo apt install qemu-system-x86 -y
$ qemu-system-x86_64 -kernel bzImage -append "console=tty0 console=ttyS0" -nographic
SeaBIOS (version 1.15.0-1)
iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+07F8B340+07ECB340 CA00
Booting from ROM...
Kernel Offset: disabled
---[ end Kernel panic - not syncing: No working init found.  Try passing init= option to kernel.

能够看到最终内核 panic, 因为没有任何 root 文件体系, 也没有 init 代码. 这时候, 咱们能够经过 -initrd 来发动, 里边能够包括一个 busybox.

怎么制造一个 initrd

运用 github.com/aweeraman/k… 提供的东西, 按照说明文档, 履行第一步 ./mk-initrd 就能生成 initramfs.cpio.gz, 里边包括了 initrc 和 busybox.

再次测验发动:

$ qemu-system-x86_64 -kernel bzImage -initrd initramfs.cpio.gz -append "console=tty0 console=ttyS0" -nographic
#这次成功发动

怎么做一个最小的内核并发动进入指令行

制造最小内核

在 Linux 内核源代码根目录履行 make help, 你能看到各种有关装备的子指令, 比方:

  1. defconfig: New config with default from ARCH supplied defconfig.
  2. allnoconfig: New config where all options are answered with no.
  3. allyesconfig: New config where all options are accepted with yes.
  4. tinyconfig: Configure the tiniest possible kernel.

这儿咱们重视的是 tinyconfig, 所以咱们先整理一下, 然后运用 tinyconfig 生成 .config 文件, 然后制造 image, 最终运用 qemu 去履行:

$ make mrproper
$ make tinyconfig
$ make all -j 8
    Kernel: arch/x86/boot/bzImage is ready
$ qemu-system-x86_64 -kernel bzImage  -append "console=tty0 console=ttyS0 init=/bin/sh" -nographic
SeaBIOS (version 1.15.0-1)
iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+07F8B340+07ECB340 CA00
Booting from ROM..

最终发现日志停在 Booting from ROM.. 就没有任何消息了.

这是因为 tinyconfig 包括的驱动或许装备太少, 导致没有后续输出, 咱们需求在 tinyconfig 的基础上增加一些装备.

增加装备

增加装备运用 make menuconfig 来修正, 最终保存就好.

  1. 64-bit kernel

    Linux 内核的编译, 启动和制作ISO镜像

  2. Device Drivers -> Character devices -> Enable TTY

    Linux 内核的编译, 启动和制作ISO镜像

  3. Device Drivers -> Character devices -> Serial drivers -> 8250/16550 and compatible serial support -> Console on 8250/16550 and compatible serial port

    Linux 内核的编译, 启动和制作ISO镜像

  4. General setup > Configure standard kernel features (expert users) -> Enable support for printk

    Linux 内核的编译, 启动和制作ISO镜像

保存上面装备, 而且做一个新的image:

$ make all -j 8
$ qemu-system-x86_64 -kernel bzImage  -append "console=tty0 console=ttyS0 init=/bin/sh" -nographic
SeaBIOS (version 1.15.0-1)
iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+07F8B340+07ECB340 CA00
Booting from ROM..
Run /bin/sh as init process
Kernel panic - not syncing: Requested init /bin/sh failed (error -2).
Kernel Offset: disabled
---[ end Kernel panic - not syncing: Requested init /bin/sh failed (error -2). ]---

能够看到 Kernel panic, 因为咱们仅仅发动 kernel, 没有root 文件体系, 也没有运用 initrd 的ramdisk.

修正config 支撑 initrd

运用 make menuconfig 继续修正

  1. General setup -> Initial RAM filesystem and RAM disk (initramfs/initrd) support

    Linux 内核的编译, 启动和制作ISO镜像

  2. Executable file formats -> Kernel support for ELF binaries

    Linux 内核的编译, 启动和制作ISO镜像

保存, 然后 make all -j 8 再次制造image, 然后运转:

$ qemu-system-x86_64 -kernel bzImage -initrd initramfs.cpio.gz -append "console=tty0 console=ttyS0 init=/bin/sh" -nographic
Run /init as init process
Failed to execute /init (error -8)
Run /bin/sh as init process
/bin/sh: can't access tty; job control turned off
/ # input: ImExPS/2 Generic Explorer Mouse as /devices/platform/i8042/serio1/input/input2
clocksource: tsc: mask: 0xffffffffffffffff max_cycles: 0x311fac54234, max_idle_ns: 440795352581 ns
clocksource: Switched to clocksource tsc
uname -a
Linux (none) 6.4.0-rc5+ #3 Wed Jun  7 08:19:32 PDT 2023 x86_64 GNU/Linux
/ # ls /bin/
...
/ # ps aux
PID   USER     TIME  COMMAND

能够看到 kernel 发动后履行了 /bin/sh, 咱们运用 uname能看到 kernel 的版本号, 可是ps 没有任何输出. 那是因为咱们没有挂载 proc 文件体系. 同时履行挂载 proc 文件体系的脚本在 initramfs.cpio.gz 内部的 init 文件里, 它是一个 shell, 所以要使 kernel 支撑 shell 的 #!.

再次经过 make menuconfig 修正装备:

  1. Executable file formats -> Kernel support for scripts starting with #!
    Linux 内核的编译, 启动和制作ISO镜像
    2. File systems > Pseudo filesystems -> (/proc file system support & sysfs file system support)
    Linux 内核的编译, 启动和制作ISO镜像

修正完保存, 然后重新制造 image, 而且运转:

$ qemu-system-x86_64 -kernel bzImage -initrd initramfs.cpio.gz -append "console=tty0 console=ttyS0 init=/bin/sh" -nographic
ps
PID   USER     TIME  COMMAND
    1 0         0:00 {init} /bin/sh /init
    2 0         0:00 [kthreadd]
    3 0         0:00 [kworker/0:0-eve]
    4 0         0:00 [kworker/0:0H]
    5 0         0:00 [kworker/u2:0-ev]
    6 0         0:00 [mm_percpu_wq]
    7 0         0:00 [ksoftirqd/0]
    8 0         0:00 [oom_reaper]
    9 0         0:00 [writeback]
   10 0         0:00 [kswapd0]
   11 0         0:00 [kworker/u2:1-ev]
   12 0         0:00 [kworker/0:1-eve]
   13 0         0:00 [kworker/u2:2-ev]
   14 0         0:00 [kworker/0:2]
   19 0         0:00 sh
   20 0         0:00 ps

怎么制造一个可运转的 ISO 文件

创立文件结构而且复制数据

$ mkdir -p iso/boot/grub
$ cp bzImage iso/boot/
$ cp initramfs.cpio.gz  iso/boot/

创立 grub.cfg 文件

 $ vim iso/boot/grub/grub.cfg
set default=0
set timeout=10# Load EFI video drivers. This device is EFI so keep the
# video mode while booting the linux kernel.
insmod efi_gop
insmod font
if loadfont /boot/grub/fonts/unicode.pf2
then
        insmod gfxterm
        set gfxmode=auto
        set gfxpayload=keep
        terminal_output gfxterm
fimenuentry 'myos' --class os {
    insmod gzio
    insmod part_msdos
    linux /boot/bzImage init=/bin/sh console=ttyS0 console=tty0
    initrd /boot/initramfs.cpio.gz
}

装置 xorriso, mtools 而且制造 ISO image:

$ sudo apt install xorriso mtools -y
$ grub-mkrescue -o myos.iso iso/
$ ls -lah myos.iso

运用 Qemu 测验新的 ISO image

$ qemu-system-x86_64 -boot d -cdrom myos.iso -nographic

Linux 内核的编译, 启动和制作ISO镜像