linux设备驱动学习(5) 高级字符驱动程序操作--ioctl

来源:互联网 发布:mysql 大于小于 编辑:程序博客网 时间:2024/05/15 06:08
 

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;

}

 

 

 

原创粉丝点击