ioctl函數詳細說明

本函數影響由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 成員也被更新,以反映存放在緩衝區中的信息量

通常來說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部分。
 
 
使用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中對不一樣的命令傳送不一樣的數據結構,本文只是爲描述方便而在不一樣命令中使用了相同的數據結構。
相關文章
相關標籤/搜索