?? ioctl 函數 參數 詳解 (2).txt
字號:
ioctl 函數 參數 詳解
本函數影響由fd參數引用的一個打開的文件。
#include
int ioctl( int fd, int request, .../* void *arg */ );
返回0:成功 -1:出錯
第三個參數總是一個指針,但指針的類型依賴于request參數。
我們可以把和網絡相關的請求劃分為6類:
套接口操作
文件操作
接口操作
ARP高速緩存操作
路由表操作
流系統
下表列出了網絡相關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成員也被更新,以反映存放在緩沖區中的信息量
一般來講ioctl在用戶程序中的調用是:
ioctl(int fd,int command, (char*)argstruct)
ioctl調用與網絡編程有關(本文只討論這一點),文件描述符fd實際上是由socket()系統調用返回的。參數command的取值由/usr/include/linux/sockios.h所規定。這些command的由于功能的不同,可分為以下幾個小類:
• 改變路由表 (例如 SIOCADDRT, SIOCDELRT),
• 讀/更新 ARP/RARP 緩存(如:SIOCDARP, SIOCSRARP),
• 一般的與網絡接口有關的(例如 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部分。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -