简介
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数,提供了一种获得设备信息和向设备发送控制参数的手段。用于向设备发控制和配置命令,有些命令需要控制参数,这些数据是不能用read / write 读写的,称为Out-of-band数据。也就是说,read / write读写的数据是in-band数据,是I/O操作的主体,而ioctl 命令传送的是控制信息,其中的数据是辅助的数据。
函数声明:
int ioctl(int, int, ...);
int ioctl(int handle, int cmd,[int *argdx, int argcx]);
第一个参数一般为open()打开的文件描述符fd。
include/asm-generic/ioctl.h中宏定义
#define _IOC_NRBITS 8 //序数(number)字段的字位宽度,8bits
#define _IOC_TYPEBITS 8 //幻数(type)字段的字位宽度,8bits
#ifndef _IOC_SIZEBITS
# define _IOC_SIZEBITS 14 //大小(size)字段的字位宽度,14bits
#endif
#ifndef _IOC_DIRBITS
# define _IOC_DIRBITS 2 //方向(direction)字段的字位宽度,2bits
#endif
#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
#ifndef _IOC_NONE
# define _IOC_NONE 0U //没有数据传输
#endif
#ifndef _IOC_WRITE
# define _IOC_WRITE 1U //向设备写入数据,驱动程序必须从用户空间读入数据
#endif
#ifndef _IOC_READ
# define _IOC_READ 2U //从设备中读取数据,驱动程序必须向用户空间写入数据
#endif
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
#ifndef __KERNEL__
#define _IOC_TYPECHECK(t) (sizeof(t))
#endif
/* used to create numbers */
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0) //构造无参数的命令编号
#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))) //用于双向传输
根据size的设置情况,我猜它会取值sizeof(int、long或者一个数组)......
#define _IOR_BAD(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW_BAD(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR_BAD(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)//解析数据方向
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)//解析幻数
#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)
应用实例
应用层用户接口
// Get device name.
char buffer[80];
ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer)
#define EVIOCGNAME(len) _IOC(_IOC_READ, 'E', 0x06, len)
#define _IOC(dir,type,nr,size) (((dir) << _IOC_DIRSHIFT) | ((type) << _IOC_TYPESHIFT)
| ((nr) << _IOC_NRSHIFT) | ((size) << _IOC_SIZESHIFT))
内核ioctl实现
用户传下来的就是一个cmd,看下kernel中对命令的解析。
#define EVIOCGNAME(len) _IOC(_IOC_READ, 'E', 0x06, len) /* get device name */
static long evdev_do_ioctl(struct file *file, unsigned int cmd,
void __user *p, int compat_mode)
{
size = _IOC_SIZE(cmd); //获取用户需要的数据长度
/* Now check variable-length commands */
#define EVIOC_MASK_SIZE(nr) ((nr) & ~(_IOC_SIZEMASK << _IOC_SIZESHIFT))//去掉size
switch (EVIOC_MASK_SIZE(cmd)) {
case EVIOCGNAME(0):
return str_to_user(dev->name, size, p);//将dev->name copy到user空间p,dev是一个input_dev
}
}