本文主要介紹在 MacOS 上使用 qemu 搭建 Linux Kernel 的開發環境。(在開始以前須要注意的是,本文中的 Linux 開發環境是一個遠程服務器,而 qemu 被安裝在本地的 MacOS 上。一般並不須要這樣折騰,直接將 qemu 安裝在 Linux 中更加方便,並且 qemu 是能夠 -nographic
無圖形界面運行的。)html
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
首先,爲了進行內核開發,須要一個現成的 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
按需編譯內核,此處只進行簡單說明(基於內核 v4.13)。工具
能夠先執行 make help
能夠查看 make 支持哪些 target。
一般先進行內核編譯配置:
make menuconfig
會啓動一個基於文本的配置界面進行各類選項、模塊、驅動等配置。或者也能夠直接使用目標平臺默認的配置,如針對 x86_64 平臺(後續平臺相關的地方均以 x86_64 爲例進行說明)可使用:
make x86_64_defconfig
配置完成後相應的配置項會保存在 .config
文件中。下一次執行 make menuconfig
時能夠 load 這份配置文件,在此基礎上進行修改。
咱們構建一個壓縮過的內核鏡像:
make bzImage
編譯成功後,bzImage 文件將出如今 arch/x86_64/boot/bzImage
。記住文件路徑或者拷貝到一個方便的路徑,便於後續啓動時使用。
接下來,編譯在配置階段選擇的內核模塊:
make modules
編譯好的內核模塊 *.ko
文件存在於模塊對應的源碼目錄中。
編譯好內核之後,咱們就可使用 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)
從日誌內容能夠看出,內核啓動到必定階段後嘗試加載根文件系統,但咱們沒有指定任何磁盤設備,因此沒法掛載根文件系統。並且上一節中編譯出來的內核模塊如今也沒有用上,內核模塊也須要存放到文件系統中供內核須要的時候進行加載。
因此,接下來須要製做一個磁盤鏡像文件供內核做爲根文件系統加載。
如上一節所述,須要製做一個磁盤鏡像文件做爲根文件系統供內核加載,同時也用於存放編譯好的內核模塊,以及後續所需的各類配套工具程序。
使用 qemu-img
建立一個 512M 的磁盤鏡像文件:
qemu-img create -f raw disk.raw 512M
如今 disk.raw 文件就至關於一塊磁盤,爲了在裏面存儲文件,須要先進行格式化,建立文件系統。好比在 Linux 系統中使用 ext4 文件系統進行格式化:
mkfs -t ext4 ./disk.raw
格式化完成以後,能夠在 Linux 系統中以 loop 方式將磁盤鏡像文件掛載到一個目錄上,這樣就能夠操做磁盤鏡像文件中的內容了。
下面的命令將磁盤鏡像文件掛載到 img 目錄上:
sudo mount -o loop ./disk.raw ./img
如今能夠將以前編譯好的內核模塊安裝到磁盤鏡像中了。命令以下:
sudo make modules_install \ # 安裝內核模塊 INSTALL_MOD_PATH=./img # 指定安裝路徑
執行完成後便可在 ./img/lib/modules/
下看到安裝好的內核模塊。
準備好磁盤鏡像文件後,使用下面的命令再次啓動 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 程序供內核啓動。
經常使用的 init 程序有下面幾種:
ls
、cat
等。busybox 很是輕量級,能夠編譯出徹底獨立無依賴的 busybox 套件。這裏選用 busybox 做爲 init 程序及其它命令工具的提供者。
下載 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
編譯好 busybox 以後須要將其安裝到磁盤鏡像中以供使用。執行以下命令進行安裝:
make CONFIG_PREFIX=<path_to_disk_img_mount_point> install
CONFIG_PREFIX
用於指定安裝路徑,須要指定到以前磁盤鏡像文件的掛載目錄,好比 ./img
。進入磁盤鏡像掛載目錄查看,常見的文件系統結構已經創建起來了。查看 bin 和 sbin 目錄下的命令,能夠看到都是連接到 bin/busybox
的,busybox 會根據執行時的文件名來執行不一樣的功能。
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 程序須要一些配置才能正常運行起來。
參考 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,進行到咱們熟悉的環境,能夠執行各類經常使用命令了。
查看當前系統環境,會發現當前文件系統結構是不完整的。好比沒有 /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 掛載點都相應有了內容。
本文介紹了經過 qemu 做爲模擬器,本身動手編譯內核,並從頭配置 init 進程,構建出一個最小的可運行系統,可用於驗證對內核的改動。
經過此次開發環境搭建,對系統的啓動過程有了一個粗略的瞭解。但這只是邁出了第一步,後續還有長路漫漫。