博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux驱动ioctl的使用
阅读量:4125 次
发布时间:2019-05-25

本文共 8813 字,大约阅读时间需要 29 分钟。

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

ioctl调用流程

用户空间的ioctl()

#include 
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 d references.

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

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

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

驱动中的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的兼容方法,也是在无大内核锁的情况下调用。在《Linux Kernel Development》中对两种ioctl方法有详细的解说。

  

So Many Ioctls!

Not long ago, there existed only a single ioctlmethod. Today, there are three methods.unlocked_ioctl() is the same as ioctl(), except it is called without the Big KernelLock (BKL). It is thus up to the author of that function to ensure proper synchronization.Because the BKL is a coarse-grained, inefficient lock, drivers should implementunlocked_ioctl() and not ioctl().

compat_ioctl() is also called without the BKL, but its purpose is to provide a 32-bit compatible ioctl method for 64-bit systems. How you implement it depends on your existing ioctlcommands. Older drivers with implicitly sized types (such as long) should implement a compat_ioctl() method that works appropriately with 32-bit applications. This generally means translating the 32-bit values to the appropriate types for a 64-bit kernel. New driversthat have the luxury of designing their ioctl commands from scratch should ensure all their arguments and data are explicitly sized, safe for 32-bit apps on a 32-bit system, 32-bit apps on a 64-bit system, and 64-bit apps on a 64-bit system. These drivers can then point the compat_ioctl() function pointer at the same function as

unlocked_ioctl().

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

// fs/ioctl.cstatic long vfs_ioctl(struct file *filp, unsigned int 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;} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

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
2
3
4
5
6
7
  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实现中不检查,通常可以忽略该参数。

通常而言,为了方便会使用宏_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))) 
1
2
3
4
5
6
7
8
_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) 
1
2
3
4
5
6
7
8

ioctl-test,实例分析

  本例假设一个带寄存器的设备,设计了一个ioctl接口实现设备初始化、读写寄存器等功能。在本例中,为了携带更多的数据,ioctl的第三个可变参数为指针类型,指向自定义的结构体struct msg

1、ioctl-test.h,用户空间和内核空间共用的头文件,包含ioctl命令及相关宏定义,可以理解为一份“协议”文件,代码如下:

// ioctl-test.h#ifndef __IOCTL_TEST_H__#define __IOCTL_TEST_H__#include 
// 内核空间// #include
// 用户空间/* 定义设备类型 */#define IOC_MAGIC 'c'/* 初始化设备 */#define IOCINIT _IO(IOC_MAGIC, 0)/* 读寄存器 */#define IOCGREG _IOW(IOC_MAGIC, 1, int)/* 写寄存器 */#define IOCWREG _IOR(IOC_MAGIC, 2, int)#define IOC_MAXNR 3struct msg { int addr; unsigned int data;};#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

2、ioctl-test-driver.c,字符设备驱动,实现了unlocked_ioctl接口,根据上层用户的cmd执行对应的操作(初始化设备、读寄存器、写寄存器)。在接收上层cmd之前应该对其进行充分的检查,流程及具体代码实现如下:

// ioctl-test-driver.c......static const struct file_operations fops = {    .owner = THIS_MODULE,    .open = test_open,    .release = test_close,    .read = test_read,    .write = etst_write,    .unlocked_ioctl = test_ioctl,};......static long test_ioctl(struct file *file, unsigned int cmd, \                        unsigned long arg){    //printk("[%s]\n", __func__);    int ret;    struct msg my_msg;    /* 检查设备类型 */    if (_IOC_TYPE(cmd) != IOC_MAGIC) {        pr_err("[%s] command type [%c] error!\n", \            __func__, _IOC_TYPE(cmd));        return -ENOTTY;     }    /* 检查序数 */    if (_IOC_NR(cmd) > IOC_MAXNR) {         pr_err("[%s] command numer [%d] exceeded!\n",             __func__, _IOC_NR(cmd));        return -ENOTTY;    }        /* 检查访问模式 */    if (_IOC_DIR(cmd) & _IOC_READ)        ret= !access_ok(VERIFY_WRITE, (void __user *)arg, \                _IOC_SIZE(cmd));    else if (_IOC_DIR(cmd) & _IOC_WRITE)        ret= !access_ok(VERIFY_READ, (void __user *)arg, \                _IOC_SIZE(cmd));    if (ret)        return -EFAULT;    switch(cmd) {    /* 初始化设备 */    case IOCINIT:        init();        break;    /* 读寄存器 */    case IOCGREG:        ret = copy_from_user(&msg, \            (struct msg __user *)arg, sizeof(my_msg));        if (ret)             return -EFAULT;        msg->data = read_reg(msg->addr);        ret = copy_to_user((struct msg __user *)arg, \                &msg, sizeof(my_msg));        if (ret)             return -EFAULT;        break;    /* 写寄存器 */    case IOCWREG:        ret = copy_from_user(&msg, \            (struct msg __user *)arg, sizeof(my_msg));        if (ret)             return -EFAULT;        write_reg(msg->addr, msg->data);        break;    default:        return -ENOTTY;    }    return 0;} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

3、ioctl-test.c,运行在用户空间的测试程序:

// ioctl-test.c#include 
#include
#include
#include
#include
#include
#include
#include
#include "ioctl-test.h"int main(int argc, char **argv){ int fd; int ret; struct msg my_msg; fd = open("/dev/ioctl-test", O_RDWR); if (fd < 0) { perror("open"); exit(-2); } /* 初始化设备 */ ret = ioctl(fd, IOCINIT); if (ret) { perror("ioctl init:"); exit(-3); } /* 往寄存器0x01写入数据0xef */ memset(&my_msg, 0, sizeof(my_msg)); my_msg.addr = 0x01; my_msg.data = 0xef; ret = ioctl(fd, IOCWREG, &my_msg); if (ret) { perror("ioctl read:"); exit(-4); } /* 读寄存器0x01 */ memset(&my_msg, 0, sizeof(my_msg)); my_msg.addr = 0x01; ret = ioctl(fd, IOCGREG, &my_msg); if (ret) { perror("ioctl write"); exit(-5); } printf("read: %#x\n", my_msg.data); return 0;} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
你可能感兴趣的文章
学习大数据需要什么基础?
查看>>
大数据零基础学习并不难,从0到1你需要的不仅仅是努力!
查看>>
走进大数据,感受大数据
查看>>
学习大数据需要掌握哪些Java技术
查看>>
不懂技术也能看懂云计算,大数据,人工智能
查看>>
大数据知识派:Linux新手如何成为大数据工程师?
查看>>
大数据学习路线(完整详细版,含整套教程)
查看>>
大数据开发高薪必备,价值上万的大数据及人工智能教学资料+思维导图
查看>>
想要成为大数据工程师?你需要掌握以下知识
查看>>
大数据、人工智能、机器学习与深度学习是什么意思?之间有什么关系?
查看>>
你的大数据学习路线
查看>>
做大数据工程师需要掌握哪些技能呢?
查看>>
大数据、云计算系统顶级架构师课程学习路线图
查看>>
如何通过合理的学习规划,快速入门大数据开发
查看>>
大数据入门学习?
查看>>
大数据经典学习路线(转载)
查看>>
大数据学习方向
查看>>
大数据职位所需的数据场技能
查看>>
新一代大数据技术架构
查看>>
目前围绕Hadoop体系的大数据架构,主要有哪几种,有什么优缺点?
查看>>