ioctl 和fcntl

程序員怎麼查看和控制一個設備的設置呢??node

#include <sys/ioctl.h>
ioctl函數提供對鏈接到fd的設備驅動程序的屬性和操做的訪問linux

其原型爲
extern int ioctl(int fd,unsigned long int request[,char * arg ....])ios

fd 是打開設備的描述符  request 是函數代碼(相似特定的操做同樣,這是與設備相關的)
arg  是參數  可選的程序員

 

返回值是 失敗 -1  並設置 errno編程

 成功通常是0  可是任然要視具體狀況而定 
 由於有些屬性須要使用返回值 這時不必定是0了
 void prinft_screen_dimension(int fd)
 {
  int  fd;  // 
  struct winsize buf;
 
  if (ioctl( fd, TIOCGWINZ,&buf ) != -1){
   printf("%d rows x %d cols\n",buf.ws_row,buf.ws_col);
   printf("%d wide x %d tall\n",buf.ws_xpixel,buf.ws_ypixel);
  } 
 }緩存

 每種設備都有本身所支持的設備屬性集以及ioctl操做集網絡

 

fcntl  是用來設置和修改描述符的的屬性app

 通常來講咱們對文件的寫入都是帶有緩衝的,
 可是在數據比較重要的時候擔憂意外事故致使緩衝數據丟失
 這時咱們能夠設置數據存入是同步的(即關閉緩衝屬性)
 描述符的屬性被編碼到一個整數中了
 fcntl經過讀寫該整數位來設置文件描述符的屬性。
 使用fcntl取得此值  修改後 再用fcntl寫回
 
 #include <fcntl.h>
 fcntl 原型爲  
 extern int fcntl(int fd, int _cmd,... )
 
 調用形式有
 fcntl(int fd,int cmd);
 fcntl(int fd,int cmd,long arg)
 fcntl(int fd,int cmd,struct flock *lock) 
 fd 值相應的描述符,cmd 是指相應的操做  
 相應操做能夠查看man fcntl  會發現fcntl不僅會幹這些喲。
 
 這裏咱們用fcntl進行演示(關閉fd的緩衝屬性 注意只有塊設備與實際文件有這個屬性)
 int fd,va;
 
 va = fcntl( fd,F_GETFD);
 va | = O_SYNC;
 fcntl( fd,F_SETFD,va);
 
 固然也能夠直接在open的同時進行設置。 
 不過open的屬性設置沒有用fcntl那樣靈活。異步

 

本函數影響由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的因爲功能的不一樣,可分爲如下幾個小類:
? 改變路由表 (例如 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 (resultsocket

相關文章
相關標籤/搜索