linux设备驱动——ioctl函数分析

 一个字符设备驱动通常会实现常规的打开、关闭、读、写等功能,但在一些细分的情境下,如果需要扩展新的功能,通常以增设ioctl()命令的方式实现,其作用类似于“拾遗补漏”。在文件I/O中,ioctl扮演着重要角色,本文将以驱动开发为侧重点,从用户空间到内核空间纵向分析ioctl函数。

用户空间的ioctl()

#include <sys/ioctl.h> 

int ioctl(int fd, int cmd, ...) ;
  • 1
  • 2
  • 3

参数描述
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

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

 int ret; ret = ioctl(fd, MYCMD);

   if (ret == -1) {

       printf("ioctl:%s\n", strerror(errno));

   }

驱动中的ioctl()

    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
  • 1
  • 2

  在新版内核中,unlocked_ioctl()与compat_ioctl()取代了ioctl()。unlocked_ioctl(),顾名思义,应该在无大内核锁(BKL)的情况下调用;compat_ioctl(),compat全称compatible(兼容的),主要目的是为64位系统提供32位ioctl的兼容方法,也是在无大内核锁的情况下调用。

tips:在字符设备驱动开发中,一般情况下只要实现unlocked_ioctl()即可,因为在vfs层的代码是直接调用unlocked_ioctl()。

// fs/ioctl.c
static long vfs_ioctl(struct file *filp, unsignedint cmd, unsigned long arg)
{
    int error = -ENOTTY;
    if (!filp->f_op || !filp->f_op->unlocked_ioctl)          
        goto out;
    error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
    if (error == -ENOIOCTLCMD) {
        error = -ENOTTY;
    }  
out:
    return error;
}

ioctl命令,用户与驱动之间的协议

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

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

在内核中,提供了宏接口以生成上述格式的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))
  1. dir(direction),ioctl命令访问模式(数据传输方向),占据2bit,可以为_IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据;

  2. type(device type),设备类型,占据8bit,在一些文献中翻译为“幻数”或者“魔数”,可以为任意char型字符,例如‘a’、‘b’、‘c’等等,其主要作用是使ioctl命令有唯一的设备标识;
    tips:Documentions/ioctl-number.txt记录了在内核中已经使用的“魔数”字符,为避免冲突,在自定义ioctl命令之前应该先查阅该文档。

  3. nr(number),命令编号/序数,占据8bit,可以为任意unsigned char型数据,取值范围0~255,如果定义了多个ioctl命令,通常从0开始编号递增;

  4. size,涉及到ioctl第三个参数arg,占据13bit或者14bit(体系相关,arm架构一般为14位),指定了arg的数据类型及长度,如果在驱动的ioctl实现中不检查,通常可以忽略该参数。

  5. 通常而言,为了方便会使用宏_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)))
  6. #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)

 


 







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值