本函數影響由fd 參數引用的一個打開的文件。node
#include<unistd.h>linux
int ioctl( int fd, int request, .../* void *arg */ );ios
返回0 :成功 -1 :出錯編程
第三個參數老是一個指針,但指針的類型依賴於request 參數。緩存
咱們能夠把和網絡相關的請求劃分爲6 類:網絡
套接口操做數據結構
文件操做app
接口操做異步
ARP 高速緩存操做socket
路由表操做
流系統
下表列出了網絡相關ioctl 請求的request 參數以及arg 地址必須指向的數據類型:
類別 |
Request |
說明 |
數據類型 |
套 接 口 |
SIOCATMARK SIOCSPGRP SIOCGPGRP |
是否位於帶外標記 設置套接口的進程ID 或進程組ID 獲取套接口的進程ID 或進程組ID |
int int int |
文
件
|
FIONBIN 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 |
增長路徑 刪除路徑 |
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 : 根據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 成員也被更新,以反映存放在緩衝區中的信息量
在咱們進一步討論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中對不一樣的命令傳送不一樣的數據結構,本文只是爲描述方便而在不一樣命令中使用了相同的數據結構。
|