提要

What: 基于虚拟机创建的操作系统(ubuntu18.04),满足实验环境的要求。
Why: 方便学习操作系统,可以通过虚拟机参与对内核的开发编译,运行和调试。
Where: 基于物理机系统(Ubuntu 20.04)对实验环境部署
Who: 适用linux内核学习者
When: 20210506 - 20210508

特此说明: 最近在学习linux内核,刘超的趣谈Linux操作系统是比较重要的参考资料,本文内容也有很多参考的地方;
重要提示: 搭建环境的具体过程,记录到该文档,经过梳理后测试没啥问题,保证可复现。如果你根据这个文档,在实现该环境过程中出现任何问题的话,欢迎留言反馈,将尽快得到回复。

物理机: 创建虚拟机和系统

0、检查前提条件

grep -Eoc '(vmx|svm)' /proc/cpuinfo # 机器是否支持硬件虚拟化(支持: 返回非0; 不支持: 返回0)

sudo apt update
sudo apt install cpu-checker
kvm-ok # 检查虚拟化能力被开启,否则需要配置系统BIOS
#INFO: /dev/kvm exists
#KVM acceleration can be used

1、设置网桥

sudo brctl addbr br0 # 创建 网桥
#sudo tunctl -b # 创建 tap 设备
sudo ip link set br0 up
#sudo ip link set tap0 up
#sudo brctl addif br0 tap0
sudo ifconfig br0 192.168.57.1/24

brctl show # 查看

# 还需要设置 /etc/sysctl.conf 文件中 net.ipv4.ip_forward=1 参数,并且
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE # eth0 为能上网的网卡

2、安装KVM

sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virtinst virt-manager

# 添加你的用户到“libvirt” 和 “kvm” 用户组
sudo usermod -aG libvirt $USER
sudo usermod -aG kvm $USER

3、创建虚拟机

qemu-img create -f qcow2 ubuntutest.img 100G # 创建虚拟机镜像
vim domain.xml # 配置domain.xml文件(用于 qemu 管理虚拟机,内容见末尾)

virsh define domain.xml # 生效
virsh list --all # 查看
virsh start ubuntutest # 启动虚拟机 ubuntutest

ps aux | grep qemu # 查看进程

4、安装ubuntu

# 准备: 获取系统镜像 ubuntu-18.04.2-live-server-amd64.iso
# 方式一: 启动图形界面后,添加系统镜像(ubuntutest -> open -> add Hardware -> 添加CD ROM),配置到 boot options,启动
virt-manager

# 方式二: 如下命令
qemu-system-x86_64 -enable-kvm -name ubuntutest  -m 8192 -hda ubuntutest.img -cdrom ubuntu-18.04.2-live-server-amd64.iso -boot d -vnc :19 # 启动
vncviewer :19 # 登入

虚拟机: 配置内核编译环境

0、前提条件

# 配置网络环境
vim /etc/netplan/50-cloud-init.yaml # ip地址配置文件(静态配置ip可访问外网, 内容见末尾)
netplan apply # 使配置生效
ping www.baidu.com # 测试: 能ping通公网

# 安装依赖环境
apt-get install openssh-server -y # 先配置sshd服务,使用ssh访问
apt-get install libncurses5-dev libssl-dev bison flex libelf-dev gcc make openssl libc6-dev -y

1、下载内核源码

apt-get install linux-source-4.15.0 # 代码下载到 /usr/src/
cd /usr/src/
tar vjxkf linux-source-4.15.0.tar.bz2 # 解压

2、编译内核源码

cd linux-source-4.15.0/
make menuconfig # 定义编译选项: 激活 CONFIG_DEBUG_INFO 和 CONFIG_FRAME_POINTER 选项
vim .config # 查看

nohup make -j8 > make1.log 2>&1 &
nohup make modules_install > make2.log 2>&1 &
nohup make install > make3.log 2>&1 &

3、查看grub.confmenu.lst

vim /boot/grub/grub.cfg # 查看`grub.conf`和`menu.lst`中多出新的内核项
vim /boot/grub/menu.lst # 重启虚拟机, 进入的时候,会出现 GRUB 界面。我们选择 Ubuntu 高级选项,然后选择第一项进去

实验一: 添加virsh console登入ubuntutest控制台功能

说明场景: 为了使用virsh工具登入ubuntutest控制台(避免使用nvcviewer,比较麻烦),需要在虚拟机系统中修改grub.confmenu.lst两个文件,会在系统启动过程中生效。
1、虚拟机: 配置grub.confmenu.lst

# 在 grub.cfg 中,在 submenu ‘Advanced options for Ubuntu’ 这一项,在这一行的 linux /boot/vmlinuz-4.15.0-55-generic root=UUID=470f3a42-7a97-4b9d-aaa0-26deb3d234f9 ro console=ttyS0 maybe-ubiquity 中,加上了 console=ttyS0。
vim /boot/grub/grub.cfg

# 在 menu.lst 文件中,在 Ubuntu 18.04.2 LTS, kernel 4.15.0-55-generic 这一项,在 kernel /boot/vmlinuz-4.15.0-55-generic root=/dev/hda1 ro console=hvc0 console=ttyS0 这一行加入 console=ttyS0。
vim /boot/grub/menu.lst

# 让配置生效
shutdown -h now # (虚拟机)关机ubuntutest
virsh start ubuntutest # (宿主机)重启ubuntutest

2、宿主机: 进入虚拟机控制台

virsh console ubuntutest
#Connected to domain ubuntutest
#Escape character is ^]

实验二: 添加系统调用和测试应用程序

说明场景: 做实验,在内核代码上添加一个系统调用, 然后编译内核; 选择新内核起来后,在写对应的用户程序调用它,是否能跑通? 通过log查看测试结果
1、内核层添加系统调用

# 编辑
cd /usr/src/linux-source-4.15.0/
vim arch/x86/entry/syscalls/syscall_64.tbl # 向系统调用表添加记录, 系统调用号以及相应的处理函数(内容见末尾)
vim include/linux/syscalls.h # 向系统调用头文件, 添加系统调用函数声明(内容见末尾)
vim kernel/sys.c # 添加系统调用函数的实现(内容见末尾)

# 编译内核
nohup make -j8 > make1.log 2>&1 &
nohup make modules_install > make2.log 2>&1 &
nohup make install > make3.log 2>&1 &

# 启动新内核: 重启虚拟机, 进入的时候,会出现 GRUB 界面。我们选择 Ubuntu 高级选项,然后选择对应的内核版本进入

2、用户层添加测试程序

# 编辑
vim demo.c # 内容见末尾

# 编译
gcc demo.c -o demo

# 运行
./demo
#return 63 from kernel mode.

3、验证结果

hinzer@pc:~$ tail -f /var/log/syslog
May  7 07:57:24 pc kernel: [   55.807520] User Mode says I am liuchao from user mode. to the Kernel Mode!

虚拟机: 配置内核调试环境(gdb)

用 debug进程 的方式 debug内核 ,尝试配置这样的实验环境

1、准备

# 1. 配置内核支持debug: 激活 CONFIG_DEBUG_INFO 和 CONFIG_FRAME_POINTER 选项
# 2. 安装gdp工具到宿主机
sudo apt-get install gdb
# 3. 拷贝内核源码到宿主机
scp -r hinzer@192.168.57.100:/usr/src/linux-source-4.15.0 ./
# 4. 找到gdb运行的内核二进制文件: /boot/vmlinuz-xxx (或者源码目录下的 vmlinux)

2、配置qemu启动参数

virsh edit ubuntutest # 修改虚拟机定义文件(见末尾)

3、配置内核启动参数

# 在 grub.cfg 中,在 submenu ‘Advanced options for Ubuntu’ 这一项,在这一行的 linux /boot/vmlinuz-4.15.0-55-generic root=UUID=470f3a42-7a97-4b9d-aaa0-26deb3d234f9 ro console=ttyS0 maybe-ubiquity 中,加上了 nokaslr
vim /boot/grub/grub.cfg

# 在 menu.lst 文件中,在 Ubuntu 18.04.2 LTS, kernel 4.15.0-55-generic 这一项,在 kernel /boot/vmlinuz-4.15.0-55-generic root=/dev/hda1 ro console=hvc0 console=ttyS0 这一行加入 nokaslr
vim /boot/grub/menu.lst

# 让配置生效
shutdown -h now # (虚拟机)关机ubuntutest
virsh start ubuntutest # (宿主机)重启ubuntutest

4、测试: 在线内核调式

gdb vmlinux
(gdb) b sys_sayhelloworld 
(gdb) target remote :1234 # attach 到内核
(gdb) c
Continuing.
# 虚拟机上执行: ./demo 通过系统调用 sys_sayhelloworld ,触发断点
#Thread 1 hit Breakpoint 1, sys_sayhelloworld (words=0x564c5e72e7c4 "I am liuchao from user mode.", count=29) at kernel/sys.c:2601

配置文件

<1> domain.xml

<domain type='kvm'>
  <name>ubuntutest</name>
  <uuid>0f0806ab-531d-6134-5def-c5b4955292aa</uuid>
  <memory unit='GiB'>8</memory>
  <currentMemory unit='GiB'>8</currentMemory>
  <vcpu placement='static'>3</vcpu>
  <os>
    <type arch='x86_64' machine='pc-i440fx-trusty'>hvm</type>
    <boot dev='hd'/>
  </os>
  <features>
    <acpi/>
    <apic/>
    <pae/>
  </features>
  <clock offset='utc'/>
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>restart</on_crash>
 <devices>
    <emulator>/usr/bin/kvm</emulator>
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2'/>
      <source file='/home/hinzer/source/ubuntutest/ubuntutest.img'/>
      <target dev='vda' bus='virtio'/>
    </disk>
    <controller type='pci' index='0' model='pci-root'/>
    <interface type='bridge'>
      <mac address='fa:16:3e:6e:89:ce'/>
      <source bridge='br0'/>
      <target dev='tap1'/>
      <model type='virtio'/>
    </interface>
    <serial type='pty'>
      <target port='0'/>
    </serial>
    <console type='pty'>
      <target type='serial' port='0'/>
    </console>
    <graphics type='vnc' port='-1' autoport='yes' listen='0.0.0.0'>
      <listen type='address' address='0.0.0.0'/>
    </graphics>
    <video>
      <model type='cirrus'/>
    </video>
  </devices>
</domain>

<2> 50-cloud-init.yaml

network:
    ethernets:
        ens3:
                addresses: [192.168.57.100/24]
                gateway4: 192.168.57.1
                dhcp4: no
                nameservers:
                        addresses: [8.8.8.8,114.114.114.114]
                optional: true
    version: 2

<3> 系统调用记录(arch/x86/entry/syscalls/syscall_64.tbl)

332     common  statx                   sys_statx
333     64      sayhelloworld           sys_sayhelloworld

<4> 系统调用声明(include/linux/syscalls.h)

asmlinkage long sys_statx(int dfd, const char __user *path, unsigned flags,
                          unsigned mask, struct statx __user *buffer);

asmlinkage int sys_sayhelloworld(char * words, int count);

<5> 系统调用实现(kernel/sys.c)

asmlinkage int sys_sayhelloworld(char * words, int count){
  int ret;
  char buffer[512];
  if(count >= 512){
    return -1;
  }
  copy_from_user(buffer, words, count);
  ret=printk("User Mode says %s to the Kernel Mode!", buffer);
  return ret;
}

<6> 测试程序(demo.c)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/kernel.h>
#include <sys/syscall.h>
#include <string.h>

int main ()
{
  char * words = "I am liuchao from user mode.";
  int ret;
  ret = syscall(333, words, strlen(words)+1);
  printf("return %d from kernel mode.\n", ret);
  return 0;
}

<7> edit-ubuntutest


<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
  <name>ubuntutest</name>
......
......
  </devices>
  <qemu:commandline>
    <qemu:arg value='-s'/>
  </qemu:commandline>
</domain>

参考资料

  • 如何在 Ubuntu 20.04 上安装 KVM - 雪梦科技
  • 60 | 搭建操作系统实验环境(上)- 极客时间/刘超
  • 49 | 虚拟机:如何成立子公司,让公司变集团? - 极客时间/刘超
  • Ubuntu开机进入/不进入grub界面 - CSDN
  • 61 | 搭建操作系统实验环境(下) - 极客时间/刘超