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控制都是通過這一部分的代碼實現的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章