功 能
控制I/O設備 ,提供了一種獲得設備信息和向設備傳送控制參數的手段。用於向設備發控制和配置命令 ,有些命令需要控制參數,這些數據是不能用read / write 讀寫的,稱為Out-of-band數據。也就是說,read / write 讀寫的數據是in-band數據,是I/O操作的主體,而ioctl 命令傳送的是控制信息,其中的數據是輔助的數據。
用 法: int ioctl(int handle, int cmd,[int *argdx, int argcx]);
返回值:成功為0,出錯為-1
usr/include/asm-generic/ioctl.h中定義的宏的注釋:
#define _IOC_NRBITS 8 //序數(number)欄位的
字位寬度,8bits
#define _IOC_TYPEBITS 8 //幻數(type)欄位的字位寬度,8bits
#define _IOC_SIZEBITS 14 //大小(size)欄位的字位寬度,14bits
#define _IOC_DIRBITS 2 //方向(direction)欄位的字位寬度,2bits
#define _IOC_NRMASK ((1 << _IOC_NRBITS)-1) //序數欄位的掩碼,0x000000FF
#define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1) //幻數欄位的掩碼,0x000000FF
#define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1) //大小欄位的掩碼,0x00003FFF
#define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1) //方向欄位的掩碼,0x00000003
#define _IOC_NRSHIFT 0 //序數欄位在整個欄位中的位移,0
#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS) //幻數欄位的位移,8
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS) //大小欄位的位移,16
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS) //方向欄位的位移,30
/*
* Direction bits.
*/
#define _IOC_NONE 0U //沒有數據傳輸
#define _IOC_WRITE 1U //向設備寫入數據,驅動程式必須從
用戶空間讀入數據
#define _IOC_READ 2U //從設備中讀取數據,
驅動程式必須向用戶空間寫入數據
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
/*
* used to create numbers
*/
//構造無參數的命令編號
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
//用於向驅動程式寫入數據命令
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
//用於雙向傳輸
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
/*
*used to decode ioctl numbers..
*/
//從命令參數中解析出數據方向,即寫進還是讀出
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
//從命令參數中解析出幻數type
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
//從命令參數中解析出序數number
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
//從命令參數中解析出用戶數據大小
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
/* ...and for the drivers/sound files... */
#define IOC_IN (_IOC_WRITE << _IOC_DIRSHIFT)
#define IOC_OUT (_IOC_READ << _IOC_DIRSHIFT)
#define IOC_INOUT ((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT)
#define IOCSIZE_MASK (_IOC_SIZEMASK << _IOC_SIZESHIFT)
#define IOCSIZE_SHIFT (_IOC_SIZESHIFT)
程式例:
#include <stdlib.h>
#include <stdio.h>
#include <sys/ioctl.h>
int main(void) {
..int stat;
/* use func 8 to determine if the default drive is removable */
..stat = ioctl(0, 8, 0, 0);
..if (!stat)
....printf("Drive %c is removable.\n", getdisk() + 'A');
..else
....printf("Drive %c is not removable.\n", getdisk() + 'A');
..return 0;
}
int ioctl( int fd, int request, .../* void *arg */ ) 詳解
第三個參數總是一個
指針,但指針的類型依賴於request 參數。我們可以把和網路相關的請求劃分為6 類:
檔案操作
接口操作
ARP 高速快取操作
路由表操作
流系統
下表列出了網路相關ioctl請求的request 參數以及arg 地址必須指向的數據類型:
類別 | Request | 說明 | 數據類型 |
套 接 口 | SIOCATMARK SIOCSPGRP SIOCGPGRP | 是否位於帶外標記 設定套接口的進程ID 或進程組ID 獲取套接口的進程ID 或進程組ID | int int int |
文 件 | FIONBIO FIOASYNC FIONREAD FIOSETOWN FIOGETOWN | 設定/ 清除非阻塞I/O 標誌 設定/ 清除信號驅動異步I/O 標誌 獲取接收快取區中的位元組數 設定檔案的進程ID 或進程組ID 獲取檔案的進程ID 或進程組ID | int int int int int |
接 口 | SIOCGIFCONF SIOCSIFADDR SIOCGIFADDR SIOCSIFFLAGS SIOCGIFFLAGS SIOCSIFDSTADDR SIOCGIFDSTADDR SIOCGIFBRDADDR SIOCSIFBRDADDR SIOCGIFNETMASK SIOCSIFNETMASK SIOCGIFMETRIC SIOCSIFMETRIC SIOCGIFMTU SIOCxxx | 獲取所有接口的清單 設定接口地址 獲取接口地址 設定接口標誌 獲取接口標誌 設定點到點地址 獲取點到點地址 獲取廣播地址 設定廣播地址 獲取子網掩碼 設定子網掩碼 獲取接口的測度 設定接口的測度 獲取接口MTU (還有很多取決於系統的實現) | struct ifconf struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq |
ARP | SIOCSARP SIOCGARP SIOCDARP | 創建/ 修改ARP 表項 獲取ARP 表項 刪除ARP 表項 | struct arpreq struct arpreq struct arpreq |
路 由 | SIOCADDRT SIOCDELRT SIOCRTMSG | 增加路徑 刪除路徑 獲取路由表 | struct rtentry struct rtentry struct rtentry |
流 | I_xxx | | |
套接口操作:
明確用於套接口操作的ioctl請求有三個, 它們都要求ioctl的第三個參數是指向某個整數的一個指針。
SIOCATMARK: 如果本套接口的的度指針當前位於帶外標記,那就通過由第三個參數指向的整數返回一個非0 值;否則返回一個0 值。POSIX 以函式sockatmark 替換本請求。
SIOCGPGRP : 通過第三個參數指向的整數返回本套接口的進程ID 或進程組ID ,該ID 指定針對本套接口的SIGIO 或SIGURG 信號的接收進程。本請求和fcntl 的F_GETOWN 命令等效,POSIX 標準化的是fcntl 函式。
SIOCSPGRP : 把本套接口的進程ID 或者進程組ID 設定成第三個參數指向的整數,該ID 指定針對本套接口的SIGIO 或SIGURG 信號的接收進程,本請求和fcntl 的F_SETOWN 命令等效,POSIX 標準化的是fcntl 操作。
檔案操作:
以下5 個請求都要求ioctl的第三個參數指向一個整數。
FIONBIO : 根據ioctl的第三個參數指向一個0 或非0 值分別清除或設定本套接口的非阻塞標誌。本請求和O_NONBLOCK 檔案狀態標誌等效,而該標誌通過fcntl 的F_SETFL 命令清除或設定。
FIOASYNC : 根據ioctl 的第三個參數指向一個0 值或非0 值分別清除或設定針對本套接口的信號驅動異步I/O 標誌,它決定是否收取針對本套接口的異步I/O 信號(SIGIO )。本請求和O_ASYNC 檔案狀態標誌等效,而該標誌可以通過fcntl 的F_SETFL 命令清除或設定。
FIONREAD : 通過由ioctl的第三個參數指向的整數返回當前在本套接口接收緩衝區中的位元組數。本特性同樣適用於檔案,管道和終端。
FIOSETOWN : 對於套接口和SIOCSPGRP 等效。
FIOGETOWN : 對於套接口和SIOCGPGRP 等效。
必要性
如果不用IOCTL的話,也能實現對設備I/O通道的控制,但那就是蠻擰了。例如,我們可以在驅動程式中實現WRITE的時候檢查一下是否有特別約定的數據流通過,如果有的話,那么後面就跟著控制命令(一般在SOCKET編程中常常這樣做)。不過如果這樣做的話,會導致代碼分工不明,程式結構混亂,程式員自己也會頭昏眼花的。所以,我們就使用IOCTL來實現控制的功能。要記住,用戶程式所作的只是通過命令碼告訴驅動程式他想做什麼,至於怎么解釋這些命令和怎么實現這些命令,這都是驅動程式要做的事情。
實現操作
讀者只要把write換成ioctl,就知道用戶程式的ioctl是怎么和驅動程式中的ioctl實現聯繫在一起的了。我這裡說一個大概思路,因為我覺得《Linux設備驅動程式》這本書已說的非常清晰了,不過得花一些時間來看。在驅動程式中實現的ioctl函式體內,實際上是有一個switch{case}結構,每一個case對應一個命令碼,做出一些相應的操作。怎么實現這些操作,這是每一個程式員自己的事情,因為設備都是特定的,這裡也沒法說。關鍵在於怎么樣組織命令碼,因為在ioctl中命令碼是唯一聯繫用戶程式命令和驅動程式支持的途徑。命令碼的組織是有一些講究的,因為我們一定要做到命令和設備是一一對應的,這樣才不會將正確的命令發給錯誤的設備,或是把錯誤的命令發給正確的設備,或是把錯誤的命令發給錯誤的設備。這些錯誤都會導致不可預料的事情發生,而當程式員發現了這些奇怪的事情的時候,再來調試程式查找錯誤,那將是非常困難的事情。所以在Linux核心中是這樣定義一個命令碼的:
____________________________________
| 設備類型 | 序列號 | 方向 |數據尺寸|
|----------|--------|------|--------|
| 8 bit | 8 bit |2 bit |8~14 bit|
|----------|--------|------|-------|
這樣一來,一個命令就變成了一個整數形式的命令碼。不過命令碼非常的不直觀,所以Linux Kernel中提供了一些宏,這些宏可根據便於理解的字元串生成命令碼,或是從命令碼得到一些用戶能理解的字元串以標明這個命令對應的設備類型、設備序列號、數據傳送方向和數據傳輸尺寸。這些宏我就不在這裡解釋了,具體的形式請讀者察看Linux核心原始碼中的和,檔案里給除了這些宏完整的定義。這裡我只多說一個地方,那就是"幻數"。幻數是個字母,數據長度也是8,所以就用一個特定的字母來標明設備類型,這和用一個數字是相同的,只是更加利於記憶和理解。就是這樣,再沒有更複雜的了。更多的說了也沒有,讀者還是看一看原始碼吧,推薦各位閱讀《Linux 設備驅動程式》所帶原始碼中的short一例,因為他比較短小,功能比較簡單,能看明白ioctl的功能和細節。
其他信息
cmd參數怎么得出?
這裡確實要說一說,cmd參數在用戶程式端由一些宏根據設備類型、序列號、傳送方向、數據尺寸等生成,這個整數通過
系統調用傳遞到核心中的驅動程式,再由驅動程式使用解碼宏從這個整數中得到設備的類型、序列號、傳送方向、數據尺寸等信息,然後通過switch{case}結構進行相應的操作。要透徹理解,只能是通過閱讀原始碼,我這篇文章實際上只是個引子。Cmd參數的組織還是比較複雜的,我認為要搞熟他還是得花不少時間的,不過這是值得的,驅動程式中最難的是對中斷的理解。
總結
ioctl其實沒有什麼非常難的東西需要理解,關鍵是理解cmd命令碼是怎么在用戶程式里生成並在驅動程式里解析的,程式設計師最主要的工作量在switch{case}結構中,因為對設備的I/O控制都是通過這一部分的代碼實現的。