IOCTL Linux设备驱动

150

请问有人能帮我解释一下,

  1. IOCTL是什么?
  2. 它用来做什么?
  3. 我怎样才能使用它?
  4. 为什么不能定义一个新函数完成与IOCTL相同的工作?
2个回答

202

ioctl 函数对于实现设备驱动程序以设置设备配置非常有用。例如,具有配置选项以检查和设置字体系列、字体大小等的打印机。 ioctl 可用于获取当前字体以及将字体设置为新字体。用户应用程序使用 ioctl 向打印机发送代码,告诉它返回当前字体或将字体设置为新字体。

int ioctl(int fd, int request, ...)
  1. fd是文件描述符,由open返回;
  2. request是请求代码。例如,GETFONT将从打印机获取当前字体,SETFONT将在打印机上设置字体;
  3. 第三个参数是void *。根据第二个参数,第三个参数可能存在也可能不存在,例如,如果第二个参数是SETFONT,第三个参数可以是字体名称,例如"Arial"

int request不仅仅是一个宏。用户应用程序需要生成请求代码,设备驱动程序模块需要确定必须使用哪种配置来操作设备。应用程序使用ioctl发送请求代码,然后在设备驱动程序模块中使用请求代码确定要执行的操作。

请求代码有4个主要部分

    1. A Magic number - 8 bits
    2. A sequence number - 8 bits
    3. Argument type (typically 14 bits), if any.
    4. Direction of data transfer (2 bits).  
如果请求代码是SETFONT用于在打印机上设置字体,则数据传输的方向将从用户应用程序到设备驱动程序模块(用户应用程序向打印机发送字体名称"Arial")。
如果请求代码是GETFONT,则方向是从打印机到用户应用程序。
为了生成请求代码,Linux提供了一些预定义的函数宏。
1. _IO(MAGIC,SEQ_NO)都是8位,0到255,例如我们想要暂停打印机。这不需要数据传输。因此,我们将生成以下请求代码。
#define PRIN_MAGIC 'P'
#define NUM 0
#define PAUSE_PRIN __IO(PRIN_MAGIC, NUM) 

现在使用 ioctl

ret_val = ioctl(fd, PAUSE_PRIN);

驱动程序模块中对应的系统调用将接收该代码并暂停打印机。

  1. __IOW(MAGIC,SEQ_NO,TYPE) MAGICSEQ_NO与上文相同,TYPE指定下一个参数的类型,回想一下ioctl的第三个参数是void *。在__IOW中,W表示数据流从用户应用程序到驱动程序模块。例如,假设我们要将打印机字体设置为"Arial"
#define PRIN_MAGIC 'S'
#define SEQ_NO 1
#define SETFONT __IOW(PRIN_MAGIC, SEQ_NO, unsigned long)

此外,

char *font = "Arial";
ret_val = ioctl(fd, SETFONT, font); 

现在font是一个指针,这意味着它是一个地址,最好表示为unsigned long,因此_IOW的第三部分提到了类型。此外,字体的这个地址以unsigned long的形式传递到相应的系统调用中,并且我们需要在使用之前将其转换为适当的类型。内核空间可以访问用户空间,因此这样可以工作。其他两个类似于函数的宏是__IOR(MAGIC, SEQ_NO, TYPE)__IORW(MAGIC, SEQ_NO, TYPE),数据流将分别从内核空间流向用户空间和双向流动。

请让我知道这是否有所帮助!


1
我想知道上面的__IOW,__IOR和__IORW函数是否正确(我的意思是有些情况下有双下划线,有些情况下没有。我从未使用过双下划线)...感谢清晰的解释! - jcoppens
讲解得非常清楚,谢谢!您能否提供一个使用此ioctl的驱动程序端的小代码片段? - Aadishri
1
请参考 https://opensourceforu.com/2011/08/io-control-in-linux/ 以获取示例。 - chandola
讲解得非常清楚。谢谢。我认为应该是_IOWR而不是_IORW。 - mms

109

“ioctl”指“输入/输出控制”,是一种特定于设备的系统调用。在Linux中只有很少的系统调用(300-400个),这些调用并不能表达设备可能具有的所有独特功能。因此,驱动程序可以定义一个ioctl,允许用户空间应用程序向其发送命令。然而,ioctls不太灵活,并且容易变得混乱(存在许多“魔术数字”,它们要么有效,要么无效),并且也可能存在安全问题,因为你将缓冲区传递到内核中,处理不当会容易出现问题。

另一种替代方法是使用“sysfs”接口,在/sys/下设置一个文件,并读/写该文件以从驱动程序中获取信息。以下是如何设置的示例:

static ssize_t mydrvr_version_show(struct device *dev,
        struct device_attribute *attr, char *buf)
{
    return sprintf(buf, "%s\n", DRIVER_RELEASE);
}

static DEVICE_ATTR(version, S_IRUGO, mydrvr_version_show, NULL);

在驱动程序安装期间:

device_create_file(dev, &dev_attr_version);

例如,您会在设备的 /sys/ 目录中拥有一个文件,例如块驱动程序的 /sys/block/myblk/version

另一种更常用的方法是使用 netlink,这是一种 IPC(进程间通信)方法,可通过 BSD 套接字接口与驱动程序通信。例如,WiFi 驱动程序就使用了它。然后,您可以使用 libnllibnl3 库从用户空间与其通信。


3
这个答案在某种程度上回答了这个问题。 - Vishal Sahu
sysfs 接口曾经使得访问更加便捷,但现在已被弃用。 - user5395338
@Seamus 仅适用于GPIO,据我所知,许多其他驱动程序使用sysfs。 - Inductiveload
对的,“deprecated”有几个意思。它实际上是一个相当模糊的词。而GPIO的deprecated sysfs就是一个典型例子:它仍然在GPIO中得到积极使用,这也是我评论的重点:GPIO sysfs是一个deprecated API,但它仍然以合理的方式工作 - 而不像libgpiod那样奇怪。 - user5395338
没错,但是 sysfs 的用途远不止于 GPIO(尽管在像树莓派这样的嵌入式系统中,这可能是用户主要接触的内核驱动程序之一)。例如,我认为 /sys/block 不会被移除。 - Inductiveload

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接