(轉)ioctl()

一、 什麼是ioctl。 
      ioctl是設備驅動程序中對設備的I/O通道進行管理的函數。所謂對I/O通道進行管理,就
      是對設備的一些特性進行控制,例如串口的傳輸波特率、馬達的轉速等等。它的調用個數
      如下: 
      int ioctl(int fd, ind cmd, …); 
      其中fd就是用戶程序打開設備時使用open函數返回的文件標示符,cmd就是用戶程序對設
      備的控制命令,至於後面的省略號,那是一些補充參數,一般最多一個,有或沒有是和
      cmd的意義相關的。 
      ioctl函數是文件結構中的一個屬性分量,就是說如果你的驅動程序提供了對ioctl的支
      持,用戶就可以在用戶程序中使用ioctl函數控制設備的I/O通道。 
      
      二、 ioctl的必要性 
      如果不用ioctl的話,也可以實現對設備I/O通道的控制,但那就是蠻擰了。例如,我們可
      以在驅動程序中實現write的時候檢查一下是否有特殊約定的數據流通過,如果有的話,
      那麼後面就跟着控制命令(一般在socket編程中常常這樣做)。但是如果這樣做的話,會
      導致代碼分工不明,程序結構混亂,程序員自己也會頭昏眼花的。 
      所以,我們就使用ioctl來實現控制的功能。要記住,用戶程序所作的只是通過命令碼告
      訴驅動程序它想做什麼,至於怎麼解釋這些命令和怎麼實現這些命令,這都是驅動程序要
      做的事情。 
      
      三、 ioctl如何實現 
      這是一個很麻煩的問題,我是能省則省。要說清楚它,沒有四五千字是不行的,所以我這
      裏是不可能把它說得非常清楚了,不過如果有讀者對用戶程序怎麼和驅動程序聯繫起來感
      興趣的話,可以看我前一陣子寫的《write的奧祕》。讀者只要把write換成ioctl,就知
      道用戶程序的ioctl是怎麼和驅動程序中的ioctl實現聯繫在一起的了。 
      我這裏說一個大概思路,因爲我覺得《Linux設備驅動程序》這本書已經說的非常清楚
      了,但是得化一些時間來看。 
      在驅動程序中實現的ioctl函數體內,實際上是有一個switch{case}結構,每一個case對
      應一個命令碼,做出一些相應的操作。怎麼實現這些操作,這是每一個程序員自己的事
      情,因爲設備都是特定的,這裏也沒法說。關鍵在於怎麼樣組織命令碼,因爲在ioctl中
      命令碼是唯一聯繫用戶程序命令和驅動程序支持的途徑。 
      命令碼的組織是有一些講究的,因爲我們一定要做到命令和設備是一一對應的,這樣纔不
      會將正確的命令發給錯誤的設備,或者是把錯誤的命令發給正確的設備,或者是把錯誤的
      命令發給錯誤的設備。這些錯誤都會導致不可預料的事情發生,而當程序員發現了這些奇
      怪的事情的時候,再來調試程序查找錯誤,那將是非常困難的事情。 
      所以在Linux核心中是這樣定義一個命令碼的: 
      ____________________________________
      | 設備類型 | 序列號 | 方向 |數據尺寸|
      |----------|--------|------|--------|
      | 8 bit    |  8 bit |2 bit |8~14 bit|
      |----------|--------|------|--------|

      這樣一來,一個命令就變成了一個整數形式的命令碼。但是命令碼非常的不直觀,所以
      Linux Kernel中提供了一些宏,這些宏可根據便於理解的字符串生成命令碼,或者是從
      命令碼得到一些用戶可以理解的字符串以標明這個命令對應的設備類型、設備序列號、數
      據傳送方向和數據傳輸尺寸。
       
      這些宏我就不在這裏解釋了,具體的形式請讀者察看Linux核心源代碼中的和,文件裏給
      除了這些宏完整的定義。這裏我只多說一個地方,那就是"幻數"。 
      幻數是一個字母,數據長度也是8,所以就用一個特定的字母來標明設備類型,這和用一
      個數字是一樣的,只是更加利於記憶和理解。就是這樣,再沒有更復雜的了。 
      更多的說了也沒有,讀者還是看一看源代碼吧,推薦各位閱讀《Linux 設備驅動程序》所
      帶源代碼中的short一例,因爲它比較短小,功能比較簡單,可以看明白ioctl的功能和細
      節。 

      四、 cmd參數如何得出 
      這裏確實要說一說,cmd參數在用戶程序端由一些宏根據設備類型、序列號、傳送方向、
      數據尺寸等生成,這個整數通過系統調用傳遞到內核中的驅動程序,再由驅動程序使用解
      碼宏從這個整數中得到設備的類型、序列號、傳送方向、數據尺寸等信息,然後通過
      switch{case}結構進行相應的操作。 
      要透徹理解,只能是通過閱讀源代碼,我這篇文章實際上只是一個引子。Cmd參數的組織
      還是比較複雜的,我認爲要搞熟它還是得花不少時間的,但是這是值得的,驅動程序中最
      難的是對中斷的理解。 

      五、 小結 
      ioctl其實沒有什麼很難的東西需要理解,關鍵是理解cmd命令碼是怎麼在用戶程序裏生成
      並在驅動程序裏解析的,程序員最主要的工作量在switch{case}結構中,因爲對設備的

      I/O控制都是通過這一部分的代碼實現的。


 

以下是生成命令碼的相關宏定義_IO()   _IOR()  _IO_W()   _IOWR()

在驅動程序裏, ioctl() 函數上傳送的變量 cmd 是應用程序用於區別設備驅動程序請求處理內容的值。cmd除了可區別數字外,還包含有助於處理的幾種相應信息。 cmd的大小爲 32位,共分 4 個域:
     bit31~bit30 2位爲 “區別讀寫” 區,作用是區分是讀取命令還是寫入命令。
     bit29~bit15 14位爲 "數據大小" 區,表示 ioctl() 中的 arg 變量傳送的內存大小。
     bit20~bit08  8位爲 “魔數"(也稱爲"幻數")區,這個值用以與其它設備驅動程序的 ioctl 命令進行區別。
     bit07~bit00   8位爲 "區別序號" 區,是區分命令的命令順序序號。
像 命令碼中的 “區分讀寫區” 裏的值可能是 _IOC_NONE (0值)表示無數據傳輸,_IOC_READ (讀), _IOC_WRITE (寫) , _IOC_READ|_IOC_WRITE (雙向)。
內核定義了 _IO() , _IOR() , IOW() 和 _IOWR() 這 4 個宏來輔助生成上面的 cmd 。下面分析 _IO() 的實現,其它的類似:< xmlnamespace prefix ="o" ns ="urn:schemas-microsoft-com:office:office" />

在 asm-generic/ioctl.h 裏可以看到 _IO() 的定義

      #define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)

再看 _IOC() 的定義

     #define _IOC(dir,type,nr,size) \
                   (((dir)  << _IOC_DIRSHIFT) | \
                   ((type) << _IOC_TYPESHIFT) | \
                   ((nr)   << _IOC_NRSHIFT) | \
                   ((size) << _IOC_SIZESHIFT))

可見,_IO() 的最後結果由 _IOC() 中的 4 個參數移位組合而成。
再看 _IOC_DIRSHIT 的定義

            #define _IOC_DIRSHIFT    (_IOC_SIZESHIFT+_IOC_SIZEBITS)

      _IOC_SIZESHIFT 的定義

           #define _IOC_SIZESHIFT    (_IOC_TYPESHIFT+_IOC_TYPEBITS)

      _IOC_TYPESHIF 的定義

           #define _IOC_TYPESHIFT    (_IOC_NRSHIFT+_IOC_NRBITS)

      _IOC_NRSHIFT 的定義

           #define _IOC_NRSHIFT    0

      _IOC_NRBITS 的定義

          #define _IOC_NRBITS    8

      _IOC_TYPEBITS 的定義

         #define _IOC_TYPEBITS    8

由上面的定義,往上推得到

      引 用

       _IOC_TYPESHIFT = 8

       _IOC_SIZESHIFT = 16

       _IOC_DIRSHIFT = 30

所以,(dir)  << _IOC_DIRSHIFT) 表是 dir 往左移 30 位,即移到 bit31~bit30 兩位上,得到方向(讀寫)的屬性;
       (size) << _IOC_SIZESHIFT) 位左移 16 位得到“數據大小”區;
       (type) << _IOC_TYPESHIFT) 左 移 8位得到"魔數區" ;
       (nr)   << _IOC_NRSHIFT)      左移 0 位( bit7~bit0) 。
這樣,就得到了 _IO() 的宏值。

這幾個宏的使用格式爲

  • _IO (魔數, 基數);
  • _IOR (魔數, 基數, 變量型)
  • _IOW  (魔數, 基數, 變量型)
  • _IOWR (魔數, 基數,變量型 )

魔數 (magic number)
      魔數範圍爲 0~255 。通常,用英文字符 "A" ~ "Z" 或者 "a" ~ "z" 來表示。設備驅動程序從傳遞進來的命令獲取魔數,然後與自身處理的魔數想比較,如果相同則處理,不同則不處理。魔數是拒絕誤使用的初步輔助狀態。設備驅動 程序可以通過 _IOC_TYPE (cmd) 來獲取魔數。不同的設備驅動程序最好設置不同的魔數,但並不是要求絕對,也是可以使用其他設備驅動程序已用過的魔數。
基(序列號)數
      基數用於區別各種命令。通常,從 0開始遞增,相同設備驅動程序上可以重複使用該值。例如,讀取和寫入命令中使用了相同的基數,設備驅動程序也能分辨出來,原因在於設備驅動程序區分命令時 使用 switch ,且直接使用命令變量 cmd值。創建命令的宏生成的值由多個域組合而成,所以即使是相同的基數,也會判斷爲不同的命令。設備驅動程序想要從命令中獲取該基數,就使用下面的宏:
_IOC_NR (cmd)
通常,switch 中的 case 值使用的是命令的本身。
變量型
      變量型使用 arg 變量指定傳送的數據大小,但是不直接代入輸入,而是代入變量或者是變量的類型,原因是在使用宏創建命令,已經包含了 sizeof() 編譯命令。比如 _IOR() 宏的定義是:

      引用

      #define _IOR(type,nr,size)    _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))

而 _IOC_TYPECHECK() 的定義正是:

      引用

      #define _IOC_TYPECHECK(t) (sizeof(t))

設備驅動程序想要從傳送的命令獲取相應的值,就要使用下列宏函數:
      _IOC_SIZE(cmd)

_IO 宏

      該宏函數沒有可傳送的變量,只是用於傳送命令。例如如下約定:

      引用

      #define TEST_DRV_RESET _IO ('Q', 0)

此時,省略由應用程序傳送的 arg 變量或者代入 0 。在應用程序中使用該宏時,比如:

      ioctl (dev, TEST_DEV_RESET, 0)   或者  ioctl (dev, TEST_DRV_RESET) 。
這是因爲變量的有效因素是可變因素。只作爲命令使用時,沒有必要判 斷出設備上數據的輸出或輸入。因此,設備驅動程序沒有必要執行設備文件大開選項的相關處理。

_IOR 宏
     該函數用 於創建從設備讀取數據的命令,例如可如下約定:

     引用

     #define TEST_DEV_READ  _IRQ('Q', 1, int)

這說明應用程序從設備讀取數據的大小爲 int 。下面宏用於判斷傳送到設備驅動程序的 cmd 命令的讀寫狀態:
     _IOC_DIR (cmd)
運行該宏時,返回值的類型 如下:

  • _IOC_NONE                             :  無屬性
  • _IOC_READ                             :  可讀屬性
  • _IOC_WRITE                           : 可寫屬性
  • _IOC_READ | _IOC_WRITE : 可讀,可寫屬性

使用該命令時,應用程序的 ioctl() 的 arg 變量值指定設備驅動程序上讀取數據時的緩存(結構體)地址。
_IOW 宏
      用於創建設 備上寫入數據的命令,其餘內容與 _IOR 相同。通常,使用該命令時,ioctl() 的 arg 變量值指定設備驅動程序上寫入數據時的緩存(結構體)地址。
_IOWR 宏
      用於創建設備上讀寫數據的命令。其餘內 容與 _IOR 相同。通常,使用該命令時,ioctl() 的 arg 變量值指定設備驅動程序上寫入或讀取數據時的緩存 (結構體) 地址。
_IOR() , _IOW(), IORW() 的定義
      #define _IOR(type,nr,size)    _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
      #define _IOW(type,nr,size)    _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
      #define _IOWR(type,nr,size)    _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

 



關於IOCTL驅動的編寫方法LDD這本書確實寫的比較明白了,在這呢我就簡單的做一個介紹。這裏我主要描述自己編寫IOCTL驅動時所遇到的問題及其原因。
    驅動裏的ioctl函數主要實現不用read,write函數的與用戶空間的簡單數據交互及無參數的命令控制。那麼我們如何實現這幾種功能的IOCTL函數呢?ioctl驅動中以SWITCH{case A,case B}結構以實現對不同命令的響應,首先我們要對我們要使用的“A”,“B”命令定義一個整個操作系統內唯一的標識,同時這個標識又能夠表明我們的操作類型及傳遞參數的類型(如果需要的話)。linux系統的每一個命令號被分爲多個位字段,這些位字段包括type,number,direction,size,分別表示幻數(與設備相關的一個字母,以避免與內核衝突),序數(命令編號),方向位(以用戶空間爲參照的讀,寫和無數據傳輸),size(傳遞參數類型)。內核中/include/asm/ioctl.h,/Documentation/ioctl-number.txt兩個文件表明了我們應該如何定義ioctl命令編號及自定義的幻數,。
    看起來構造IOTCL命令這麼複雜,慶幸的是內核已經爲我們編寫了構造命令編號的宏命令。_IO(type,nr),_IOR(type,nr,datatype),_IOW(type,nr,datatype)。它們在<linux/ioctl.h>包含的<asm/ioctl.h>中有定義,所以在我們的驅動中應包含這個頭文件。其中_IO()用做無參數的命令編號,_IOR()用做從驅動中讀取數據的命令編號,_IOW()用做寫入數據命令。 LDD中用:define SCULL_IOC_MAGIC 'k' ,define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC,1,int)表示SCULL_IOCSQUANTUM命令編號爲向驅動中寫數據,命令編號爲1,傳送參數類型爲int。

這裏把我所寫的部分代碼貼出來:
 驅動程序部分代碼:
 #define MOTOR_MAGIC  'k'
 #define SET_PULSE _IOW(MOTOR_MAGIC,  1,int)
  .
  .
  static int motor_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
     int data=0; 
      int retval;
      switch (cmd) {
      case SET_PULSE:
              if (copy_from_user(&data, (int *)arg, sizeof(int)))
               return -EFAULT;
             .
             .
             .
}
    
應用程序部分代碼:  
    #define MOTOR_MAGIC  'k'
    #define SET_PULSE _IOW(MOTOR_MAGIC,  1,int)
    int pulse,fileno;
    fileno = open("/tmp/usb",O_RDWR); 
    if (fileno == -1) { 
        printf("open device key error!\n"); 
        return 0; 
    }
       .
       .
       printf("Please input the pulse:\n"
       scanf("%d",pulse);  
       if(ioctl(fileno,SET_PULSE,pulse)<0){
          perror("ioctl error");
          exit(1);
       }       
        .
        . 
  這裏我們一定要注意的是data 和pulse的數據類型一定要與SET_PULSE _IOW(MOTOR_MAGIC,  1,int)中定義的int類型嚴格一致,即使是unsigned int 和int類型之間也是有很大差別的,我就是因爲設定了unsigned int類型的data和pulse卻使用SET_PULSE _IOW(MOTOR_MAGIC,  1,int)搞了我兩三天才搞明白。 再有就是ioctl函數的聲明一定要與 static int motor_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg);相一致,我剛開始就是定義錯了,也是搞了我好長時間才明白。因爲內核非常的相信你,它不會檢查你的IOTCL定義有沒有問題,它只負責把你的IOCTL函數映射到驅動的ioctl操作上。其中inode和filp對應應用程序傳遞的文件描述符,這和傳遞給open方法的參數一樣,參數cmd 由用戶空間不經修改的傳遞給驅動程序,可選的arg參數無論用戶空間傳遞的是指針還是值,它都以unsigned long的形式傳遞給驅動程序。

  還有就是我在驅動中明明是用的是使用地址的copy_from_usr函數,但用戶程序卻只能傳遞值.搞了半天最後才知道,原來我的用戶程序中的scanf(x),'x'沒有加'&'符號,所以這時從終端把讀到的數據寫到了'x'所代表的地址處,也就是說現在'x'所代表的地址正是所讀入的數據.呵呵,總結出的經驗就是----->對庫函數的參數類型一定要搞清楚.



在Linux字符設備驅動入門(一)中,我們實現了字符設備的簡單讀寫字符功能,接下來我們要在這個基礎上加入ioctl功能。首先,我們先來看看3.0內核下../include/linux/fs.h中file_operations結構體的定義:


struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **);
    long (*fallocate)(struct file *file, int mode, loff_t offset,
              loff_t len);
};

      紅色字體已經標出在kernel 3.0中已經完全刪除了struct file_operations 中的ioctl 函數指針,剩下unlocked_ioctlcompat_ioctl,取而代之的是unlocked_ioctl,主要改進就是不再需要上大內核鎖 (調用之前不再先調用lock_kernel()然後再unlock_kernel())。

       所以,在hellow.c中,我們在file_operations中加入成員函數hello_ioctl(紅色字體部分):

/* file operations for hello device */
static struct file_operations hello_ops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = hello_ioctl,
    .open = hello_open,
    .read = hello_read,
    .write = hello_write,
    .release = hello_release,
};

hello_ioctl()的定義如下:

static int hello_ioctl( struct file *file,
            unsigned int cmd, unsigned long arg)
{    int temp = 0;
    switch(cmd)
    {
      case HELLO_CMD1:
               {
            temp = 1;
            if(copy_to_user( (int *)arg, &temp, sizeof(int))) return -EFAULT;
            break;
               }
      case HELLO_CMD2:
            {
            temp = 2;
            if(copy_to_user( (int *)arg, &temp, sizeof(int))) return -EFAULT;
            break;
            }
    }
    printk( KERN_NOTICE"ioctl CMD%d done!\n",temp);    

return 0;

}


       這裏強調一下cmd的定義:

#define HELLO_MAGIC 'k'
#define HELLO_CMD1    _IO(HELLO_MAGIC,0x1a)
#define HELLO_CMD2    _IO(HELLO_MAGIC,0x1b)

            其中'k'爲幻數,要按照Linux內核的約定方法爲驅動程序選擇ioctl編號,應該首先看看include/asm/ioctl.h和Documentation/ioctl-number.txt這兩個文件,下面是ioctl.h的部分內容,也是比較重要的:

_IO(type, nr)
       用於構造無參數的命令編號;
_IOR(type, nr, datatype)
       用於構造從驅動程序中讀取數據的命令編號;
_IOW(type, nr, datatype)
       用於寫入數據的命令;
_IOWR(type, nr, datatype)
       用於雙向傳輸。
注意千萬不能重複定義。


注意對幻數的編號千萬不能重複定義,如ioctl-number.txt已經說明‘k'的編號已經被佔用的範圍爲:

'k'    00-0F    linux/spi/spidev.h    conflict!
'k'    00-05    video/kyro.h        conflict!

     所以我們在這裏分別編號爲0x1a和0x1b,到這裏,我們已經完成了對ioctl功能的編寫,接下來就是在測試程序中利用系統調用來測試它。

=============================================================

ioctl測試程序

=============================================================
#include <stdio.h>  
#include <fcntl.h>  
#include <stdlib.h>  
#include <string.h>  
#include <sys/types.h>  
#include <sys/stat.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define HELLO_MAGIC 'k'  //當然我們也可以定義一個相應的頭文件,把ioctl的cmd放進裏面,然後再include進 來
#define HELLO_CMD1    _IO(HELLO_MAGIC,0x1a)
#define HELLO_CMD2    _IO(HELLO_MAGIC,0x1b)

      int main(void)
{
    int ioctl_rdata;
    int fd, ret;

    fd = open ( "/dev/hellow" , O_RDWR);
    if ( fd == -1 )
    {
      perror("open");
      exit(0);
        }

    ret = ioctl( fd, HELLO_CMD2,&ioctl_rdata);
    if ( ret == -1)
    {
     perror("ioctl");
     exit(0);
    }
    printf("ioctl_rdata= %d \n",ioctl_rdata);

    close(fd);
    return 0;
}
=============================================================

運行結果

=============================================================

root@Ubuntu:~/share/hellow# insmod hellow.ko
root@Ubuntu:~/share/hellow# mknod /dev/hellow c 251 0
root@Ubuntu:~/share/hellow# ./a.out
ioctl_rdata= 2 
root@Ubuntu:~/share/hellow# dmesg | tail

[ 2431.126532] hello init. major:251, minor:0
[ 2453.326022] Hello device open!

[ 2453.326047] ioctl CMD2 done!
[ 2453.326487] Hello device close!


分享自:http://blog.csdn.net/dong_zhihong/article/details/7755711?reload

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章