编译内核并制作微小Linux系统
编译内核并制作微小Linux系统
本博客演示自定义内核,并制作一个简单的Linux系统,目的在于熟悉Linux系统的启动流程
CentOS5 系统启动流程图
编译内核
本实验在CentOS7.3的宿主机上进行编译
第一步: 准备环境, 安装所需的依赖包
- 下载内核
- 安装包组 Development tools
- 从官网下载内核 
 可以直接登录官网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
- 解压 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/
- 安装包组Development Tools
[root@localhost app]# yum -y groupinstall "Development Tools"
- 根据实际情况安装所缺的依赖包 
 当执行编译时,有时会缺少依赖的软件包,根据错误提示安装所缺的软件,我安装的时候提示缺少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 命令基于该模板文件上进行配置。
- 从当前系统目录下拷贝模板文件到目录/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
- 使用 make menuconfig 命令编辑 .config文件,在此基础上配置内核选项
[root@localhost linux-4.12.10]# make menuconfig
启动了如下文本窗口界面,每个选项之前的标志符含义如下:
- [*]
 此项配置将编译到内核文件- /boot/vmlinuz-*中
- [ ]
 此项不编译
- <M>
 此项配置将编译到模块- /lib/modules/VERSION/目录下
 各配置选项的以上状态使用- Tab键切换
此处我们修改内核附加版本名和让内核支持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 选项退出,并保证
第三步: 编译 
使用命令 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 文件系统
- 使用 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 
- 创建 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启动程序,复制命令到根目录下等
- 在当前宿主机上创建两个目录/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命令测试网络是否能连同
