Linux驱动之ioctl函数——学习笔记(4)

如何创建一个字符设备以及open、close、write、read等函数的使用以及file_operations结构体注释请看Linux驱动之创建字符驱动——学习笔记(3)

这篇文章单独说明一下ioctl的配置及使用。


一、知识点引入

1、指针函数选择

通过查看file_operations结构体成员可以发现配置ioctl函数指针有两个成员:

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

compat_ioctl这个函数是为了使32位的应用程序可以与64位的内核驱动兼容而产生的。
最主要实现的功能就是:

  • 将应用层32位的数据转成64位传给64位内核驱动。
  • 将内核驱动64位的数据转成32位传给32位内核驱动。

所以调用关系如下:

  • 当应用层是32位程序,内核及架构是32位程序,那么驱动的unlocked_ioctl函数被调用。
  • 当应用层是32位程序,内核及架构是64位程序,那么驱动的compat_ioctl函数被调用。
  • 当应用层是64位程序,内核及架构是64位程序,那么驱动的unlocked_ioctl函数被调用。

简单的说,就是在32位的内核驱动中只需要实现unlocked_ioctl函数,而在64位的内核驱动中必须实现unlocked_ioctlcompat_ioctl两个函数。

2、unlocked_ioctl函数参数说明

compat_ioctlunlocked_ioctl的参数以及意义其实是一样的,所以就只拿其中一个说明就好了。

原型: unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)

  • 参数(*file):struct file结构体,定义在include/linux/fs.h。文件结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的 struct file
  • 参数(cmd):IO操作的指令,与应用层的ioctl的第二个函数相同,下面详细说明。
  • 参数(arg):传递参数的指针地址(用户空间的),就是应用层的 ioctl 的第三个参数地址。使用的话强制转 __user 指针,然后再使用 copy_from_user 或者 copy_to_user 进行读写操作。

需要详细解释的是第二个参数(cmd)。从函数原型可以看出这是个 int 类型的数据,有32个bit,是应用层与驱动之间的指令“协议”。
为了保证该指令“协议”的唯一性,在Linux中提供了一种统一格式,将32位的 int 型数据划分为四个部分:

  • dir(2 bit,第 31-30 位): 表示 ioctl 命令对设备的操作类型,2个bit有4中可能,分别为 访问模式(数据传输方向),可以为无、读、写、读写,相应的宏分别为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,定义在kernel\include\uapi\asm-generic\ioctl.h中。
  • size(13/14bit,第 29-16位):,ARM 架构一般为 14 bit,表示每次读写数据的长度,即 ioctl 函数第三个参数 arg 数据的长度。
  • type(8 bit,第 15-8 位): 表示设备类型,可以为任意 char 型字符,其主要作用是使 ioctl 命令有唯一的设备标识,通常用英文字符 “A” ~ “Z” 或者 “a” ~ “z” 来表示。可以参考kernel/Documentation/ioctl/ioctl-number.txt文件,选取一个还没有别定义的字符进行设置。一些文献译为 “幻数” 或者 “魔数”。
  • nr(8bit,第 7-0位): 命令编号,可以为任意 unsigned char 型数据,取值范围 0~255。一个命令对应一个编号,通常从 0 开始编号递增。

为了方便使用,在Linux内核定义了相应的宏来辅助生成该cmd命令。

kernel\include\uapi\asm-generic\ioctl.h

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

#ifndef __KERNEL__
#define _IOC_TYPECHECK(t) (sizeof(t))
#endif

/* 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(魔数, 编号),作用在 无数据读写 的命令。
  • _IOR(魔数, 编号, 变量类型) ,作用在 读数据 的命令。
  • _IOW(魔数, 编号, 变量类型),作用在 写数据 的命令。
  • _IOWR(魔数, 编号,变量类型),作用在 读写数据 的命令。

相对应的Linux内核也有四个宏用来解析命令

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

这四个宏的使用格式分别为:

  • _IOC_DIR(命令),解析出数据方向。
  • _IOC_TYPE(命令) ,解析出设备类型。
  • _IOC_NR(命令),解析出命令编号。
  • _IOC_SIZE(命令),解析出数据长度。

二、开始编程

1、实现的内容及流程
(1)要实现的内容
(2)实现的流程
  • 通过宏定义命令类型。
  • 实现unlocked_ioctl函数内容,对命令进行分类,对数据进行读写。
  • 应用层程序使用ioctl函数对驱动进行操作。
(3)实现的目标
  • 应用层通过ioctl函数向应用发送一个无数据操作的命令。
  • 应用层通过ioctl函数向应用发送一个写入数据的命令。
  • 应用层通过ioctl函数向应用发送一个读取数据的命令。
  • 应用层通过ioctl函数向应用发送一个写入数据同时又读取数据的命令。
2、驱动编程内容
(1)声明宏定义及添加头文件
#include <sys/ioctl.h>


typedef enum {
    IO_NONE = 0,
    IO_WRITE = 1,
    IO_READ = 2,
    IOCTL_WRITE_READ = 3,
    IO_MAX
} IOCTL_NUMBER;                                                                /* IO命令编号枚举 */

#define IOCTL_MAGIC                 '~'                                        /* 定义一个魔数,随便定义个 */
#define IOCTL_NONE                  _IO(IOCTL_MAGIC, IO_NONE)                  /* 无操作命令 */
#define IOCTL_WRITE                 _IOW(IOCTL_MAGIC, IO_WRITE, unsigned char[128])  /* 写数据命令 */
#define IOCTL_READ                  _IOR(IOCTL_MAGIC, IO_READ, unsigned char[128])   /* 读数据命令 */
#define IOCTL_WRITE_READ            _IOWR(IOCTL_MAGIC, IOCTL_WRITE_READ, unsigned char[128])  /* 读写数据命令 */

这是利用Linux内核定义的宏生成的ioctl命令。

(2)unlocked_ioctl函数实现
/**************************************************************************************************
**  函数名称:  hello_ioctl
**  功能描述:  应用层调用ioctl函数时同步调用此函数
**  输入参数:   *file:文件结构体
**           cmd:IO操作的指令
**           arg:传递参数的指针地址(用户空间 的)
**  输出参数:  无
**  返回参数:  成功返回0,失败返回-1
**************************************************************************************************/
long hello_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    void __user *ptr;                                                          /* 数据地址指针 */
    int arg_len;                                                               /* 数据长度 */
    long ret;
    
    ptr =  (void __user*) arg;                                                 /* 强转用户空间数据地址 */
    
    if (_IOC_TYPE(cmd) != IOCTL_MAGIC) {                                       /* 检查魔数 */
        PRINT("[KERNEL]:%s, MAGIC is not match !\n", __FUNCTION__);
        return -1; 
    }
    
    if (_IOC_NR(cmd) > IO_MAX) {                                               /* 检查编号 */
        PRINT("[KERNEL]:%s, number over IO_MAX !\n", __FUNCTION__);
        return -1;
    }

    arg_len = _IOC_SIZE(cmd);
    
    switch (cmd) {
        case IOCTL_NONE:                                                       /* 无操作命令 */
            PRINT("[KERNEL]:%s ------ IOCTL_NONE\n", __FUNCTION__);
            ret = 0;
            break;
        case IOCTL_WRITE:                                                      /* 写数据命令 */
            PRINT("[KERNEL]:%s ------ IOCTL_WRITE\n", __FUNCTION__);
            ret = ioctl_write(ptr, arg_len);
            break;
        case IOCTL_READ:                                                       /* 读数据命令 */
            PRINT("[KERNEL]:%s ------ IOCTL_READ\n", __FUNCTION__);
            ret = ioctl_read(ptr, arg_len);
            break;
        case IOCTL_WRITE_READ:                                                 /* 读写数据命令 */
            PRINT("[KERNEL]:%s ------ IOCTL_WRITE_READ\n", __FUNCTION__);
            ret = ioctl_write_read(ptr, arg_len);
            break;

        default:
            ret = -1;
            break;
    }

    return ret;
}
  • 参数arg为应用层传递过来的参数数据指针,属于用户空间地址,需要使用copy_to_usercopy_from_user等函数进行读写。
  • 根据命令进行分类,分发到不同函数中进行数据处理。
(3)将函数指针赋值给file_operations结构体。
static struct file_operations hello_fops = {
    .owner = THIS_MODULE,
    .open = hello_open,
    .release = hello_release,
    .read = hello_read,
    .write = hello_write,
    .unlocked_ioctl = hello_ioctl,
};                                                                             /* 文件操作结构体 */
(4)写命令处理函数
/**************************************************************************************************
**  函数名称:  ioctl_write
**  功能描述:  写命令执行函数
**  输入参数:   *ptr:用户空间数据地址
**           len:数据长度
**  输出参数:  无
**  返回参数:  成功返回0,失败返回-1
**************************************************************************************************/
static long ioctl_write(void __user *ptr, int len)
{
    if (0 == access_ok(VERIFY_WRITE, ptr, len)) {                              /* 检查用户空间内存块是否可用 */
        return -1;
    }
    
    memset(test_buff, 0, sizeof(test_buff));
    
    if (0 != copy_from_user(test_buff, ptr, sizeof(test_buff))) {
        PRINT("[KERNEL]:ERROR: %s write error!\n\n", __FUNCTION__);
        return -1;
    }
    
    PRINT("[KERNEL]:%s ------ test_buff = %s\n", __FUNCTION__, test_buff);

    return 0;
}
(5)读命令处理函数
/**************************************************************************************************
**  函数名称:  ioctl_read
**  功能描述:  读命令执行函数
**  输入参数:   *ptr:用户空间数据地址
**           len:数据长度
**  输出参数:  无
**  返回参数:  成功返回0,失败返回-1
**************************************************************************************************/
static long ioctl_read(void __user *ptr, int len)
{
    if (0 == access_ok(VERIFY_READ, ptr, len)) {                              /* 检查用户空间内存块是否可用 */
        return -1;
    }
    
    strcat(test_buff, " from kernel!");

    PRINT("[KERNEL]:%s test_buff = %s\n", __FUNCTION__, test_buff);
    
    if (0 != copy_to_user(ptr, test_buff, sizeof(test_buff))) {
        PRINT("[KERNEL]:ERROR: %s write error!\n\n", __FUNCTION__);
        return -1;
    }

    return 0;
}

(6)读写命令处理函数
/**************************************************************************************************
**  函数名称:  ioctl_write_read
**  功能描述:  读写命令执行函数
**  输入参数:   *ptr:用户空间数据地址
**           len:数据长度
**  输出参数:  无
**  返回参数:  成功返回0,失败返回-1
**************************************************************************************************/
static long ioctl_write_read(void __user *ptr, int len)
{
    if (0 == access_ok(VERIFY_WRITE, ptr, len)) {                              /* 检查用户空间内存块是否可用 */
        return -1;
    }

    /* 读取应用层写入的数据 */
    memset(test_buff, 0, sizeof(test_buff));
    
    if (0 != copy_from_user(test_buff, ptr, sizeof(test_buff))) {
        PRINT("[KERNEL]:ERROR: %s write error!\n\n", __FUNCTION__);
        return -1;
    }
    
    PRINT("[KERNEL]:%s ------ test_buff = %s\n", __FUNCTION__, test_buff);

    
    /* 向应用层传输数据 */
    memset(test_buff, 0, sizeof(test_buff));
    sprintf(test_buff, "%s", "It is changed by kernel driver!");
    
    PRINT("[KERNEL]:%s test_buff = %s\n", __FUNCTION__, test_buff);
    
    if (0 != copy_to_user(ptr, test_buff, sizeof(test_buff))) {
        PRINT("[KERNEL]:ERROR: %s write error!\n\n", __FUNCTION__);
        return -1;
    }

    return 0;
}
3、应用编码内容
(1)源码
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>

/*************************************************************************************************/
//                           局部宏定义
/*************************************************************************************************/
#define IOCTL_MAGIC                 '~'                                        /* 定义一个魔数,随便定义个 */
#define IOCTL_NONE                  _IO(IOCTL_MAGIC, 0)                        /* 无操作命令 */
#define IOCTL_WRITE                 _IOW(IOCTL_MAGIC, 1, unsigned char[128])   /* 写数据命令 */
#define IOCTL_READ                  _IOR(IOCTL_MAGIC, 2, unsigned char[128])   /* 读数据命令 */
#define IOCTL_WRITE_READ            _IOWR(IOCTL_MAGIC, 3, unsigned char[128])  /* 读写数据命令 */

#define DELAY50         usleep(50 * 1000);                                     /* 延迟50ms */

int main(int argc,char **argv)
{
    int ret;                                                                   /* 读写返回值 */
    int handle;                                                                /* 文件标识符 */
    char str[128];                                                             /* 测试用数组 */

    handle = open("/dev/hello", O_RDWR);                                       /* 打开驱动设备 */
    
    if (handle == -1) {
        printf("open /dev/hello fail!\n");
        return 0;
    }
    
    DELAY50
    
    ret = ioctl(handle, IOCTL_NONE, str);                                      /* 发送无数据命令给驱动 */
    DELAY50
    printf("[user]:IOCTL_NONE, ret = %d\n", ret);                              /* 无数据命令执行结果 */
    DELAY50

    memset(str, 0, sizeof(str));
    sprintf(str, "%s", "ABCDEFG");                                             /* 填写测试的内容 */
    ret = ioctl(handle, IOCTL_WRITE, str);                                     /* 发送带数据的命令给驱动 */
    DELAY50
    printf("[user]:IOCTL_WRITE, ret = %d, str = %s\n", ret, str);              /* 写结果 */
    DELAY50
    
    memset(str, 0, sizeof(str));
    ret = ioctl(handle, IOCTL_READ, str);                                      /* 发送读数据的命令给驱动 */
    DELAY50
    printf("[user]:IOCTL_READ, ret = %d, str = %s\n", ret, str);               /* 读结果 */
    DELAY50
    
    memset(str, 0, sizeof(str));
    sprintf(str, "%s", "QWERT123456");                                         /* 填写测试的内容 */
    ret = ioctl(handle, IOCTL_WRITE_READ, str);                                /* 发送带数据的命令给驱动,并从驱动读取数据出来 */
    DELAY50
    printf("[user]:IOCTL_WRITE_READ, ret = %d, str = %s\n", ret, str);         /* 读结果 */
    DELAY50
    
    close(handle);

    return 0;
}

应用层需要和驱动中相同的命令宏定义,可以写在头文件中引用,也可以分开定义。

三、测试

加载驱动hello.ko后执行hello应用层程序,结果如下。
根据结果可以看出在应用层分别调用了ioctl的四个命令,进行数据的读写操作,驱动都可以收到数据或者修改再传回。

root@imx6qsabresd:/tmp# ./hello 
[24853.936864] [KERNEL]:hello_open ------ 
[24853.990864] [KERNEL]:hello_ioctl ------ IOCTL_NONE
[user]:IOCTL_NONE, ret = 0
[24854.096308] [KERNEL]:hello_ioctl ------ IOCTL_WRITE
[24854.101198] [KERNEL]:ioctl_write ------ test_buff = ABCDEFG
[user]:IOCTL_WRITE, ret = 0, str = ABCDEFG
[24854.207285] [KERNEL]:hello_ioctl ------ IOCTL_READ
[24854.212089] [KERNEL]:ioctl_read test_buff = ABCDEFG from kernel!
[user]:IOCTL_READ, ret = 0, str = ABCDEFG from kernel!
[24854.318376] [KERNEL]:hello_ioctl ------ IOCTL_WRITE_READ
[24854.323721] [KERNEL]:ioctl_write_read ------ test_buff = QWERT123456
[24854.330081] [KERNEL]:ioctl_write_read test_buff = It is changed by kernel driver!
[user]:IOCTL_WRITE_READ, ret = 0, str = It is changed by kernel driver!
[24854.437848] [KERNEL]:hello_release ------ 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hrx-@@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值