ioctl函数详解

ioctl 是设备驱动程序中设备控制接口函数,一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,在一些需要细分的情境下,如果需要扩展新的功能,通常以增设 ioctl() 命令的方式实现。

在文件 I/O 中,ioctl 扮演着重要角色,本文将以驱动开发为侧重点,从用户空间到内核空间纵向分析 ioctl 函数。

 

用户空间 ioctl

#include <sys/ioctl.h> 

int ioctl(int fd, int cmd, ...) ;
参数描述
fd文件描述符
cmd交互协议,设备驱动将根据 cmd 执行对应操作
可变参数 arg,依赖 cmd 指定长度以及类型

ioctl() 函数执行成功时返回 0,失败则返回 -1 并设置全局变量 errorno 值,如下:

EBADF d is not a valid descriptor. 
EFAULT argp references an inaccessible memory area. 
EINVAL Request or argp is not valid. 
ENOTTY d is not associated with a character special device. 
ENOTTY The specified request does not apply to the kind of object that the descriptor d references.

因此,在用户空间使用 ioctl 时,可以做如下的出错判断以及处理:

int ret;
ret = ioctl(fd, MYCMD);
if (ret == -1) {
    printf("ioctl: %s\n", strerror(errno));
}

在实际应用中,ioctl 最常见的 errorno 值为 ENOTTY(error not a typewriter),顾名思义,即第一个参数 fd 指向的不是一个字符设备,不支持 ioctl 操作,这时候应该检查前面的 open 函数是否出错或者设备路径是否正确

ioctl 用户与驱动之间的协议

前文提到 ioctl 方法第二个参数 cmd 为用户与驱动的 “协议”,理论上可以为任意 int 型数据,可以为 0、1、2、3……,但是为了确保该 “协议” 的唯一性,ioctl 命令应该使用更科学严谨的方法赋值,在linux中,提供了一种 ioctl 命令的统一格式,将 32 位 int 型数据划分为四个位段,如下图所示:

在内核中,提供了宏接口以生成上述格式的 ioctl 命令:

// include/uapi/asm-generic/ioctl.h

#define _IOC(dir,type,nr,size) \
    (((dir)  << _IOC_DIRSHIFT) | \
     ((type) << _IOC_TYPESHIFT) | \
     ((nr)   << _IOC_NRSHIFT) | \
     ((size) << _IOC_SIZESHIFT))

  • dir(direction),ioctl 命令访问模式(数据传输方向),占据 2 bit,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据;
  • type(device type),设备类型,占据 8 bit,在一些文献中翻译为 “幻数” 或者 “魔数”,可以为任意 char 型字符,例如
    ‘a’、’b’、’c’ 等等,其主要作用是使 ioctl 命令有唯一的设备标识;
  • nr(number),命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增;
  • size,涉及到 ioctl 函数 第三个参数 arg ,占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度,如果在驱动的 ioctl 实现中不检查,通常可以忽略该参数;

通常而言,为了方便会使用宏 _IOC() 衍生的接口来直接定义 ioctl 命令:

// include/uapi/asm-generic/ioctl.h

/* 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)))

_IO:       定义不带参数的 ioctl 命令
_IOW:      定义带写参数的 ioctl 命令(copy_from_user)
_IOR:      定义带读参数的ioctl命令(copy_to_user)
_IOWR:     定义带读写参数的 ioctl 命令

同时,内核还提供了反向解析 ioctl 命令的宏接口:

// include/uapi/asm-generic/ioctl.h

/* 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)
  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ioctl函数Linux系统中用于设备控制的函数,它可以用来向设备发送控制命令,或者获取设备状态信息等。 ioctl函数的原型如下: ```c int ioctl(int fd, unsigned long request, ...); ``` 其中,fd表示设备文件描述符,request表示设备控制命令,后面的可变参数用于传递控制命令的参数。 request参数一般是一个32位的整数,由四个部分组成: ```c ioctl命令 = (魔数 << 8) | 命令序号 | 方向 | 大小 ``` 其中,魔数是一个16位的数,用于标识该ioctl命令所属的设备类型;命令序号是一个8位的数,用于标识该ioctl命令的具体含义;方向用于表示该ioctl命令是读操作还是写操作;大小用于表示该ioctl命令的参数大小。 例如,下面是一个ioctl命令: ```c #define MY_IOCTL _IOW('k', 1, int) ``` 其中,'k'是魔数,1是命令序号,_IOW表示该ioctl命令是写操作,int表示该ioctl命令参数的大小。 在应用程序中调用ioctl函数时,需要传入一个指向控制命令参数的指针,如下: ```c int val = 10; ioctl(fd, MY_IOCTL, &val); ``` 在设备驱动中,可以通过switch语句来处理ioctl命令,如下: ```c long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int val; switch (cmd) { case MY_IOCTL: if (copy_from_user(&val, (int __user *)arg, sizeof(val))) return -EFAULT; // 处理MY_IOCTL命令 break; // 处理其他命令 default: return -ENOTTY; } return 0; } ``` 其中,copy_from_user函数用于将用户空间中的数据拷贝到内核空间,sizeof(val)表示拷贝的数据大小。处理完命令后,需要返回0代表成功,或者返回一个负数代表错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值