编译内核并制作微小Linux系统

编译内核并制作微小Linux系统

本博客演示自定义内核,并制作一个简单的Linux系统,目的在于熟悉Linux系统的启动流程


CentOS5 系统启动流程图

Alt text


编译内核

本实验在CentOS7.3的宿主机上进行编译

第一步: 准备环境, 安装所需的依赖包

  • 下载内核
  • 安装包组 Development tools

  1. 从官网下载内核
    可以直接登录官网https://www.kernel.org/下载或使用命令wget下载。此处使用wget将文件下载到/usr/src目录下
[root@localhost src]# wget -cO /usr/src/linux-4.12.10.tar.xz https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.12.10.tar.xz
  1. 解压 linux-4.12.10.tar.xz,并切换到linux-4.12.10目录下
[root@localhost src]# tar -xvf /usr/src/linux-4.12.10.tar.xz -C /usr/src/

[root@localhost src]# cd /usr/src/linux-4.12.10/
  1. 安装包组Development Tools
[root@localhost app]# yum -y groupinstall "Development Tools"
  1. 根据实际情况安装所缺的依赖包
    当执行编译时,有时会缺少依赖的软件包,根据错误提示安装所缺的软件,我安装的时候提示缺少ncurses-devel,使用yum将其安装。
[root@localhost linux-4.12.10]# yum -y install ncurses-devel

[root@localhost linux-4.12.10]# yum -y install openssl-devel


第二步: 创建.config配置文件

编译内核之前,我们需先创建 .config 文件,以便使用make工具编译时根据该文件的配置参数进行相应模块的编译。

.config 可以通过以下方式生成

  • 自己编写,此种方式工作量巨大
  • 使用命令 make config 通过命令行的方式遍历每个选项进行配置
  • 使用命令make menuconfig,推荐使用此种方式,该种方式是通过文本窗口界面进行配置。可以从当前的系统中拷贝模板文件(/boot/config-VERSION.)到当前目录下改名为.config,再使用 make menuconfig 命令基于该模板文件上进行配置。

  1. 从当前系统目录下拷贝模板文件到目录/src/linux-4.12.10/ 下,并命名为 .config
 [root@localhost linux-4.12.10]# cp /boot/config-3.10.0-514.el7.x86_64 /usr/src/linux-4.12.10/.config
  1. 使用 make menuconfig 命令编辑 .config文件,在此基础上配置内核选项
[root@localhost linux-4.12.10]# make menuconfig

启动了如下文本窗口界面,每个选项之前的标志符含义如下:

  • [*]
    此项配置将编译到内核文件/boot/vmlinuz-*
  • [ ]
    此项不编译
  • <M>
    此项配置将编译到模块/lib/modules/VERSION/目录下
    各配置选项的以上状态使用Tab键切换

Alt text

此处我们修改内核附加版本名和让内核支持ntfs文件系统

a. 将内核版本名 4.12.10 修改为 4.12.10-MiniLinux.1.0
路径:General setup —> () Local version - append to kernel release , 输入 -MiniLinux.1.0

b. 启用 ntfs 模块功能
路径:File systems —> DOS/FAT/NT Filesystems —> <M>NTFS file system support

c. 按TAB键选择 EXIT 选项退出,并保证

Alt text

第三步: 编译
使用命令 make 进行编译,编译需要很长时间,我的电脑编译耗时半个小时才完成

# make -j 4 使用4线程进行编译
[root@localhost linux-4.12.10]# make -j 4

#编译完后,占用了9.9G的磁盘空间
[root@localhost linux-4.12.10]# du -sh .
9.9G

第四步 安装模块
编译完后,我们所需要的内核及功能模块均已在当前编译的目录下,现在需将这些文件移动到系统指定的目录下,对于模块将移动到/lib/modules/ VERSION目录下,执行命令make modules_install

# 安装功能模块
[root@localhost linux-4.12.10]# make modules_install

# 查看/lib/modules目录下出现了我们编译的模块 4.12.10-MiniLinux.1.0, 3.10.0-514.el7.x86_64为宿主机原来的模块
[root@localhost linux-4.12.10]# ls /lib/modules/
3.10.0-514.el7.x86_64 4.12.10-MiniLinux.1.0

第五步 安装内核
使用命令 make install 将内核移动到/boot目录下,执行该命令将进行下面的操作。

  • 将内核文件vmlinuz-VERSION 移动到 /boot/ 目录下
  • 生成 /boot/initramfs-VERSION 文件
  • 在 /boot/grub2/grub.cfg中添加了相应的启动项
[root@localhost linux-4.12.10]# make install

[root@localhost linux-4.12.10]# ls /boot
config-3.10.0-514.el7.x86_64
grub
grub2
initramfs-0-rescue-d1a79f69e4264d8eb18e0fa5bfd2ffc5.img
initramfs-3.10.0-514.el7.x86_64.img
initramfs-3.10.0-514.el7.x86_64kdump.img
initramfs-4.12.10-MiniLinux.1.0.img <= 新增加的
initrd-plymouth.img
symvers-3.10.0-514.el7.x86_64.gz
System.map
System.map-3.10.0-514.el7.x86_64
System.map-4.12.10-MiniLinux.1.0 <= 新增加的
vmlinuz
vmlinuz-0-rescue-d1a79f69e4264d8eb18e0fa5bfd2ffc5
vmlinuz-3.10.0-514.el7.x86_64
vmlinuz-4.12.10-MiniLinux.1.0 <= 新增加的

# 新增加了4.12.10的内核启动项
[root@localhost linux-4.12.10]# egrep -A 14 "menuentry.*4.12.10-MiniLinux.1.0" /boot/grub2/grub.cfg
menuentry 'CentOS Linux (4.12.10-MiniLinux.1.0) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-3.10.0-514.el7.x86_64-advanced-415f4bda-8f07-41de-9b9b-28bae246ef76' {
load_video
set gfxpayload=keep
insmod gzio
insmod part_msdos
insmod xfs
set root='hd0,msdos1'
if [ x$feature_platform_search_hint = xy ]; then
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,msdos1 --hint-efi=hd0,msdos1 --hint-baremetal=ahci0,msdos1 --hint='hd0,msdos1' 556f4215-dc0b-4ed9-ad3f-5fc178f7e9ec
else
search --no-floppy --fs-uuid --set=root 556f4215-dc0b-4ed9-ad3f-5fc178f7e9ec
fi
linux16 /vmlinuz-4.12.10-MiniLinux.1.0 root=UUID=415f4bda-8f07-41de-9b9b-28bae246ef76 ro crashkernel=auto rhgb quiet LANG=en_US.UTF-8
initrd16 /initramfs-4.12.10-MiniLinux.1.0.img
}

第六步 清空编译目录
编译后占用了接近10G的磁盘空间,为了节省空间,我们可以使用命令将编译产生的文件清除
make clean 清除大多数编译生成的文件,但会保留.config等文件
make mrproper 清除所有编译产生的文件,包括.config文件

# 清理之前的空间
[root@localhost linux-4.12.10]# du -sh .
9.9G .
[root@localhost linux-4.12.10]# make clean
使用 make clean 清理后的空间
[root@localhost linux-4.12.10]# du -sh .
846M .
# .config文件还在
[root@localhost linux-4.12.10]# ls .config
.config
# 使用 make mrproper 清理空间
[root@localhost linux-4.12.10]# make mrproper
[root@localhost linux-4.12.10]# du -sh .
837M .

第七步 测试
重启系统,并选择使用4.10.12的内核启动系统

查看系统内核版本, 就是我们订制的 4.12.10-MiniLinux.1.0 系统

[root@localhost ~]# uname -r
4.12.10-MiniLinux.1.0

查看是否能识别NTFS的文件系统,将NTFS的U盘插到当前主机上,并挂载

[root@localhost ~]# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sdb 8:16 0 20G 0 disk
sr0 11:0 1 7.7G 0 rom
sdc 8:32 1 14.4G 0 disk
└─sdc1 8:33 1 14G 0 part <= U盘,被识别为/dev/sdc设备
sda 8:0 0 100G 0 disk
├─sda4 8:4 0 1K 0 part
├─sda2 8:2 0 50G 0 part /
├─sda5 8:5 0 25G 0 part /app
├─sda3 8:3 0 2G 0 part [SWAP]
└─sda1 8:1 0 1G 0 part /boot
# 挂载U盘
[root@localhost ~]# mkdir /mnt/usb
[root@localhost ~]# mount /dev/sdc1 /mnt/usb/
# 插上U盘后,内核自动加载了usb_storage模块
[root@localhost ~]# lsmod | grep usb_storage
usb_storage 69632 2 uas

虽然我们能够挂载NTFS的文件系统,并读取该系统上的文件,但遗憾的是没办法写入文件,Linux内核对NTFS的文件系统支持还是有限的

[root@localhost ~]# cd /mnt/usb/
[root@localhost usb]# > 1.txt
-bash: 1.txt: Read-only file system
[root@localhost usb]# mount -o remount rw /mnt/usb/
mount: cannot remount rw read-write, is write-protected

订制 Linux 系统

本实验在宿主机为 CentOS6.9 的系统上完成

此处订制一个迷你linux系统,主要在于理解Linux的启动流程,该系统启动的第一个进程为/bin/bash,替换原有的/bin/init,因为/bin/init依赖的配置文件过多,此处不演示。

第一步 准备空硬盘,创建分区,文件系统
首先我们准备一块20G的新硬盘插到宿主机上,然后进行以下操作

  • 使用MBR分区方式,分为两个区,一个挂载/boot目录,另一个挂载根 /
  • 在两个分区上创建 ext4 文件系统

  1. 使用 fdisk 命令分区
# 分区之前
[root@zhubiao ~]# lsblk /dev/sdb
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sdb 8:16 0 20G 0 disk

# 创建分区
[root@zhubiao ~]# fdisk /dev/sdb

# 创建的新分区
[root@zhubiao ~]# lsblk /dev/sdb
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sdb 8:16 0 20G 0 disk
├─sdb1 8:17 0 109.8M 0 part
└─sdb2 8:18 0 19.9G 0 part
  1. 创建 ext4 文件系统
# 创建ext4文件系统
[root@zhubiao ~]# mkfs.ext4 /dev/sdb1
[root@zhubiao ~]# mkfs.ext4 /dev/sdb2

# 创建的文件系统
[root@zhubiao ~]# blkid /dev/sdb{1,2}
/dev/sdb1: UUID="41cde125-e983-4c2f-8456-99c3b0ab8024" TYPE="ext4"
/dev/sdb2: UUID="2bf95a1d-4053-489e-9968-74628aeb24e4" TYPE="ext4"


第二步 挂载分区
将/dev/sdb1 和 /dev/sdb2 挂载到当前宿主机上,sdb1 将做为 MiniLinux /boot 分区,sdb2 作为MiniLinux / 分区。只有挂载了分区我们才能在新硬盘上写入数据,如创建GRUB启动程序,复制命令到根目录下等

  1. 在当前宿主机上创建两个目录/mnt/boot 和 /mnt/root 做为两个分区的挂载点
    注意:sdb1将来是作为MiniLinux /boot 的独立分区使用,必须挂载到宿主机名字为boot的目录的下,才能使用命令grub-install正确创建GRUB启动程序。
# 创建两个目录
[root@zhubiao ~]# mkdir /mnt/{boot,root}

# 将分区挂载到创建的目录上
[root@zhubiao ~]# mount /dev/sdb1 /mnt/boot/
[root@zhubiao ~]# mount /dev/sdb2 /mnt/root/


第三步 安装 grub
我们使用grub引导内核启动,使用命令 grub-install 可以创建 gurb stage1, stage1_5, stage2阶段的启动程序,并装到硬盘指定的扇区上。
这里的grub指的是grub legacy,不是grub2喔。

# 安装grub程序
[root@zhubiao ~]# grub-install --root-directory=/mnt/ /dev/sdb

# 在/mnt/boot目录下生成了grub各阶段的启动程序
[root@zhubiao ~]# tree /mnt/boot/
/mnt/boot/
├── grub
│ ├── device.map
│ ├── e2fs_stage1_5
│ ├── fat_stage1_5
│ ├── ffs_stage1_5
│ ├── iso9660_stage1_5
│ ├── jfs_stage1_5
│ ├── minix_stage1_5
│ ├── reiserfs_stage1_5
│ ├── stage1
│ ├── stage2
│ ├── ufs2_stage1_5
│ ├── vstafs_stage1_5
│ └── xfs_stage1_5
└── lost+found


第四步 复制内核和虚拟文件系统

我们创建的 MiniLinux 的内核计划使用自己定制的4.12.4版的内核,将自己所定制的内核复制到当前宿主机的 /mnt/boot/ 目录下。注意当前宿主机使用的内核也需要切换到所定制的内核,因为我们生成虚拟文件系统 initramfs 需与内核匹配。

复制内核到 /mnt/boot/ 目录下

# 宿主机内核版本
[root@zhubiao ~]# uname -r
4.12.4
[root@zhubiao boot]# cp /boot/vmlinuz-4.12.4 /mnt/boot/
[root@zhubiao boot]# ls /mnt/boot/vmlinuz-4.12.4
/mnt/boot/vmlinuz-4.12.4

使用 mkinitrd 创建虚拟文件系统 initramfs-VERSION.img

[root@zhubiao boot]# mkinitrd /mnt/boot/initramfs-`uname -r`.img `uname -r`
[root@zhubiao boot]# ls /mnt/boot/initramfs-4.12.4.img


第五步 创建 grub.conf 文件
自己编写gurb.conf文件,用于生成系统启动菜单,告知内核根目录所在的分区等信息。

[root@zhubiao boot]# cat /mnt/boot/grub/grub.conf
default=0
timeout=3
title MiniLinux 4.12.4
root (hd0,0)
kernel /vmlinuz-4.12.4 root=UUID=2bf95a1d-4053-489e-9968-74628aeb24e4 selinux=0 init=/bin/bash
initrd /initramfs-4.12.4.img

第六步 创建一级目录
现在我们需要创建MiniLinux 的“/” 下的一级目录, 我们计划sdb2作为 MiniLinux 的根分区,现在sdb2 挂载到了宿主机的/mnt/root 目录下,所以现在在/mnt/boot目录下创建一级目录

[root@zhubiao root]# mkdir /mnt/root/{bin,boot,etc,lib,lib64,usr,prc,sys,mnt,sbin,var,dev,root}
[root@zhubiao root]# tree /mnt/root/
/mnt/root/
├── bin
├── boot
├── dev
├── etc
├── lib
├── lib64
├── lost+found
├── mnt
├── prc
├── root
├── sbin
├── sys
├── usr
└── var


第七步 复制命令 网卡模块

我们计划从宿主机上复制一些常用的命令和网卡模块到MiniLinux系统上,命令可能使用到共享库文件,所以复制命令的时候需将命令所依赖的库一起复制到相应的目录下,为了实现此功能,我编写了一个脚本。

  • 复制命令 : bash, ifconfig,ls,ping,vi, mount,umount,ps,pstree, insmod,hexdump,cp,rm,df,lsblk,insmod,rmmod
  • 复制网卡模块 :e1000

复制命令及所依赖库的脚本

[root@zhubiao root]# cat ~/cpcmd.sh 
#!/bin/bash

#---------------------------------------
# Author: zhuenbiao
# File Name: cpcwd.sh
# Create Time: 2017-08-18 12:34:56
# Modified Time: 2017-08-18 12:34:56
# Version: 0.1
# Description:
#---------------------------------------

# Copy command
cpcmd() {
local cmdpath cmddir scmd

if which $1 &> /dev/null ; then
scmd=`which $1`
cmdpath="${2}${scmd}"
cmddir=${cmdpath%/*}
else
echo -e "The $1 is not a command\n"
return 1
fi
[ -d "$cmddir" ] || mkdir -p "$cmddir"
[ -f "${cmdpath}" ] && {
echo "$scmd is exisits"
} || {
cp "$scmd" "$cmddir"
echo "$scmd --> $cmdpath"
}
echo

}

# Copy library
cplib() {
local cmdpath libpath libdir
cmdpath=`which $1` &>/dev/null
for lib in `ldd $cmdpath | egrep "/[^[:space:]]+" -o`;do
libpath="${2}${lib}"
libdir="${libpath%/*}"

[ -d "$libdir" ] || mkdir -p "$libdir"

[ -a "$libpath" ] || {
cp "$lib" "$libdir"
echo "$lib --> $libpath"
}
done
echo
}

# echo Usage
usage() {
echo "---------------------------------------------------"
echo -e "# Usage: Input a command or Enter quit to exit\n"
}

# Input root directory
rootdir=
i=1
while [ -z "$rootdir" ]; do
read -p "Please input a root directory: " rootdir
rootdir=`echo "$rootdir" | egrep -o "^.*[^/]"`
if [ $i -ge 3 ];then
echo "Enter more than 3 times"
exit 1
fi
let i++
done

# Create root directory
[ -f "$rootdir" ] || mkdir -p "$rootdir" &> /dev/null

while true;do
usage
j=1
cmd=
while [ -z "$cmd" ]; do
read -p "Please input a command: " cmd
if [ $j -ge 3 ];then
echo "Enter more than 3 times"
exit 1
fi
let j++
done

case "$cmd" in
[q|Q]|[qQ][uU][iI][tT])
exit
;;
*)
cpcmd "$cmd" "$rootdir"

[ "$?" -ne 0 ] && continue

cplib "$cmd" "$rootdir"
;;
esac
done

使用脚本复制命令

[root@zhubiao root]# bash ~/cpcmd.sh 
Please input a root directory: /mnt/root
---------------------------------------------------
# Usage: Input a command or Enter quit to exit

Please input a command: ls
/bin/ls --> /mnt/root/bin/ls

/lib64/libselinux.so.1 --> /mnt/root/lib64/libselinux.so.1
/lib64/librt.so.1 --> /mnt/root/lib64/librt.so.1
/lib64/libcap.so.2 --> /mnt/root/lib64/libcap.so.2
/lib64/libacl.so.1 --> /mnt/root/lib64/libacl.so.1
/lib64/libc.so.6 --> /mnt/root/lib64/libc.so.6
/lib64/libdl.so.2 --> /mnt/root/lib64/libdl.so.2
/lib64/ld-linux-x86-64.so.2 --> /mnt/root/lib64/ld-linux-x86-64.so.2
/lib64/libpthread.so.0 --> /mnt/root/lib64/libpthread.so.0
/lib64/libattr.so.1 --> /mnt/root/lib64/libattr.so.1

---------------------------------------------------
# Usage: Input a command or Enter quit to exit

Please input a command: bash
/bin/bash --> /mnt/root/bin/bash

/lib64/libtinfo.so.5 --> /mnt/root/lib64/libtinfo.so.5
...

复制后的命令目录结构如下所示

[root@zhubiao root]# tree /mnt/root/
/mnt/root/
├── bin
│ ├── bash
│ ├── cp
│ ├── df
│ ├── ls
│ ├── lsblk
│ ├── mount
│ ├── ping
│ ├── ps
│ ├── rm
│ ├── umount
│ └── vi
├── boot
├── dev
├── etc
├── lib
├── lib64
│ ├── ld-linux-x86-64.so.2
│ ├── libacl.so.1
│ ├── libattr.so.1
│ ├── libblkid.so.1
│ ├── libcap.so.2
│ ├── libc.so.6
│ ├── libdl.so.2
│ ├── libidn.so.11
│ ├── libproc-3.2.8.so
│ ├── libpthread.so.0
│ ├── librt.so.1
│ ├── libselinux.so.1
│ ├── libsepol.so.1
│ ├── libtinfo.so.5
│ └── libuuid.so.1
├── lost+found
├── mnt
├── prc
├── root
├── sbin
│ ├── blkid
│ ├── ifconfig
│ ├── insmod
│ └── rmmod
├── sys
├── usr
│ └── bin
│ ├── hexdump
│ └── pstree
└── var

复制网络驱动模块e1000

# 创建目录用于存放模块
[root@zhubiao root]# mkdir /mnt/root/lib/modules/4.12.4/ -p

[root@zhubiao root]# cp /lib/modules/4.12.4/kernel/drivers/net/ethernet/intel/e1000/e1000.ko /mnt/root/lib/modules/4.12.4/

第八步 将硬盘拆下放到新的主机上,启动系统

如图所示,启动系统后进入了bash环境下,进行如下测试:

  • 使用命令 insmod 加载网络模块
  • 使用ifconfig 配置临时ip地址。
  • 使用 ping 命令测试网络是否能连同

Alt text