本文目标是构建随处可用的内核开发环境,用于Linux内核调度器的研究。开发环境的开发套件由docker,ubuntu14.04,qemu,vim,zsh等构成。

按照该开发环境的套件要求,将在docker中构建ubuntu14.04系统,并构建内核编译和源码查看环境。主要步骤如下:

  1. 基于Docker安装Ubuntu-14.04
  2. 登录Docker中的Ubuntu-14.04
  3. 在ubuntu中安装qemu,vim,zsh
  4. 下载Linux内核源码,并编译
  5. 创建磁盘,启动编译后的内核

1. 基于Docker安装Ubuntu-14.04。指令如下所示:

axel -n 10 https://download.openvz.org/template/precreated/ubuntu-14.04-x86_64.tar.gz

cat ubuntu-14.04-x86_64.tar.gz | docker import - ubuntu:14.04

docker images

2. 登录Docker中的ubuntu-14.04

docker run -p 301:22 -d --name test ubuntu /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf

ssh root@127.0.0.1 -p 301

3. 在ubuntu中安装qemu,vim,zsh

sudo apt-get install qemu vim zsh axel wget curl

4. 下载Linux内核源码,并编译

axel -n 10 https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.18.tar.gz

tar -zxfv linux-4.18.tar.gz

cd linux-4.18

关于Linux内核编译,可以参考博客。具体过程如下:

make help  # 查看make参数
make x86_64_defconfig

结果发现,缺乏编译器,此外,在编译过程中,还发现缺乏依赖库。因此,一次安装如下依赖库

sudo apt-get install bison flex qemu bc build-essential gcc g++ libelf-dev libssl-dev
make x86_64_defconfig
make bzImage  # 编译内核
make modules # 编译模块

编译完成之后,将生成内核压缩文件arch/x86/boot/bzImage

5. 创建磁盘,启动编译后的内核

起初,打算直接在docker中用qemu启动bzImage,然而,出现了图形化界面使用错误的情况。

root# qemu-system-x86_64 -m 512M -smp 4 -kernel ./bzImage
Could not initialize SDL(No available video device) - exiting

鉴于这样的情况,有两种方案:

(1)在qemu启动时使用-curses参数,可直接启动(亲测有效)

(2)将容器中编译好的压缩内核bzImage复制(docker cp)到本地host系统中,基于qemu启动。

基于第二种方案构建的开发环境,缺少了docker一贯的隔离性,即要求host系统中安装qemu环境,且主机系统和虚拟机系统的内容来回传递。因此,直接使用第一种方案,但是需要另外连接的终端终止当前qemu程序才能重新回到容器的shell中。

在启动编译后的压缩内核时,出现无法找到文件系统的错误:

怎么容容器内部编辑文件 容器编译_Linux kernel

为此,需要创建磁盘镜像,制作根文件系统,且准备init程序才能启动编译后的内核。

但是在创建磁盘并挂在的过程中,出现无法找到loop设备的错误。

qemu-img create -f raw disk.raw 512M
mkfs -t ext4 ./disk.raw   # 若没有mkfs.ext4,则安装e2fsprogs套件,sudo apt-get install e2fsprogs
sudo mount -o loop ./disk.raw ./img
mount: Could not find any loop device. Maybe this kernel does not know
        about the loop device? (If so, recompile or `modprobe loop'.)

最快捷方便的解决方案就是在docker run时,加入特权:

docker run --privileged=true ...

目前,可以挂在文件系统,但是缺乏启动程序init,因此基于busybox构建启动程序。但是在编译过程中,出现缺少curses.h头文件的情况,这是因为缺少套件ncurses devel。

make defconfig
make menuconfig
In file included from scripts/kconfig/lxdialog/checklist.c:24:0:
 scripts/kconfig/lxdialog/dialog.h:31:20: fatal error: curses.h: No such file or directory
  #include CURSES_LOC
                     ^
 compilation terminated.
 make[2]: *** [scripts/kconfig/lxdialog/checklist.o] Error 1
 make[1]: *** [menuconfig] Error 2
 make: *** [menuconfig] Error 2sudo apt-get install libncurses5-dev # 缺少ncurses devel套件

然后,再次编译时,将busybox编译为静态的二进制文件。然后make编译,并安装到挂在的img目录中。

Busybox Settings --->
              --- Build Options
               [*] Build BusyBox as a static binary (no shared libs)
# make 
# make CONFIG_PREFIX=<path_to_disk_img_mount_point> install
# ls <path_to_disk_img_mount_point>  #可以看到包含了更多目录

怎么容容器内部编辑文件 容器编译_怎么容容器内部编辑文件_02

此外,还需要创建额外的文件,包括rcS,inittab,proc,sys,dev等,具体可以参考博客

以下是直接可用的脚本,需要修改相应的路径。

#!/bin/sh

# note: Linux kernel and busybox are in the same directory
path_to_Linux_kernel=linux-4.18
path_to_busybox=busybox-1.30.0
path_to_root=`pwd`

cd $path_to_Linux_kernel
#make clean
#make distclean
#make x86_64_defconfig
#make bzImage
#make modules

# obtain the bzImage
cp arch/x86/boot/bzImage $path_to_root

# create disk by type ext4
cd $path_to_root
if [ -d img ]; then
    umount img
    rm -rf img
fi

if [ -e disk.raw ]; then
    rm disk.raw
fi

qemu-img create -f raw disk.raw 512M
mkfs -t ext4 disk.raw

mkdir img
sudo mount -o loop disk.raw img  # note that, docker run --privileged=true

# install modules and init program
cd $path_to_Linux_kernel
sudo make modules_install INSTALL_MOD_PATH=../img

cd $path_to_root/$path_to_busybox
if [ -e busybox ]; then
    make CONFIG_PREFIX=../img install
else
    echo "Busybox is not built yet. Please build the busybox first!"
    exit 2
fi

# update the init program
cd $path_to_root/img

mkdir -p etc/init.d
mkdir proc
mkdir sys
mkdir dev

touch etc/init.d/rcS
echo "#/bin/sh" > etc/init.d/rcS
echo "mount -t proc proc /proc" >> etc/init.d/rcS
echo "mount -t sysfs sysfs /sys" >> etc/init.d/rcS
chmod +777 etc/init.d/rcS

# start the bzImage
cd $path_to_root
qemu-system-x86_64 -curses -m 512M -smp 4 -kernel bzImage -drive format=raw,file=disk.raw -append "init=/linuxrc root=/dev/sda"

最后,在docker容器中的ubuntu就可以启动qemu了。

qemu-system-x86_64 -curses -m 512M -smp 2 -kernel bzImage -drive format=raw,file=disk.raw -append "init=/linuxrc root=/dev/sda"

终结

在所有的环境搭建好之后,要保存(commit)当前容器的状态,并推送到远程网络(push),这样就可以在任何一台装有docker的系统中,使用该环境,进行Linux内核开发和学习。

docker commit -m "update with busybox" <container ID> <image name>[:<image tag>]