1. 程式人生 > >IOCTL函式用法詳解

IOCTL函式用法詳解

ioctl是裝置驅動程式中對裝置的I/O通道進行管理的函式所謂對I/O通道進行管理,就是對裝置的一些特性進行控制,例如串列埠的傳輸波特率、馬達的轉速等等。它的呼叫個數如下: 
int ioctl(int fd, ind cmd, …); 
    其中fd是使用者程式開啟裝置時使用open函式返回的檔案標示符,cmd是使用者程式對裝置的控制命令,至於後面的省略號,那是一些補充引數,一般最多一個,這個引數的有無和cmd的意義相關。 

    ioctl函式是檔案結構中的一個屬性分量,就是說如果你的驅動程式提供了對ioctl的支援,使用者就可以在使用者程式中使用ioctl函式來控制裝置的I/O通道。

簡單介紹一下函式:

int (*ioctl) (struct inode * node, struct file *filp, unsigned int cmd, unsigned long arg);

引數:

1)inodefileioctl的操作有可能是要修改檔案的屬性,或者訪問硬體。要修改

檔案屬性的話,就要用到這兩個結構體了,所以這裡傳來了它們的指標。

2)cmd:命令,接下來要長篇大論地說。

3)arg:引數,接下來也要長篇大論。

返回值:

1)如果傳入的非法命令,ioctl返回錯誤號-EINVAL

2)核心中的驅動函式返回值都有一個預設的方法,只要是正數,核心就會傻乎乎的認為這是正確的返回,並把它傳給應用層,如果是負值,核心就會認為它是錯誤號了。

Ioctl裡面多個不同的命令,那就要看它函式的實現來決定返回值了。打個比方,如果ioctl裡面有一個類似read的函式,那返回值也就可以像read一樣返回。

當然,不返回也是可以的。

ioctl如何實現
    在驅動程式中實現的ioctl函式體內,實際上是有一個switch{case}結構,每一個case對應一個命令碼,做出一些相應的操作。怎麼實現這些操作,這是應用程式自己的事情。
在ioctl中命令碼是唯一聯絡使用者程式命令和驅動程式支援的途徑如果有兩個不同的裝置,但它們的ioctlcmd(命令碼)卻一樣的,哪天有誰不小心開啟錯了,並且呼叫ioctl,這樣就完蛋了。因為這個檔案裡面同樣有
cmd對應實現,故,我們可以自己生成未使用的命令碼。
所以在Linux核心中是這樣定義一個命令碼的

一個cmd被分為了4個段,每一段都有各自的意義,cmd的定義在<linux/ioctl.h>。注:但實際上<linux/ioctl.h>中只是包含了<asm/ioctl.h>,這說明了這是跟平臺相關的,ARM的定義在<arch/arm/include/asm/ioctl.h>,但這檔案也是包含別的檔案<asm-generic/ioctl.h>,千找萬找,終於找到了。

<asm-generic/ioctl.h>中,cmd拆分如下:

全部都在<asm-generic/ioctl.h>ioctl-number.txt這兩個文件有說明
http:/..../linux/include/asm-generic/ioctl.h
#define _IOC(dir,type,nr,size) \
(((dir)  << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr)   << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
____________________________________
| 裝置型別 | 序列號 | 方向 |資料尺寸|
|----------|--------|------|--------|
| 8 bit | 8 bit |2 bit |8~14 bit|
|----------|--------|------|--------|

這樣一來,一個命令就變成了一個整數形式的命令碼;但是命令碼非常的不直觀,所以Linux Kernel中提供了一些巨集這些巨集可根據便於理解的字串生成命令碼,或者是從命令碼得到一些使用者可以理解的字串以標明這個命令對應的裝置型別、裝置序列號、資料傳送方向和資料傳輸尺寸。

幻數:說得再好聽的名字也只不過是個0~0xff的數,佔8bit(_IOC_TYPEBITS)。這個數是用來區分不同的驅動的,像裝置號申請的時候一樣,核心有一個文件給出一些推薦的或者已經被使用的幻數。在核心檔案中定義如下
Ioctl-number.txt (f:\sourceproject\linux-kernel\linux-3.14.26-g2489c02\documentation\ioctl)

點選(此處)摺疊或開啟

  1. Code Seq#(hex)    Include File        Comments
  2. ========================================================
  3. 0x00    00-1F    linux/fs.h        
  4. 0x00    00-1F    scsi/scsi_ioctl.h    
  5. 0x00    00-1F    linux/fb.h        
  6. 0x00    00-1F    linux/wavefront.h    
  7. 0x02    all    linux/fd.h
  8. 0x03    all    linux/hdreg.h
  9. 0x04    D2-DC    linux/umsdos_fs.h    Dead since 2.6.11, but don't reuse these.
  10. 0x06    all    linux/lp.h
  11. 0x09    all    linux/raid/md_u.h
  12. 0x10    00-0F    drivers/char/s390/vmcp.h
  13. 0x10    10-1F    arch/s390/include/uapi/sclp_ctl.h
  14. 0x10    20-2F    arch/s390/include/uapi/asm/hypfs.h
  15. 0x12    all    linux/fs.h
  16.         linux/blkpg.h
  17. 0x1b    all    InfiniBand Subsystem    <http://infiniband.sourceforge.net/>
  18. 0x20    all    drivers/cdrom/cm206.h
  19. 0x22    all    scsi/sg.h
  20. '#'    00-3F    IEEE 1394 Subsystem    Block for the entire subsystem
  21. '$'    00-0F    linux/perf_counter.h, linux/perf_event.h
  22. .....................
  23. ....................

四、CMD引數如何得出

    cmd引數在使用者程式端由一些巨集根據裝置型別、序列號、傳送方向、資料尺寸等生成,這個整數通過系統呼叫傳遞到核心中的驅動程式,再由驅動程式使用解碼巨集從這個整數中得到裝置的型別、序列號、傳送方向、資料尺寸等資訊,然後通過switch{case}結構進行相應的操作。
    Linux核心已經提供了相應的巨集來自動生成ioctl命令碼

_IO(type,nr)   //無資料傳輸
_IOR(type,nr,size)  //從裝置讀資料 
_IOW(type,nr,size)  //向裝置寫資料
_IOWR(type,nr,size)  //同時有讀寫資料

    上面的命令已經定義了方向,我們要傳的是幻數(type)序號(nr)大小(size)。在這裡szie的引數只需要填引數的型別,如int,上面的命令就會幫你檢測型別的正確然後賦值sizeof(int)
相對的,Linux核心也提供了相應的巨集來從ioctl命令號種解碼相應的域值:

_IOC_DIR(nr) //從命令中提取方向
_IOC_TYPE(nr) //從命令中提取幻數
_IOC_NR(nr)  //從命令中提取序數
_IOC_SIZE(nr)  //從命令中提取資料大小

例:
/*include_cmd.hpp*/
#define LED_IOC_MAGIC 0x13  //定義幻數
#define LED_MAX_NR    3          //定義命令的最大序數
#define LED_GPRS_MAGIC _IO(LED_IOC_MAGIC,0x00)  //0x00  用”巨集+幻數來自動生成ioctl命令碼
#define LED_WIFI_MAGIC _IO(LED_IOC_MAGIC,0x01)  //0x00
#define LED_BT_MAGIC _IO(LED_IOC_MAGIC,0x02)  //0x00

/*test.cpp*/
fd = open();
ioctl(fd,LED_GPRS_MAGIC,0);
ioctl(fd,LED_GPRS_MAGIC,1);
ioctl(fd,LED_WIFI_MAGIC ,0);
ioctl(fd,LED_WIFI_MAGIC ,1);

/*test_ioctl.c*/
int test_ioctl (struct inode *node, struct file *filp, unsigned int cmd, unsigned long arg)
{
    if(_IOC_TYPE(cmd) !=LED_IOC_MAGIC ) return -EINVAL;   //提取出幻數做檢驗
    if(_IOC_NR(cmd
) > LED_MAX_NR) return -EINVAL;          //提取命令序數
    switch(cmd){
    case LED_GPRS_MAGIC:
     if(arg==0){
    //..........
    }else if(arg ==1){
    //..........
    }
    break;
    case LED_WIFI_MAGIC:
    //..........
    break;

    }

}
arg引數:

如果arg是一個整數,可以直接使用;
  如果是指標,我們必須確保這個使用者地址是有效的,因此,使用之前需要進行正確檢查。
  內部有檢查的,

不需要檢測的:

[cpp] view plain copy  print?
  1. copy_from_user  
  2. copy_to_user  
  3. get_user  
  4. put_user  

需要檢測的: [cpp] view plain copy  print?
  1. __get_user  
  2. __put_user