十天学Linux内核之第九天---向内核添加代码
睡了個好覺,很晚才起,好久沒有這么舒服過了,今天的任務不重,所以壓力不大,呵呵,現(xiàn)在的天氣真的好冷,不過實驗室有空調(diào),我還是喜歡待在這里,有一種不一樣的感覺,在寫了這么多天之后,自己有些不懂的頁漸漸的豁然開朗了嗎,而且也交到了一些朋友,真是相當開心啊。今天將介紹一下向內(nèi)核中添加代碼,一起來看看吧~
先來熟悉一下文件系統(tǒng),通過/dev可以訪問Linux的設備,我們以men設備驅(qū)動程序為例來看看隨機數(shù)是如何產(chǎn)生的,源代碼在dirvers/char/mem.c上可以查看
static int memory_open(struct inode * inode * inode,struct file * filp) {switch (iminor(inode)) { //switch語句根據(jù)從設備號來初始化驅(qū)動程序的數(shù)據(jù)結(jié)構case 1:...case 8:filp->f_op = &random_fops;break;case 9:filp->f_op = &urandom_fops;break;那么上述程序的filps和fop是什么呢?實際上filp只是一個文件結(jié)構指針,而fop是一個file_operations結(jié)構指針,內(nèi)核通過file_operations結(jié)構來確定操作文件時要調(diào)用的函數(shù),下面的file_operations結(jié)構用于隨機設備驅(qū)動的部分內(nèi)容,代碼在include/linux/fs.h上可以查看到:
struct file {struct list_head f_list;struct dentry *f_dentry;struct vfsmount *f_vfsmnt;struct file_operations *f_op;atomic_t f_count;unsigned int f_flags; ...struct address_space *f_mapping; };q驅(qū)動程序所實現(xiàn)的函數(shù)必須符合file_operations結(jié)構中所列出的函數(shù)原型,代碼在dirvers/char/random.c上可以查看:
struct file_operations random_fops = {.read = random_read,.write = random_write,.poll = random_poll, //poll操作允許某種操作之前查看該操作是否阻塞.ioctl = random_ioctl, }; //隨機設備提供的操作有以上struct file_operations urandom_fops = {.read = random_read,.write = random_write,.ioctl = random_ioctl, }; //urandom設備提供的操作有以上如果設備驅(qū)動程序在內(nèi)核空間運行,但是緩沖區(qū)卻位于用戶空間,那我們該如何才能安全訪問buf中的數(shù)據(jù)呢,下面來說下數(shù)據(jù)在用戶空間和內(nèi)核空間之間的奧秘,Linux提供的copy_to_user()和copy_from_user()使得驅(qū)動程序可以在內(nèi)核空間和用戶空間上傳遞數(shù)據(jù),在read_random()中,通過extract_entropy()函數(shù)來實現(xiàn)這個功能,下面代碼在dirvers/char/random.c上可以查看(下面的代碼沒有敲完,主要是不是很懂,望大神指教)
static ssize_t extract_entropy(struct entract_syore *r,void *buf,size_t nbytes,int flags) { ... {static ssize_t extract_entropy(struct entropy_store *r,void *buf,size_t nbytes,int flags) { ...內(nèi)核空間和用戶空間的程序可能都需要使用已經(jīng)獲得的隨機數(shù),內(nèi)核空間的程序可以通過不設置標志位來避免函數(shù)copyto_user()帶來的額外開銷。除了通過設備驅(qū)動程序向內(nèi)核添加代碼之外,還有別的方式 的,用戶空間可以通過系統(tǒng)調(diào)用來訪問內(nèi)核服務程序和系統(tǒng)硬件,這里不多闡釋,都知道有這回事就行了。
?
下面我們來介紹怎么去編寫源代碼,當我們?nèi)ゾ帉懸粋€復雜的設備驅(qū)動程序時,也許要輸出驅(qū)動程序中定義的某些符合,以便讓內(nèi)核其它模塊使用,這些通常被用在低級的驅(qū)動程序中,以便根據(jù)這些基本的函數(shù)來構建更高級的驅(qū)動程序,在Linux2.6內(nèi)核中,code monkey可以用如下兩個宏輸出符號,代碼在include/linux/module.h中查看:
#define EXPORT_SYMBOL(sym)__EXPORT_SYMBOL(sym, "")#define EXPORT_SYMBOL_GPL(sym)__EXPORT_SYMBOL(sym, "_gpl")目前為止,我們介紹的設備 驅(qū)動程序都是主動操作,或者對設備的數(shù)據(jù)進行讀寫操作,那么它的功能不止這些的時候會怎么樣呢?在Linux中,設備驅(qū)動程序解決這些問題的典型方式就是使用ioctl。ioctl是設備驅(qū)動程序中對設備的I/O通道進行管理的函數(shù)。所謂對I/O通道進行管理,就是對設備的一些特性進行控制,例如串口的傳輸波特率、馬達的轉(zhuǎn)速等等。
調(diào)用個數(shù)如下:
int ioctl(int fd, ind cmd, …);其中fd就是用戶程序打開設備時使用open函數(shù)返回的文件標示符,cmd就是用戶程序?qū)υO備的控制命令,至于后面的省略號,那是一些補充參數(shù),一般最多一個,有或沒有是和cmd的意義相關的。ioctl函數(shù)是文件結(jié)構中的一個屬性分量,就是說如果你的驅(qū)動程序提供了對ioctl的支持,用戶就可以在用戶程序中使用ioctl函數(shù)控制設備的I/O通道。
?
ioctl命令號:
dir:
代表數(shù)據(jù)傳輸?shù)姆较?#xff0c;占2位,可以是_IOC_NONE(無數(shù)據(jù)傳輸,0U),_IOC_WRITE(向設備寫數(shù)據(jù),1U)或_IOC_READ(從設備讀數(shù)據(jù),2U)或他們的邏輯或組合,當然只有_IOC_WRITE和_IOC_READ的邏輯或才有意義。
type:
描述了ioctl命令的類型,8位。每種設備或系統(tǒng)都可以指定自己的一個類型號,ioctl用這個類型來表示ioctl命令所屬的設備或驅(qū)動。一般用ASCII碼字符來表示,如 'a'。nr:
ioctl命令序號,一般8位。對于一個指定的設備驅(qū)動,可以對它的ioctl命令做一個順序編碼,一般從零開始,這個編碼就是ioctl命令的序號。size:
ioctl命令的參數(shù)大小,一般14位。ioctl命令號的這個數(shù)據(jù)成員不是強制使用的,你可以不使用它,但是我們建議你指定這個數(shù)據(jù)成員,通過它我們可以檢查用戶空間數(shù)據(jù)的大小以避免錯誤的數(shù)據(jù)操作,也可以實現(xiàn)兼容舊版本的ioctl命令。?
ioctl返回值:
? ioctl函數(shù)的返回值是一個整數(shù)類型的值,如果命令執(zhí)行成功,ioctl返回零,如果出現(xiàn)錯誤,ioctl函數(shù)應該返回一個負值。這個負值會作為errno值反饋給調(diào)用此ioctl的用戶空間程序。關于返回值的具體含義,請參考<linux/errno.h>和<asm/errno.h>頭文件。
?
ioctl參數(shù):
首先要說明這個參數(shù)是有用戶空間的程序傳遞過來的,因此這個指針指向的地址是用戶空間地址,在Linux中,用戶空間地址是一個虛擬地址,在內(nèi)核空間是無法直接使用它的。為了解決在內(nèi)核空間使用用戶空間地址的數(shù)據(jù),Linux內(nèi)核提供了以下函數(shù),它們用于在內(nèi)核空間訪問用戶空間的數(shù)據(jù),定義在<asm/uaccess.h>頭文件中:
unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n); unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n);copy_from_user和copy_to_user一般用于復雜的或大數(shù)據(jù)交換,對于簡單的數(shù)據(jù)類型,如int或char,內(nèi)核提供了簡單的宏來實現(xiàn)這個功能:
#define get_user(x,ptr) #define put_user(x,ptr)//x是內(nèi)核空間的簡單數(shù)據(jù)類型地址,ptr是用戶空間地址指針。
cmd參數(shù)如何得出:
一個cmd參數(shù)被分為4段,每段都有其特殊的含義,cmd參數(shù)在用戶程序端由一些宏根據(jù)設備類型、序列號、傳送方向、數(shù)據(jù)尺寸等生成,這個整數(shù)通過系統(tǒng)調(diào)用傳遞到內(nèi)核中的驅(qū)動程序,再由驅(qū)動程序使用解碼宏從這個整數(shù)中得到設備的類型、序列號、傳送方向、數(shù)據(jù)尺寸等信息,然后通過switch{case}結(jié)構進行相應的操作。解釋一下四部分,全部都在<asm-generic/ioctl.h>和ioctl-number.txt這兩個文檔有說明的 。
1)幻數(shù):說得再好聽的名字也只不過是個0~0xff的數(shù),占8bit(_IOC_TYPEBITS)。這個數(shù)是用來區(qū)分不同的驅(qū)動的,像設備號申請的時候一樣,內(nèi)核有一個文檔給出一些推薦的或者已經(jīng)被使用的幻數(shù)
2)序數(shù):用這個數(shù)來給自己的命令編號,占8bit(_IOC_NRBITS),我的程序從1開始排序。
?
3)數(shù)據(jù)傳輸方向:占2bit(_IOC_DIRBITS)。如果涉及到要傳參,內(nèi)核要求描述一下傳輸?shù)姆较?#xff0c;傳輸?shù)姆较蚴且詰脤拥慕嵌葋砻枋龅摹?/span>
- _IOC_NONE:值為0,無數(shù)據(jù)傳輸。
- _IOC_READ:值為1,從設備驅(qū)動讀取數(shù)據(jù)。
- _IOC_WRITE:值為2,往設備驅(qū)動寫入數(shù)據(jù)。
- _IOC_READ|_IOC_WRITE:雙向數(shù)據(jù)傳輸。
4)數(shù)據(jù)大小:與體系結(jié)構相關,ARM下占14bit(_IOC_SIZEBITS),如果數(shù)據(jù)是int,內(nèi)核給這個賦的值就是sizeof(int)。
?
?ioctl如何實現(xiàn):
在驅(qū)動程序中實現(xiàn)的ioctl函數(shù)體內(nèi),實際上是有一個switch{case}結(jié)構,每一個case對應一個命令碼,做出一些相應的操作。怎么實現(xiàn)這些操作,這是每一個程序員自己的事情,因為設備都是特定的,這里也沒法說,關鍵在于怎么樣組織命令碼,因為在ioctl中命令碼是唯一聯(lián)系用戶程序命令和驅(qū)動程序支持的途徑。
命令碼的組織是有一些講究的,因為我們一定要做到命令和設備是一一對應的,這樣才不會將正確的命令發(fā)給錯誤的設備,或者是把錯誤的命令發(fā)給正確的設備,或者是把錯誤的命令發(fā)給錯誤的設備。這些錯誤都會導致不可預料的事情發(fā)生,而當程序員發(fā)現(xiàn)了這些奇怪的事情的時候,再來調(diào)試程序查找錯誤,那將是非常困難的事情
所以在Linux核心中是這樣定義一個命令碼的:
____________________________________
| 設備類型 | 序列號 | 方向 |數(shù)據(jù)尺寸|
|----------|--------|------|--------|
| 8 bit??? |? 8 bit |2 bit |8~14 bit|
|----------|--------|------|--------|
這樣一來,一個命令就變成了一個整數(shù)形式的命令碼。但是命令碼非常的不直觀,所以Linux Kernel中提供了一些宏,這些宏可根據(jù)便于理解的字符串生成命令碼,或者是從命令碼得到一些用戶可以理解的字符串以標明這個命令對應的設備類型、設備序列號、數(shù)據(jù)傳送方向和數(shù)據(jù)傳輸尺寸。
?
在內(nèi)核中是無法直接訪問用戶空間地址數(shù)據(jù)的。因此凡是從用戶空間傳遞過來的指針數(shù)據(jù),務必使用內(nèi)核提供的函數(shù)來訪問它們。這里有必要再一次強調(diào)的是,在內(nèi)核模塊或驅(qū)動程序的編寫中,我們強烈建議你使用內(nèi)核提供的接口來生成并操作ioctl命令號,這樣可以對命令號賦予特定的含義,使我們的程序更加的健壯;另一方面也可以提高程序的可移植性。
?
最后我們來介紹一下添加代碼后的編譯和調(diào)試,在內(nèi)核中添加代碼后就需要不斷運行,修復錯誤,我們知道當對/proc文件系統(tǒng)進行讀寫操作時,它的每一個結(jié)點都鏈接到一個內(nèi)核函數(shù),在Linux2.6內(nèi)核中,要想你的設備能夠被訪問,首先就要在/proc文件系統(tǒng)中創(chuàng)建一個入口,這個可以通過creat_proc_read_entry()來實現(xiàn),代碼在include/linux/proc_fs.h上查看:
static inline struct proc_dir_entry *create_proc_read_entry(const char *name,mode_t mode,struct proc_dir_entry *base,read_proc_t *read_proc,void * data)*name是結(jié)點在/proc文件系統(tǒng)的入口,*base指向設置proc文件的目標路徑,如果它的值為NULL,表示該文件就在/proc目錄下,讀取該文件可以調(diào)用*read_proc指向的函數(shù)。這里也不多加闡釋了,整個也是很簡單的過程。
小結(jié)
今天的重點是iotcl函數(shù)了,其中還有很多向內(nèi)核中添加代碼的細節(jié)沒有講到,主要是這些都涉及到過多的操作,需要大家多看源代碼并且多動手在Linux上操作才能完全掌握,,今天寫的一些也借鑒了一些大牛的文章,總之 收獲很多,最后幾天了,真的是很開心啦,和大家一起分享真的很快樂的~~
?
版權所有,轉(zhuǎn)載請注明轉(zhuǎn)載地址:http://www.cnblogs.com/lihuidashen/p/4255826.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結(jié)
以上是生活随笔為你收集整理的十天学Linux内核之第九天---向内核添加代码的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 终于感觉掌握了一门重要的技术
- 下一篇: linux 其他常用命令