ioctl

头文件: #include<unistd.h>   
功 能: 控制I/O设备 ,提供了一种获得设备信息或设备控制参数的手段。
原型:int ioctl(int fd, ind cmd, …);
返回值:成功为0,出错为-1

其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。

ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道。

使用ioctl来实现控制的功能。要记住,用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情,因为设备都是特定的,这里也没法说。关键在于怎么样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径。

命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,这样才不会将正确的命令发给错误的设备,或者是把错误的命令发给正确的设备,或者是把错误的命令发给错误的设备。这些错误都会导致不可预料的事情发生,而当程序员发现了这些奇怪的事情的时候,再来调试程序查找错误,那将是非常困难的事情。

所以在Linux核心中是这样定义一个命令码的:

____________________________________

| 设备类型 | 序列号 | 方向 |数据尺寸|

|----------|--------|------|--------|

| 8 bit    |  8 bit |2 bit |8~14 bit|

|----------|--------|------|--------|

#define _IOC_NRBITS 8 //序数(number)字段的字位宽度,8bits

#define _IOC_TYPEBITS 8 //幻数(type)字段的字位宽度,8bits  
#define _IOC_SIZEBITS 14 //大小(size)字段的字位宽度,14bits  
#define _IOC_DIRBITS 2 //方向(direction)字段的字位宽度,2bits

这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。

这些宏我就不在这里解释了,具体的形式请读者察看Linux核心源代码中的和,文件里给除了这些宏完整的定义。这里我只多说一个地方,那就是"幻数"。

幻数是一个字母,数据长度也是8,所以就用一个特定的字母来标明设备类型,这和用一个数字是一样的,只是更加利于记忆和理解。就是这样,再没有更复杂的了。

cmd命令:

cmd参数在用户程序端由一些宏根据设备类型、序列号、传送方向、数据尺寸等生成,这个整数通过系统调用传递到内核中的驱动程序,再由驱动程序使用解码宏从这个整数中得到设备的类型、序列号、传送方向、数据尺寸等信息,然后通过switch{case}结构进行相应的操作。

include/asm/ioctl.h中定义的宏的注释:

#define         _IOC_NRBITS         8                  //序数(number)字段的字位宽度,8bits

#define         _IOC_TYPEBITS      8                   //幻数(type)字段的字位宽度,8bits

#define         _IOC_SIZEBITS       14                 //大小(size)字段的字位宽度,14bits

#define         _IOC_DIRBITS        2                   //方向(direction)字段的字位宽度,2bits

#define         _IOC_NRMASK        ((1 << _IOC_NRBITS)-1)    //序数字段的掩码,0x000000FF

#define         _IOC_TYPEMASK   ((1 << _IOC_TYPEBITS)-1)  //幻数字段的掩码,0x000000FF

#define         _IOC_SIZEMASK     ((1 << _IOC_SIZEBITS)-1)   //大小字段的掩码,0x00003FFF

#define         _IOC_DIRMASK      ((1 << _IOC_DIRBITS)-1)    //方向字段的掩码,0x00000003

#define        _IOC_NRSHIFT       0                                                        //序数字段在整个字段中的位移,0

#define        _IOC_TYPESHIFT   (_IOC_NRSHIFT+_IOC_NRBITS)         //幻数字段的位移,8

#define        _IOC_SIZESHIFT    (_IOC_TYPESHIFT+_IOC_TYPEBITS)  //大小字段的位移,16

#define        _IOC_DIRSHIFT      (_IOC_SIZESHIFT+_IOC_SIZEBITS)    //方向字段的位移,30

/*

* Direction bits.

*/

#define _IOC_NONE     0U     //没有数据传输

#define _IOC_WRITE   1U     //向设备写入数据,驱动程序必须从用户空间读入数据

#define _IOC_READ     2U     //从设备中读取数据,驱动程序必须向用户空间写入数据

/*

*_IOC 宏将dirtypenrsize四个参数组合成一个cmd参数,如下图:

*

*/


500)this.width=500;" border=0>

#define _IOC(dir,type,nr,size) /

       (((dir)  << _IOC_DIRSHIFT) | /

        ((type) << _IOC_TYPESHIFT) | /

        ((nr)   << _IOC_NRSHIFT) | /

        ((size) << _IOC_SIZESHIFT))

/*

* used to create numbers 

*/

//构造无参数的命令编号

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


//构造从驱动程序中读取数据的命令编号

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


//用于向驱动程序写入数据命令

#define _IOW(type,nr,size)    _IOC(_IOC_WRITE,(type),(nr),sizeof(size))


//用于双向传输

#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

 

/* 

*used to decode ioctl numbers..

 */

//从命令参数中解析出数据方向,即写进还是读出

#define _IOC_DIR(nr)          (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)

//从命令参数中解析出幻数type

#define _IOC_TYPE(nr)              (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)

//从命令参数中解析出序数number

#define _IOC_NR(nr)           (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)

//从命令参数中解析出用户数据大小

#define _IOC_SIZE(nr)         (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)

 

/* ...and for the drivers/sound files... */

 

#define IOC_IN            (_IOC_WRITE << _IOC_DIRSHIFT)

#define IOC_OUT         (_IOC_READ << _IOC_DIRSHIFT)

#define IOC_INOUT     ((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT)

#define IOCSIZE_MASK      (_IOC_SIZEMASK << _IOC_SIZESHIFT)

#define IOCSIZE_SHIFT      (_IOC_SIZESHIFT)

ldd3 scull_ioctl
scull源代码中ioctl方法:

int scull_ioctl(struct inode *inode, struct file *filp,unsigned int cmd, unsigned long arg)
{

 int err = 0, tmp;
 int retval = 0;
    
 if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
 if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;

 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;

 switch(cmd) {

   case SCULL_IOCRESET:
  scull_quantum = SCULL_QUANTUM;
  scull_qset = SCULL_QSET;
  break;
       
   case SCULL_IOCSQUANTUM:

  if (! capable (CAP_SYS_ADMIN))
   return -EPERM;
  retval = __get_user(scull_quantum, (int __user *)arg);
  break;

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

   case SCULL_IOCGQUANTUM: 

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

   case SCULL_IOCQQUANTUM: 

  return scull_quantum;

   case SCULL_IOCXQUANTUM: 

  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: 

  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;

   case SCULL_P_IOCTSIZE:
  scull_p_buffer = arg;
  break;

   case SCULL_P_IOCQSIZE:
  return scull_p_buffer;


   default:  

  return -ENOTTY;
 }
 return retval;

}

首先ioctl方法的原型是int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg),这个方法是用于设备控制的公共接口。前两个参数我们已经很熟悉了,cmd是用户空间传递给驱动程序的命令,arg参数是可选的,由用户空间以规定的unsigned long的形式传递给驱动程序。

首先驱动程序要分析ioctl编号的位字段来检查命令号是否合法,利用_IOC_TYPE,_IOC_NR两个宏检查,如果不合法,按照POSIX标准的规定则放回-ENOTTY。接着利用access_ok函数检查要读写的用户空间地址是否合法。

接下来就是对cmd命令的判断选择不同的操作,以下分别阐述:

SCULL_IOCRESET:驱动设备内存空间的重置,主要对quantum、qset(以下也是对这两个变量的改变)

SCULL_IOCSQUANTUM:由于只有授权用户才能更改这两个值,先进行用户的级别判断,特权不够则返回。利用__get_user(scull_quantum, (int __user *)arg)获得用户空间的数据,arg指向参数值。

SCULL_IOCTQUANTUM: :此时arg本身就是参数值,赋给scull_quantum。

SCULL_IOCGQUANTUM:利用__put_user(scull_quantum, (int __user *)arg)把scull_quantum传到arg指向的用户空间。

SCULL_IOCQQUANTUM:比较简单,直接返回结果。

SCULL_IOCXQUANTUM:获得arg指向的值存到scull_quantum,并把先前scull_quantum的值传到arg指向的用户空间,达到交换的功能。

SCULL_IOCHQUANTUM::SCULL_IOCTQUANTUM+SCULL_IOCQQUANTUM

以下关于qset的操作与上面差不多,在此不再赘述,好了这就是ioctl方法的源码了
Linux ioctl(Input Output Control)是一组特殊的系统调用,用于控制设备驱动程序的状态或功能,以便进行低级别的I/O操作。这些操作对于直接与硬件交互非常有用,例如改变USB设备的配置、调整串口通信参数或是对网络适配器进行更细致的控制。 ioctl的基本语法形式是一个整数值,这个值包含两部分信息: 1. **主要编号**(通常是大写字母M):标识 ioctl 的用途类别。 2. **次要编号**(通常是小写字母m):进一步细分具体的 ioctl 操作。 例如 `IOCTL_MDEV` 可能表示某一种设备驱动的相关指令集,`IOCTL_MDEV_SET_SPEED` 则是一个具体的指令,用于设置某个设备的速度。 典型的 ioctl 调用看起来像这样: ```c int ioctl(int fd, unsigned long request, ...); ``` 这里的参数解释如下: - `fd`:文件描述符,关联到设备或文件的一个索引,通常由 open 或其他 I/O 函数返回。 - `request`:一个由驱动程序特定的长整型值,包含指令和可能的数据。 - `...`:额外的数据,取决于请求的具体内容。 ioctl 的应用例子很多,比如在网络编程中调整套接字选项,或者在音频硬件编程中控制混音器的状态。它们之所以重要是因为它们让应用程序能够以比标准 I/O 更底层的方式控制设备,从而提高性能或实现特定功能。 了解 ioctl 的使用可以帮助开发者深入理解系统级别的细节,以及更好地优化他们的程序与硬件的交互。 --- 相关问题 - : 1. ioctl 与其他 I/O 控制方式的区别是什么? 2. 在实际编程中如何正确地使用 ioctl 进行设备控制? 3. 对于不同的硬件设备(如 USB 设备、串口、网络设备),常见的 ioctl 请求有哪些?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值