今天進入《Linux設(shè)備驅(qū)動程序》第六章高級字符設(shè)備驅(qū)動程序操作的學習,學習的過程和簡單字符設(shè)備驅(qū)動程序的學習是一樣的,看書,看程序,然后就是看Tek的博客筆記。依然tek的博客中對于這一部分的知識點概括的很詳細了,所以我依舊談談對這一部分自己的理解體會。
?? ?總的來說這一塊雖然叫做高級字符設(shè)備驅(qū)動程序操作,涉及的知識點特別是函數(shù)比簡單字符設(shè)備驅(qū)動少多了,但是僅僅是考慮這一點的話那就錯了。前面的簡單字符設(shè)備驅(qū)動函數(shù)的種類雖然比較多,但是它是有限的,可以說基本上就是那幾個系統(tǒng)調(diào)用在驅(qū)動上的實現(xiàn)。相對于簡單字符設(shè)備驅(qū)動,高級字符設(shè)備驅(qū)動操作的定義簡單,也就意味著留給我們操作的空間也就很大了。就如書中講的?除了讀取和寫入設(shè)備以外,我們還希望通過設(shè)備驅(qū)動程序執(zhí)行各種類型的硬件控制,這些操作就通過ioctl來實現(xiàn)。可以這么理解,把簡單驅(qū)動程序看成是C語言中的基本類型,如int, char, double,這些基本類型各不同,但是是有限的,而ioctl就相當于結(jié)構(gòu)體類型,雖然基本的結(jié)構(gòu)差不多,但是每個結(jié)構(gòu)體卻是各不相同的,而且需要我們自己去定義。
在用戶空間下,ioctl系統(tǒng)條用具有如下原型:
int?ioctl(int?fd,?unsigned long cmd,?...);
而驅(qū)動程序的ioctl方法原型為:
int?(*ioctl)?(struct innode?*innode,struct file?*filp,?unsigned?int?cmd,?unsigned long arg);
?? ?雖然兩者版本不同,但是其中的參數(shù)還是有很大關(guān)系的,用戶空間下的fd指的是文件描述符,和驅(qū)動程序下的innode,filp相對應,而cmd參數(shù)則一致。不管可選的參數(shù)arg是否由用戶給定為一個整數(shù)或一個指針,它都以一個unsigned long的形式傳遞。如果調(diào)用程序不傳遞arg參數(shù), 被驅(qū)動收到的 arg 值是未定義的。因為在arg參數(shù)上的類型檢查被關(guān)閉了,所以若一個非法參數(shù)傳遞給 ioctl,編譯器是無法報警的,且任何關(guān)聯(lián)的錯誤難以查找。參數(shù)可以是整數(shù)參數(shù),也可以是指針,如果用指針的話,就可以向ioctl調(diào)用傳遞任意的數(shù)據(jù)了,這樣設(shè)備就可以跟用戶空間交換任意的數(shù)據(jù)了。當然這個指針所指向的用戶空間數(shù)據(jù)必須是有效的才行,如何判斷它的有效性后面會講到。
一、ioctl步驟
?? ?
實現(xiàn)ioctl方法的步驟一般包括兩步,定義命令和實現(xiàn)命令。
1、定義命令
?? ?前面說過,ioctl給予我們很大的操作空間,而定義命令就是我們定義自己對設(shè)備操作的第一步了。要按 Linux 內(nèi)核的約定方法為驅(qū)動選擇 ioctl 的命令號, 應該首先看看 include/asm/ioctl.h 和 Documentation/ioctl-number.txt這兩個文件。?
要使用的位字段符號定義在<linux/ioctl.h>?:
type(幻數(shù)):8 位寬(_IOC_TYPEBITS),參考ioctl-number.txt選擇一個數(shù),并在整個驅(qū)動中使用它。
number(序數(shù)):順序編號,8 位寬(_IOC_NRBITS)。
direction(數(shù)據(jù)傳送的方向):可能的值是 _IOC_NONE(沒有數(shù)據(jù)傳輸)、_IOC_READ、 _IOC_WRITE和 _IOC_READ|_IOC_WRITE (雙向傳輸數(shù)據(jù))。該字段是一個位掩碼(兩位), 因此可使用 AND 操作來抽取_IOC_READ 和 _IOC_WRITE。
size(數(shù)據(jù)的大小):寬度與體系結(jié)構(gòu)有關(guān),ARM為14位.可在宏 _IOC_SIZEBITS 中找到特定體系的值.?
?? ?一般來說一個驅(qū)動程序肯定是針對某一種設(shè)備的,而命令中的幻數(shù)位段就是為了區(qū)分這個驅(qū)動程序是否是針對這個設(shè)備的,也就是說幻數(shù)和設(shè)備驅(qū)動是在一個層次上的,每個設(shè)備驅(qū)動只有一個幻數(shù),兩個不同設(shè)備驅(qū)動之間的幻數(shù)不同。一個設(shè)備驅(qū)動上可能會有好幾個命令,我們就用number(序數(shù))來區(qū)分,也就是同一設(shè)備驅(qū)動下的不同命令的序數(shù)是不一樣的。
?? ?在編程過程中,?<asm/ioctl.h>定義了一些構(gòu)造命令編號的宏:
_IO(type,nr)/*沒有參數(shù)的命令*/
_IOR(type,?nr,?datatype)/*從驅(qū)動中讀數(shù)據(jù)*/
_IOW(type,nr,datatype)/*寫數(shù)據(jù)*/
_IOWR(type,nr,datatype)/*雙向傳送*/
這個頭文件還定義了用來解開這個字段的宏:
_IOC_DIR(nr)
_IOC_TYPE(nr)
_IOC_NR(nr)
_IOC_SIZE(nr)
2、實現(xiàn)命令
?? ?實現(xiàn)命令從用戶空間來看就是實現(xiàn)ioctl這個系統(tǒng)調(diào)用,實現(xiàn)ioctl系統(tǒng)調(diào)用可分為實現(xiàn)三個技術(shù)環(huán)節(jié):返回值、參數(shù)使用、命令操作。
返回值
?? ?ioctl的實現(xiàn)通常就是一個機遇命令號的switch語句,就是前面命令定義的在這個設(shè)備驅(qū)動中幾個命令。但是當命令號不能匹配合法操作時,比如你用戶空間下傳入的命令號在設(shè)備驅(qū)動中根本就沒有定義。此時,有些內(nèi)核會返回 -ENVAL(Invalid argument 非法參數(shù)),這是合理的。而POSIX 標準規(guī)定:如果使用了不合適的 ioctl 命令號,應當返回-ENOTTY 。這個錯誤碼被 C 庫解釋為"不合適的設(shè)備 ioctl。然而,它返回-EINVAL仍是相當普遍的。
參數(shù)使用
?? ?前面講用戶空間ioctl系統(tǒng)調(diào)用中的“...”代表著可選的arg參數(shù),不管用戶程序使用的是指針還是整數(shù)值,驅(qū)動程序的ioctl方法都識別成unsigned long的形式。但是驅(qū)動在使用這個傳過來的參數(shù)時,就會有區(qū)別了,如果傳遞的是一個整數(shù),它可以直接使用,這是沒有問題的。如果是一個指針,驅(qū)動程序也就要以指針來對待它,就要小心了。比如當一個指針指向用戶空間時,就必須確保指向的用戶空間時合法的,此時驅(qū)動程序就要負責對用到的用戶空間地址做適當?shù)臋z查了。
?? ?用戶空間地址的檢測有課分為兩種,一種是調(diào)用的內(nèi)核函數(shù)是帶有檢測的,這樣就不需要檢測了,如
copy_from_user
copy_to_user
get_user
put_user
另外一種是調(diào)用的內(nèi)核函數(shù)是不帶有檢測的,如
__get_user
__put_user
我們在調(diào)用這兩個函數(shù)傳輸數(shù)據(jù)前,首先要通過函數(shù)access_ok驗證地址。
int?access_ok(int?type,?const?void?*addr,?unsigned long size);? //從addr指的地址下進行size個字節(jié)的type(read或者write)檢測
?? ?關(guān)于access_ok,有兩點有趣之處需要注意。第一,它并沒有完成驗證內(nèi)存的全部工作,而只檢查了所引用的內(nèi)存是否位于進程有對應訪問權(quán)限的區(qū)域內(nèi),特別是要確保訪問地址沒有指向內(nèi)核空間的內(nèi)存區(qū)。第二,大多數(shù)驅(qū)動常年供需代碼中都不需要真正調(diào)用access_ok,因為會用到更好的內(nèi)存管理程序。
命令操作
?? ?命令操作就是switch語句選中的命令下要完成的事,這是用戶程序要求內(nèi)核對設(shè)備做的真正的事,相當于這個函數(shù)被調(diào)用了。
二、ioctl在scull中的實現(xiàn)分析
1、定義命令
?? ?下面是scull中的一些ioctl命令定義(scull.h)。這些命令是用來設(shè)置和獲取驅(qū)動程序的配置參數(shù)。
/*?使用“k”作為幻數(shù)?*/
#define SCULL_IOC_MAGIC?'k'
/*?在你自己的代碼中,請使用不同的8位數(shù)字*/
#define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC,?0)
/*
?*?S means?"Set"?through a ptr,
?*?T means?"Tell"?directly with the argument value
?*?G means?"Get":?reply by setting through a pointer
?*?Q means?"Query":?response?is?on?the return value
?*?X means?"eXchange":?switch G?and?S atomically
?*?H means?"sHift":?switch T?and?Q atomically
?*/
#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC,?1,?int)
#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC,?2,?int)
#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC,?3)
#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC,?4)
#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC,?5,?int)
#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC,?6,?int)
#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC,?7)
#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC,?8)
#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC,?9,?int)
#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10,?int)
#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC,?11)
#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC,?12)
/*
?*?The other entities only have?"Tell"?and?"Query",?because they're
?*?not?printed?in?the book,?and?there's no need?to?have all six.
?*?(The previous stuff was only there?to?show different ways?to?do?it.
?*/
#define SCULL_P_IOCTSIZE _IO(SCULL_IOC_MAGIC,?13)
#define SCULL_P_IOCQSIZE _IO(SCULL_IOC_MAGIC,?14)
/*?...?more?to?come?*/ ?#define SCULL_IOC_MAXNR 14
?? ?程序先定義了一個幻數(shù),用字符“k”表示,這個是隨機的,只要所有的幻數(shù)一致就行。接著定義SCULL_IOC_MAX個不同的cmd。
2、實現(xiàn)中的ioctl參數(shù)使用
int?scull_ioctl(struct inode?*inode,?struct file?*filp,
?????????????????unsigned?int?cmd,?unsigned long arg)
{
????int?err?=?0,?tmp;
????int?retval?=?0;
????
????/*
?????*?抽取類型和編號位字段,并拒絕錯誤的命令號:
?????*?在調(diào)用access_ok()之前返回ENOTTY(不恰當?shù)膇octl)
?????*/
????if?(_IOC_TYPE(cmd)?!=?SCULL_IOC_MAGIC)?return?-ENOTTY;
????if?(_IOC_NR(cmd)?>?SCULL_IOC_MAXNR)?return?-ENOTTY;
????/*
?????*?方向是一個位掩碼,而VERIFY_WRITE用于R/W傳輸 ?????*/
????if?(_IOC_DIR(cmd)?&?_IOC_READ)
????????err?=?!access_ok(VERIFY_WRITE,?(void __user?*)arg,?_IOC_SIZE(cmd));
????else?if?(_IOC_DIR(cmd)?&?_IOC_WRITE)
????????err?=?!access_ok(VERIFY_READ,?(void __user?*)arg,?_IOC_SIZE(cmd));
????if?(err)?return?-EFAULT;
????switch(cmd)?{ case ... ... } ... }
?? ?首先用解開為字段的宏來判斷類型和編號是不是這個驅(qū)動程序的,也就是前面說的一個驅(qū)動中只有一個固定的幻數(shù),這是區(qū)別去其他驅(qū)動的關(guān)鍵。
?? ?特別要注意的是,定義命令的時候direction位段是從應用程序的角度看的,也就是說,IOC_READ意味著從設(shè)備中讀取數(shù)據(jù),IOC_WRITE意味著向設(shè)備中寫數(shù)據(jù)。而access_ok中的第一個參數(shù)type(VERITY_READ或VERIFY_WRITE),取決于要執(zhí)行的動作是要讀取還是寫入用戶空間的內(nèi)存區(qū)。所以他們之間的概念剛好相反。
3、scull中ioctl命令的實現(xiàn)
?? ?正如前面說過的,scull中ioctl命令實現(xiàn)也就是用了一個switch語句,在每個cmd下都有不用的操作函數(shù)入口。
三、測試
?? ?測試程序我還是運用了簡單字符設(shè)備測試程序里思想,其中運用了ioctl給設(shè)備傳送了參數(shù)quantum,觀察默認quantum和改變quantum下的寫讀操作。
模塊程序:?ioctl.zip??
測試程序:?ioctl-test.zip???
實驗板測試輸出:
#?./ioctl_test
write code=20?
ever out?
read code=20? quantum has been changed ? ? ? [0]=0?[1]=1?[2]=2?[3]=3?[4]=4
[5]=5?[6]=6?[7]=7?[8]=8?[9]=9
[10]=10?[11]=11?[12]=12?[13]=13?[14]=14
[15]=15?[16]=16?[17]=17?[18]=18?[19]=19
Now?the quantum?is?changed?
write code=6?
write code=6?
write code=6?
writever out?
ever out ? ? ??
ever out?
ever out?
e code=2?
read code=6?
read code=6?
read code=6?
read code=2?
[0]=0?[1]=1?[2]=2?[3]=3?[4]=4
[5]=5?[6]=6?[7]=7?[8]=8?[9]=9
[10]=10?[11]=11?[12]=12?[13]=13?[14]=14
[15]=15?[16]=16?[17]=17?[18]=18?[19]=19
總結(jié)
以上是生活随笔為你收集整理的《Linux设备驱动程序》学习2—高级字符设备驱动ioctl的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。