前置
設定kernel版本至2.6
可以使用kernel source tree
一個資料夾存放所有kernel相關資源,可以在此建立新kenrel 、安裝、並且重啟新的kernel
Ubuntu 實例
參照這裡How to install an old kernel
Ubuntu版本要舊到可以兼容kernel 2.6
- 下載對應版本三檔
- 移動到某資料夾,sudo dpkg -i *.deb
- sudo reboot 並在期間長壓shift 進入grub模式,並選擇對應的kernel 開機
- 最後使用uname -r 檢查版本是否正確
雷點之一:
dpkg 發生相依問題
解法:注意錯誤訊息,看缺少的東西
比如此次缺少module-init-tools,故需
sudo apt install module-init-tools
雷點之二:
Failed to symbolic-link /boot/initrd.img-<version> to initrd.img: File exists
解法: sudo rm /initrd.img
注意這個行為非常危險,需要先行備份或使用虛擬機等安全情況下使用
雷點之三:
開機時顯示 FATAL kernel is too old
解法: 要安裝舊版本的ubuntu系統
小知識1:
Ubuntu 很多檔案格式是與Debian共用的,所以可以看到這次下載的副檔名皆為deb
小知識2:
uname 可以看很多系統資訊 uname –help 看選擇
小知識3:
GNU GRUB(簡稱GRUB)是一個來自GNU項目的啟動引導程序。
GRUB是多啟動規範的實現,它允許用戶可以在計算機內同時擁有多個操作系統,並在計算機啟動時選擇希望運行的操作系統。 GRUB可用於選擇操作系統分區上的不同內核,也可用於向這些內核傳遞啟動參數
如何用18.x 的Ubuntu寫kernel ?
- 製作Makefile
obj-m += <yourfile>.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
其中檔案要用C語言寫,並且最後make成.ko檔
- 查看訊息使用dmesg
hello world - 第一個module
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
printk(KERN_ALERT "Hello, world\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, cruel world\n");
}
module_init(hello_init);
module_exit(hello_exit);
此程式碼大綱:
- MODULE_LICENSE提供證明,才進的去kernel
- 創建兩個函式,一個用在載入kernel時,讓kernel顯示訊息,另一個用在移除kernel時,讓kernel顯示訊息
- module_init 與 module_exit 則是link上述function 至所說的時點中
printk函數
存在於Linux kernel函數庫中,類似於C libarary的printf,但因為是kernel,所以獨立於(不需要)C libarary
透過insmod插入kernel後即可取用kernel內的公開符號(public symbols)
KERN_ALERT 則告知此訊息的重要性(priorty)
insmod , rmmod
用於插入與移除寫好的module
比如insmod ./hello.ko 就會將hello.ko放入kernel,並依函數定義顯示Hello, world
rmmod hello 就會將hello.ko移出kernel,並依函數定義顯示Goodbye, cruel world
- 註: 不同系統不一定會將結果顯示在螢幕上,可能也需要dmesg去看,或者去/var/log/messages 找
init 與 exit 的工作
兩者有點像事件導向的程式設計
init告訴kernel,功能已準備就緒,隨時可以調用我
exit告訴kernel,功能已被移除,不要再調用我
同時exit,也必須負責回收init所要過的資源
關於module
kernel module與一般程式碼之不同
- kernel module 幾乎無法link外部函式,只能用內部kernel已存在的(如printk)
- kernel module 發生error是很危險的,可能導致系統崩潰
user mode 與 kernel mode
OS 必須盡到分配資源 與 阻止未授權行為的功能,因此user mode 與 kernel mode出現於OS 理論中
實作上會在CPU內建立gate,以區分執行等級,低階等級下某些重要操作不被允許
同時Unix/Linux系統則利用這個硬體特性,做出了sudo與user兩個模式
這兩個模式也當然會有自己的memory分區
當有system call 發生 或 hardware interrupt,程式就會由user mode 轉入 kernel mode
Kernel code 的system call會執行於原呼叫程式(caller),並且能調用原程式的資訊 (比如printk(var) 的 var變數紀錄於原process的記憶體中)
而處理interrupt的程式則獨立於任何process
最後,module的角色可視為kernel功能的延伸,因此是在kernel space中執行,並且可能同時執行system call 與 interrupt handle
Concurrency
很直覺的,kernel , device 的資源一定是被眾多process競逐的
所以需注意Critical section問題
並且linux適用於多核心與支持interrupt,preempt 因此 Concurrency是很重要的議題 (效率 與 確保資訊正確 )
linux 的kernel 包含 driver 需要有可重入(reentrancy)的考量
關於 可重入介紹
簡單來說就是要考慮到共用的問題
Current Process
kernel可經由一個current 的 global item得到當下執行的process (current 定義於 asm/current.h)
current 會回傳一個process的 struct task_struct 指標 (struct task_struct 定義於 linux/sched.h)
所謂當下執行的process,即為呼叫kernel system call的process (比如 open , read …)
然而,在現今多核心的情況下,再加上current 常常被呼叫到,因此current並不是真正意義上的global, 而只是被存於kernel stack 的 task_struct 指標
device driver 可以僅引入linux/sched.h就可以呼叫
比如
printk(KERN_INFO "The process is \"%s\" (pid %i)\n",
current->comm, current->pid);
會印出process名字 與其 process id (名字就是file name )
同時也可以看出current會儲存這兩個資訊
其他注意事項
- kernel分配到的記憶體stack非常小,通常只有一張4096-byte page
所以需要龐大變數時,最好動態的創造(heap)
Review: 記憶體規劃架構
- kernel API 有一部分 double underscore (__)的函式
這些函式非常底層,使用時需要額外注意 - kernel 不(輕易)做浮點數運算 參考這裡的討論
簡而言之是為了效率問題,管理FPU(float point unit)太花時間
編譯與載入 module
關於深入文檔
可以由Documentation/kbuild、Documentation/Changes 查到完整的記載
其中ubuntu 要apt install linux-doc
並於 /usr/share/doc/linux-doc
查看
基礎的makefile (編譯部分)
# If KERNELRELEASE is defined, we've been invoked from the
# kernel build system and can use its language.
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
# Otherwise we were called directly from the command
# line; invoke the kernel build system.
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
其中obj-m是設定目標檔
KERNELDIR 是設定插入的kernel路徑
M=$(PWD) 則是泛化的指定當前路徑 ,M option是指會移到該路徑進行make
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
寫死的例子是
make -C ~/kernel-2.6 M=`~/driver_learning` modules
modules是一個target,會去尋找obj-m裡面的清單進行make
載入部分
前面提及的insmod可以完成此任務
通常載入時會順便給Module Parameters,這個行為被稱為load-time configuration, 相較於compile-time configuration會更有彈性
insmod被實做於kernel/module.c
其中sys_init_moudle會分配 kernel memory給module使用、解析module內的kernel reference對應到kernel symbol table
並且呼叫module 的 init 函式
(注意: 在kernel中,system calls都會有 sys_ 的前綴詞,grep它們會很方便)
(注意2: 上次解決新電腦wifi問題時,也有使用到的modprobe功能基本上和insmod一樣是插入module,不一樣的是
modprobe會在其他現有的module中尋找欲插入module中未定義的kernel symbol,簡單來說就是比較適合在已有的龐大系統中,加入新的小設定 )
rmmod 會在module被kernel相信仍在作用時拒絕移出,有強制移除,但為了安全考量通常reboot會比較適當
lsmod 會列出現在已載入kernel的module
lsmod其實就是去讀取/proc/modules這個虛擬檔案
(也可以從/sys/module得到看到一樣的資訊)
如下例
Kernel Symbol Table
insmod會透過public kernel symbols table 來對應undfined symbol
(比如 printk , KERN_INFO)
該table會含global kernel item的地址,也就是函數與變數的位址
module在載入kernel時,所exported的symbol也會變成kernel symbols table 的一部分
未來可供其他module使用 (modprobe的例子)
module stacking :
可依高低階區分module,將高階的module建立在其他module之上 (使用他們定義的symbol)
可藉由
EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);
兩種指令引入新變數,其中後者只能給GPL認證的使用
開始撰寫Device Driver
必備標頭檔
<linux/module>有眾多定義好的kernel symbol可以調用
<linux/init> 則有module_init() module_exit() 的定義
以上資訊在ubuntu中可以於 /lib/modules/$( uname -r )/build/include/linux 中找到
習慣上會將 MODULE_ 開頭的變數至於檔案最後
init , exit function
static int __init initialization_function(void)
{
/* Initialization code here */
}
module_init(initialization_function);
是泛化的init module架構
同時因為只允許該檔案使用,通常會宣告static
Review static function Static function只能被宣告的Compilation Unit使用
__init關鍵字,會讓kernel知道這個函數在init完後就沒用了,可以丟棄
module可以註冊(register)許多不同的設備,包含device ,filesystem…
會由一個特殊的kernel function負責註冊,並且會此函數要吃被註冊的設備的資料結構的指標
同時此資料結構內也會有指標指向module
註冊函數會以register_ 開頭
同理 exit函數也有架構:
static void __exit cleanup_function(void)
{
/* Cleanup code here */
}
module_exit(cleanup_function);
需要有exit函數,kernel才會允許卸載(unloaded)
__exit關鍵字表達此函數只能在卸載時被呼叫
參考資料
Ch2: Building and Running Modules
https://lwn.net/Kernel/LDD3/