linux最小版本编译

86 阅读7分钟

参考:【【教程】带你编译内核,手搓自己的Linux发行版!】 www.bilibili.com/video/BV1bF…

step1 内核源代码与编译

内核源码下载地址https://www.kernel.org/,如图:

image.png 可以下载和自己编译机器版本一致的内核,否则可能会出现版本冲突问题,版本一致会省些事,如果乐意折腾其他版本也可以尝试,不过因为我希望第一次尽可能的成功,所有还是选择了与编译机器内核版本一致的。

# 可以使用 uname -r 命令查看,比如我的
luoye@fn:~/code/linux$ uname -r
6.12.18-trim

下载到本地并且解压:

ubuntu@fn:~/code/linux-compile$ ls
linux-6.18.2.tar.xz
ubuntu@fn:~/code/linux-compile$ tar -xf linux-6.18.2.tar.xz 
ubuntu@fn:~/code/linux-compile$ ls
linux-6.18.2  linux-6.18.2.tar.xz

进入解压后的目录并生成配置,这里我们直接使用默认的配置:

ubuntu@fn:~/code/linux-compile$ cd linux-6.18.2
ubuntu@fn:~/code/linux-compile/linux-6.18.2$ make clean
ubuntu@fn:~/code/linux-compile/linux-6.18.2$ make defconfig
  HOSTCC  scripts/basic/fixdep
  HOSTCC  scripts/kconfig/conf.o
  HOSTCC  scripts/kconfig/confdata.o
  HOSTCC  scripts/kconfig/expr.o
  LEX     scripts/kconfig/lexer.lex.c
  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
#

编译(编译时间看cpu的核心数量和频率,我的机器大概5min左右):

make -j 12 # 12是指我们的核心数量,有多少用多少,加速编译

成功后显示如下:

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

我们编译好的产物就是arch/x86/boot/bzImage

step2 制作shell工具(直接使用busybox)

下载地址:https://www.busybox.net/downloads/,版本上这个没什么特殊的要求,不过要和linux内核的版本兼容。

image.png 这里我选择的是:busybox-1.37.0

下载后解压:

ubuntu@fn:~/code/linux-compile$ ls
busybox-1.37.0.tar.bz2  linux-6.18.2  linux-6.18.2.tar.xz
ubuntu@fn:~/code/linux-compile$ tar -xf busybox-1.37.0.tar.bz2 
ubuntu@fn:~/code/linux-compile$ ls
busybox-1.37.0  busybox-1.37.0.tar.bz2  linux-6.18.2  linux-6.18.2.tar.xz

进入目录并编译:

注意:进入menuconfig后,配置静态编译(Settings -> Build static binary),我们的小linux里面并没有共享库,直接静态编译就行

ubuntu@fn:~/code/linux-compile$ cd busybox-1.37.0
ubuntu@fn:~/code/linux-compile/busybox-1.37.0$ make menuconfig
scripts/kconfig/mconf Config.in
#
# using defaults found in /dev/null
#
*** End of configuration.
*** Execute 'make' to build the project or try 'make help'.

编译和上面的linux编译命令一样:

make -j 12 # 这个快很多
   ...
  CC      libbb/xrealloc_vector.o
  CC      libbb/xregcomp.o
  AR      libbb/lib.a
  LINK    busybox_unstripped
Static linking against glibc, can't use --gc-sections
Trying libraries: crypt m resolv rt
 Library crypt is not needed, excluding it
 Library m is needed, can't exclude it (yet)
 Library resolv is needed, can't exclude it (yet)
 Library rt is not needed, excluding it
 Library m is needed, can't exclude it (yet)
 Library resolv is needed, can't exclude it (yet)
Final link with: m resolv

可以将我们编译好的内容安装到指定的文件夹:

ubuntu@fn:~/code/linux-compile/busybox-1.37.0$ mkdir ../busybox
ubuntu@fn:~/code/linux-compile/busybox-1.37.0$ make install CONFIG_PREFIX=../busybox
ubuntu@fn:~/code/linux-compile/busybox-1.37.0$ cd ../busybox
ubuntu@fn:~/code/linux-compile/busybox$ ls
bin  linuxrc  sbin  usr

不过我们现在的busybox里面只有些默认的命令行工具,如下:

ubuntu@fn:~/code/linux-compile/busybox/bin$ ls
arch     cp             egrep     hush      ls          mt             reformime     setserial  uname
ash      cpio           false     ionice    lsattr      mv             resume        sh         usleep
base32   cttyhack       fatattr   iostat    lzop        netstat        rev           sleep      vi
base64   date           fdflush   ipcalc    makemime    nice           rm            stat       watch
busybox  dd             fgrep     kbd_mode  mkdir       pidof          rmdir         stty       zcat
cat      df             fsync     kill      mknod       ping           rpm           su
chattr   dmesg          getopt    link      mktemp      ping6          run-parts     sync
chgrp    dnsdomainname  grep      linux32   more        pipe_progress  scriptreplay  tar
chmod    dumpkmap       gunzip    linux64   mount       printenv       sed           touch
chown    echo           gzip      ln        mountpoint  ps             setarch       true
conspy   ed             hostname  login     mpstat      pwd            setpriv       umount

后续我们需要操作网络时还需要一些if*命令,这里可以直接在,在busybox安装的bin目录直接执行下面脚本:

# 筛选 busybox 内置的 if* 命令,批量创建软链接(当前目录下)
./busybox --list | grep ^if | while read cmd; do
[ ! -f "$cmd" ] && ln -s ./busybox "$cmd" # 避免重复创建,已存在则跳过 
done

执行完成后像下面这样就好了:

ubuntu@fn:~/code/linux-compile/busybox/bin$ ls -l | grep if
lrwxrwxrwx 1 ubuntu Users       9 Jan 10 22:23 ifconfig -> ./busybox
lrwxrwxrwx 1 ubuntu Users       9 Jan 10 22:23 ifdown -> ./busybox
lrwxrwxrwx 1 ubuntu Users       9 Jan 10 22:23 ifenslave -> ./busybox
lrwxrwxrwx 1 ubuntu Users       9 Jan 10 22:23 ifplugd -> ./busybox
lrwxrwxrwx 1 ubuntu Users       9 Jan 10 22:23 ifup -> ./busybox

step3 创建磁盘镜像

将我们的引导文件和存在操作系统的盘文件都放置在磁盘镜像上

先创建一个512MB的空文件

# 写入一个512MB的空文件
# dd 写入工具
# if=/dev/zero 输入设备
# of=linux.img 输出设备
# bs=1M 块大小
# count=512 块数量
dd if=/dev/zero of=linux.img bs=1M count=512

使用gdisk创建磁盘分区,操作如下:

ubuntu@fn:~/code/linux-compile$ gdisk linux.img
GPT fdisk (gdisk) version 1.0.9

Partition table scan:
  MBR: not present
  BSD: not present
  APM: not present
  GPT: not present

Creating new GPT entries in memory.

Command (? for help): n
Partition number (1-128, default 1): 
First sector (34-1048542, default = 2048) or {+-}size{KMGTP}: 
Last sector (2048-1048542, default = 1046527) or {+-}size{KMGTP}: 50MB
Current type is 8300 (Linux filesystem)
Hex code or GUID (L to show codes, Enter = 8300): EF00 # 这里告诉gdisk创建分区类型为EFI
Changed type of partition to 'EFI system partition'

Command (? for help): n
Partition number (2-128, default 2): 
First sector (34-1048542, default = 104448) or {+-}size{KMGTP}: 
Last sector (104448-1048542, default = 1046527) or {+-}size{KMGTP}: 
Current type is 8300 (Linux filesystem)
Hex code or GUID (L to show codes, Enter = 8300): w
Hex code or GUID (L to show codes, Enter = 8300): 
Changed type of partition to 'Linux filesystem'

Command (? for help): w

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): y
OK; writing new GUID partition table (GPT) to linux.img.
Warning: The kernel is still using the old partition table.
The new table will be used at the next reboot or after you
run partprobe(8) or kpartx(8)
The operation has completed successfully.

step4 挂载磁盘镜像并且安装grub

将磁盘镜像挂载到虚拟设备

sudo losetup -f -P linux.img # 挂载磁盘镜像

sudo losetup -d /dev/loop0 # detach掉该设备,一般都是/dev/loop0

lsblk  # 查看挂载情况 可以看到挂载的磁盘
ubuntu@fn:~/code/linux-compile$ lsblk
NAME                                              MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
loop0                                               7:0    0   512M  0 loop  
├─loop0p1                                         259:0    0    49M  0 part  
└─loop0p2                                         259:1    0   460M  0 part  

格式化磁盘分区:

sudo mkfs.fat -F32 /dev/loop0p1 # 格式化第一个分区的文件系统,这里使用fat32,因为是uefi引导,所以使用fat32
sudo mkfs.ext4 /dev/loop0p2 # 格式化第二个分区的文件系统,这里使用ext4,ext4在linux下性能比较好

grub引导程序安装:

ubuntu@fn:~/code/linux-compile$ mkdir mnt
ubuntu@fn:~/code/linux-compile$ sudo mount /dev/loop0p1 mnt
ubuntu@fn:~/code/linux-compile$ sudo grub-install --target=x86_64-efi --efi-directory=$(realpath mnt) --bootloader-id=GRUB --removable --recheck
Installing for x86_64-efi platform.
Installation finished. No error reported.

创建引导文件,并将引导文件中指向的数据分区uuid修改为我们的磁盘镜像中的数据分区:

# 先看看数据分区的uuid
ubuntu@fn:~/code/linux-compile$ sudo blkid /dev/loop0p2
/dev/loop0p2: UUID="10ff8c37-b739-4757-9744-8e82b5f7f5a7" BLOCK_SIZE="1024" TYPE="ext4" PARTLABEL="Linux filesystem" PARTUUID="e6e70227-6ac6-47e4-bdd2-2942e03789b9"

ubuntu@fn:~/code/linux-compile/mnt$ sudo mkdir -p boot/grub
ubuntu@fn:~/code/linux-compile/mnt$ sudo cp EFI/BOOT/grub.cfg ./boot/grub/
ubuntu@fn:~/code/linux-compile/mnt$ sudo vim boot/grub/grub.cfg 

修改完成后的grub.cfg是这样的:

search.fs_uuid 10ff8c37-b739-4757-9744-8e82b5f7f5a7 root hd1,gpt2 
set prefix=($root)'/boot/grub'
configfile $prefix/grub.cfg

保险起见可以将EFI/BOOT目录下的grub.cfg给覆盖掉

ubuntu@fn:~/code/linux-compile/mnt$ sudo cp boot/grub/grub.cfg EFI/BOOT/grub.cfg

#这里注意下,/EFI/BOOT/grub.cfg这个是兜底的引导文件,磁盘引导默认使用的是/boot/grub/grub.cfg,只有当/boot/grub/grub.cfg不存在时,才会使用/EFI/BOOT/grub.cfg

解除挂载:

ubuntu@fn:~/code/linux-compile$ sudo umount mnt

step5 安装系统到数据分区

还是先挂载:

ubuntu@fn:~/code/linux-compile$ sudo mount /dev/loop0p2 mnt

拷贝系统文件:

ubuntu@fn:~/code/linux-compile$ sudo cp linux-6.18.2/arch/x86/boot/bzImage ./mnt/

将编译好的busybox命令行工具也拷贝进去:

ubuntu@fn:~/code/linux-compile$ sudo cp -r busybox/* mnt
ubuntu@fn:~/code/linux-compile$ cd mnt/
ubuntu@fn:~/code/linux-compile/mnt$ ls
bin  bzImage  linuxrc  lost+found  sbin  usr

创建系统文件夹:

ubuntu@fn:~/code/linux-compile/mnt$ sudo mkdir boot # 创建引导文件存放目录
ubuntu@fn:~/code/linux-compile/mnt$ sudo mkdir proc # 创建系统运行时信息目录
ubuntu@fn:~/code/linux-compile/mnt$ sudo mkdir sys # 创建系统目录,运行时设备信息
ubuntu@fn:~/code/linux-compile/mnt$ sudo mkdir dev # 创建设备目录
ubuntu@fn:~/code/linux-compile/mnt$ ls
bin  boot  bzImage  linuxrc  lost+found  proc  sbin  sys  usr

将系统移动到boot目录:

ubuntu@fn:~/code/linux-compile/mnt$ sudo mv bzImage boot/

创建引导文件:

ubuntu@fn:~/code/linux-compile/mnt$ cd boot/
ubuntu@fn:~/code/linux-compile/mnt/boot$ sudo mkdir grub
ubuntu@fn:~/code/linux-compile/mnt/boot$ cd grub/
ubuntu@fn:~/code/linux-compile/mnt/boot/grub$ vim grub.cfg
ubuntu@fn:~/code/linux-compile/mnt/boot/grub$ sudo vim grub.cfg

引导文件内容如下(里的uuid和partuuid就是我们数据磁盘的信息):

menuentry "box_linux"{
        insmod part_gpt
        insmod fat
        insmod ext2
        insmod normal

        search --no-floppy --fs-uuid --set=root 10ff8c37-b739-4757-9744-8e82b5f7f5a7 # 这里就是数据盘根文件系统的uuid

        # 下面设置下引导参数
        linux /boot/bzImage root=PARTUUID=e6e70227-6ac6-47e4-bdd2-2942e03789b9 rw init=/boot/init rootdelay=3 nomodeset console=ttyS0
}

接下来创建我们的init文件:

ubuntu@fn:~/code/linux-compile/mnt$ cd boot/
ubuntu@fn:~/code/linux-compile/mnt/boot$ sudo vim init

init文件具体内容如下:

#!/bin/sh

mount -t sysfs none /sys
mount -t proc none /proc
mount -t devtmpfs devtmpfs /dev

mknod /dev/console c 5 1
mknod /dev/ttyS0 c 4 64
exec /bin/sh > /dev/ttyS0 2>&1

记得给个可执行权限:

ubuntu@fn:~/code/linux-compile/mnt/boot$ sudo chmod +x init
ubuntu@fn:~/code/linux-compile/mnt/boot$ ls -l init
-rwxr-xr-x 1 root root 174 Jan 10 23:04 init

step6 使用qemu启动

我们先解除挂载并且detach掉磁盘

ubuntu@fn:~/code/linux-compile$ sudo umount mnt
ubuntu@fn:~/code/linux-compile$ sudo losetup -d /dev/loop0

使用qemu启动下试试:

# 串口输出、内存配置的完整命令
qemu-system-x86_64 \
    -drive file=./linux.img,format=raw,if=virtio \
    -bios /usr/share/ovmf/OVMF.fd \
    -m 1G \
    -netdev user,id=n0 \
    -device virtio-net-pci,netdev=n0 \
    -nographic \
    -serial mon:stdio \
    -cpu qemu64,-vmx \
    -boot c

一般成功应该就可以看到引导了,直接选中进去就行了。

step7 网络配置

上面可以基本跑起来,不过如果需要折腾网络的话还需要些配置。 还是一样的先映射磁盘镜像,然后挂载数据磁盘,现在我们假设已经挂载好了数据磁盘

1.创建/etc/network/interfaces

sudo vim etc/network/interfaces

文件内容如下(启动时激活lo接口和eth0网卡接口):

# 第一部分:配置本地环回网卡 lo(必选) 
auto lo # 1. 系统启动/执行 ifup -a 时,自动激活 lo 网卡 
iface lo inet loopback # 2. 定义 lo 网卡的类型:IPv4(inet)、环回模式(loopback) 

# 第二部分:配置物理网卡 eth0(核心) 
auto eth0 # 1. 系统启动/执行 ifup -a 时,自动激活 eth0 网卡(可选,不加则需手动 ifup eth0) 
iface eth0 inet dhcp # 2. 定义 eth0 网卡的类型:IPv4(inet)、通过 DHCP 自动获取网络参数

# 可以直接拷贝下面这个:
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet dhcp
  1. 创建/usr/share/udhcpc/default.script
#!/bin/sh
# 1. 给网卡配置 IP 地址 + 子网掩码
[ -n "$ip" ] && ip addr add $ip/$subnet dev $interface
# 2. 设置系统默认网关(让系统能访问外网)
[ -n "$router" ] && ip route add default via $router
# 3. 写入 DNS 服务器地址(让系统能解析域名)
[ -n "$dns" ] && echo "nameserver $dns" > /etc/resolv.conf

# 可以直接拷贝这个:
#!/bin/sh
[ -n "$ip" ] && ip addr add $ip/$subnet dev $interface
[ -n "$router" ] && ip route add default via $router
[ -n "$dns" ] && echo "nameserver $dns" > /etc/resolv.conf

创建完成后记得给文件可执行权限chmod +x default.script

当执行ifup eth0时,dhcp会默认执行default.script脚本。

# 执行命令不变
qemu-system-x86_64 \
    -drive file=./linux.img,format=raw,if=virtio \
    -bios /usr/share/ovmf/OVMF.fd \
    -m 1G \
    -netdev user,id=n0 \
    -device virtio-net-pci,netdev=n0 \
    -nographic \
    -serial mon:stdio \
    -cpu qemu64,-vmx \
    -boot c

进入设备后需要手动执行下:

ifup lo # 设置环回地址
ifup eth0 # 激活网卡并且获取ip

不过最好还是放在init文件中,避免每次进入都要执行一次。正常就是这样:

~ # ping -c 2 www.baidu.com
PING www.baidu.com (36.152.44.93): 56 data bytes
64 bytes from 36.152.44.93: seq=0 ttl=255 time=19.380 ms
64 bytes from 36.152.44.93: seq=1 ttl=255 time=14.961 ms

--- www.baidu.com ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 14.961/17.170/19.380 ms
~ # ping -c 2 127.0.0.1
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: seq=0 ttl=64 time=3.420 ms
64 bytes from 127.0.0.1: seq=1 ttl=64 time=0.863 ms

--- 127.0.0.1 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.863/2.141/3.420 ms

step8 包管理工具安装(apk)

安装apk
仓库网址:https://gitlab.alpinelinux.org/alpine/apk-tools/-/releases(这里可以下载静态包)

密钥地址:这个文件夹下aports-master/main/alpine-keys
https://gitlab.alpinelinux.org/alpine/aports
要拷贝到/etc/apk/keys下

源配置:/etc/apk/repositories
http://dl-cdn.alpinelinux.org/alpine/latest-stable/main
http://dl-cdn.alpinelinux.org/alpine/latest-stable/community

不过上面这个源太慢了,可以使用阿里源
https://mirrors.aliyun.com/alpine/v3.14/main
https://mirrors.aliyun.com/alpine/v3.14/community


尝试安装第一个包:(外网的源,比较慢,会以为是卡住了,其实不是)
apk --initdb add file

虚拟机里面下载根证书:
apk add ca-certificates-bundle

之后就可以改成https了

修改完成后可以试试安装vim
apk add vim