看板 b97902HW 關於我們 聯絡資訊
用 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)
purincess:推一個! 10/22 07:32
davidpanda:L大強者救援文出現了 @.@! 10/22 08:18
fereshte:大強者!!! 10/22 09:28
jimmyken793:Q&A第一個的暫時性解法:sudo rmmod kvm-intel 10/22 09:33
jimmyken793:如果是intel cpu的話 10/22 09:34
jlg79531:謝謝Logan!! 10/22 10:18
lmr3796:救援來了! 10/22 11:03
hanabi:有看有推!! 10/22 18:56
andy74139:推!! :) 10/23 09:49
※ 編輯: LoganChien 來自: 61.224.103.133 (10/23 12:03)
averangeall:有看有推!!! 12/16 12:43