跳至主要內容

Linux 内核编译及运行

未央大约 6 分钟linuxqemu内核

今天在家没事干,编译一下linux内核。准备编译的内核版本是2.6,源码通过git获取,如下:

$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git

由于网络环境的原因,下载速度可能会非常慢。解决方案可以是开代理,或者你也可以使用我重新打包的版本。

原始仓库大小为3.7GB,经gzip压缩后大小是2.54GB。我比较暴力,直接删除了原始的.git目录,仓库大小缩减至1.2GB,经gzip压缩后的大小是192MB

$ ls -lh
total 192M
drwxrwxr-x 24 laoli laoli 4.0K 88 12:16 linux-2.6
-rw-rw-r--  1 laoli laoli 192M 88 12:18 linux-2.6.tar.gz

这一顿操作之后仓库大小得到了显著的优化,带来的结果就是你克隆代码的时候会快很多。

仓库地址:

实验环境

我使用的环境如下:

$ uname -a
Linux lowb 5.11.0-25-generic #27~20.04.1-Ubuntu SMP Tue Jul 13 17:41:23 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.2 LTS
Release:        20.04
Codename:       focal

安装如下依赖:

$ sudo apt install build-essential qemu-system-x86 -y
$ sudo apt install flex bison -y
$ sudo apt install libssl-dev libelf-dev -y

第一组中的build-essential包含gcc编译器并自动安装makeqemu-system-x86是模拟器,用于启动和调试内核。

第二组flex bison在生成内核配置文件时会用到。

第三组libssl-dev libelf-dev是内核源码编译时需要的依赖。

开始

创建工作目录并获取源码:

$ mkdir -p ~/projects/linux_kernel
$ cd ~/projects/linux_kernel
$ git clone https://github.com/kviccn/linux-2.6.git
$ cd linux-2.6

生成内核配置文件:

$ make defconfig
  YACC    scripts/kconfig/parser.tab.[ch]
  HOSTCC  scripts/kconfig/lexer.lex.o
  HOSTCC  scripts/kconfig/menu.o
  HOSTCC  scripts/kconfig/parser.tab.o
  HOSTCC  scripts/kconfig/preprocess.o
  HOSTCC  scripts/kconfig/symbol.o
  HOSTCC  scripts/kconfig/util.o
  HOSTLD  scripts/kconfig/conf
*** Default configuration is based on 'x86_64_defconfig'
#
# configuration written to .config
#

输出信息中显示内核配置已经写入.config文件。

查看.config

$ more .config
#
# Automatically generated file; DO NOT EDIT.
# Linux/x86 5.14.0-rc4 Kernel Configuration
#
CONFIG_CC_VERSION_TEXT="gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
CONFIG_CC_IS_GCC=y
CONFIG_GCC_VERSION=90300
CONFIG_CLANG_VERSION=0
CONFIG_AS_IS_GNU=y
CONFIG_AS_VERSION=23400
CONFIG_LD_IS_BFD=y
CONFIG_LD_VERSION=23400
CONFIG_LLD_VERSION=0
CONFIG_CC_CAN_LINK=y
CONFIG_CC_CAN_LINK_STATIC=y
CONFIG_CC_HAS_ASM_GOTO=y
CONFIG_CC_HAS_ASM_INLINE=y
CONFIG_CC_HAS_NO_PROFILE_FN_ATTR=y
CONFIG_IRQ_WORK=y
CONFIG_BUILDTIME_TABLE_SORT=y
CONFIG_THREAD_INFO_IN_TASK=y

#
# General setup
#
CONFIG_INIT_ENV_ARG_LIMIT=32
# CONFIG_COMPILE_TEST is not set
CONFIG_LOCALVERSION=""
# CONFIG_LOCALVERSION_AUTO is not set
--More--(0%)

配置信息非常多,但是我们现在不关心这个。

一旦.config文件成功生成,执行make就可以进行内核的编译了。

$ make
...
Kernel: arch/x86/boot/bzImage is ready  (#1)

如果不做重定向的话输出信息非常多,我用...省略了,输出信息的最后一行是生成的内核所在的位置。 当前目录下的arch/x86/boot/目录下的bzImage文件就是最终生成的内核映像。

$ ls -lh arch/x86/boot/bzImage
-rw-rw-r-- 1 laoli laoli 9.1M 88 09:37 arch/x86/boot/bzImage

该文件大小只有9.1M,这是经过gzip压缩之后的内核映像。原始的未经压缩的内核映像生成在源码根目录下:

$ ls -lh vmlinux
-rwxrwxr-x 1 laoli laoli 61M 88 09:37 vmlinux

大小为61M,所以压缩还是很有必要的。

查看文件类型:

$ file vmlinux
vmlinux: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=d713c99a0a437a2b4a0429eded699ebdc7452a8b, not stripped

是我们熟悉的elf格式的可执行文件。

内核已经编译完成,下一个主题运行

运行

运行可以在物理机器上,也可以在虚拟机上。我打算在虚拟机上运行,因为直接在物理机运行有点危险,稍有不慎,搞错什么配置或者误删什么文件那系统可能就起不起来了。虽说也可以修复,但是每次重新运行内核查看效果都要重启,这谁顶得住。

先将刚才生成的内核映像bzImage复制一份出来方便我们操作:

$ mkdir ~/projects/linux_kernel/demo
$ cd ~/projects/linux_kernel/demo
$ cp ../linux-2.6/arch/x86/boot/bzImage .

第一次尝试:

$ qemu-system-x86_64 -kernel bzImage

qemu的窗口中最终输出如下信息然后卡住不动:

[    2.385237] md: If you don't use raid, use raid=noautodetect
[    2.385489] md: Autodetecting RAID arrays.
[    2.385595] md: autorun ...
[    2.385746] md: ... autorun DONE.
[    2.387977] VFS: Cannot open root device "(null)" or unknown-block(0,0): err6
[    2.388398] Please append a correct "root=" boot option; here are the availa:
[    2.388852] 0b00         1048575 sr0
[    2.388890]  driver: sr
[    2.389203] Kernel panic - not syncing: VFS: Unable to mount root fs on unkn)
[    2.389531] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 5.14.0-rc4+ #1
[    2.389754] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.134
[    2.390145] Call Trace:
[    2.390929]  dump_stack_lvl+0x34/0x44
[    2.391191]  panic+0xf6/0x2b7
[    2.391292]  mount_block_root+0x196/0x21a
[    2.391442]  mount_root+0xec/0x10a
[    2.391532]  prepare_namespace+0x136/0x165
[    2.391628]  kernel_init_freeable+0x203/0x20e
[    2.391734]  ? rest_init+0xb0/0xb0
[    2.391819]  kernel_init+0x11/0x110
[    2.391902]  ret_from_fork+0x22/0x30
[    2.392488] Kernel Offset: 0x9600000 from 0xffffffff81000000 (relocation ran)
[    2.392972] ---[ end Kernel panic - not syncing: VFS: Unable to mount root f-

其实我们已经成功了。系统提示VFS: Cannot open root device "(null)" or unknown-block(0,0),说无法打开root device。这很合理,因为我们并没有在虚拟机中设置root device

这个好解决,我们直接从系统里复制个initrd.img过来就行了。

$ cp /boot/initrd.img .

第二次尝试:

$ qemu-system-x86_64 -m 1G -kernel bzImage -initrd initrd.img

这次我们将虚拟机内存调整到了1G(内存太小会报System is deadlocked on memory的错),并且指定了initrd参数。

qemu窗口输出如下:

[    2.440277] cfg80211: failed to load regulatory.db
[    2.441549] ALSA device list:
[    2.441957]   No soundcards found.
[    2.519941] Freeing unused kernel image (initmem) memory: 1368K
[    2.522951] Write protecting the kernel read-only data: 20480k
[    2.525515] Freeing unused kernel image (text/rodata gap) memory: 2032K
[    2.526401] Freeing unused kernel image (rodata/data gap) memory: 536K
[    2.526846] Run /init as init process
Loading, please wait...
[    2.737525] input: ImExPS/2 Generic Explorer Mouse as /devices/platform/i8043
Starting version 245.4-4ubuntu3.11
[    4.676711] e1000 0000:00:03.0 enp0s3: renamed from eth0
[    4.862901] ata_id (100) used greatest stack depth: 13848 bytes left
Begin: Loading essential drivers ... done.
Begin: Running /scripts/init-premount ... done.
Begin: Mounting root file system ... Begin: Running /scripts/local-top ... done.
Begin: Running /scripts/local-premount ... done.
No root device specified. Boot arguments must include a root= parameter.


BusyBox v1.30.1 (Ubuntu 1:1.30.1-4ubuntu6.3) built-in shell (ash)
Enter 'help' for a list of built-in commands.

(initramfs)

最后一行出现了命令提示符,让我们来敲个熟悉的ls康康:

(initramfs) ls
dev      bin      init     lib64    sbin     var      tmp
root     conf     lib      libx32   scripts  sys
kernel   etc      lib32    run      usr      proc
(initramfs)

大功告成。

但事情肯定不能止步于此,我们连Hello World!都没打出来呢,让我们在加点料。

加料之前先解释一下initrd参数是干啥的,这个参数用于指定initramfs。那initramfs又是啥,这个东西把名字拆开各位就应该差不多能看懂了init ram fs。一种初始时加载到内存中的文件系统。

制作 initramfs

写个经典的Hello World,用于在系统启动时执行

#include <stdio.h>

void main() {
  printf("Hello laoli!\n");
  fflush(stdout);
  while(1);
}

编译,静态连接:

$ cc -static -o helloworld helloworld.c

生成initramfs

$ echo helloworld | cpio -o --format=newc > initrd-hw.img

initramfs需要打包成 cpio 档案。

运行:

$ qemu-system-x86_64 \
    -kernel bzImage \
    -initrd initrd-hw.img \
    -append "root=/dev/ram rdinit=/helloworld"
  • -kernel:提供内核镜像bzImage
  • -initrd:提供initramfs
  • -append:提供内核参数,指引rootfs所在分区,指引init命令路径

这里通过-initrd指定initramfs为我们刚生成的initrd-hw.img。 通过-append进行额外配置,将根目录设置为内存,启动时的init程序设置为我们刚才编写的helloworld

qemu窗口的输出如下:

[    1.722424] sched_clock: Marking stable (1693370913, 28776387)->(1810106690,)
[    1.723876] registered taskstats version 1
[    1.724012] Loading compiled-in X.509 certificates
[    1.728386] PM:   Magic number: 1:692:874
[    1.729844] printk: console [netcon0] enabled
[    1.729998] netconsole: network logging started
[    1.732385] cfg80211: Loading compiled-in X.509 certificates for regulatory e
[    1.742962] kworker/u2:0 (62) used greatest stack depth: 14840 bytes left
[    1.752414] cfg80211: Loaded X.509 cert 'sforshee: 00b28ddf47aef9cea7'
[    1.754064] platform regulatory.0: Direct firmware load for regulatory.db fa2
[    1.754550] cfg80211: failed to load regulatory.db
[    1.755837] ALSA device list:
[    1.756077]   No soundcards found.
[    1.823450] Freeing unused kernel image (initmem) memory: 1368K
[    1.834264] Write protecting the kernel read-only data: 20480k
[    1.836722] Freeing unused kernel image (text/rodata gap) memory: 2032K
[    1.837592] Freeing unused kernel image (rodata/data gap) memory: 536K
[    1.837971] Run /helloworld as init process
Hello laoli!
[    2.023897] tsc: Refined TSC clocksource calibration: 1797.503 MHz
[    2.026978] clocksource: tsc: mask: 0xffffffffffffffff max_cycles: 0x19e8f2ds
[    2.035717] clocksource: Switched to clocksource tsc
[    2.321988] input: ImExPS/2 Generic Explorer Mouse as /devices/platform/i8043

输出信息中提示Run /helloworld as init process,并且输出Hello laoli!

(完)