Linux內核開發_1_編譯LInux內核

目錄

 

1. 準備工作

1.1 學習環境

1.2 下載Linux內核源碼

1.3 解壓Linux內核

1.4 目錄結構介紹

2. Linux內核配置

2.1 配置選項

1. make config

2. make menuconfig

3. make gconfig

3 開始配置

1. 配置解釋

General setup 通用選項

Enable loadable module support 可加載模塊

Enable the block layer 塊設備層

System Type arm 佔用配置,一般是廠家提供,與第7項代替了原有的Processor type and features

[ ]FIQ Mode Serial Debugger,一般不選。

6Bus support 總線支持

x86_64_defconfig

我的配置

4. 編譯Linux源碼

4.1 Linux編譯生成文件解釋

5 運行Linux內核

5.1 qemu

6. 簡單的文件系統和init

建議


1. 準備工作

1.1 學習環境

本系列教程使用的環境如下:
操作系統版本:

Linux ubuntu 18.04

Linux內核版本:

cat /proc/version
Linux version 4.15.0-20-generic (buildd@lgw01-amd64-039)\
 (gcc version 7.3.0 (Ubuntu 7.3.0-16ubuntu3)) #21-Ubuntu SMP Tue Apr 24 06:16:15 UTC 2018

學習的Linux內核版本:

linux-4.10.15

1.2 下載Linux內核源碼

首先我們需要下載Linux-4.10.15內核,我們可以直接使用wget下載:

wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.10.15.tar.xz

下載完成之後就會看到一個名爲 “linux-4.10.15.tar.xz”的文件,可以看到後綴格式是.tar.xz,雙重壓縮格式

1.3 解壓Linux內核

雙重壓縮格式,我們依次解壓先用“xz”命令解壓:

xz -d linux-4.10.15.tar.xz

-d是代表解壓的意思
解壓完成後就會在當前目錄看到一個名爲“linux-4.10.15.tar”,解壓完xz後還有一重tar,在使用tar命令解壓一次就可以得到原內核文件,這裏建議解壓到/usr/src目錄下,這裏沒有別的意思,是Linux內核開發者們給我的建議,這個在行業裏是一個開發標準,一般Linux源代碼都是放在這個目錄下,你可以在任何發行版裏的這個目錄下看到他們所使用的Linux內核源碼

sudo tar -xf linux-4.10.15.tar -C /usr/src/.

之後我們就可以在/usr/src目錄下看到我們的linux源碼了,同時還有發行版的
在這裏插入圖片描述
隨後我們進入到這個目錄下,查看一下這個目錄的文件體系

cd linux-4.10.15

1.4 目錄結構介紹

/arch

不同CPU架構下的核心代碼。其中的每一個子目錄都代表Linux支持的CPU架構

/block

塊設備通用函數

/crypto

常見的加密算法的C語言實現代碼,譬如crc32、md5、sha1等

/Documentation

說明文檔,對每個目錄的具體作用進行說明

/drivers

內核中所有設備的驅動程序,其中的每一個子目錄對應一種設備驅動

/firmware

固件代碼

/fs

Linux支持的文件系統代碼,及各種類型的文件的操作代碼。每個子目錄都代表Linux支持的一種文件系統類型

/include

內核編譯通用的頭文件

/init

內核初始化的核心代碼

/ipc

內核中進程間的通信代碼

/kernel

內核的核心代碼,此目錄下實現了大多數Linux系統的內核函數。與處理器架構相關的內核代碼在/kernel/$ARCH/kernel

/lib

內核共用的函數庫,與處理器架構相關的庫在/kernel/$ARCH/lib

/mm

內存管理代碼,譬如頁式存儲管理內存的分配和釋放等。與具體處理器架構相關的內存管理代碼位於/arch/$ARCH/mm目錄下

/net

網絡通信相關代碼

/samples

示例代碼

/scripts

用於內核配置的腳本文件,用於實現內核配置的圖形界面

/security

安全性相關的代碼

/tools

Linux中的常用工具

/usr

內核啓動相關的代碼

/virt

內核虛擬機相關的代碼

2. Linux內核配置

2.1 配置選項

Linux內核提供了三種配置的模式

1. make config

此模式非常不建議使用,除非你的時間非常多,因爲這個方式是通過終端輸出的方式挨個,也就是逐個問你設置選項,非常的耗時,而且不方便

我們測試一下,在終端窗口輸入:

make config

呀哈,出了一個錯:

“Command 'make' not found, but can be installed with:”

提示我們系統上沒有make這個命令,這是因爲我在學習時使用的是新系統,下載的是簡潔版的,裏面只帶了基本的工具鏈,這樣的第三方命令沒有包含其中,所以需要我們自己下載,這裏下載一下就好啦:

sudo apt install make

安裝完成之後輸入上面說的命令:

又出錯了,這次是告訴我們沒有GCC:“/bin/sh: 1: gcc: not found”

我們在安裝一下,這裏不需要指定gcc版本,直接輸入gcc,軟件倉庫會給我們根據當前系統內核選擇一個最優的gcc版本下來,提示y/n,選擇y即可:

sudo apt install gcc

安裝完成之後在接着輸入“make config”:

make config

這次出現了一個報錯:

“scripts/basic/fixdep.c:449:1: fatal error: opening dependency file scripts/basic/.fixdep.d: Permission denied”

不要急,一開始沒有看到後面的“Permission denied”時,我以爲是文件代碼出錯了,後面仔細一看,是告訴我們沒有權限,因爲是在用戶目錄下,所以需要加上“sudo”來獲取權限

sudo make config

加上sudo後正確運行了:

他會逐步詢問所有的配置信息,要求你輸入y/n,這樣是非常耗時的,而且很多選項初學者可能根本不太瞭解,所以非常不建議使用這個選項,我們按下ctrl+c退出這個配置工具。

2. make menuconfig

這個配置工具是基於menu文字圖形庫編寫的,非常推薦這個選項,界面友好,其次是第一次使用這個選項會提供一些默認參數,無論是對初學者還是經驗豐富的開發者們來說,都是一個非常好的選擇

sudo make menuconfig

報了一個錯誤信息:

“<command-line>:0:12: fatal error: curses.h: No such file or directory”

提示找不到curses.h,這個是menu裏的庫文件,這很明顯告訴我們缺少menu的庫

所以這裏我們安裝一下:

sudo apt install libncurses5-dev

在此運行後輸出:

“Your display is too small to run Menuconfig!
It must be at least 19 lines by 80 columns.
scripts/kconfig/Makefile:28: recipe for target 'menuconfig' failed”

意思是,這個配置工具在編寫時對我們的終端窗口大小進行了限制,長和寬必須滿足19*80

這裏我們把終端窗口擴大一點,或者直接f11進入全屏模式都可以(全屏模式下按f11會退出全屏模式),然後在運行:

成功進入到配置界面

注:linux內核中一個功能模塊有三種編譯方法:一種是編入、一種去去除、一種是模塊化。所謂編入就是將這個模塊的代碼直接編譯連接到zImage中去,去除就是將這個模塊不編譯鏈接到zImage中,模塊化是將這個模塊仍然編譯,但是不會將其鏈接到zImage中,會將這個模塊單獨鏈接成一個內核模塊.ko文件,將來linux系統內核啓動起來後可以動態的加載或卸載這個模塊。
在menuconfig中選項前面的括號裏,*表示編入,空白表示去除,M表示模塊化。

3. make gconfig

sudo make gconfig

報錯:

/bin/sh: 1: pkg-config: not found
*
* Unable to find the GTK+ installation. Please make sure that
* the GTK+ 2.0 development package is correctly installed...
* You need gtk+-2.0, glib-2.0 and libglade-2.0.
*
原因也很明確的告訴我們需要安裝gtk+2.0的庫,這裏我們安裝一下:

sudo apt-get install libgtk2.0-dev libglib2.0-dev libglade2-dev

安裝完成之後運行一次看一下效果:

sudo make gconfig

3 開始配置

1. 配置解釋

General setup 通用選項

   

選項

作用

[*]Prompt for development and/or incomplete code/drivers

設置界面中顯示還在開發或者還沒有完成的代碼與驅動,最好選上,許多設備都需要它才能配置。

[ ]Cross-compiler tool prefix

交叉編譯工具前綴,如果你要使用交叉編譯工具的話輸入相關前綴。默認不使用。嵌入式linux更不需要。

[ ]Local version - append to kernel release

自定義版本,也就是uname -r可以看到的版本,可以自行修改,沒多大意義。

[ ]Automatically append version information to the version string

自動生成版本信息。這個選項會自動探測你的內核並且生成相應的版本,使之不會和原先的重複。這需要Perl的支持。由於在編譯的命令make-kpkg 中我們會加入- – append-to-version 選項來生成自定義版本,所以這裏選N。

Kernel compression mode (LZMA)

選擇壓縮方式。

[ ]Support for paging of anonymous memory (swap)

交換分區支持,也就是虛擬內存支持,嵌入式不需要。

[*]System V IPC

爲進程提供通信機制,這將使系統中各進程間有交換信息與保持同步的能力。有些程序只有在選Y的情況下才能運行,所以不用考慮,這裏一定要選。

[*]POSIX Message Queues

這是POSIX的消息隊列,它同樣是一種IPC(進程間通訊)。建議你最好將它選上。

[*]BSD Process Accounting

允許進程訪問內核,將賬戶信息寫入文件中,主要包括進程的創建時間/創建者/內存佔用等信息。可以選上,無所謂。

[*]BSD Process Accounting version 3 file format

選用的話統計信息將會以新的格式(V3)寫入,注意這個格式和以前的 v0/v1/v2 格式不兼容,選不選無所謂。

[ ]Export task/process statistics through netlink (EXPERIMENTAL)

通過通用的網絡輸出工作/進程的相應數據,和BSD不同的是,這些數據在進程運行的時候就可以通過相關命令訪問。和BSD類似,數據將在進程結束時送入用戶空間。如果不清楚,選N(實驗階段功能,下同)。

[ ]Auditing support

審計功能,某些內核模塊需要它(SELINUX),如果不知道,不用選。

[ ]RCU Subsystem

一個高性能的鎖機制RCU 子系統,不懂不瞭解,按默認就行

[ ]Kernel .config support

將.config配置信息保存在內核中,選上它及它的子項使得其它用戶能從/proc/ config.gz中得到內核的配置,選上,重新配置內核時可以利用已有配置

[ ]Enable access to .config through /proc/config.gz

上一項的子項,可以通過/proc/ config.gz訪問.config配置,上一個選的話,建議選上。

16)Kernel log buffer size (16 => 64KB, 17 => 128KB)

內核日誌緩存的大小,使用默認值即可。12 => 4 KB,13 => 8 KB,14 => 16 KB單處理器,15 => 32 KB多處理器,16 => 64 KB,17 => 128 KB。

[ ]Control Group support(有子項)

控制組支持,使用默認即可

Example debug cgroup subsystem

cgroup子系統調試例子

Namespace cgroup subsystem

cgroup子系統命名空間

Device controller for cgroups

cgroups設備控制器

Cpuset support

只有含有大量CPU(大於16個)的SMP系統或NUMA(非一致內存訪問)系統才需要它。

[ ]enable deprecated sysfs features to support old userspace tools

在某些文件系統上(比如debugfs)提供從內核空間向用戶空間傳遞大量數據的接口,一般不選

[ ]Kernel->user space relay support (formerly relayfs)

內核系統區和用戶區進行傳遞通訊的支持,這個選項在特定文件系統(relayfs)中提供數據接口支持,它可以支持從內核空間到用戶空間的大批量數據傳遞的支持。不清楚可以不選。

[ ]Namespaces support,(有子項)

命名空間支持,允許服務器爲不同的用戶信息提供不同的用戶名空間服務。

[]Initial RAM filesystem and RAM disk (initramfs/initrd) support

初始RAM的文件和RAM磁盤( initramfs /initrd)支持(如果要採用initrd啓動則要選擇,否則可以不選),不需要,不用選。嵌入式linux一般不選。

[ ]Optimize for size

-Os代替-O2參數,可能會有二進制錯誤問題,一般不選。

(0)Default panic timeout

添0即可。

[*]Configure standard kernel features (for small systems)

特殊內核用到,可以不選,嵌入式linux則必選。

[ ]Enable the Anonymous Shared Memory Subsystem

啓用匿名共享內存子系統,不清楚可以不選。

[ ]Enable AIO support

支持AIO(Asynchronous I/O 異步事件非阻塞I/O),(包含aio.h, aio_read,向內核發出讀命令,aio_write向內核寫命令,詳細見‘AIO介紹‘文檔),AIO機制爲服務器端高併發應用程序提供了一種性能優化的手段。加大了系統吞吐量,所以一般用於大型服務器,一般不用選。

[ ]Kernel Performance Events And Counters(有子項)

性能相關的事件和計數器支持(既有硬件的支持也有軟件的支持).大多數現代CPU都會通過性能計數寄存器對特定類型的硬件事件(指令執行,緩存未命中,分支預測失敗)進行計數,同時又絲毫不會減慢內核和應用程序的運行速度.這些寄存器還會在某些事件計數到達特定的閾值時觸發中斷,從而可以對代碼進行性能分析. Linux Performance Event 子系統對上述特性進行了抽象,提供了針對每個進程和每個CPU的計數器,並可以被 tools/perf/ 目錄中的"perf"工具使用.

[*]Enable VM event counters for /proc/vmstat

允許在/proc/vmstat中包含虛擬內存事件記數器。

[*]Enable SLUB debugging support

支持SLUB內存分配管理器調試,

[ ]Disable heap randomization

禁用隨即head,選不選均可。

Choose SLAB allocator (SLUB (Unqueued Allocator)) --->

選擇內存分配管理器,強烈推薦使用SLUB。

[ ]Profiling support

剖面支持,用一個工具來掃描和計算計算機的剖面圖,支持系統測評,一般開發人員使用,不選。

[ ]Kprobes

Kprobes 提供了一個強行進入任何內核例程並從中斷處理器無干擾地收集信息的接口。使用 Kprobes 可以 輕鬆地收集處理器寄存器和全局數據結構等調試信息。開發者甚至可以使用 Kprobes 來修改 寄存器值和全局數據結構的值。

選中後linux內核 將附帶此工具

GCOV-based kernel profiling --->

基於GCOV的代碼覆蓋率,可以來審評代碼

Enable loadable module support 可加載模塊

選項

作用

[ ]Forced module loading

強行加載模塊,不建議選。

[*]Module unloading

支持模塊卸載,必須選上。

[ ]Forced module unloading

強行卸載模塊,即使內核認爲這樣並不安全,也就是說你可以把正在使用中的模快卸載掉。如果你不是內核開發人員或者骨灰級的玩家,不建議選。

[ ]Module versioning suppor

這個功能可以讓你使用其它版本的內核模塊,除非特殊需要,一般不選。

[ ]Source checksum for all modules

這個功能是爲了防止更改了內核模塊的代碼但忘記更改版本號而造成版本衝突,現在很少使用,不選。

Enable the block layer 塊設備層

選項

作用

[*]Support for large (2TB+) block devices and files

僅在使用大於2TB的塊設備時需要

[*]Block layer SG support v4

通用SCSI設備第四版支持。

[*]Block layer data integrity support

塊設備數據完整性支持。

[*]IO Schedulers --->(有子項)

IO調度器

[ ]Anticipatory I/O scheduler

搶先式I/O調度器,大多數塊設備只有一個物理查找磁頭(例如一個單獨的SATA硬盤),將多個隨機的小寫入流合併成一個大寫入流,用寫入延時換取最大的寫入吞吐量.適用於大多數環境,特別是寫入較多的環境(比如文件服務器)

[ ]Deadline I/O scheduler

期限式I/O調度器,輪詢的調度器,簡潔小巧,提供了最小的讀取延遲和尚佳的吞吐量,特別適合於讀取較多的環境(比如數據庫)。

]CFQ I/O scheduler

使用QoS策略爲所有任務分配等量的帶寬,避免進程被餓死並實現了較低的延遲,可以認爲是上述兩種調度器的折中.適用於有大量進程的多用戶系統CFQ調度器嘗試爲所有進程提供相同的帶寬。它將提供平等的工作環境,對於桌面系統很合適。

Default I/O scheduler (CFQ) --->

默認IO調度器有上面三個IO調度器:搶先式是傳統的,它的原理是一有響應,就優先考慮調度。如果你的硬盤此時在運行一項工作,它也會暫停下來先響應用戶。期限式則是:所有的工作都有最終期限,在這之前必須完成。當用戶有響應時,它會根據自己的工作能否完成,來決定是否響應用戶。CFQ則是平均分配資源,不管你的響應多急,也不管它的工作量是多少,它都是平均分配,一視同仁的。

System Type arm 佔用配置,一般是廠家提供,與第7項代替了原有的Processor type and features

<無子選項>

[ ]FIQ Mode Serial Debugger,一般不選。

<無子選項>

6Bus support 總線支持

選項

作用

PCI support

PCI總線支持,主板上最長用的插槽,最好選上,arm linux可以不選,arm一般沒有PCI總線。

PCCard (PCMCIA/CardBus) support

微通道總線,一般老式筆記本有這種插槽,筆記本選上,arm linux 不選。

Kernel Features 內核特徵

選項

作用

[ ] Tickless System (Dynamic Ticks)

非固定平率系統,能讓內核運行的更有效率,並且省電,pc下可選,特別是筆記本,arm linux一般不用選。

[ ] High Resolution Timer Support

支持高頻率時間發生器,需要硬件兼容,但大多數PC和ARM都不支持,不選

Memory split (2G/2G user/kernel split) --->

內核與用戶空間各佔2G,內核空間0-0x7FFFFFFF,用戶空間80000000-FFFFFFFF

Preemption Model (No Forced Preemption (Server)) --->

內核搶佔模式。普通PC用戶一般選2,arm linux 選1就可以。

No Forced Preemption (Server)

禁止內核搶佔,適合服務器環境。針對於高吞吐量的設計,但有可能延時較長,適用於服務器或科學運算,或向要最大的運算能力,而不理會調度上的延時。

Voluntary Kernel Preemption (Desktop)

自願內核搶佔,適合普通的桌面環境。已降低吞吐量爲代價,降低內核調度的最大延時,提供更快的應用程序響應,即使系統已經高負荷運轉,應用程序仍然能運行的很“流暢”,適合用戶桌面環境

Preemptible Kernel (Low-Latency Desktop)

主動內核搶佔,適合運行實時程序的環境。更低的吞吐量,進一步降低內核的調度延時,使應用程序更加流暢。

[ ]Compile the kernel in Thumb-2 mode

編譯Thumb-2 mode內核,一般不選

*] Use the ARM EABI to compile the kernel

與下面綁定配置。

[*] Allow old ABI binaries to run with this kernel (EXPERIMENTAL)

對於嵌入式系統(jffs2 yaffs2),這兩個要選上,否則很可能啓動的時候報錯(kernel panic- not syncing: Attempted to kill init!)

[ ] High Memory Support (EXPERIMENTAL)(有子項)

1G物理內存以下不選,超過1G才選。(配置略有變化,以前的選項是OFF(<1G),4G(>1G && <4G),64G(>4G))。1

Allocate 2nd-level pagetables from highmem

1G物理內存以下不選,超過1G(小於4G)才選

Allocate 3nd-level pagetables from highmem

大於4G,選擇此項目。

Memory model (Flat Memory) --->

一般選"Flat Memory",其他選項涉及內存熱插拔。

[ ] Enable KSM for page merging

允許linux內核識別出包含相同內容的內存頁,然後合併這些內存頁,將數據整合在一個位置可以多次引用,特殊功能,不用選。

(4096) Low address space to protect from user allocation

設置低端內存大小,默認4096即可

[ ] Use kernel mem{cpy,set}() for {copy_to,clear}_user() (EXPERIMENTAL)

這個資料目前暫時沒找到合理的解釋,可以先忽略不選

x86_64_defconfig

這裏我們可以使用Linux下一些自帶的配置文件,可以使用“make x86_64_defconfig” 這樣就生成了一個x86_64的amd架構的linux內核(64位),如果要生成arm平臺的架構的話需要修改配置文件,這裏我目前還沒有打算學習arm架構的配置工作,所以先選中amd的,如果要生成i386的可以使用"make i386_defconfig"

你可以在arch/架構名/configs目錄下找到對應的配置文件,也可以直接copy到你的根目錄名字改爲.config就可以了,這些是Linux內核自帶的一些基礎配置

我的配置

下面這個是我的配置,因爲在Linux下配置不當,雖然編譯可以過但是運行會出現問題,如內核恐慌,或者VFS加載失敗等,這裏是我在之前實驗中編譯成功且運行沒有問題的一次配置,如果你編譯時遇到了問題,可以參考下面的配置:

鏈接: https://pan.baidu.com/s/1rdre2xcv0-GZWeRw-xsyyw

提取碼: 6zfg

下載後用unzip命令解壓:

unzip config.zip

然後目錄下會出現一個config的文件,在copy到我們的linux內核根目錄下,copy的新名字要在前面加個“.”,這樣Linux內核才能識別:

cp config 你的linux內核目錄源碼/.config

4. 編譯Linux源碼

編譯方法:

sudo make bzImage -j4

這裏解釋一下,如果直接sudo make是無法生成bzImage的,在之前的版本里可以,但是在4.4版本上無法這樣,bzImage是x86_64架構的壓縮鏡像文件

選項裏“j”,代表多線程編譯,n代表線程數

如:“make -j32”

拆分32個線程來編譯這個項目,線程數量請根據自己機器的配置,如果配置不好開這麼多線程,調度起來很慢,也容易卡住。

一般情況下,建議你的處理器如果是4核,那麼建議每個核分出2個線程也就是8線程

“make j8”

這樣讓你的CPU壓力不至於那麼大。

如果make的時候看到輸出信息,你覺得很亂不舒服,可以使用重定向“>”的方法來屏蔽這些輸出信息

make > test.txt

當出錯時make會停下來,就可以到這個文件裏去查看輸出信息了

如果不想生成輸出文件又想屏蔽輸出信息,Linux開發者沒有爲我們提供這個選項,但是我們可以利用Linux系統下的dev/null 黑洞文件,把輸出信息重定向進去

make > /dev/null

黑洞文件相當於windows回收站,但是這個文件不會保存數據,凡是進去的任何數據,就會被自動刪除,找不回的.

輸入命令後,Make就會開始自動化編譯

這個期間,可以去喝杯咖啡,因爲編譯非常耗時

一步到位,沒有出現任何編譯出錯的問題,這就是選擇相仿內核版本的好處

4.1 Linux編譯生成文件解釋

arch裏有不同架構的文件夾,如arm,x86,x86_64,當編譯完成之後這些文件的boot目錄裏會生成這些壓縮文件,根目錄下會生成vmlinux文件,這個文件是未壓縮的目標文件

下面是這些文件的作用:

   

vmlinux

編譯出來的最原始的內核文件,未壓縮。

zImage

是vmlinux經過gzip壓縮後的文件。

bzImage

bz表示“big zImage”,不是用bzip2壓縮的。兩者的不同之處在於,zImage解壓縮內核到低端內存(第一個640K),bzImage解壓縮內核到高端內存(1M以上)。如果內核比較小,那麼採用zImage或bzImage都行,如果比較大應該用bzImage。

uImage

U-boot專用的映像文件,它是在zImage之前加上一個長度爲0x40的tag。

vmlinuz

是bzImage/zImage文件的拷貝或指向bzImage/zImage的鏈接。

initrd

是“initial ramdisk”的簡寫。一般被用來臨時的引導硬件到實際內核vmlinuz能夠接管並繼續引導的狀態。

5 運行Linux內核

5.1 qemu

我們精簡版的操作系統是不帶這些第三方工具的,所以我們先安裝一下:

sudo apt install qemu

安裝完成之後,arch下x86是指32位的Linux內核,而x86_64是指64位的內核,64位是可以運行32位程序的,未來32位架構將逐漸被淘汰

這裏通過給qemu的-kernel指定內核參數,上面我們說過編譯產生的文件是壓縮文件,qemu可以正確運行嗎?

答:可以,qemu會自動幫我們解析,我們只需要使用-kernel指定就好了

-kernel是指定內核文件的意思

qemu-system-x86_64 -kernel arch/x86/boot/bzImage

運行結果:

可以看到內核成功跑起來了,但是報了一個錯誤

end Kernel painc - not syncing: VFS:Unable to mount root fso on unknown-block(0,0)

Linux內核在運行時需要文件根系統的支持,但是這裏我們並沒有生成文件系統,所以Linux會報這個錯誤

除了文件系統以外Linux還會在初始化完成之後並且成功加載文件系統之後會去fork一個進程,名爲init,這裏先不做詳細討論,後續的學習文章裏對Linux內核這塊做一個詳細的解析

這個init就是守護進程,所有用戶空間下的進程都由它來主動創建,就類似我們剛剛打開終端產生的shell一樣

6. 簡單的文件系統和init

這裏先教大家制作一個簡單的文件系統和init

Linux內核對文件系統有一定的格式要求,如NFS和SVR格式的文件系統,這是基於UNIX演化來的,所以我們需要把文件系統製作成NFS/SVR格式的,這裏推薦一個命令:“CPIO”,這個命令可以幫助我們生成SVR格式的文件系統,我們的配置選項裏默認使用SVR的文件系統格式。

在製作根文件系統之前,我們需要一個init,先用c語言製作一個init:

vim init.c

代碼:

#include <stdio.h>

int main(){
    printf("\nhello Linux Kernel!\n");
    while(1);
}

注意這裏末尾一定要加while(1),否則無法正確執行init和輸出,經過分析inux內核的0號進程(init)運行時會初始化相關工作,然後在去fork一個子進程並把cpu控制權交給子進程。

同時,init作爲父進程不能被結束,因爲一旦死掉,用戶態空間下就沒一個進程,而這個init就是我們Linux上被稱爲守護進程的東西,一旦死掉,整個用戶態下所有的進程都會被一併殺死。

這樣的話用戶態就相當於沒了,那麼內核就會產生異常了,會報內核恐慌,attempted kill init這樣的問題,來告訴我們init有問題

這是我根據資料查到的Linux內核第一次調度INIt的一個過程。

這裏我們只是簡單的寫一個init程序,後面我們使用buysbox來完成相關初始化,目前正在研究buysbox是如何去完成這些初始化的,等研究完成,會寫一篇文章來告訴大家。

這裏我們使用靜態編譯,因爲我們等下要使用的文件系統是臨時製作的,裏面除了包含init以外不會有任何庫,所以不可以動態加載,必須使用靜態:

gcc -static -O0 init.c -o init

這裏使用“-O0".不要讓編譯器給我們的init進行優化,防止編譯器偷懶優化掉某些指令,但是這段代碼比較少,也沒啥可以優化的,也可以不加。

這裏我們製作一個臨時的根目錄文件:

echo init | cpio -o --format=newc > rootfs

注意CPIO的格式,CPIO選項 -o 是從輸出流裏讀取數據,而echo init是把init文件輸出到輸出流裏,然後CPIO從輸出流將文件讀取到rootfs裏,這裏 --format=newc 是指使用SVR4的格式,而>是流重定向。

注意這裏在使用這個命令前不要創建目錄,不然會出錯,cpio會自動幫我們生成對應格式的文件

輸出:

1651 blocks

如果生成成功,會告訴我們輸出的文件大小

這裏給上它權限,保證qemu在運行時加載到rootfs時有足夠的權限

sudo chmod 777 rootfs

這裏我們需要使用的運行命令是:

qemu-system-x86_64   \
     -kernel ./bzImage 內核文件
     -initrd ./rootfs  臨時根文件系統
     -append "root=根文件系統 rdinit=第一個init程序"

這裏給大家解釋一下這些選項的意思,-kernel上面說過了,-initrd的意思是臨時的根文件系統,Linux內核在加載根文件系統之前,VFS會去使用臨時的文件系統做相關的初始化工作,當一切就緒後纔會去調用實際的根文件系統。

這裏我們將它指向我們的臨時文件系統,我們這個簡單的文件系統可以先給Linux內核使用,實際的根文件系統是通過-append選項指定的,這個選項可以給內核運行參數,其中root就是指向了根文件系統,這裏我們也可以給它指定我們用於臨時的文件系統,但是根文件系統不是這麼簡單的,所以我們上面的簡單文件系統只有一個init,真正的根文件系統還需要一些其它的設備文件,這裏我們先不做多討論,後面文件系統這塊我們在深入探討。

這裏rdinit的意思是告訴內核啓動後從根文件系統裏尋找一個可執行程序的文件名

我們輸入命令:

qemu-system-x86_64 -kernel arch/x86/boot/bzImage -initrd ./rootfs1 -append "rdinit=/init"

因爲我電腦上沒有根文件系統,所以root就沒傳參進去。

運行結果:

可以看到我們的“hello linux kernel!”打印出來了!

這是一件非常值得高興的事,因爲我編譯了許多天,我在一邊編譯一遍學習它的內核源碼,雖然進度很緩慢,但是我覺得這是一件能讓人成長的事情,非常值得高興,我踩了很多坑,所以這裏非常建議大家在編譯時一定要選擇與發行版內核相仿的Linux內核版本編譯。

因爲內核向前兼容,如果你書上用的老版本代碼,那麼在新版本一樣可以用,甚至新版本上的代碼會比老版本的代碼更好,更健壯,因爲Linux主版本會收錄許多次版本上的優點,同時也會修復許多bug,Linux在不斷的完善。

建議


這一段話是我在經歷許多天的編譯Linux內核過程中習得的總結,可以跳過。
   起初我學習Linux內核的時候,是參考“Linux內核設計與實現”這本書來學的,書上使用的Linux內核版本是:“2.34.6”,這個版本已經很老了,最初我使用的是ubnutu20來編譯它的,雖然編譯過程中遇到了很多問題,但是都一 一解決了,最後運行時會出現許許多多的問題,如:VFS無法加載根文件系統而引起的文件恐慌,還有kill init這樣的問題,最初我以爲是配置的問題,在我根據查找到資料,和仔細學習了一邊如何配置Linux內核後,我發現其實這些基本上用默認的就可以了。
      大多數的除了特定需求一般無需裁剪,尤其是剛入門的學生,最好是使用默認配置,後來我覺得可能是最新版的ubtunu使用的軟件倉庫裏的lib版本太高了,雖然可以編譯過,但是有一小部分的lib庫可能對舊函數不支持或者說已經廢棄了,這是我目前認爲的原因,我只是在ubtunu上安裝了舊版本的gcc,但是並沒有選擇降級glib等庫,這可能是原因之一,但是如果我使用了舊版本的glib庫還有openssl等,那麼一些ubtunu上較新的軟件可能使用了新版本glib庫裏的一些新增的特性,導致這些使用動態加載的軟件們無法正常運行。
      所以最後我選擇使用老版本的centOS,可是無論是centOS或者是ubtunu對這些老版本的操作系統所使用的倉庫代碼已經廢棄了,所以在這個上面是無法下載任何lib的,我必須修改源,使用國內或者國外帶有老版本倉庫的源纔可以,即便使用了這些,ubtunu或者centOS這些老版本的操作系統在編譯過程中也會出現一些零零散散的問題,由於版本太老使用起來非常不順手,最後我向一些Linux內核的開發者們尋求意見,他們給出的一件事建議去編譯3.0或者4.0以上的Linux版本內核,因爲太老的Linux內核在目前較新的操作系統平臺上已經很難在編譯出來了,原因是因爲Linux依賴gnu的軟件體系,而gnu的軟件體系在不斷的升級進化,每次的升級,都會被用在正在開發中最新的Linux內核,而除了原始版的Linux內核不是在Linux上編譯出來的以外,其餘的Linux內核版本都是在Linux內核上開發而來的,而使用的開發工具(gcc)就是那個時代最新的版本。
      其次是不同的發行版它自帶的一些依賴LIB都是比較新的,因爲它也要爲自己的Linux內核提供一個運行環境,而問題的關鍵是,當我們編譯Linux內核時,不確定編譯器會不會把當前操作系統上的一些依賴LIB編譯到Linux內核裏去。
      如正在編譯Linux2.6,他使用的glib是3,那麼我們當前的發行版使用的是6
      一旦編譯起來,gcc會從系統環境裏把6的一些依賴lib鏈接到2.6裏去了,從而導致某些函數可能在未來的運行結果或者參數要求發生了一點變化,因爲有向前兼容的方法,但是這個函數或者編譯器選項很可能會被廢棄掉。
      
   不確定的因素很多,所以這裏我給大家的建議是,如果你想編譯Linux內核,最好選擇一個與它使用的Linux內核版本相仿的Linux發行版來編譯它
      如我選擇學習Linux內核,並且選擇的Linux內核版本是4.10.15,那麼我需要一個與它使用的內核版本相仿的操作系統,所以這裏我選擇ubtunu18.04,它使用的內核版本是4.15.0。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章