http://www.dss.cn/Article/embed_system/200511/792.html
Linux內核/模塊開發常見問題集(FAQ)
轉載自水木清華
歡迎大家補充
1. 請推薦一些好的Linux內核參考書?
2. 源代碼問題
2.1 如何得到某一版本的Linux內核源代碼?
2.2 請問xx命令、xx庫的源碼是哪個文件?
2.3 linux-2.x.x.tar.gz.sign 文件有什麼用途?
2.4 請推薦一些源代碼查看工具?
2.5 內核patch如patch-2.6.3怎麼用?
2.6 如何統計linux內核有多少行代碼?
2.7 xx結構的定義在哪個內核源文件中?
2.8 volatile和__volatile__是什麼意思?
2.9 do{ ... } while(0)是什麼意思?
2.10 list_entry的定義是怎麼回事?
2.11 校內查看linux 內核源代碼地址
3. 模塊編程問題
3.1 模塊編程需要注意什麼?
3.2 爲什麼insmod一個模塊時顯示版本不匹配?
3.3 爲什麼出現Unresolved Symbol錯誤?
3.4 爲什麼出現no license錯誤?
3.5 爲什麼看不到用printk打印的信息?
4. 內核開發問題
4.1 怎麼製作、使用patch文件?
4.2 在內核中可以使用系統調用嗎?
4.3 在內核中怎麼打開並操作一個文件?
4.4 在內核中讀寫文件時爲什麼會出現EFAULT(-14)錯誤?
4.5 怎麼在系統中增加一個自己的系統調用?
4.6 怎麼在內核中加入我自己的驅動程序?
4.7 怎麼通過程序得到cpu和mem使用率?
4.8 如何獲得高精度的系統時間?
4.9 怎麼進行系統性能調諧?
4.10 內核中怎麼進行互斥?
5. 其它問題
5.1 如何學習Linux內核?
5.2 如何下載精華區?
5.3 init進程是核心進程嗎?init與初始進程是不是一回事?
5.4 initrd(.img)有什麼用?
6. 關於本FAQ
7. Changelog
1. 請推薦一些好的Linux內核參考書?
a.《Linux Device Drivers, 2nd Edition》,有中文譯本
b.《Understanding the Linux Kernel, 2nd Edition》
c.《Linux內核源代碼情景分析》,分上下兩冊
d.《邊幹邊學-Linux內核指導》
e.《Linux內核2.4版源代碼分析大全》
f.《Linux Kernel Development》
g.《IA-64 Linux Kernel: Design and Implementation》
注:a電子版可在http://www.oreilly.com/catalog/linuxdrive2/
下載;
f和g比較新,在國內比較難買到。
也可以版面查詢文章標題 "Linux內核書籍推薦&介紹"
2. 源代碼問題
2.1 如何得到某一版本的Linux內核源代碼?
a. http://www.kernel.org
或ftp://ftp.kernel.org
,這是Linux內核版本的發佈
網站。
b. 很多鏡像或本地網站也提供部分Linux內核版本的下載,多用ftp搜索引擎。
linuxaid.com提供的mirror:
ftp://ftp.linuxaid.lkams.kernel.org/pub/mirrors/kernel/linux/kernel
c. 一般的Linux發行版如Redhat之類會隨盤提供相應的內核源代碼,不過這個源代
碼往往是改動過的,與標準Linux內核有差異。
2.2 請問xx命令、xx庫的源碼是哪個文件?
a. 一個系統除了內核以外,還需要有shell、gcc等一系列工具和命令以及C庫等一
系列庫,這些作爲應用程序其源代碼都不在內核中,需要另外下載相應的源代碼。
b. 對於Redhat系統,可以用rpm -qf命令來查找某一命令所在的軟件包,然後再找
相應的源代碼包安裝。
c. gnu.org有很多軟件源代碼如bash/glibc/binutils/make/gcc的源代碼。
d. 可在http://www.rpmfind.net
或http://www.google.com
去搜一搜。
2.3 linux-2.x.x.tar.gz.sign 文件有什麼用途?
這是一個數字簽名文件,用來校驗linux-2.x.x.tar.gz這個文件在簽名後是沒有
被第三方修改過,更詳細的信息參考http://www.kernel.org/signature.html
。
2.4 請推薦一些源代碼查看工具?
a. Windows系統可以用Source Insight,Linux系統可以用Source Navigator。
b. vim或emacs編輯器,配合cscope、ctags、etags等交叉索引工具。
c. vim或emacs編輯器,配合grep、egrep等文本搜索工具,不過最好要對源代碼目
錄結構有所熟悉
d. LXR,以網頁的形式通過瀏覽器瀏覽,安裝複雜(debian下安裝容易,請版面
搜尋lxr)
校外:可以直接訪問http://lxr.linux.no/source/
在線閱讀Linux內核源代碼。
校內:可以訪問http://10.214.14.127/lxr/http/source/
,如果需要特定版本
可以聯繫vatano,只有空間足夠就放上去。
e. GNU global,可以在命令行用,也可以生成hypertext,類似lxr,但更省事。
2.5 內核patch如patch-2.6.3怎麼用?
a. 內核patch一般是針對前一個版本,如patch-2.6.3是針對2.6.2的內核。
b. 內核patch一般是和ChangeLog對應,如patch-2.6.3對應於ChangeLog-2.6.3。
c. 在內核patch中查找Makefile關鍵字可得到相關信息,如在patch-2.6.0中有:
diff -Nru a/Makefile b/Makefile
--- a/Makefile Wed Dec 17 19:00:07 2003
+++ b/Makefile Wed Dec 17 19:00:07 2003
@@ -1,7 +1,7 @@
VERSION = 2
PATCHLEVEL = 6
SUBLEVEL = 0
-EXTRAVERSION = -test11
+EXTRAVERSION =
d. 找到了針對的內核就可以用patch來升級內核了。
2.6 如何統計linux內核有多少行代碼?
嘗試以下shell命令:
find /usr/src/linux-2.x.x -name "*.[chS]" | xargs cat | wc -l
2.7 xx結構的定義在哪個內核源文件中?
a. 請使用源碼查看工具,見問題2.4。
b. 如果用grep等文本搜索工具,主要在include/linux和include/asm兩個目錄下
搜索。
2.8 volatile和__volatile__是什麼意思?
a. volatile是C語言定義的關鍵字,gcc爲了需要又定義了__volatile__,它和
volatile表達的是同一意思。
b. volatile的本意是"易變的",由於訪問寄存器的速度快於訪存,所以編譯器一般
都會作優化以減少訪存。如果變量加上volatile修飾,則編譯器就不會對此變量
的讀寫操作進行優化,即不通過寄存器緩衝而直接訪存。
c. __asm__ __volatile__一起指示編譯器不要改動優化後面的彙編語句。
2.9 do{ ... } while(0)是什麼意思?
a. 主要是爲了避免宏在不同情況展開可能會出現的一些錯誤。
b. 在http://www.kernelnewbies.org/faq/
上有詳細介紹。
2.10 list_entry的定義是怎麼回事?
a. list_entry的定義在內核源文件include/linux/list.h中:
#define list_entry(ptr, type, member) /
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
b. 其功能是根據list_head型指針ptr換算成其宿主結構的起始地址,該宿主結構是
type型的,而ptr在其宿主結構中定義爲member成員。如下圖:
req-->|type型對象起始地址
|
|... ...
ptr-->|ptr指針所指的member成員地址
|
|... ...
ptr指向圖中所示的位置,通過(unsigned long)(&((type*)0)->member)得到ptr
和req之間的差值,ptr減去這個差值就得到了type型宿主結構的指針req,返回
類型爲(type*)。
2.11 校內查看Linux內核源代碼的地址
http://10.214.14.127/lxr/http/source/
3. 模塊編程問題
3.1 模塊編程需要注意什麼?
a. 在gcc編譯選項中增加-c
b. 在gcc編譯選項中定義兩個宏:-DMODULE -D__KERENL__
或直接在源文件中定義這兩個宏:
#define MODULE
#define __KERNEL__
c. 在源文件中包括module.h文件:
#include <linux/module.h>
d. 假定你現在運行的內核的源碼目錄絕對路徑是MyKernelSrcPath,在gcc編譯時
增加選項:
-I $MyKernelSrcPath/include (如-I /usr/src/linux/include)
注意MyKernelSrcPath必須是指向與當前運行的系統內核匹配的(版本一致)、
能編譯成功的(保證源代碼目錄的完整性)、已經config過的(保證有.config和
include/linux/autoconf.h文件)內核源碼目錄
注意:通常不要 -I /usr/include/linux
e. 如果要用inline功能,需要在gcc編譯選項中增加-O2
f. init_module()函數必須return 0,否則會出現Device or resource busy錯誤。
3.2 爲什麼insmod一個模塊時顯示版本不匹配?
a. 見上面3.1->d
b. 某些時候用insmod -f能夠成功加載,但需謹慎使用。
3.3 爲什麼出現Unresolved Symbol錯誤?
a. 首先查看文件/proc/ksyms,看內核有沒有輸出這個符號,不同的內核版本如
2.2和2.4輸出的符號會有些變化。
b. 如果內核輸出的符號帶有版本控制信息如符號printk_R12345678,則性質同
問題3.2。
c. 注意:現在有很多版本都不輸出sys_call_table了,另想辦法吧!
3.4 爲什麼出現no license錯誤?
在源文件加入下面一行(加在文件頭部,尾部均可):
MODULE_LICENSE("GPL");
3.5 爲什麼看不到用printk打印的信息?
a. 打印消息受級別的限制,消息級別可以通過printk設置,如:
printk("<n>something"); /* 其中0<=n<=7 */
假設控制檯的消息級別爲m, 當n<m時消息打印到控制檯,否則不打印。
這樣一方面可以提高要打印消息本身的級別(數字越小級別越高),
另一方面可以改變控制檯的消息級別(可從1到8),如改爲8可用以下命令:
# echo "8" > /proc/sys/kernel/printk
b. 用dmesg命令看。
c. 當系統運行klogd和syslogd時,內核消息就會由klogd分發到syslogd,
syslogd會根據配置文件/etc/syslog.conf作相應處理,具體可以查看syslogd
和syslog.conf的man頁。
4. 內核開發問題
4.1 怎麼製作、使用patch文件?
a. patch文件是由diff命令生成的,使用patch文件用patch命令,具體可查看diff
和patch的man頁和info。
b. diff命令的常用選項組合是urN,如:
diff -urN linux/ my_linux/ >mypatch.diff
4.2 在內核中可以使用系統調用嗎?
a. 可以。內核源代碼中就有使用系統調用的例子,如open()、execve()等。
b. 在內核中使用系統調用必須要在源文件中包括以下兩行:
#define __KERNEL_SYSCALLS__
#include <linux/unistd.h>
c. 內核中使用系統調用的相關定義可查看文件include/asm/unistd.h。
如果要用的系統調用該文件中沒有定義,可以按照其格式自行添加。
d. 如果要在模塊中使用系統調用,必須要自己定義errno如:
int errno;
內核在lib/errno.c中定義了errno,但該符號不導出,所以模塊編程時需要自己
定義errno,用以存放系統調用出錯號。
4.3 在內核中怎麼打開並操作一個文件?
a. 直接用open()、read()等系統調用,見問題4.2。
b. 用filp_open()函數打開文件,得到struct file *的指針fp。
使用指針fp進行相應操作,如讀文件可以用fp->f_ops->read。
最後用filp_close()函數關閉文件。
filp_open()、filp_close()函數在fs/open.c定義,在include/linux/fs.h中
聲明。
c. 自己寫包裝函數,可參照文件fs/exec.c中的open_exec()和kernel_read()函數。
在http://www.linuxforum.net/forum/showflat.php?Cat=&Board=linuxK
&Number=363455&page=&view=&sb=&o=&vc=1上有些代碼可以參照。
4.4 在內核中讀寫文件時爲什麼會出現EFAULT(-14)錯誤?
a. 內核文件系統提供的read()和write()之類的函數,期望是對用戶態程序服務的,
所以它會驗證讀寫緩衝區不超過用戶空間的上限即0xC000 0000。但現在內核中
要讀寫文件,緩衝區在內核中即地址會超過0xC000 0000。
b. 在讀寫文件前先得到當前fs:mm_segment_t old_fs=get_fs();
並設置當前fs爲內核fs:set_fs(KERNEL_DS);
在讀寫文件後再恢復原先fs: set_fs(old_fs);
set_fs()、get_fs()等相關宏在文件include/asm/uaccess.h中定義。
4.5 怎麼在系統中增加一個自己的系統調用?
去http://www.linuxaid.com.cn/engineer/ideal/kernel/new_syscall.htm
和http://www.xenotime.net/linux/syscall_ex/
看看。
4.6 怎麼在內核中加入自己的驅動程序?
a. 去http://www-900.ibm.com/developerWorks/cn/linux/kernel/l-kerconf/
index.shtml看看,瞭解一下整個內核的配置編譯系統。
b. 在相應位置建立自己的源碼目錄、文件、Makefile等。
c. 修改上層Makefile,把自己的程序加入到內核編譯系統中。
d. 修改上層Config.in,把自己的程序加入到內核配置系統中。
e. 確保自己的初始化函數被調用。有兩種方法,一是顯式調用,即在原來的系統
初始化函數中直接加入對自己的調用,如字符設備就在drivers/char/mem.c中的
chr_dev_init()函數中加入,塊設備就在drivers/block/ll_rw_blk.c中的
blk_dev_init()函數中加入。另一種方法是用initcall,用宏module_init來申
明你的初始化函數,操作系統在初始化到一定階段後會自動通過init/main.c中
的do_initcalls()函數來統一調用這些初始化函數。module_init宏在文件
include/linux/init.h中定義。
4.7 怎麼通過程序得到cpu和mem使用率?
a. 這些信息的最終來源都是/proc目錄下的文件,如/proc/stat等。
b. procps包下的命令如top、vmstat等實現了這些功能,可以參照其源代碼。
c. procps包可從Redhat發行版中得到,也可從http://www.surriel.com/procps/
處獲得。
4.8 如何獲得高精度的系統時間?
a. Linux中jiffy是時鐘的基本單位,對於一般的系統來說配置成10ms。大多數時
鐘相關的系統調用都是基於jiffy,所以精度不會太高。
b. 可以考慮使用TSC(time stamp counter)、rtc(real time clock)等寄存器來獲得
高精度時鐘,具體可查看相關的硬件手冊。
4.9 怎麼進行系統性能調諧?
a. IBM developworks:
http://www-900.ibm.com/developerWorks/cn/linux/l-kperf/index.shtml
http://www-900.ibm.com/developerWorks/cn/linux/management/tune/index.sht
ml
b. Linux Performance Tuning項目:http://linuxperf.nl.linux.org/
c. http://www.fixdown.com/article/article/724.htm
4.10 內核中怎麼進行互斥?
a. Linux內核中有兩種機制實現互斥:semaphore和spinlock。semaphore是讓進
程睡眠等待資源,這一般假設無法預測資源什麼時候可以獲得;spin_lock一般
用在SMP中,它假設所等待的資源馬上就會被釋放,所以循環等待資源。
semaphore只能用於非中斷環境(典型的中斷環境過程包括象timer之類的中斷
服務程序,softirq等)的進程間互斥,spinlock可以用於所有的進程間包括不同
cpu的進程間的互斥,spinlock主要用於保護短小的臨界區,使用時必須要特別注
意死鎖問題。
b. semaphore是通過進程調度來實現互斥的。進程請求獲取semaphore時,如果
semaphore空閒則該進程獲得semaphore,設置標誌並返回;如果semaphore忙
(其它用戶已經獲得semaphore)則系統構建等待隊列並通過進程調度機制讓本進
程睡眠。進程釋放semaphore時,系統按一定規則通過等待隊列喚醒一個睡眠進
程。對semaphore可執行up()和down()操作,詳見include/asm/semaphore.h文件。
c. spinlock主要是爲SMP互斥而引入的。在請求獲取spinlock時,如果空閒則獲得
spinlock,設置標誌並返回。如果spinlock已經被其它用戶獲得而處於忙狀態,
系統就會一直佔用CPU資源,不停查詢spinlock的狀態直到獲得spinlock。
5. 其它問題
5.1 如何學習Linux內核?
請先閱讀本版精華區內核學習目錄的相關文章。
5.2 如何下載精華區?
a. 除了88提供的下載,還可以通過腳本下載-_-
5.3 init進程是核心進程嗎?init與初始進程是不是一回事?
Linux操作系統在系統初始化之初就捏造了一個原始進程(原始進程在系統初始化
完畢後就演化成idle進程),當系統初始化進行到一定階段,原始進程會創建(通
過kernel_thread()函數)出來init進程,init進程繼續進行系統初始化工作並在最
後執行execve("/sbin/init",...),這樣init就從原來的核心進程搖身一變成用戶
進程(用戶程序/sbin/init)了。init進程的pid爲1,原始進程(idle進程)的
pid爲0。所有其它的進程都由init進程派生,用ps或pstree命令可以看到這一點。
5.4 initrd(.img)有什麼用?
a. initrd(.img)是一個文件系統映像,裏面一般包含一些特殊的硬件模塊尤其是存
儲設備如scsi/raid/ext3模塊,以便在保持內核足夠小的同時又支持儘可能多的硬
件設備,常被安裝程序使用。
initrd(.img)也不是必需的,只要必要的模塊編譯進內核就可以不用initrd(.img)。
b. 在使用了initrd(.img)時,系統引導的大致過程如下:
1)Loader程序(如lilo和grub)加載內核和initrd(.img)
2)內核解壓縮initrd(.img)爲正常的RAM盤文件系統並掛接爲根分區
3)執行linuxrc,在此過程中會加載硬件模塊
4)在linuxrc終止後,真正的根文件系統被掛接
5)在根文件系統上完成正常的引導過程。對於正常的系統而言,執行/sbin/init,
這時控制就會轉到正常的大家所熟知的啓動過程。而對於安裝程序,只需將控制
轉到安裝過程的第一階段,由它完成後續的安裝環境的加載,設備的進一步初始
化等操作。
c. 要使用initrd(.img)首先內核必須配置成支持initrd:
CONFIG_BLK_DEV_RAM=y
CONFIG_BLK_DEV_INITRD=y
其次要在Loader腳本中增加相應指示。如在grub.conf中增加一行:
initrd /boot/initrd-2.4.20.img
d. 可用mkinitrd命令創建initrd(.img)文件:
mkinitrd imagefilename kernelversion
如對於2.4.20的內核可以:
mkinitrd /boot/initrd-2.4.20.img 2.4.20
e. 具體可查看Documentation/initrd.txt和man mkiinitrd。mkinitrd命令執行的詳
細過程可以直接查看/sbin/mkinitrd(shell腳本)文件。
6. 關於本FAQ
本FAQ主要根據本版以前的文章整理而成。
特別感謝mada、pepp等網友提出寶貴意見!
7. Changelog
2004/05/10 junky
--發佈FAQ 1.0
--調整了目錄結構
--增加了Q2.5,Q2.6,Q5.1,Q5.2,Q5.4
--Q1增加了2本新書
--Q2.2增加了gnu.org的鏈接
--Q3.1增加了init_module()返回0
--Q3.2增加了insmod -f
--Q4.2增加了errno的說明
--Q4.9改名並增加了IBM developworks的鏈接
2004/07/07 junky
--刪除Q2.11
--增加Q2.11
--修改Q1, Q2.4, Q5.2
2004/12/15 vatano