linux驱动开发学习笔记十九:认识一下ioctl函数

一、ioctl介绍

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制。

如果不用ioctl的话,也可以实现对设备I/O通道的控制,但那是蛮拧了。例如,我们可以在驱动程序中实现write的时候检查一下是否有特殊约定的数据流通过,如果有的话,那么后面就跟着控制命令。但是如果这样做的话,会导致代码分工不明,程序结构混乱,程序员自己也会头昏眼花的。所以,我们就使用ioctl来实现控制的功能。要记住,用户程序所作的只是通过命令码(cmd)告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。

二、ioctl相关函数原型介绍

  • 在应用程序中
int  ioctl(int fd, unsigned long cmd, ...)
  • fd:文件操作符

  • cmd:交互协议,设备驱动将根据 cmd 执行对应操作

  • …:可变参数 arg

  • 在驱动程序中

long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg);
long (*compat_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg);

unlocked_ioctl,顾名思义,应该在无大内核锁(BKL)的情况下调用;compat_ioctl,compat 全称 compatible(兼容的),主要目的是为 64 位系统提供 32 位 ioctl 的兼容方法,也是在无大内核锁的情况下调用。在字符设备驱动开发中,一般情况下只要实现 unlocked_ioctl 函数即可。

三、ioctl 用户与驱动之间的协议

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

  • dir(direction),ioctl 命令访问模式(数据传输方向),占据 2 bit,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据。
  • type(device type),是个0-0xff的数或者一个字符,占8bit。这个数是用来区分不同的驱动的,像设备号一样,内核有一个文档(Documentation/ioctl/ioctl-number.txt)给出一些推荐的或者已经被使用的幻数。
  • nr(number),命令编号/序数,8 bit,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增。
  • size,涉及到 ioctl 函数 第三个参数 arg ,占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度,只需要填参数的类型,如int,函数就会帮你检测类型的正确性然后赋值sizeof(int)。

为了方便我们会使用宏 _IOC() 衍生的接口来直接定义 ioctl 命令,函数原型如下所示:

#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相关函数怎么使用(伪代码)

  • 驱动程序
#define CLOSE_CMD           _IO(0XEF, 1)           //关闭命令               
#define OPEN_CMD            _IO(0XEF, 2)          //打开命令             
#define SETPERIOD_CMD       _IOW(0xEF, 3, int)    //设置周期
static const struct file_operations timerdev_fops = {
    .owner		=	THIS_MODULE,
	.open		=	timer_open,
    .unlocked_ioctl = timer_ioctl,
	.release	=	timer_release,
};
static long timer_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	switch (cmd) {
	case CLOSE_CMD: 
		break;
	case OPEN_CMD:
		break;
	case SETPERIOD_CMD:
		break;
	}
	return ret;
}
  • 应用程序
#define CLOSE_CMD           _IO(0XEF, 1)           //关闭命令               
#define OPEN_CMD            _IO(0XEF, 2)          //打开命令             
#define SETPERIOD_CMD       _IOW(0xEF, 3, int)    //设置周期
/* 循环读取 */
    while(1) {
        printf("Input CMD:");
        ret = scanf("%d", &cmd);
        if(ret !=1 ) {
            gets(str);  /* 防止卡死 */
        }

        if(cmd == 1) {          /* 关闭 */
            ioctl(fd, CLOSE_CMD, &arg);  
        } else if(cmd == 2) {   /* 打开 */
            ioctl(fd, OPEN_CMD, &arg); 
        } else if(cmd == 3) {    /* 设置周期 */
            printf("Input Timer period:");
            ret = scanf("%d", &arg);
            if(ret !=1 ) {
                gets(str);
            }
            ioctl(fd, SETPERIOD_CMD, &arg); 
        }
    }

当我们在应用程序调用ioctl函数的时候,和我们之前说过的open、write等函数一样,它是通过一个file_operations 类型的结构体实现与驱动程序中的timer_ioctl函数联系上的。我们在应用程序中给ioctl函数传递cmd等参数,此时cmd能够通过unlocked_ioctl 传递给驱动程序中的timer_ioctl函数,该函数接收到cmd等参数之后就可以进行一系列的操作。具体执行什么操作,是由我们自己决定的,也即看我们想要这些cmd分别代表什么意思。

参考文章:

扫描二维码关注公众号,回复: 11264319 查看本文章

linux 内核 - ioctl 函数详解

linux ioctl()详解

猜你喜欢

转载自blog.csdn.net/qq_39507748/article/details/105963307