Linux 内核编译及运行
今天在家没事干,编译一下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 8月 8 12:16 linux-2.6
-rw-rw-r-- 1 laoli laoli 192M 8月 8 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编译器并自动安装make;qemu-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 8月 8 09:37 arch/x86/boot/bzImage
该文件大小只有9.1M,这是经过gzip压缩之后的内核映像。原始的未经压缩的内核映像生成在源码根目录下:
$ ls -lh vmlinux
-rwxrwxr-x 1 laoli laoli 61M 8月 8 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!
(完)
