使用 qemu 搭建內核開發環境

本文主要介紹在 MacOS 上使用 qemu 搭建 Linux Kernel 的開發環境。(在開始以前須要注意的是,本文中的 Linux 開發環境是一個遠程服務器,而 qemu 被安裝在本地的 MacOS 上。一般並不須要這樣折騰,直接將 qemu 安裝在 Linux 中更加方便,並且 qemu 是能夠 -nographic 無圖形界面運行的。)html

1. 爲何須要 qemu?

qemu 是一個硬件虛擬化程序( hypervisor that performs hardware virtualization),與傳統的 VMware / VirtualBox 之類的虛擬機不一樣,它能夠經過 binary translation 模擬各類硬件平臺(好比在 x86 機器上模擬 ARM 處理器)。而 VirtualBox 等更可能是經過虛擬化來進行資源隔離,以便在其上運行多個 guest os。linux

基於 qemu 的硬件模擬能力,咱們能夠輕鬆搭建指定硬件平臺的運行實驗環境。git

qemu 與 VirtualBox 另外一個不一樣點在於,在 VirtualBox 上必須安裝一個完整的操做系統套件,而經過 qemu 咱們能夠經過參數直接啓動到一個裸的 Linux Kernel,連 bootloader 都不須要關心。在此以外,按需配置相關工具套件與啓動好的 Kernel 一塊兒工做便可。shell

qemu 提供的這種高度可定製化的『白盒』能力,使得咱們能夠按需構建快速、輕量級的開發環境,提供流暢的開發體驗。bash

2. 環境準備

首先,爲了進行內核開發,須要一個現成的 Linux 操做系統環境。能夠是一個經過 ssh 工做的遠程 Linux Server,或者也能夠在 MacOS 上經過 VirtualBox (或者使用 qemu 也能夠)安裝一個虛擬機用於開發。VirtualBox 的安裝和 Linux Guest OS 的安裝配置此處略過不提。服務器

接下來,安裝 qemu。在 MacOS 上可使用 Homebrew 包管理工具進行安裝(本文使用的 qemu 版本爲 2.9.0_2):app

brew install qemu

安裝完成後,能夠看到系統中有不少個 qemu-system- 開頭的命令,用於模擬各類硬件平臺,好比 qemu-system-x86_64 。運行其中一個命令來驗證安裝是否成功:ssh

qemu-system-x86_64

上述命令會啓動一個相似 VirtualBox 虛擬機啓動時的窗口。固然,因爲咱們沒有指定任何設備,最終會提示找不到可啓動設備。ide

3. 編譯內核

按需編譯內核,此處只進行簡單說明(基於內核 v4.13)。工具

3.1 內核編譯配置

能夠先執行 make help 能夠查看 make 支持哪些 target。

一般先進行內核編譯配置:

make menuconfig

會啓動一個基於文本的配置界面進行各類選項、模塊、驅動等配置。或者也能夠直接使用目標平臺默認的配置,如針對 x86_64 平臺(後續平臺相關的地方均以 x86_64 爲例進行說明)可使用:

make x86_64_defconfig

配置完成後相應的配置項會保存在 .config 文件中。下一次執行 make menuconfig 時能夠 load 這份配置文件,在此基礎上進行修改。

3.2 編譯內核和模塊

咱們構建一個壓縮過的內核鏡像:

make bzImage

編譯成功後,bzImage 文件將出如今 arch/x86_64/boot/bzImage。記住文件路徑或者拷貝到一個方便的路徑,便於後續啓動時使用。
接下來,編譯在配置階段選擇的內核模塊:

make modules

編譯好的內核模塊 *.ko 文件存在於模塊對應的源碼目錄中。

4. 啓動內核

編譯好內核之後,咱們就可使用 qemu 啓動內核了。只須要使用 -kernel 參數告訴 qemu 內核文件的位置便可:

qemu-system-x86_64 \
    -m 512M \  # 指定內存大小
    -smp 4\  # 指定虛擬的 CPU 數量
    -kernel ./bzImage  # 指定內核文件路徑

上述命令假設編譯好的 bzImage 內核文件就存放在當前目錄下。由於以前編譯好的內核文件是在 VirtualBox 的虛擬機中(或者在遠程服務器上),而 qemu 在本地 MacOS 上,能夠經過 VirtualBox 的 share folder 來共享目錄,或者使用 NFS 共享,甚至簡單使用 rsync 來在二者之間同步文件。後續關於文件同步與共享再也不贅述。

不出意外的話,就能夠在啓動窗口中看到內核的啓動日誌了。在內核啓動的最後,會出現一條 panic 日誌:

Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0, 0)

從日誌內容能夠看出,內核啓動到必定階段後嘗試加載根文件系統,但咱們沒有指定任何磁盤設備,因此沒法掛載根文件系統。並且上一節中編譯出來的內核模塊如今也沒有用上,內核模塊也須要存放到文件系統中供內核須要的時候進行加載。

因此,接下來須要製做一個磁盤鏡像文件供內核做爲根文件系統加載。

5. 製做磁盤鏡像

如上一節所述,須要製做一個磁盤鏡像文件做爲根文件系統供內核加載,同時也用於存放編譯好的內核模塊,以及後續所需的各類配套工具程序。

5.1 建立磁盤鏡像文件

使用 qemu-img 建立一個 512M 的磁盤鏡像文件:

qemu-img create -f raw disk.raw 512M

如今 disk.raw 文件就至關於一塊磁盤,爲了在裏面存儲文件,須要先進行格式化,建立文件系統。好比在 Linux 系統中使用 ext4 文件系統進行格式化:

mkfs -t ext4 ./disk.raw

5.2 掛載磁盤鏡像文件

格式化完成以後,能夠在 Linux 系統中以 loop 方式將磁盤鏡像文件掛載到一個目錄上,這樣就能夠操做磁盤鏡像文件中的內容了。
下面的命令將磁盤鏡像文件掛載到 img 目錄上:

sudo mount -o loop ./disk.raw ./img

5.3 安裝內核模塊

如今能夠將以前編譯好的內核模塊安裝到磁盤鏡像中了。命令以下:

sudo make modules_install \ # 安裝內核模塊
INSTALL_MOD_PATH=./img  # 指定安裝路徑

執行完成後便可在 ./img/lib/modules/ 下看到安裝好的內核模塊。

5.4 使用磁盤鏡像文件做爲根文件系統

準備好磁盤鏡像文件後,使用下面的命令再次啓動 qemu:

qemu-system-x86_64 \
    -m 512M \
    -smp 4\
    -kernel ./bzImage \
    -drive format=raw,file=./disk.raw \  # 指定文件做爲磁盤
    -append "root=/dev/sda"  # 內核啓動參數,指定根文件系統所在設備

這一次,內核再也不報根文件系統找不到了。可是報了另外一個錯誤:

Kernel panic - not syncing: No working init found. Try passing init= option to Kernel. See Linux Documentation/admin-guide/init.rst for guidance.

這說明內核啓動已經接近完成了,準備啓動 1 號進程,也就是 init 進程。但咱們的啓動參數裏面沒有指定 init 選項,並且磁盤鏡像中也沒有相應的 init 程序。所以,接下來須要準備一個 init 程序供內核啓動。

6. 準備 init 程序

經常使用的 init 程序有下面幾種:

  • sysv init:傳統 Linux 系統中最經常使用的 init 程序
  • systemd:目前最流行的 init 程序,不少主流發行版都已經切換到 systemd。systemd 針對 sysv init 啓動速度慢、沒法並行以及管控能力弱等問題進行了從新設計。參見 Rethinking PID 1
  • busybox init:通知用在嵌入式等小型系統中。除了 init 程序外,busybox 還包含了不少經常使用的命令工具,好比 lscat 等。busybox 很是輕量級,能夠編譯出徹底獨立無依賴的 busybox 套件。

這裏選用 busybox 做爲 init 程序及其它命令工具的提供者。

6.1 編譯 busybox

下載 busybox 的源碼到 Linux 系統中,準備進行編譯,這裏使用的 busybox 版本爲 1.27.2。

busybox 的編譯流程與內核很像,這裏咱們基於默認配置進行編譯。首先,執行以下命令讓默認配置生效:

make defconfig

接下來,在默認配置的基礎上進行定製:

make menuconfig

這裏有一個重要的配置,由於 busybox 將被用做 init 程序,並且咱們的磁盤鏡像中沒有任何其它庫,因此 busybox 須要被靜態編譯成一個獨立、無依賴的可執行文件,以避免運行時發生連接錯誤。配置路徑以下:

Busybox Settings --->
       --- Build Options
       [*] Build BusyBox as a static binary (no shared libs)

最後,配置完成後執行編譯:

make

編譯完成後在當前目錄下能夠看到 busybox 可執行文件,查看大小才 2.5M 左右。整個 busybox 套件只有這一個可執行文件,裏面包含了若干工具。好比:

./busybox ls -l
./busybox ps

6.2 安裝 busybox 到磁盤鏡像

編譯好 busybox 以後須要將其安裝到磁盤鏡像中以供使用。執行以下命令進行安裝:

make CONFIG_PREFIX=<path_to_disk_img_mount_point> install

CONFIG_PREFIX 用於指定安裝路徑,須要指定到以前磁盤鏡像文件的掛載目錄,好比 ./img。進入磁盤鏡像掛載目錄查看,常見的文件系統結構已經創建起來了。查看 bin 和 sbin 目錄下的命令,能夠看到都是連接到 bin/busybox 的,busybox 會根據執行時的文件名來執行不一樣的功能。

6.3 使用 busybox 做爲 init 程序

busybox 安裝完成以後,使用內核啓動參數 init= 來指定 busybox 做爲 init 程序,再次嘗試啓動。

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

上述命令經過 init=/linuxrc 指定了 init 程序爲根目錄下的 linuxrc,其實是一個指向 busybox 的軟連接。

這一次內核成功找到了 init 程序而且建立出 init 進程,可是 init 執行過程當中出現以下報錯:

can't run '/etc/init.d/rcS': No such file or directory

can't open /dev/tty3: No such file or directory
can't open /dev/tty4: No such file or directory

看樣子,init 程序須要一些配置才能正常運行起來。

6.4 配置 busybox init

參考 busybox 代碼中的 文檔 可知,init 啓動後會掃描 /etc/inittab 配置文件,這個配置文件決定了 init 程序的行爲。而 busybox init 在沒有 /etc/inittab 文件的狀況下也能工做,由於它有默認行爲。它的默認行爲至關於以下配置:

::sysinit:/etc/init.d/rcS
::askfirst:/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init
tty2::askfirst:/bin/sh
tty3::askfirst:/bin/sh
tty4::askfirst:/bin/sh

參考文檔,咱們提供一份 /etc/inittab 配置文件以下:

::sysinit:/etc/init.d/rcS
::askfirst:/bin/ash
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init

而且根據配置,咱們建立可執行文件 /etc/init.d/rcS,內容以下(暫時什麼事都不作):

#!/bin/sh

配置完成之後再次嘗試啓動,此次將成功啓動,而且出現以下提示:

Please press Enter to activate this console.

按提示按下 Enter 鍵以後將會啓動 shell,進行到咱們熟悉的環境,能夠執行各類經常使用命令了。

6.5 掛載 /dev, /proc, /sys 文件系統

查看當前系統環境,會發現當前文件系統結構是不完整的。好比沒有 /dev, /proc 以及 /sys 掛載點。這樣咱們沒法經過 /dev 查看系統中的設備,若是執行 df 命令也會由於沒有 /proc 掛載點而報錯:

df: /proc/mounts: No such file or directory

所以,咱們須要手工建立 /dev, /proc, /sys 這三個目錄。/dev 目錄建立完成後重啓系統便可工做,但 /proc 和 /sys 須要執行掛載纔可工做,能夠將 /proc 和 /sys 的掛載動做放到 /etc/init.d/rcS 中,每次系統啓動時自動掛載。修改 /etc/init.d/rcS 內容以下:

#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys

從新啓動系統查看,能夠看到 /dev, /proc, /sys 掛載點都相應有了內容。

7. 小結

本文介紹了經過 qemu 做爲模擬器,本身動手編譯內核,並從頭配置 init 進程,構建出一個最小的可運行系統,可用於驗證對內核的改動。
經過此次開發環境搭建,對系統的啓動過程有了一個粗略的瞭解。但這只是邁出了第一步,後續還有長路漫漫。

同步發佈:https://hellogc.net/archives/121

相關文章
相關標籤/搜索