linux設備驅動學習(5) 高級字符驅動程序操作--ioctl

 

ioctl

驅動程序可以使用ioctl執行硬件控制。

兩種原型:

1.在用戶空間

int ioctl(int fd,unsigned long cmd,...);

fd:文件描述符

cmd:控制命令

,,,:可選參數:插入*argp,具體內容依賴於cmd

2.驅動程序

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

inode與filp兩個指針對應於應用程序傳遞的文件描述符fd,這和傳遞open方法的參數一樣。

cmd 由用戶空間直接不經修改的傳遞給驅動程序

arg 可選

 

cmd:

四個位字段:type,number,direction,size

type:幻數,8位

number:序數,8位

direction:涉及內容包括_IOC_NONE(無數據傳輸),_IOC_READ(從設備中讀),_IOC_WRITE,_IOC_READ|_IOC_WRITE(雙向數據傳輸)

size:表示所涉及的用戶數據大小,通常爲13位或是14位,具體可通過宏_IOC_SIZEBITS找到針對特定體系結構的具體數值。內核不會檢查這個位字段,對該字段的檢查可以幫助我們檢測用戶空間的錯誤。

另外,<asm/ioctl.h>定義了一些構造命令編號的宏,

_IOR(type,nr,datetype)  構造從驅動程序中讀取數據的命令

_IO(type,nr) 用於構造無參數的命令編號

_IOW(type,nr,datetype) 用於寫入命令的編號

_IOWR(type,nr,datatype) 雙向傳輸

type,number通過參數傳入,size通過對datatype參數取sizeof獲取

 

還有一些解開位字段的宏:_IOC_DIR(nr),_IOC_TYPE(nr),_IOC_NR(nr),_IOC_SIZE(nr)

 

對非法的ioctl命令一般會返回-EINVAL

 

預定義命令

在使用ioctl命令編號時,一定要避免與預定義命令重複,否則,命令衝突,設備不會響應

下列ioctl命令對任何文件(包括設備特定文件)都是預定義的:

FIOCTLX  設置執行時關閉標誌

FIONCLEX  清除執行時關閉標誌

FIOASYNC  設置或復位文件異步通知

FIOQSIZE  返回文件或目錄大小

FIONBIO  文件非阻塞型IO,file ioctl non-blocking i/o

 

如何使用ioctl的附加參數:arg

1:arg是個整數,那簡單,直接用

2:arg是個指針,麻煩點,需檢測後才能用

分析:使用指針,首先得保證指針指向的地址合法。因此,在使用這個指針之前,我們應該使用

<asm/uaccess.h>中聲明的  int  access_ok(int type,const void *addr,unsigend long size)  返回值爲1(成功)或0(失敗),如果返回失敗,驅動程序通常返回-EFAULT給調用者。

來驗證地址的合法性。

type: VERIFY_READ 或是  VERIFY_WRITE,取決於是讀取還是寫入用戶空間內存區。

addr: 一個用戶空間的地址

size: 如果要讀取或寫入一個int型數據,則爲sizeof(int)

如果在該地址處既要讀取,又要寫入,則應該用:VERIFY_WRITE,因爲它是VERIFY_READ的超集

注意:首先, access_ok不做校驗內存存取的完整工作; 它只檢查內存引用是否在這個進程有合理權限的內存範圍中,且確保這個地址不指向內核空間內存。其次,大部分驅動代碼不需要真正調用 access_ok,而直接使用put_user(datum, ptr)和get_user(local, ptr),它們帶有校驗的功能,確保進程能夠寫入給定的內存地址,成功時返回 0, 並且在錯誤時返回 -EFAULT.。

使用舉例:

int err=0,tmp;

int retval;

/*抽取類型和編號位字段,並拒絕錯誤的命令號:在調用access_ok之前返回ENOTTY(不恰當的ioctl)*/

if(_IOC_TYPE(cmd)!=SCULL_IOC_MAGIC)  return -ENOTTY;

if(_IOC_NR(cmd)>SCULL_IOC_MAXNR)   return -ENOTTY;

 

/*方向是一個位掩碼,而VERIFY_WRITE用於R/W*傳輸。“類型”是針對用戶空間而言,而access_ok面向內核,因此,讀取和寫入,恰好相反*/

if(_IOC_DIR(cmd) & _IOC_READ)

    err=!access_ok(VERIFY_WRITE,(void __user *)arg,_IOC_SIZE(cmd));

else   if(_IOC_DIR(cmd) & _IOC_WRITE)

    err=!access_ok(VERIFY_READ,(void __user *)arg,_IOC_SIZE(cmd));

if(err) return -EFAULT;

 

<asm/uaccess.h>

put_user(datum,ptr);

__put_user(datum,ptr);

使用時,速度快,不做類型檢查,使用時可以給ptr傳遞任意類型的指針參數,只要是個用戶空間的地址就行,傳遞的數據大小依賴於ptr參數的類型。

put_user   vs   __put_user:使用前做的檢查,put_user多些,__put_user少些,

一般用法:實現一個讀取方法時,可以調用__put_user來節省幾個時鐘週期,或者在複製多項數據之前調用一次access_ok,像上面代碼一樣。

 

get_user(datum.ptr);

__get_user(datum,ptr);

接收的數據被保存在局部變量local中,返回值說明其是否正確。同樣,__get_user應該在操作地址被access_ok後使用。

 

權能與受限操作

來由:驅動程序必須進行附加的檢查以確認用戶是否有權進行請求的操作

權能作用:基於權能的系統拋棄了那種要麼全有,要麼全無的特權分配方式,而是把特權操作劃分成了獨立的組。

<linux/capability.h>

int capable(int capability);

在執行一項特權之前,應先檢查其是否具有這個權利

if (! capable (CAP_SYS_ADMIN))
 return -EPERM;

 

CAP_DAC_OVERRIDE/*越過在文件和目錄上的訪問限制(數據訪問控制或 DAC)的能力。*/

CAP_NET_ADMIN /*進行網絡管理任務的能力, 包括那些能夠影響網絡接口的任務*/

CAP_SYS_MODULE /*加載或去除內核模塊的能力*/

CAP_SYS_RAWIO /*進行 "raw"(裸)I/O 操作的能力. 例子包括存取設備端口或者直接和 USB 設備通訊*/

CAP_SYS_ADMIN /*截獲的能力, 提供對許多系統管理操作的途徑*/

CAP_SYS_TTY_CONFIG /*執行 tty 配置任務的能力*/

switch(cmd) {

   case SCULL_IOCRESET:
  scull_quantum = SCULL_QUANTUM;
  scull_qset = SCULL_QSET;
  break;
       
   case SCULL_IOCSQUANTUM: /* Set: arg points to the value */
  if (! capable (CAP_SYS_ADMIN))          //capable
   return -EPERM;
  retval = __get_user(scull_quantum, (int __user *)arg);//_get_user

   break;

   case SCULL_IOCTQUANTUM: /* Tell: arg is the value */
  if (! capable (CAP_SYS_ADMIN))
   return -EPERM;
  scull_quantum = arg;
  break;

case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */

retval = __put_user(scull_quantum, (int __user *)arg);
  break;

   case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */
  return scull_quantum;

   case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */
  if (! capable (CAP_SYS_ADMIN))
   return -EPERM;
  tmp = scull_quantum;
  retval = __get_user(scull_quantum, (int __user *)arg);
  if (retval == 0)
   retval = __put_user(tmp, (int __user *)arg);
  break;

   case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */
  if (! capable (CAP_SYS_ADMIN))
   return -EPERM;
  tmp = scull_quantum;
  scull_quantum = arg;
  return tmp;
       
   case SCULL_IOCSQSET:
  if (! capable (CAP_SYS_ADMIN))
   return -EPERM;
  retval = __get_user(scull_qset, (int __user *)arg);
  break;

   case SCULL_IOCTQSET:
  if (! capable (CAP_SYS_ADMIN))
   return -EPERM;
  scull_qset = arg;
  break;

   case SCULL_IOCGQSET:
  retval = __put_user(scull_qset, (int __user *)arg);
  break;

   case SCULL_IOCQQSET:
  return scull_qset;

   case SCULL_IOCXQSET:
  if (! capable (CAP_SYS_ADMIN))
   return -EPERM;
  tmp = scull_qset;
  retval = __get_user(scull_qset, (int __user *)arg);
  if (retval == 0)
   retval = put_user(tmp, (int __user *)arg);
  break;

   case SCULL_IOCHQSET:
  if (! capable (CAP_SYS_ADMIN))
   return -EPERM;
  tmp = scull_qset;
  scull_qset = arg;
  return tmp;

        /*
         * The following two change the buffer size for scullpipe.
         * The scullpipe device uses this same ioctl method, just to
         * write less code. Actually, it's the same driver, isn't it?
         */

   case SCULL_P_IOCTSIZE:
  scull_p_buffer = arg;
  break;

   case SCULL_P_IOCQSIZE:
  return scull_p_buffer;


   default:  /* redundant(��), as cmd was checked against MAXNR */
  return -ENOTTY;
 }
 return retval;

}

 

 

 

發佈了32 篇原創文章 · 獲贊 6 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章