本函數影響由fd參數引用的一個打開的文件。
#include
#include
int ioctl( int fd, int request, .../* void *arg */ );
返回0:成功 -1:出錯
第三個參數總是一個指針,但指針的類型依賴於request參數。
我們可以把和網絡相關的請求划分為6類:
套接口操作
文件操作
接口操作
ARP高速緩存操作
路由表操作
流系統
下表列出了網絡相關ioctl請求的request參數以及arg地址必須指向的數據類型:
(圖1)
套接口操作:
明確用於套接口操作的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: 根據iocl的第三個參數指向一個0值或非0值分別清除或設置針對本套接口的信號驅動異步I/O標志,它決定是否收取針對本套接口的異步I/O信號(SIGIO)。本請求和O_ASYNC文件狀態標志等效,而該標志可以通過fcntl的F_SETFL命令清除或設置。
FIONREAD: 通過由ioctl的第三個參數指向的整數返回當前在本套接口接收緩沖區中的字節數。本特性同樣適用於文件,管道和終端。
FIOSETOWN: 對於套接口和SIOCSPGRP等效。
FIOGETOWN: 對於套接口和SIOCGPGRP等效。
接口配置:
得到系統中所有接口由SIOCGIFCONF請求完成,該請求使用ifconf結構,ifconf又使用ifreq
結構,如下所示:
Struct ifconf{
int ifc_len; // 緩沖區的大小
union{
caddr_t ifcu_buf; // input from user->kernel
struct ifreq *ifcu_req; // return of structures returned
}ifc_ifcu;
};
#define ifc_buf ifc_ifcu.ifcu_buf //buffer address
#define ifc_req ifc_ifcu.ifcu_req //array of structures returned
#define IFNAMSIZ 16
struct ifreq{
char ifr_name[IFNAMSIZ]; // interface name, e.g., “le0”
union{
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
short ifru_flags;
int ifru_metric;
caddr_t ifru_data;
}ifr_ifru;
};
#define ifr_addr ifr_ifru.ifru_addr // address
#define ifr_dstaddr ifr_ifru.ifru_dstaddr // otner end of p-to-p link
#define ifr_broadaddr ifr_ifru.ifru_broadaddr // broadcast address
#define ifr_flags ifr_ifru.ifru_flags // flags
#define ifr_metric ifr_ifru.ifru_metric // metric
#define ifr_data ifr_ifru.ifru_data // for use by interface
再調用ioctl前我們必須先分撇一個緩沖區和一個ifconf結構,然后才初始化后者。如下圖
展示了一個ifconf結構的初始化結構,其中緩沖區的大小為1024,ioctl的第三個參數指向
這樣一個ifconf結構。
ifc_len
Ifc_buf
1024
--------------------->緩存
假設內核返回2個ifreq結構,ioctl返回時通過同一個ifconf結構緩沖區填入了那2個ifreq結構,ifconf結構的ifc_len成員也被更新,以反映存放在緩沖區中的信息量
一般來講ioctl在用戶程序中的調用是:
ioctl(int fd,int command, (char*)argstruct)
ioctl調用與網絡編程有關(本文只討論這一點),文件描述符fd實際上是由socket()系統調用返回的。參數command的取值由/usr/include/linux/sockios.h所規定。這些command的由於功能的不同,可分為以下幾個小類:
1.改變路由表 (例如 SIOCADDRT, SIOCDELRT),
2. 讀/更新 ARP/RARP 緩存(如:SIOCDARP, SIOCSRARP),
3. 一般的與網絡接口有關的(例如 SIOCGIFNAME, SIOCSIFADDR 等等)
在Gooodies 目錄下有很多樣例程序展示了如何使用ioctl。當你看這些程序時,注意參數argstruct是與參數command相關的。例如,與路由表相關的 ioctl使用rtentry這種結構,rtentry定義在/usr/include/linux/route.h(參見例子 adddefault.c)。與ARP有關的ioctl調用使用arpreq結構,arpreq定義在 /usr/include/linux/if_arp.h(參見例子arpread.c)
與網絡接口有關的ioctl調用使用的command參數通常看起來像SIOCxIFyyyy的形式,這里x要 么是S(設定set,寫write),要么是G(得到get,讀read)。在getifinfo.c程序中就使用了這種形式的command參數來讀 IP地址,硬件地址,廣播地址和得到與網絡接口有關的一些標志(flag)。在這些ioctl調用中,第三個參數是ifreq結構,它在 /usr/include/linux/if.h中定義。在某些情況下, ioctrl調用可能會使用到在sockios.h之外的新的定義,例如,WaveLAN無線網絡卡會保存有關無線網絡信號強度的信息,這對用戶的程序可 能有用。但用戶怎么得到這種信息呢?我們的第一個本能是在sockios.h中定義新的ioctl命令,例如SIOCGIFWVLNSS(它的英文縮寫表 示WaveLAN的信號強度)。但不幸的是,這種命令不是對所有其他的網絡接口(例如:loopback環回接口)有意義,而且不應當允許對於 WAVLAN卡以外的網絡接口使用ioctl命令。那么,我們需要的是這樣一種機制:它能夠定義一種與網絡接口相關的ioctl命令。幸運的是,在 Linux操作系統中已經為實現這個目的內建了一種掛鈎(hook)機制。當你再次看sockios.h文件時,你將發現每一種設備已經預先定義了 SIOCDEVPRIVATE的ioctl命令。而它的實現將留給開發相應驅動程序的人去完成。
通常,一個用戶程序使用ioctl (sockid,SIOCDEVPRIVATE,(char*)&ifr)來調用與某種設備(指像WaveLAN那樣的特殊設備)相關的 ioctl命令,這里ifr是struct ifreq ifr形式的變量。用戶程序應當在ifr.ifr_name中填充與這個設備相關的名字,例如,假設WaveLAN使用的接口號為eth1。一般的,一個 用戶程序還需要與內核互相交換ioctl的command參數和結果,這可以通過ifr.ifr_data這個變量來實現,例如,想得到WaveLAN中 表示信號強度的信息時,可以通過返回這個變量來實現。Linux的源代碼已經包括了兩種設備de4x5和ewrk3,它們定義並且實現了特定的ioctl 調用。這兩個設備的源代碼在de4x5.h,de4x5.c,ewrk3.h,ewrk3.c中(在 /usr/src/linux/drivers/net/目錄中)。這兩種設備都定義了它們特有的結構(struct ewrk3_ioctl 和 struct de4x5_ioctl)來方便用戶程序和設備驅動之間交換信息。每次調用ioctl前,用戶程序應當在相應的結構變量中設定合適的初值,並且將 ifr.ifr_data指向該值。
在我們進一步討論ewrk3和de4x5的代碼前,讓我們仔細看看ioctl調用是如何一步步地實現的。所有的和接口相關的ioctl請求 (SIOCxIFyyyy 和 SIOCDEVPRIVATE)將會調用dev_ioctl()(在/usr/src/linux/net/core/dev.c中)。但這只是一個包裝 器(wrapper),實際的動作將由dev_ifsioc()(也在dev.c中)來實現。差不多dev_ioctl()這個函數所做的所有工作只是檢 查這個調用是否已經有了正當的權限(例如,改變路由表需要有root的權限)。而dev_ifsioc()這個函數首先要做的一些事情包括得到與 ifr.ifr_name相匹配的設備的結構(在/usr/include/linux/netdevice.h中定義)。但這是在實現特定的接口命令 (例如:SIOCGIFADDR)之后。這些特定的接口命令被放置到一個巨大的switch語句之中。其中SIOCDEVPRIVATE命令和其他的在 0x89F0到0x89FF之間的代碼將出現在switch語句中的一個分支——default語句中。內核會檢查表示設備的結構變量中,是否已經定義了 一個與設備相關的ioctl句柄(handler)。這里的句柄是一個函數指針,它在表示設備的結構變量中do_ioctl部分。如果已經設置了這個句 柄,那么內核將會執行它。
所以,如果要實現一個與設備相關的ioctl命令,所要做的只是編寫一個與這個設備相關的ioctl句柄,並且將表示這 個設備的結構變量中do_ioctl部分指向這個句柄。對於ewrk3這個設備,它的句柄是ewrk3_ioctl()(在ewrk3.c里面)並且相應 的表示該設備的結構變量由ewrk3_init()來初始化。在ewrk3_ioctl()的代碼中清晰的指出ifr.ifr_data是用作設備驅動程 序和用戶程序之間交換信息的。注意,這部分的內存可以雙向的交流信息。例如,在ewrk3的驅動程序代碼中,if.ifr_data的頭兩個字節是用來表 示特殊的動作(例如,EWRK3_SET_PROM,EWRK3_CLR_PROM),而這個動作是符合使用者(驅動程序實現了多個與設備相關的、由 SIOCDEVPRIVATE調用的命令)的要求的。另外,ifr.ifr_data中第5個字節指向的緩沖區(buffer)被用來交換其他的信息 (如:當使用EWRK3_SET_HWADDR和EWRK3_GET_HWADDR時為硬件地址)
在你深入ewrk3_ioctl()時,請注意一般情況下一個用戶進程不能直接訪問內核所在的內存。為此,驅動開發者可以使用兩個特殊的函數 memcpy_tofs()和memcpy_fromfs()。內核函數memcpy_tofs(arg1, arg2, arg3) 從地址arg2(用戶空間)向地址arg1(內核空間)拷貝arg3個字節。類似的,memcpy_fromfs(arg1,arg2,arg3)從地址 arg2(用戶空間)向地址arg1(內核空間)拷貝arg3個字節。在這些調用之前,verify_area()將會檢查這個進程是否擁有合適的訪問權 限。另外,注意使用printk()函數可以輸出debug信息。這個函數與printf()函數類似,但不能處理浮點類型的數。內核代碼不能夠使用 printf()函數。printk()函數產生的結果將記錄在/usr/adm/messages里。如果想知道更多的關於這些函數的或者與它們相關的 信息,可以參考《Linux Kernel Hacker’s Guide》(在Linux文檔網站的首頁) 這本書中Supporting Functions部分。
使用ioctl與內核交換數據
1. 前言
使用ioctl系統調用是用戶空間向內核交換數據的常用方法之一,從ioctl這個名稱上看,本意是針對I/O設備進行的控制操作,但實際並不限制是真正的I/O設備,可以是任何一個內核設備即可。
2. 基本過程
在內核空間中ioctl是很多內核操作結構的一個成員函數,如文件操作結構struct file_operations(include/linux/fs.h)、協議操作結構struct proto_ops(include/linux/net.h)等、tty操作結構struct tty_driver(include/linux/tty_driver.h)等,而這些操作結構分別對應各種內核設備,只要在用戶空間打開這些設備, 如I/O設備可用open(2)打開,網絡協議可用socket(2)打開等,獲取一個文件描述符后,就可以在這個描述符上調用ioctl(2)來向內核 交換數據。
3. ioctl(2)
ioctl(2)函數的基本使用格式為:
int ioctl(int fd, int cmd, void *data)
第一個參數是文件描述符;cmd是操作命令,一般分為GET、SET以及其他類型命令,GET是用戶空間進程從內核讀數據,SET是用戶空間進程向內核寫數據,cmd雖然是一個整數,但是有一定的參數格式的,下面再詳細說明;第三個參數是數據起始位置指針,
cmd命令參數是個32位整數,分為四部分:
dir(2b) size(14b) type(8b) nr(8b)
詳細定義cmd要包括這4個部分時可使用宏_IOC(dir,type,nr,size)來定義,而最簡單情況下使用_IO(type, nr)來定義就可以了,這些宏都在include/asm/ioctl.h中定義
本文cmd定義為:
#define NEWCHAR_IOC_MAGIC 'M'
#define NEWCHAR_SET _IO(NEWCHAR_IOC_MAGIC, 0)
#define NEWCHAR_GET _IO(NEWCHAR_IOC_MAGIC, 1)
#define NEWCHAR_IOC_MAXNR 1
要定義自己的ioctl操作,可以有兩個方式,一種是在現有的內核代碼中直接添加相關代碼進行支持,比如想通過socket描述符進行 ioctl操作,可在net/ipv4/af_inet.c中的inet_ioctl()函數中添加自己定義的命令和相關的處理函數,重新編譯內核即可, 不過這種方法一般不推薦;第二種方法是定義自己的內核設備,通過設備的ioctl()來操作,可以編成模塊,這樣不影響原有的內核,這是最通常的做法。
4. 內核設備
為進行ioctl操作最通常是使用字符設備來進行,當然定義其他類型的設備也可以。在用戶空間,可使用mknod命令建立一個字符類型設備文件,假設該設備的主設備號為123,次設備號為0:
mknode /dev/newchar c 123 0
如果是編程的話,可以用mknode(2)函數來建立設備文件。
建立設備文件后再將該設備的內核模塊文件插入內核,就可以使用open(2)打開/dev/newchar文件,然后調用ioctl(2)來傳遞數據,最后用close(2)關閉設備。而如果內核中還沒有插入該設備的模塊,open(2)時就會失敗。
由於內核內存空間和用戶內存空間不同,要將內核數據拷貝到用戶空間,要使用專用拷貝函數copy_to_user();要將用戶空間數據拷貝到內核,要使用copy_from_user()。
要最簡單實現以上功能,內核模塊只需要實現設備的open, ioctl和release三個函數即可,
下面介紹程序片斷:
static int newchar_ioctl(struct inode *inode, struct file *filep,
unsigned int cmd, unsigned long arg);
static int newchar_open(struct inode *inode, struct file *filep);
static int newchar_release(struct inode *inode, struct file *filep);
// 定義文件操作結構,結構中其他元素為空
struct file_operations newchar_fops =
{
owner: THIS_MODULE,
ioctl: newchar_ioctl,
open: newchar_open,
release: newchar_release,
};
// 定義要傳輸的數據塊結構
struct newchar{
int a;
int b;
};
#define MAJOR_DEV_NUM 123
#define DEVICE_NAME "newchar"
打開設備,非常簡單,就是增加模塊計數器,防止在打開設備的情況下刪除模塊,
當然想搞得復雜的話可進行各種限制檢查,如只允許指定的用戶打開等:
static int newchar_open(struct inode *inode, struct file *filep)
{
MOD_INC_USE_COUNT;
return 0;
}
關閉設備,也很簡單,減模塊計數器:
static int newchar_release(struct inode *inode, struct file *filep)
{
MOD_DEC_USE_COUNT;
return 0;
}
進行ioctl調用的基本處理函數
static int newchar_ioctl(struct inode *inode, struct file *filep,
unsigned int cmd, unsigned long arg)
{
int ret;
// 首先檢查cmd是否合法
if (_IOC_TYPE(cmd) != NEWCHAR_IOC_MAGIC) return -EINVAL;
if (_IOC_NR(cmd) > NEWCHAR_IOC_MAXNR) return -EINVAL;
// 錯誤情況下的缺省返回值
ret = EINVAL;
switch(cmd)
{
case KNEWCHAR_SET:
// 設置操作,將數據從用戶空間拷貝到內核空間
{
struct newchar nc;
if(copy_from_user(&nc, (const char*)arg, sizeof(nc)) != 0)
return -EFAULT;
ret = do_set_newchar(&nc);
}
break;
case KNEWCHAR_GET:
// GET操作通常會在數據緩沖區中先傳遞部分初始值作為數據查找條件,獲取全部
// 數據后重新寫回緩沖區
// 當然也可以根據具體情況什么也不傳入直接向內核獲取數據
{
struct newchar nc;
if(copy_from_user(&nc, (const char*)arg, sizeof(nc)) != 0)
return -EFAULT;
ret = do_get_newchar(&nc);
if(ret == 0){
if(copy_to_user((unsigned char *)arg, &nc, sizeof(nc))!=0)
return -EFAULT;
}
}
break;
}
return ret;
}
模塊初始化函數,登記字符設備
static int __init _init(void)
{
int result;
// 登記該字符設備,這是2.4以前的基本方法,到2.6后有了些變化,
// 是使用MKDEV和cdev_init()來進行,本文還是按老方法
result = register_chrdev(MAJOR_DEV_NUM, DEVICE_NAME, &newchar_fops);
if (result < 0) {
printk(KERN_WARNING __FUNCTION__ ": failed register character device for /dev/newchar\n");
return result;
}
return 0;
}
模塊退出函數,登出字符設備
static void __exit _cleanup(void)
{
int result;
result = unregister_chrdev(MAJOR_DEV_NUM, DEVICE_NAME);
if (result < 0)
printk(__FUNCTION__ ": failed unregister character device for /dev/newchar\n");
return;
}
module_init(_init);
module_exit(_cleanup);
5. 結論
用ioctl()在用戶空間和內核空間傳遞數據是最常用方法之一,比較簡單方便,而且可以在同一個ioctl中對不同的命令傳送不同的數據結構,本文只是為描述方便而在不同命令中使用了相同的數據結構。
from:http://hi.baidu.com/liaolihome/blog/item/e72e012a85412d9e033bf61b.html