用户空间的ioctl

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

static long vfs_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
int error = -ENOTTY;

<span class="hljs-keyword">if</span> (!filp-&gt;f_op || !filp-&gt;f_op-&gt;unlocked_ioctl)           
    <span class="hljs-keyword">goto</span> <span class="hljs-keyword">out</span>;

error = filp-&gt;f_op-&gt;unlocked_ioctl(filp, cmd, arg);
<span class="hljs-keyword">if</span> (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) </span>
(((dir) << _IOC_DIRSHIFT) |
((type) << _IOC_TYPESHIFT) | </span>
((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 <linux/ioctl.h> // 内核空间
// #include <sys/ioctl.h> // 用户空间

/* 定义设备类型 */
#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 3

struct 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);

<span class="hljs-keyword">int</span> ret;
<span class="hljs-keyword">struct</span> msg my_msg;

<span class="hljs-comment">/* 检查设备类型 */</span>
<span class="hljs-keyword">if</span> (_IOC_TYPE(cmd) != IOC_MAGIC) {
    pr_err(<span class="hljs-string">"[%s] command type [%c] error!\n"</span>, \
        __func__, _IOC_TYPE(cmd));
    <span class="hljs-keyword">return</span> -ENOTTY; 
}

<span class="hljs-comment">/* 检查序数 */</span>
<span class="hljs-keyword">if</span> (_IOC_NR(cmd) &gt; IOC_MAXNR) { 
    pr_err(<span class="hljs-string">"[%s] command numer [%d] exceeded!\n"</span>, 
        __func__, _IOC_NR(cmd));
    <span class="hljs-keyword">return</span> -ENOTTY;
}    

<span class="hljs-comment">/* 检查访问模式 */</span>
<span class="hljs-keyword">if</span> (_IOC_DIR(cmd) &amp; _IOC_READ)
    ret= !access_ok(VERIFY_WRITE, (<span class="hljs-keyword">void</span> __user *)arg, \
            _IOC_SIZE(cmd));
<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (_IOC_DIR(cmd) &amp; _IOC_WRITE)
    ret= !access_ok(VERIFY_READ, (<span class="hljs-keyword">void</span> __user *)arg, \
            _IOC_SIZE(cmd));
<span class="hljs-keyword">if</span> (ret)
    <span class="hljs-keyword">return</span> -EFAULT;

<span class="hljs-keyword">switch</span>(cmd) {
<span class="hljs-comment">/* 初始化设备 */</span>
<span class="hljs-keyword">case</span> IOCINIT:
    init();
    <span class="hljs-keyword">break</span>;

<span class="hljs-comment">/* 读寄存器 */</span>
<span class="hljs-keyword">case</span> IOCGREG:
    ret = copy_from_user(&amp;msg, \
        (<span class="hljs-keyword">struct</span> msg __user *)arg, <span class="hljs-keyword">sizeof</span>(my_msg));
    <span class="hljs-keyword">if</span> (ret) 
        <span class="hljs-keyword">return</span> -EFAULT;
    msg-&gt;data = read_reg(msg-&gt;addr);
    ret = copy_to_user((<span class="hljs-keyword">struct</span> msg __user *)arg, \
            &amp;msg, <span class="hljs-keyword">sizeof</span>(my_msg));
    <span class="hljs-keyword">if</span> (ret) 
        <span class="hljs-keyword">return</span> -EFAULT;
    <span class="hljs-keyword">break</span>;

<span class="hljs-comment">/* 写寄存器 */</span>
<span class="hljs-keyword">case</span> IOCWREG:
    ret = copy_from_user(&amp;msg, \
        (<span class="hljs-keyword">struct</span> msg __user *)arg, <span class="hljs-keyword">sizeof</span>(my_msg));
    <span class="hljs-keyword">if</span> (ret) 
        <span class="hljs-keyword">return</span> -EFAULT;
    write_reg(msg-&gt;addr, msg-&gt;data);
    <span class="hljs-keyword">break</span>;

<span class="hljs-keyword">default</span>:
    <span class="hljs-keyword">return</span> -ENOTTY;
}

<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;

}

  • 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

#include “ioctl-test.h”

int main(int argc, char **argv)
{

<span class="hljs-keyword">int</span> fd;
<span class="hljs-keyword">int</span> ret;
<span class="hljs-keyword">struct</span> msg my_msg;

fd = open(<span class="hljs-string">"/dev/ioctl-test"</span>, O_RDWR);
<span class="hljs-keyword">if</span> (fd &lt; <span class="hljs-number">0</span>) {
    perror(<span class="hljs-string">"open"</span>);
    <span class="hljs-built_in">exit</span>(-<span class="hljs-number">2</span>);
}

<span class="hljs-comment">/* 初始化设备 */</span>
ret = ioctl(fd, IOCINIT);
<span class="hljs-keyword">if</span> (ret) {
    perror(<span class="hljs-string">"ioctl init:"</span>);
    <span class="hljs-built_in">exit</span>(-<span class="hljs-number">3</span>);
}

<span class="hljs-comment">/* 往寄存器0x01写入数据0xef */</span>
<span class="hljs-built_in">memset</span>(&amp;my_msg, <span class="hljs-number">0</span>, <span class="hljs-keyword">sizeof</span>(my_msg));
my_msg.addr = <span class="hljs-number">0x01</span>;
my_msg.data = <span class="hljs-number">0xef</span>;
ret = ioctl(fd, IOCWREG, &amp;my_msg);
<span class="hljs-keyword">if</span> (ret) {
    perror(<span class="hljs-string">"ioctl read:"</span>);
    <span class="hljs-built_in">exit</span>(-<span class="hljs-number">4</span>);
}

<span class="hljs-comment">/* 读寄存器0x01 */</span>
<span class="hljs-built_in">memset</span>(&amp;my_msg, <span class="hljs-number">0</span>, <span class="hljs-keyword">sizeof</span>(my_msg));
my_msg.addr = <span class="hljs-number">0x01</span>;
ret = ioctl(fd, IOCGREG, &amp;my_msg);
<span class="hljs-keyword">if</span> (ret) {
    perror(<span class="hljs-string">"ioctl write"</span>);
    <span class="hljs-built_in">exit</span>(-<span class="hljs-number">5</span>);
}
<span class="hljs-built_in">printf</span>(<span class="hljs-string">"read: %#x\n"</span>, my_msg.data);

<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;

}

  • 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
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
是的,用户空间ioctl 函数是相关的。ioctl(I/O control)函数是用于设备驱动程序与用户空间之间进行通信的一种机制。 ioctl 函数允许用户空间程序通过文件描述符(file descriptor)与设备驱动程序进行交互,发送控制命令和数据,以实现对设备的控制、配置和查询等操作。它可以用于各种设备,例如串口、磁盘、网络设备等。 ioctl 函数的原型如下: ```cpp int ioctl(int fd, unsigned long request, ...); ``` - `fd` 是一个文件描述符,表示要进行 ioctl 操作的设备或文件。 - `request` 是一个无符号长整型数,用于指定具体的 ioctl 命令。 - `...` 是可选的参数,用于传递额外的数据或者配置参数。 在用户空间中,可以通过打开设备节点(如 `/dev/xxx`)获取一个文件描述符,然后使用 ioctl 函数与设备驱动程序进行通信。用户空间程序可以发送不同的请求码(request code)给驱动程序,以达到特定的目的。 通常,在设备驱动程序中会定义一组常量作为 ioctl 命令的请求码,用户空间程序使用这些请求码来告知驱动程序执行特定的操作。驱动程序根据请求码的不同,执行相应的处理逻辑,并返回结果给用户空间。 需要注意的是,ioctl 函数的使用需要谨慎,因为它是一种较为底层的操作,对设备的具体实现和内部结构有一定的依赖。在使用 ioctl 函数时,应该根据设备的文档或相关的驱动程序接口规范来了解具体的命令和参数的使用方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值