linux内核中ioctl函数与write,read函数的区别

        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() 编译命令

  1. 第一个分区 nr(number): 0-7位 , 命令编号/序数 0-255,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增
  2. 第二个分区type(device type): 8-15位, 设备类型,占据 8 bit,也称为 “幻数” 或者 “魔数”,可以为任意 char 型字符,例如‘a’、’b’、’c’ 等等,其主要作用是使 ioctl 命令有唯一的设备标识
  3. 第三个分区size:16-29 表示传递的数据大小,与体系结构相关,ARM下占14bit(_IOC_SIZEBITS),如果数据是int,内核给这个赋的值就是sizeof(int)。
  4. 第四个分区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)第三个参数可以是一个整形变量,也可以是一个指向某种数据结构的指针。

  • 10
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值