编写Linux内核模块——第一部分:前言
【編者的話】Linux內核模塊作為Linux內核的擴展手段,可以在運行時動態加載和卸載。它是設備和用戶應用程序之間的橋梁,可以通過標準系統調用,為應用程序屏蔽設備細節。本文來自Derek Molloy的博客,介紹了內核模塊的概念、用途,以及如何構建一個簡單的“Hello World”內核模塊。 \
前言
\在這系列文章中,將介紹如何為嵌入式Linux設備編寫Linux內核模塊。文章將從簡單的可加載內核模塊(loadable kernel module,LKM)“Hello World!”開始,進而開發通過使用中斷請求控制嵌入式Linux設備(如BeagleBone)通用輸入輸出接口(GPIO)的模塊。當我確定合適的應用程序時,我會添加更多的后續文章。 \
內核模塊是一個復雜的話題,需要一定的時間來完成。因此,我將內容拆分成幾篇文章,每篇提供一個可以實踐的示例和結果。這個話題可以寫一整本書,因此很難覆蓋每一個方面。關于編寫內核模塊的其他文章也有很多,而本文的示例都在Linux內核3.8.X以上版本構建和測試,以確保這些材料是最新且貼切的。同時,本文主要關注嵌入式系統的硬件接口。在我的書《Exploring BeagleBone》中也有相同的示例,由于本文自身包含了這些代碼,讀者無須擁有該書的副本。 \
\圖1:內核空間GPIO性能 \
本文集中討論構建和部署“Hello World!”內核模塊所需的系統設置、工具和代碼。本系列中的第二篇文章探討了如何編寫字符設備驅動和如何編寫用戶空間C/C++程序與內核空間模塊進行交互。第三篇文章探討內核空間GPIO庫代碼的使用,它結合了前兩篇文章的內容,開發中斷驅動代碼,使之能夠從Linux用戶空間控制。例如,圖1展示了示波器捕獲的通過中斷驅動內核模塊處理按鈕按下到LED亮起的圖形。在常規嵌入式Linux中(即非實時Linux的變體),該代碼展示忽略CPU開銷后,響應時間大約為20毫秒(±5微秒)。 \
什么是內核模塊
\可加載內核模塊(LKM)是Linux內核運行時加載和移除代碼的機制。該機制對于設備驅動是理想的,這使得內核可以在不知道硬件如何工作的情況下和硬件進行交互。可加載內核模塊的替代是將每個驅動代碼構建到Linux內核中。 \
沒有模塊化能力,Linux內核將會變得非常大,因為它不得不支持BeagleBone開發板上所需的每個驅動。同時,在需要添加新硬件或者升級設備驅動時,必須重新構建內核??杉虞d內核模塊功能的缺點是對于每個設備都必須維護一個驅動文件??杉虞d內核模塊在運行時加載,他們不運行在用戶空間,本質上是內核的一部分。\
\圖2:Linux用戶空間和內核空間 \
如圖2所示,內核模塊運行在內核空間,而應用程序運行在用戶空間。內核空間和用戶空間都有自己獨立的內存地址,不會相互重疊。此方法確保了運行在用戶空間中的應用程序對于硬件有一致的視圖,不用關注硬件平臺本身。內核服務通過系統調用以可控的方式提供給用戶空間。同時,內核阻止獨立的用戶空間應用程序之間相互競爭或通過使用保護級別訪問受限資源(比如超級用戶與普通用戶的權限)。 \
為什么編寫內核模塊
\在嵌入式Linux中和電子電路交互,你接觸到的是系統文件系統,并且使用低級別的文件操作來和電子電路交互。這種方式效率很低(尤其是如果你有傳統嵌入式系統開發經驗)。然而,對這些文件項進行內存映射后,對于許多應用程序來說性能是足夠的。我在書中已經證明,通過在Linux用戶空間使用pthread、回調函數和sys/poll.h,在忽略CPU開銷下,是可以做到約三分之一毫秒的響應時間。 \
另一個實現是使用內核代碼,它支持中斷。然而內核代碼難以編寫和調試。我的建議是優先嘗試在Linux用戶空間完成任務,除非已確定沒有其他可行方法。 \
本次討論的源碼
\本次討論的所有代碼都在為《Exploring BeagleBone》準備的GitHub倉庫上。代碼可以在ExploringBB GitHub倉庫內核工程目錄中公開查看,或者也可以將代碼復制到BeagleBone(或者其他Linux設備):
molloyd@beaglebone:~$ sudo apt-get install git\molloyd@beaglebone:~$ git clone https://github.com/derekmolloy/exploringBB.git\代碼中/extras/kernel/hello目錄是本文最重要的資源。為這些示例代碼自動生成的Doxygen文檔有HTML格式和PDF格式。 \
準備構建可加載內核模塊的系統
\為了構建內核代碼,需要在設備上安裝Linux內核頭文件。在典型的Linux桌面機器上,可以使用包管理器來查找和安裝正確的包。例如,在64位Debian發行版中,可以這樣做:
molloyd@DebianJessieVM:~$ sudo apt-get update\molloyd@DebianJessieVM:~$ apt-cache search linux-headers-$(uname -r)\ linux-headers-3.16.0-4-amd64 - Header files for Linux 3.16.0-4-amd64\ molloyd@DebianJessieVM:~$ sudo apt-get install linux-headers-3.16.0-4-amd64\ molloyd@DebianJessieVM:~$ cd /usr/src/linux-headers-3.16.0-4-amd64/\ molloyd@DebianJessieVM:/usr/src/linux-headers-3.16.0-4-amd64$ ls\ arch include Makefile Module.symvers scripts\本系列的前兩篇文章的示例,可以在任何桌面Linux發行版中完成構建。然而,本系列文章中,我將在BeagleBone上直接構建內核模塊,這相比于交叉編譯可以簡化步驟。安裝的內核頭文件必須和內核構建版本一致。和桌面版安裝類似,使用uname命令來識別正確的安裝版本。例如:
molloyd@beaglebone:~$ uname -a\Linux beaglebone 3.8.13-bone70 #1 SMP Fri Jan 23 02:15:42 UTC 2015 armv7l GNU/Linux\BeagleBone平臺的Linux內核頭文件可以從Robert Nelson的網站下載。比如在http://rcn-ee.net/deb/precise-armhf/,選擇準確的內核構建版本,并且在BeagleBone上下載和安裝這些Linux內核頭文件。例如:\
molloyd@beaglebone:~/tmp$ wget http://rcn-ee.net/deb/precise-armhf/v3.8.13-bone70\ /linux-headers-3.8.13-bone70_1precise_armhf.deb \100%[===========================\u0026gt;] 8,451,080 2.52M/s in 3.2s\2015-03-17 22:35:45 (2.52 MB/s) - 'linux-headers-3.8.13-bone70_1precise_armhf.deb' saved [8451080/8451080]\molloyd@beaglebone:~/tmp$ sudo dpkg -i ./linux-headers-3.8.13-bone70_1precise_armhf.deb \Selecting previously unselected package linux-headers-3.8.13-bone70\然后可以檢查頭文件是否正確安裝:
molloyd@beaglebone:~/tmp$ cd /usr/src/linux-headers-3.8.13-bone70/ \molloyd@beaglebone:/usr/src/linux-headers-3.8.13-bone70$ ls\Documentation Module.symvers crypto fs ipc mm scripts tools\Kconfig arch drivers include kernel net security usr\Makefile block firmware init lib samples sound virt\給BeagleBone使用的3.8.13-bone47版本內核的Debian發行版中,需要執行一個特殊步驟在/usr/src/linux-headers-3.8.13-bone47/arch/arm/include/mach目錄中創建一個空的timex.h文件(即touch timex.h)。bone70構建不需要此步驟。 \
警告
\編寫和測試內核模塊時很容易使系統崩潰。系統崩潰可能會損壞文件系統。雖然系統崩潰不太常見,但這是可能發生的。請備份數據或者使用一個嵌入式系統,如BeagleBone,他們能夠很方便的被重新刷寫。通過執行sudo reboot或者按BeagleBone上的重置按鈕,通常能夠恢復到正常狀態。在寫本系列文章過程中,盡管有很多很多次系統崩潰,但BeagleBones并沒有損壞過。 \
模塊代碼
\傳統計算機程序的運行生命周期相當簡單。加載器為程序分配內存,然后加載程序和所需要的動態鏈接庫。指令從一些入口開始執行(傳統C/C++程序以main()函數作為入口),語句被執行,異常被拋出,動態內存被分配和釋放,程序最終運行完成。當程序退出時,操作系統識別任何內存泄露,并釋放到內存池。 \
內核模塊不是應用程序,從一開始就沒有main()函數。內核模塊和普通應用程序的區別有: \
- 非順序執行:內核模塊使用初始化函數將自身注冊并處理請求,初始化函數運行后就結束了。內核模塊處理的請求在模塊代碼中定義。這和常用于圖形用戶界面(graphical-user interface,GUI)應用的事件驅動編程模型比較類似。 \
- 沒有自動清理:任何由內核模塊申請的內存,必須要模塊卸載時手動釋放,否則這些內存將無法使用,直到系統重啟。 \
- 不要使用printf()函數:內核代碼無法訪問為Linux用戶空間編寫的庫。內核模塊運行在內核空間,它有自己獨立的地址空間。內核空間和用戶空間的接口被清晰的定義和控制。內核模塊可以通過printk()函數輸出信息,這些輸出可以在用戶空間查看到。 \
- 會被中斷:內核模塊一個概念上困難的地方在于他們可能會同時被多個程序/進程使用。構建內核模塊時需要小心,以確保在發生中斷的時候行為一致和正確。BeagleBone有一個單核處理器(目前為止),但是我們仍然需要考慮多進程同時訪問對模塊的影響。 \
- 更高級的執行特權:通常內核模塊會比用戶空間程序分配更多的CPU周期。這看上去是一個優勢,然而需要特別注意內核模塊不會影響到系統的綜合性能。 \
- 無浮點支持:對用戶空間應用,內核代碼使用陷阱(trap)來實現整數到浮點模式的轉換。然而在內核空間中這些陷阱難以使用。替代方案是手工保存和恢復浮點運算,這是最好的避免方式,并將處理留給用戶空間代碼。
以上概念有很多需要消化,重要的是,它們都被解決,但是沒有都包含在第一篇文章中。列表1提供了第一個示例內核模塊的的代碼。當沒有提供內核參數時,代碼使用printk()函數顯示“Hello world!...”,如果提供了參數“Derek”,日志會顯示“Hello Derek!...”。列表1中的注釋使用Doxygen樣式,描述每個語句角色。更多的描述在代碼列表下放。
/**\ * @file hello.c\ * @author Derek Molloy\ * @date 4 April 2015\ * @version 0.1\ * @brief 入門的可加載內核模塊“Hello World!”,當模塊加載和移除的時候,會在/var/log/kern.log文件輸出消息。\ * 該模塊在加載的時候接受一個參數:名字,它將顯示在內核日志文件中。\ * @see http://www.derekmolloy.ie/ 查看完整描述和補充描述。\*/\\#include \u0026lt;linux/init.h\u0026gt; // 用于標記函數的宏,如__init、__exit\#include \u0026lt;linux/module.h\u0026gt; // 加載內核模塊到內核使用的核心頭文件\#include \u0026lt;linux/kernel.h\u0026gt; // 包含內核使用的類型、宏和函數\\MODULE_LICENSE(\"GPL\"); ///\u0026lt; 許可類型,它會影響到運行時行為\MODULE_AUTHOR(\"Derek Molloy\"); ///\u0026lt; 作者,當使用modinfo命令時可見\MODULE_DESCRIPTION(\"A simple Linux driver for the BBB.\"); ///\u0026lt; 模塊描述,參見modinfo命令\MODULE_VERSION(\"0.1\"); ///\u0026lt; 模塊版本\\static char *name = \"world\"; ///\u0026lt; 可加載內核模塊參數示例,這里默認值設置為“world”\module_param(name, charp, S_IRUGO); ///\u0026lt; 參數描述。charp表示字符指針(char ptr),S_IRUGO表示該參數只讀,無法修改\MODULE_PARM_DESC(name, \"The name to display in /var/log/kern.log\"); ///\u0026lt; 參數描述\\/** @brief 可加載內核模塊初始化函數\ * static關鍵字限制了該函數的可見范圍為當前C文件。\ * __init宏表示對于內置驅動(不是可加載內核模塊),該函數只在初始化的時候執行,\ * 在此之后,該函數可以廢棄,且內存可以被回收。\ * @return 當執行成功返回0\ */\static int __init helloBBB_init(void){\ printk(KERN_INFO \"EBB: Hello %s from the BBB LKM!\\總結
以上是生活随笔為你收集整理的编写Linux内核模块——第一部分:前言的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: STM32F0308开发环境的选择--C
- 下一篇: python 发送邮件解决所有乱码问题