天天看點

ioctl用法詳解 (網絡)

本函數影響由fd參數引用的一個打開的檔案。

#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”

        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;

進行ioctl調用的基本處理函數

      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操作通常會在資料緩沖區中先傳遞部分初始值作為資料查找條件,擷取全部

// 資料後重新寫回緩沖區

// 當然也可以根據具體情況什麼也不傳入直接向核心擷取資料

   ret = do_get_newchar(&nc);

   if(ret == 0){

    if(copy_to_user((unsigned char *)arg, &nc, sizeof(nc))!=0)

     return -EFAULT;

   }

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;

子產品退出函數,登出字元裝置

static void __exit _cleanup(void)

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

繼續閱讀