天天看點

Linux驅動的ioctl函數簡要說明

ioctl是裝置驅動程式中對裝置的I/O通道進行管理的函數。所謂對I/O通道進行管理,就是對裝置的一些特性進行控制,例如序列槽的傳輸波特率、馬達的轉速、LED的開關控制等等。它的函數原型如下所示:

int ioctl(int fd, ind cmd, …);

其中fd是使用者程式打開裝置時使用open函數傳回的檔案标示符,cmd是使用者程式對裝置的控制指令,至于後面的省略号,那是一些補充參數,一般最多一個,這個參數的有無和cmd的意義相關。 ioctl函數是檔案結構中的一個屬性分量,就是說如果你的驅動程式提供了對ioctl的支援,使用者就可以在使用者程式中使用ioctl函數來控制裝置的I/O通道。

應用程式中的ioctl(系統IO的内容):

int ioctl(int fd, int cmd, ...)

/*
fd對應于應用程式傳遞的檔案描述符fd
cmd 由使用者空間直接不經修改的傳遞給驅動程式
... 可選,就是要傳遞到kernel的參數,可以是結構體指針等内容。
*/
           

驅動程式中,對應的ioctl

struct file_operations {
struct module *owner;
int (*ioctl) (struct inode *inode, struct file *filep, unsigned int cmd, unsigned long args);
long (*unlocked_ioctl) (struct file *filep, unsigned int cmd, unsigned long args);
......
}
/*
在驅動程式中,ioctl和unlocked_ioctl的差別:
在2.6.36以後linux的核心中,隻支援unlocked_ioctl(),不支援ioctl()。
2.6.35.7核心中,兩個函數都可以使用。
*/
           

在驅動程式中實作的ioctl函數體内,實際上是有一個switch {case}結構,每一個case對應一個指令碼,做出一些相應的操作。怎麼實作這些操作,這是每一個程式員自己的事情,因為裝置都是特定的。關鍵在于怎麼樣組織指令碼,因為在ioctl中指令碼是唯一聯系使用者程式指令和驅動程式支援的途徑。

在Linux核心中是這樣定義一個指令碼的:

| 裝置類型  | 序列号 |  方向 | 資料尺寸 |

|----------|--------|------|-------- |

| 8 bit   |  8 bit   | 2 bit |8~14 bit|

|----------|--------|------|-------- |
           

這樣一來,一個指令就變成了一個整數形式的指令碼。但是指令碼非常的不直覺,是以Linux Kernel中提供了一些宏,這些宏可根據便于了解的字元串生成指令碼,或者是從指令碼得到一些使用者可以了解的字元串以标明這個指令對應的裝置類型、裝置序列号、資料傳送方向和資料傳輸尺寸。

1、定義指令:

  核心提供了一些宏來幫助定義指令:

//nr為序号,datatype為資料類型,如int
_IO(type, nr ) //沒有參數的指令
_IOR(type, nr, datatype) //從驅動中讀資料
_IOW(type, nr, datatype) //寫資料到驅動
_IOWR(type,nr, datatype) //雙向傳送
           

以上幾個宏的使用格式為:

_IO (魔數, 基數);

_IOR (魔數, 基數, 變量型)

_IOW (魔數, 基數, 變量型)

_IOWR (魔數, 基數,變量型 )

魔數 (magic number)

魔數範圍為 0~255 。通常,用英文字元 “A” ~ “Z” 或者 “a” ~ “z” 來表示。裝置驅動程式從傳遞進來的指令擷取魔數,然後與自身處理的魔數想比較,如果相同則處理,不同則不處理。魔數是拒絕誤使用的初步輔助狀态。裝置驅動程式可以通過 _IOC_TYPE (cmd)來擷取魔數。不同的裝置驅動程式最好設定不同的魔數,但并不是要求絕對,也是可以使用其他裝置驅動程式已用過的魔數。

基(序列号)數

基數用于差別各種指令。通常,從 0開始遞增,相同裝置驅動程式上可以重複使用該值。例如,讀取和寫入指令中使用了相同的基數,裝置驅動程式也能分辨出來,原因在于裝置驅動程式區分指令時使用 switch ,且直接使用指令變量 cmd值。建立指令的宏生成的值由多個域組合而成,是以即使是相同的基數,也會判斷為不同的指令。裝置驅動程式想要從指令中擷取該基數,就使用下面的宏:

_IOC_NR (cmd)

通常,switch 中的 case 值使用的是指令的本身。

變量型

變量型使用 arg 變量指定傳送的資料大小,但是不直接代入輸入,而是代入變量或者是變量的類型,原因是在使用宏建立指令,已經包含了 sizeof() 編譯指令。

2、實作指令:

  定義好了指令,下一步就是要實作ioctl函數了,ioctl的實作包括三個技術環節:

1)傳回值;

  ioctl函數的實作是根據指令執行的一個switch語句,但是,當指令不能比對任何一個裝置所支援的 指令時,通常傳回-EINVAL(非法參數);

2)參數使用;

  使用者空間使用  int ioctl(int fd,unsinged long cmd,…)  時,…就是要傳遞的參數;

  再通過核心空間使用  int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)或者long (*unlocked_ioctl) (struct file *filep, unsigned int cmd, unsigned long args);  中的arg傳遞;

  如果arg是一個整數,可以直接使用;

  如果是指針,我們必須確定這個使用者位址是有效的,是以,使用之前需要進行正确檢查。

内部有檢查的,不需要檢測的函數接口:

copy_from_user

copy_to_user

get_user

put_user

需要檢測的函數接口:

__get_user

__put_user

檢測函數access_ok():

static inline int access_ok(int type, const void *addr, unsigned long size)

/*
type :是VERIFY_READ 或者VERIFY_WRITE用來表明是讀使用者記憶體還是寫使用者記憶體;
addr:是要操作的使用者記憶體位址;
size:是操作的長度。如果ioctl需要從使用者空間讀一個整數,那麼size參數就等于sizeof(int);

傳回值:Access_ok傳回一個布爾值:1,是成功(存取沒問題);0,是失敗,ioctl傳回-EFAULT;
*/
           

3)指令操作;

switch(cmd)
{
 case:
 ... ...
}
           

如果不用ioctl的話,也可以實作對裝置I/O通道的控制,例如,我們可以在驅動程式中實作write的時候檢查一下是否有特殊約定的數 據流通過,如果有的話,那麼後面就跟着控制指令(一般在socket程式設計中常常這樣做)。但是如果這樣做的話,會導緻代碼分工不明,程式結構混亂,程式員 自己也會頭昏眼花的。是以,我們就使用ioctl來實作控制的功能。要記住,使用者程式所作的隻是通過指令碼(cmd)告訴驅動程式它想做什麼,至于怎麼解釋這些指令和怎麼實作這些指令,這都是驅動程式要做的事情。

ioctl其實沒有什麼很難的東西需要了解,關鍵是了解cmd指令碼是怎麼在使用者程式裡生成并在驅動程式裡解析的,程式員最主要的工作量在switch{case}結構中,因為對裝置的I/O控制都是通過這一部分的代碼實作的。