Linux设备驱动程序学习(4) -高级字符驱动程序操作[(1)ioctl and llseek]
今天進入《Linux設(shè)備驅(qū)動程序(第3版)》第六章高級字符驅(qū)動程序操作的學習。
一、ioctl
大部分設(shè)備除了讀寫能力,還可進行超出簡單的數(shù)據(jù)傳輸之外的操作,所以設(shè)備驅(qū)動也必須具備進行各種硬件控制操作的能力. 這些操作常常通過 ioctl 方法來支持,它有和用戶空間版本不同的原型:
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);需要注意的是:不管可選的參數(shù)arg是否由用戶給定為一個整數(shù)或一個指針,它都以一個unsigned long的形式傳遞。如果調(diào)用程序不傳遞arg參數(shù), 被驅(qū)動收到的 arg 值是未定義的。因為在arg參數(shù)上的類型檢查被關(guān)閉了,所以若一個非法參數(shù)傳遞給 ioctl,編譯器是無法報警的,且任何關(guān)聯(lián)的錯誤難以查找.
選擇ioctl命令
為了防止向錯誤的設(shè)備使用正確的命令,命令號應(yīng)該在系統(tǒng)范圍內(nèi)唯一。為方便程序員創(chuàng)建唯一的 ioctl 命令代號, 每個命令號被劃分為多個位字段。要按 Linux 內(nèi)核的約定方法為驅(qū)動選擇 ioctl 的命令號, 應(yīng)該首先看看 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 中找到特定體系的值.?
<linux/ioctl.h> 中包含的 <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)/*雙向傳送*/ /*type 和 number 成員作為參數(shù)被傳遞, 并且 size 成員通過應(yīng)用 sizeof 到 datatype 參數(shù)而得到*/這個頭文件還定義了用來解開這個字段的宏:
_IOC_DIR(nr) _IOC_TYPE(nr) _IOC_NR(nr) _IOC_SIZE(nr)具體的使用方法在實驗中展示。
返回值
POSIX 標準規(guī)定:如果使用了不合適的 ioctl 命令號,應(yīng)當返回-ENOTTY 。這個錯誤碼被 C 庫解釋為"不合適的設(shè)備 ioctl。然而,它返回-EINVAL仍是相當普遍的。
預定義命令
有一些ioctl命令是由內(nèi)核識別的,當這些命令用于自己的設(shè)備時,他們會在我們自己的文件操作被調(diào)用之前被解碼. 因此, 如果你選擇一個ioctl命令編號和系統(tǒng)預定義的相同時,你永遠不會看到該命令的請求,而且因為ioctl 號之間的沖突,應(yīng)用程序的行為將無法預測。預定義命令分為 3 類:
(1)用于任何文件(常規(guī), 設(shè)備, FIFO和socket) 的命令
(2)只用于常規(guī)文件的命令
(3)特定于文件系統(tǒng)類型的命令?
下列 ioctl 命令是預定義給任何文件,包括設(shè)備特定文件:
FIOCLEX :設(shè)置 close-on-exec 標志(File IOctl Close on EXec)。 FIONCLEX :清除 close-no-exec 標志(File IOctl Not CLose on EXec)。 FIOQSIZE :這個命令返回一個文件或者目錄的大小; 當用作一個設(shè)備文件, 但是, 它返回一個 ENOTTY 錯誤。 FIONBIO:"File IOctl Non-Blocking I/O"(在"阻塞和非阻塞操作"一節(jié)中描述)。?使用ioctl參數(shù)
在使用ioctl的可選arg參數(shù)時,如果傳遞的是一個整數(shù),它可以直接使用。如果是一個指針,,就必須小心。當用一個指針引用用戶空間, 我們必須確保用戶地址是有效的,其校驗(不傳送數(shù)據(jù))由函數(shù) access_ok 實現(xiàn),定義在 <asm/uaccess.h> :
int access_ok(int type, const void *addr, unsigned long size);第一個參數(shù)應(yīng)當是 VERIFY_READ(讀)或VERIFY_WRITE(讀寫);addr 參數(shù)為用戶空間地址,size 為字節(jié)數(shù),可使用sizeof()。access_ok 返回一個布爾值: 1 是成功(存取沒問題)和 0 是失敗(存取有問題)。如果它返回假,驅(qū)動應(yīng)當返回 -EFAULT 給調(diào)用者。
注意:首先, access_ok不做校驗內(nèi)存存取的完整工作; 它只檢查內(nèi)存引用是否在這個進程有合理權(quán)限的內(nèi)存范圍中,且確保這個地址不指向內(nèi)核空間內(nèi)存。其次,大部分驅(qū)動代碼不需要真正調(diào)用 access_ok,而直接使用put_user(datum, ptr)和get_user(local, ptr),它們帶有校驗的功能,確保進程能夠?qū)懭虢o定的內(nèi)存地址,成功時返回 0, 并且在錯誤時返回 -EFAULT.。
put_user(datum, ptr) __put_user(datum, ptr) get_user(local, ptr) __get_user(local, ptr)這些宏它們相對copy_to_user 和copy_from_user快, 并且這些宏已被編寫來允許傳遞任何類型的指針,只要它是一個用戶空間地址. 傳送的數(shù)據(jù)大小依賴 prt 參數(shù)的類型, 并且在編譯時使用 sizeof 和 typeof 等編譯器內(nèi)建宏確定。他們只傳送1、2、4或8 個字節(jié)。如果使用以上函數(shù)來傳送一個大小不適合的值,結(jié)果常常是一個來自編譯器的奇怪消息,如"coversion to non-scalar type requested". 在這些情況中,必須使用 copy_to_user 或者 copy_from_user。
__put_user和__get_user 進行更少的檢查(不調(diào)用 access_ok), 但是仍然能夠失敗如果被指向的內(nèi)存對用戶是不可寫的,所以他們應(yīng)只用在內(nèi)存區(qū)已經(jīng)用 access_ok 檢查過的時候。作為通用的規(guī)則:當實現(xiàn)一個 read 方法時,調(diào)用 __put_user 來節(jié)省幾個周期, 或者當你拷貝幾個項時,因此,在第一次數(shù)據(jù)傳送之前調(diào)用 access_ok 一次。
權(quán)能與受限操作
Linux 內(nèi)核提供了一個更加靈活的系統(tǒng), 稱為權(quán)能(capability)。內(nèi)核專為許可管理上使用權(quán)能并導出了兩個系統(tǒng)調(diào)用 capget 和 capset,這樣可以從用戶空間管理權(quán)能,其定義在 <linux/capability.h> 中。對設(shè)備驅(qū)動編寫者有意義的權(quán)能如下:
CAP_DAC_OVERRIDE /*越過在文件和目錄上的訪問限制(數(shù)據(jù)訪問控制或 DAC)的能力。*/ CAP_NET_ADMIN /*進行網(wǎng)絡(luò)管理任務(wù)的能力, 包括那些能夠影響網(wǎng)絡(luò)接口的任務(wù)*/ CAP_SYS_MODULE /*加載或去除內(nèi)核模塊的能力*/ CAP_SYS_RAWIO /*進行 "raw"(裸)I/O 操作的能力. 例子包括存取設(shè)備端口或者直接和 USB 設(shè)備通訊*/ CAP_SYS_ADMIN /*截獲的能力, 提供對許多系統(tǒng)管理操作的途徑*/ CAP_SYS_TTY_CONFIG /*執(zhí)行 tty 配置任務(wù)的能力*/在進行一個特權(quán)操作之前, 一個設(shè)備驅(qū)動應(yīng)當檢查調(diào)用進程有合適的能力,檢查是通過 capable 函數(shù)來進行的(定義在 <linux/sched.h> )范例如下:
if (! capable (CAP_SYS_ADMIN)) return -EPERM;二、定位設(shè)備(llseek實現(xiàn))
llseek是修改文件中的當前讀寫位置的系統(tǒng)調(diào)用。內(nèi)核中的缺省的實現(xiàn)進行移位通過修改 filp->f_pos, 這是文件中的當前讀寫位置。對于 lseek 系統(tǒng)調(diào)用要正確工作,讀和寫方法必須通過更新它們收到的偏移量來配合。
如果設(shè)備是不允許移位的,你不能只制止聲明 llseek 操作,因為缺省的方法允許移位。應(yīng)當在你的 open 方法中,通過調(diào)用 nonseekable_open 通知內(nèi)核你的設(shè)備不支持 llseek :
int nonseekable_open(struct inode *inode; struct file *filp);完整起見, 你也應(yīng)該在你的 file_operations 結(jié)構(gòu)中設(shè)置 llseek 方法到一個特殊的幫助函數(shù) no_llseek(定義在 <linux/fs.h> )。 具體的應(yīng)用在試驗程序中學習.
三、ioctl和llseek實驗。
模塊程序鏈接:ioctl_and_llseek
模塊測試程序鏈接:ioctl_and_llseek-test
ARM9實驗板的實驗現(xiàn)象是:
[Tekkaman2440@SBC2440V4]#cd /lib/modules/ [Tekkaman2440@SBC2440V4]#insmod scull.ko scull_nr_devs=1 [Tekkaman2440@SBC2440V4]#cd /tmp/ [Tekkaman2440@SBC2440V4]#./scull_test2 open scull ! SCULL_IOCSQUANTUM-SCULL_IOCQQUANTUM : scull_quantum=10 SCULL_IOCTQUANTUM-SCULL_IOCGQUANTUM : scull_quantum=6 SCULL_IOCXQUANTUM : scull_quantum=6 --> 10 SCULL_IOCHQUANTUM : scull_quantum=10 --> 6 SCULL_IOCSQSET-SCULL_IOCQQSET : scull_qset=2 SCULL_IOCTQSET-SCULL_IOCGQSET : scull_qset=4 SCULL_IOCXQSET : scull_qset=4 --> 2 SCULL_IOCHQSET : scull_qset=2 --> 4 before reset : scull_quantum=6 scull_qset=4 close scull ! reopen scull ! reopen : scull_quantum=6 scull_qset=4 write code=6 i=20 write code=6 i=14 write code=6 i=8 write code=2 lseek scull SEEK_SET-->0 ! read code=6 i=20 read code=6 i=14 read code=6 i=8 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 SCULL_IOCRESET after reset : scull_quantum=4000 scull_qset=1000 close scull ! reopen scull ! write code=20 lseek scull SEEK_CUR-10-->10 ! read code=10 [0]=10 [1]=11 [2]=12 [3]=13 [4]=14 [5]=15 [6]=16 [7]=17 [8]=18 [9]=19 lseek scull SEEK_END-20-->0 ! read code=20 [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 close scull ! [Tekkaman2440@SBC2440V4]#cat /proc/scullseq Device 0: qset 1000, q 4000, sz 20item at c3dd3d74, qset at c3f540000: c3e71000 [Tekkaman2440@SBC2440V4]#轉(zhuǎn)載于:https://www.cnblogs.com/shenhaocn/archive/2011/03/27/1996874.html
總結(jié)
以上是生活随笔為你收集整理的Linux设备驱动程序学习(4) -高级字符驱动程序操作[(1)ioctl and llseek]的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: R及RStudio下载安装教程(超详细)
- 下一篇: 递归算法经典实例python-Pytho