kernel3.0之前,叫ioctl,之后改名为unlocked_ioctl。功能和接口基本相同,名字发生了变化ioctl既可以往内核读也可以写,read/write在执行大数据量读/写时比较有优势。在应用层调用ioctl函数时,内核会调用对应驱动中的ublocked_ioctl函数,向内核读写数据。
一、ioctl函数的使用
1、驱动内的unlocked_ioctl函数
unlocked_ioctl函数属于file_operations文件操作集的一个成员,结构体内函数的定义为:
long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg)
@filp: 文件描述符,是ioctl调用对应的文件结构体指针。通过它可以获取其他文件信息,能获取文件私有数据等
@cmd: ioctl命令码,区分调用的是哪个ioctl控制函数。
@arg : ioctl操作的类型和参数。根据cmd的不同传递操作所需的参数,可能是一个整数、指针或者结构体指针类型。
2、应用层的ioctl函数
定义在头文件<sys/ioctl.h>中,是个可变参数的函数
int ioctl(int fd, int cmd, ...);
@fd : 要进行ioctl操作的文件描述符,通常是设备文件打开返回的值。
@request : ioctl命令编码,用来指定具体的ioctl操作。它是一个常量,定义在内核头文件里。
@... : 根据request不同可能有其他出入参数:
*无参数请求,此处为空。
*有一个参数请求,此处传一个指针。
*有两个以上参数请求,此处传一个void*指针来接收结构体参数。
下面以I2C为例:
cmd有I2C_SLAVE,I2C_SLAVE_FORCE,I2C_TENBIT,I2C_S3C2410_SET_SPEED几个选项;
- I2C_SLAVE:对应的arg取值为I2C从机地址,用来设定I2C从机地址;
- I2C_SLAVE_FORCE:对应的arg取值为I2C从机地址,用来修改I2C从机地址;
- I2C_TENBIT:对应的arg取值为0:从机地址为7 bit;对应的arg取值为1:从机地址为10bit。用来指定I2C从机地址的位数;
- I2C_S3C2410_SET_SPEED:对应的arg取值为I2C总线控制器分频值。用来设置I2C总线控制器时钟频率;
常用设置设置I2c从机地址为0xA0,如果选用at24c08设备,那么从机是7 bit地址,所以要右移1位,指定从机地址为7 bit,
ioctl(fd,I2C_TENBIT,0)。
ioctl(fd,I2C_SLAVE,0xA0>>1);
read()与write()函数的使用
假设子地址为12,向有子地址的器件写进7个字节:
unsigned char buf[8]={12,'s','j','s','u','n','n','y');write(fd,buf,9);/*写进7个字节,第1个字节为子地址*/
从有子地址的I2C器件读取7个字节:
unsigned char suba=0;recbuf[20];
write(fd,buf,1);/*发送子地址0*/
read(fd,recbuf,7);/*从子地址12开始读取7个字节*/
二、ioctl 用户与驱动之间的协议
魔数 (magic number)
魔数范围为 0~255 。通常,用英文字符 “A” ~ “Z” 或者 “a” ~ “z” 来表示。设备驱动程序从传递进来的命令获取魔数,然后与自身处理的魔数想比较,如果相同则处理,不同则不处理。魔数是拒绝误使用的初步辅助状态。设备驱动 程序可以通过 _IOC_TYPE (cmd) 来获取魔数。不同的设备驱动程序最好设置不同的魔数,但并不是要求绝对,也可以使用其他设备驱动程序已用过的魔数。
基(序列号)数
基数用于区别各种命令。通常,从 0开始递增,相同设备驱动程序上可以重复使用该值。例如,读取和写入命令中使用了相同的基数,设备驱动程序也能分辨出来,原因在于设备驱动程序区分命令时 使用 switch ,且直接使用命令变量 cmd值。创建命令的宏生成的值由多个域组合而成,所以即使是相同的基数,也会判断为不同的命令。设备驱动程序想要从命令中获取该基数,就使用下面的宏:
_IOC_NR (cmd)
通常,switch 中的 case 值使用的是命令的本身。
变量型
变量型使用 arg 变量指定传送的数据大小,但是不直接代入输入,而是代入变量或者是变量的类型,原因是在使用宏创建命令,已经包含了 sizeof() 编译命令
- 第一个分区 nr(number): 0-7位 , 命令编号/序数 0-255,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增
- 第二个分区type(device type): 8-15位, 设备类型,占据 8 bit,也称为 “幻数” 或者 “魔数”,可以为任意 char 型字符,例如‘a’、’b’、’c’ 等等,其主要作用是使 ioctl 命令有唯一的设备标识
- 第三个分区size:16-29 表示传递的数据大小,与体系结构相关,ARM下占14bit(_IOC_SIZEBITS),如果数据是int,内核给这个赋的值就是sizeof(int)。
- 第四个分区dir(direction):30-31 ioctl 命令访问模式(数据传输方向),占据 2 bit,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据
第一个分区和第二个分区, 主要是用来区分命令的
对于第四分区(31-30):
参数 | 功能 |
---|---|
00 | 表示应用程序和驱动程序没有数据传递 |
10 | 表示应用程序从驱动里面读数据 |
01 | 表示应用程序向驱动里面写数据 |
11 | 表示应用程序先写数据到驱动,然后再从驱动里面读数据 |
在内核中,提供了宏接口以生成上述格式的 ioctl 命令。通常,为了方便会使用宏 _IOC() 衍生的接口来直接定义 ioctl 命令。
- 合成宏:
#ifndef _IOC_NONE
# define _IOC_NONE 0U //0x00
#endif
#ifndef _IOC_WRITE
# define _IOC_WRITE 1U //0x01
#endif
#ifndef _IOC_READ
# define _IOC_READ 2U //0x10
#endif
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \ // dir << 30
((type) << _IOC_TYPESHIFT) | \ // type << 8
((nr) << _IOC_NRSHIFT) | \ //nr << 0
((size) << _IOC_SIZESHIFT)) //size << 16
#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))
宏定义 | 功能 |
_IO(type, nr) | 用来定义没有数据传递的命令 |
_IOR(type, nr, size) | 用来定义从驱动中读取数据的命令 |
_IOW(type, nr, size) | 用来定义向驱动写入数据的命令 |
_IOWR(type, nr, size) | 用来定义数据交换类型的命令**,先写入,再读取** |
参数 | 描述 |
type | 表示命令组成的幻(魔)数,也就是8-15位 |
nr | 表示命令组成的编号,也就是0-7位 |
size | 表示命令组成的参数传递大小,注意这里不是传递数字,而是数据类型, 如要传递4字节,就可以写成int |
- 分解宏
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
//(nr>>30)& 0x3
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
//(nr>>8)& 0xFF
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
// (nr)&0xFF
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
//(nr>>16)&0x3FFF
宏定义 | 功能 |
---|---|
_IOC_DIR(nr) | 分解命令的方向,也就是 30-31 位的值 |
_IOC_TYPE(nr) | 分解命令的魔数,也就是上面的8-15位的值 |
_IOC_NR(nr) | 分解命令的编号,也就是上面的0-7位的值 |
_IOC_SIZE(nr) | 分解命令的数据大小,也就是上面的16-29位 |
- 通常来说,使用read write接口,来读写数据;使用ioctl接口设置一些属性
- ioctl接口既可以读,也可以写,但是读写大数据的效率不如使用read write接口高
应用层的ioctl与驱动的ioctrl的参数对应关系
三、ioctl同read和write的区别
1、用途不同:
-
write和read函数主要用于读写设备的数据。
-
ioctl函数用于控制设备,配置设备参数,比如修改配置、获取状态等。
2、数据传输不同:
-
write和read传输的是数据流,通常传输较大数据块。
-
ioctl一般只传输控制命令和相关参数,数据量较小。
3、调用机制不同:
-
write和read根据文件偏移量顺序进行读写操作。
-
ioctl根据操作命令号来实现不同的控制功能。
4、内核处理不同:
-
write和read在内核会调用对应的驱动文件操作函数如file_operations->write/read。
-
ioctl调用 filing_operations->ioctl函数来进行设备特定控制操作
5、其他不同
-
ioctl的语义一般是非阻塞的,read和write却省是阻塞的。
-
ioctl的接口是万能的,ioctl(fd, cmd, arg)第三个参数可以是一个整形变量,也可以是一个指向某种数据结构的指针。