作者LoganChien (简子翔)
看板b97902HW
标题[教学] 用 qemu + chroot 编译与测试 Linux Kernel
时间Fri Oct 22 06:18:47 2010
用 qemu + chroot 编译与测试 Linux Kernel
听说有人光是编译 Linux Kernel 就编译了 4 个小时,
也就是说若以 10/28 为 deadline 各位只剩下 42 次
修改档案的机会...。
-- 来自地下室的哀号
序言
我个人非常不喜欢编译 kernel,因为 kernel 是作业系统当中最重要
的部分。如果 kernel 编坏了可能无法开机,而且可能弄乱原有的作业
系统。
如果万不得已要自己编译,我通常会使用 fakeroot make-kpkg 之类
的指令把 kernel 编译成 Debian 套件,再用 dpkg -i 安装。日後要
移除也比较容易。
然而我日常的作业系统就是 Ubuntu,我不希望 OS Project 1 染指我
每天要使用的系统。而且一直重新开机测试修改成果也非常累,所以
我还是比较想要使用虚拟机器 (Virtual Machine)。
我这次要介绍二个工具:qemu-kvm 与 chroot,这二个工具都是用来
创造和原本作业系统相互隔离的环境,也各有长处与短处,所以我的
攻略会交互使用二个工具。
-
qemu / qemu-kvm
这是一个虚拟机器,就像 VMWare, Virtual Box, Virtual PC 等软体,
可以用来模拟一台虚拟的电脑。
qemu 是使用 Dynamic Recompilation 模拟 CPU,不过这种技术执行速
度约为 Host CPU 的 20%。而 qemu-kvm 使用 paravirtualization,会
利用 Intel/Amd CPU 提供的虚拟机制加快模拟速度,执行速度约为 50~
80%。
-
chroot
这个是 Linux 之下的一个程式,可以帮我们把根目录替换成另一个目录,
就好像使用不同的 Linux(gcc、bash 甚至是 mv cp 等指令都会被换掉)。
在 chroot 环境下执行程式与一般环境执行程式无异,所以速度是 100%。
然而
chroot 不能完全隔离所有操作,所以要小心使用(避免使用 root
身分,或者是 sudo),以免影响原有的作业系统。
准备工作
1.
先有一套 Linux(推荐使用
Ubuntu Linux 或 Debian Linux)
以便使如 qemu-kvm 与 chroot
2. 准备一份 Linux 安装
光碟映像档(我自己是用 Ubuntu 10.10)
3. 取得 qemu 与 qemu-kvm,在 Ubuntu Linux 之下,可以使用以
下指令取得:
$ sudo apt-get install qemu qemu-kvm
4.
取得一份 Linux Kernel source code(我是用 2.6.36)
http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.36.tar.bz2
准备子系统
1. 建立虚拟机器的硬碟档案(至少要 10Gb)
$ dd if=/dev/zero of=disk.img bs=1 count=1 seek=10737418239
稍微说明一下:为了使用 chroot,我们不使用 qemu 特有可以动
态成长的硬碟格式。不过这里还是使用了一个
小技巧:直接把档
案 seek 到 10Gb - 1byte 的地方,写入一个 byte 以节省时间。
2. 用 qemu 开启子系统
$ qemu -hda disk.img -m 1024 \
-cdrom ubuntu10.10.iso -boot d
参数说明:
-hda 以此档案为主硬碟
-cdrom 以此档案为光碟映像档
-boot 开机选项 (c) 硬碟 (d) 光碟
-m 虚拟机器要有多少记忆体 (MB)
3.
依指示安装作业系统。不过要注意:在割硬碟的时候,
把整块都割
给 root (/),不要留 swap。(要选「专家模式」然後手动切割)
4. 关机时,要我们取出光碟,此时就可以把 qemu 关掉了!
5. 重新开机
$ qemu -hda disk.img -m 1024
6. 在子系统安装必要的软体(打开「应用程式->附属应用程式->终端机」)
\> sudo apt-get build-dep linux # 编译 linux 必备
\> sudo apt-get install libncurses5-dev # menuconfig 必备
7. 关机
在主系统挂载子系统的分割区
我想在主系统编译 kernel,所以我要把子系统的分割区挂载到主系
统上,再使用 chroot 把 root 切换到这一个分割区。
1. 先检查分割表计算分割区的 offset:
$ sudo losetup /dev/loop0 disk.img
$ sudo fdisk /dev/loop0
Command (m for help):
p # 用 p 列出分割表的资讯
Disk /dev/loop0: 8589 MB, 8589934592 bytes
255 heads, 63 sectors/track, 1044 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes # 记住 sector 的大小
I/O size (minimum/optimal): 512 bytes / 512 bytes
Command (m for help):
x # 进入专家模式
Expert command (m for help):
p # 再列出分割表的资讯
Disk /dev/loop0: 255 heads, 63 sectors, 1044 cylinders
Nr AF Hd Sec Cyl Hd Sec Cyl Start Size ID
1 80 32 33 0 86 53 993 2048 15955968 83 # 记住 start
Expert command (m for help):
q
$ sudo losetup -d /dev/loop0
透过以上步骤,我们知道一个 sector 的大小是 512byte,另外
子系统的 root partition 是在 2048 个 sector,所以 offset
是 512 * 2048 = 1048576 (请根据你自己的情况加以修改)
2. 挂载子系统的 root 分割区
$ mkdir partition
$ sudo losetup --offset 1048576 /dev/loop0 disk.img # 把档案当 loop device
备注:如果这一步有问题,可以试不同的 loop device:loop0 ~ loop7。
$ sudo mount /dev/loop0 partition # 挂载档案系统
备注:如果这一步有问题,大概是你的 offset 算错了!
3. 解压缩 Linux Kernel 的原始码
$ sudo chown username:username partition/usr/src
备注:暂时修改资料夹的权限。username 是你主系统档中
uid 为 1000 的使用者。一般来说应该就是你自己。
$ tar jxf linux-2.6.36.tar.bz2 -C partition/usr/src
说明:解压缩 Linux Kernel source code 到 partition/usr/src
资料夹之下。
$ sudo chown root:root partition/usr/src
备注:把权限改回来。
编译 linux 核心
1.
使用 chroot 进入子系统。要小心,chroot 的隔离不是绝对
安全的!只要有 root 权限,随时可以离开 chroot 的隔离
环境,所以要避免使用 root。
$ sudo chroot partition env -i TERM=xterm su username
参数说明:partition 是子系统的根目录,也就是我们要切换
的目标,後面是紧接着要执行的指令。
env -i 会把不相关的环境变数清掉。而 TERM=xterm 是因为
menuconfig 需要这个环境变数。
su 是把使用者切换成 username。这个 username 是子系统的
user,应该是你安装子系统时填入的名字。
2.
复制 Linux Kernel 设定档
\> cp /boot/config-2.6.35-22-generic .config
3.
执行 menuconfig。不用调整任何选项,直接 Exit 就可以了。
\> make menuconfig
4.
开始编译 Kernel 与模组
\> make -j4 bzImage modules
备注:根据每个人电脑的不同,要使用不同的 -jN。我的电
脑是双核心的 cpu,所以我觉得
-j4 很适合我。大家的电脑
应该都比我好,可以用更大的数字(但适量就好,并不是越大
越快)。
备注:
用笔电的人记得把电源接上,让 CPU 可以全速工作。
备注:在我的笔电上这一步大概要 50 分钟到 1 小时。
安装新核心
编译完成之後,我们要安装核心。不过要安装核心,必需要有 sudo
的权限,所以在 chroot 的环境下不太安全,所以我们还是要改用
qemu 开机。
1. 离开 chroot 环境,并卸载子系统的分割区。
\> exit # 离开 chroot 环境
$ sudo umount partition # 卸载档案系统 (这可能会有一点久)
$ sudo losetup -d /dev/loop0 # 解除档案与 loop device 之间的关连
2. 使用 qemu 开机
$ qemu -hda disk.img -m 1024
3. 开启终端机,依序执行以下指令
\> cd /usr/src/linux-2.6.36
\> sudo make modules_install install
\> sudo mkinitramfs -o /boot/initrd.img-2.6.36 2.6.36
4. 修改 grub2 设定档,
在 GRUB_HIDDEN_TIMEOUT=0 的前面加上 '#'。
\> sudo vi /etc/default/grub
:%s/GRUB_HIDDEN_TIMEOUT=0/#GRUB_HIDDEN_TIMEOUT=0/g
:wq
5. 更新开机选单
\> sudo update-grub2
6. 重新开机
小结
我们使用了二种创造隔离环境的方法:(1) 使用 qemu 当做我们的
虚拟机器 (2) 使用 chroot 隔离主系统与子系统。前者比较安全,
不过比较慢;後者比较快,但是比较不安全。
另外,一般我们要进入 chroot 的环境,我们要 losetup、mount、
chroot,使用完必之後,要再 exit、umount、losetup -d。程序
很烦锁,而且
如果没有 umount、losetup -d,就执行 qemu 就会
有很可怕的结果(很可怕不要问)。所以我写了一个 shell script
自动执行这些步骤:
http://w.csie.org/~b97073/B/enter-partition.sh.txt
下载後,修改 USERNAME、PARTITION_OFFSET 再 chmod +x,即可
使用 ./enter-partition.sh.txt。看到 leaving successfully
才是正常结束。
修改作业系统与重新安装
1.
$ ./enter-partition.sh # 进入 chroot
2.
\> cd /usr/src/linux-2.6.36 # 进入 source code 的资料夹
3. 修改 kernel 的 source code。
(1) kernel 资料夹之下建立 myservice.c 这个档案,并输入
助教给的程式:
#include <linux/linkage.h>
#include <linux/kernel.h>
asmlinkage int sys_myservice(int arg1) {
printk("my service is invoked!\n");
return arg1 * 10;
}
(2) 打开 arch/x86/include/asm/unistd_32.h 找到最後一个 #define __NR_...
在它的最面加上 #define __NR_myservice 341 。然後把下面的 NR_syscalls
改成 342。
(3) 打开 arch/x86/kernel/syscall_table_32.S,直接移到
最後一行,加上 .long sys_myservice
(4) 打开 include/linux/syscalls.h,移到最後,#endif 之前,加上
asmlinkage int sys_myservice(int arg1);
(5) 打开 kernel/Makefile,在一开始的地方加上
obj-y += myservice.o
4.
\> make -j4 bzImage modules
备注:如果有修改 arch/x86/include/asm/unistd_32.h,
那几乎所有的 source code (bzImage 与 modules) 都要重
新编译,又是一个小时... ,所以要尽量避免。
备注:如果只改 kernel/myservice.c kernel/signal.c
include/linux/syscalls.h 重新编译的时间不超过 10 分钟。
备注:没事千万不要 make clean!!
5.
\> exit # 离开 chroot
6.
$ qemu -hda disk.img -m 1024
7.
\> cd usr/src/linux-2.6.36
\> sudo make modules_install install # 安装新的 os
\> sudo update-initramfs -u -k 2.6.36 # 更新 initramfs
\> sudo reboot
8. 写一个测试用的程式:
#include <stdio.h>
#include <stdlib.h>
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#define __NR_myservice 341
int main(int argc, char *argv[])
{
int num = (argc > 1) ? atoi(argv[1]) : 1234;
printf("%d\n", (int)syscall(__NR_myservice, num));
return EXIT_SUCCESS;
}
然後怀着敬畏的心情,compile 并执行它!祝你好运!
9. Project 1 的部分,就如投影片所说,要在
SYSCALL_DEFINE2(kill
加料,而 sent signal 可以在
__send_signal 加料。
Q & A
Q: 喂 logan,我装了 qemu-kvm 之後,Virtual Box 就坏掉了,
你要负责!
A: qemu-kvm 相冲!所以如果你要用 Virtual Box 必需要把
qemu 与 qemu-kvm 移除。
$ sudo apt-get purge qemu qemu-kvm
Q: 怎麽看 printk 的结果?
A: sudo dmesg | tail -n 10
Q: 为什麽我按下 Alt-F4 之後,qemu 就关掉了,我要关的是里面的
视窗呀!
A: 按下左方的 Ctrl+Alt 可以锁定键盘还有滑鼠。
结语
这份作业蛮有趣的!
如果有人在结报上提到这篇文章我会很高兴的。 ^^
\
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 140.112.30.131
※ 编辑: LoganChien 来自: 140.112.30.131 (10/22 06:19)
1F:推 purincess:推一个! 10/22 07:32
2F:推 davidpanda:L大强者救援文出现了 @.@! 10/22 08:18
3F:推 fereshte:大强者!!! 10/22 09:28
4F:推 jimmyken793:Q&A第一个的暂时性解法:sudo rmmod kvm-intel 10/22 09:33
5F:→ jimmyken793:如果是intel cpu的话 10/22 09:34
6F:推 jlg79531:谢谢Logan!! 10/22 10:18
7F:推 lmr3796:救援来了! 10/22 11:03
8F:推 hanabi:有看有推!! 10/22 18:56
9F:推 andy74139:推!! :) 10/23 09:49
※ 编辑: LoganChien 来自: 61.224.103.133 (10/23 12:03)
10F:推 averangeall:有看有推!!! 12/16 12:43