linux ioctl函数 参数

99ANYc3cd6
预计阅读时长 20 分钟
位置: 首页 参数 正文

ioctl 的原型定义在 <sys/ioctl.h> 中:

linux ioctl函数 参数
(图片来源网络,侵删)
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);

ioctl 函数接受可变数量的参数,但核心是前三个,我们逐一分析。


参数详解

int fd (文件描述符)

  • 类型: int
  • 含义: 文件描述符,这是 ioctl 操作的目标,它必须是一个打开的文件、设备或套接字的描述符。
  • 如何获取:
    • 对于设备文件,通常使用 open() 系统调用打开,打开串口 /dev/ttyS0
      int fd = open("/dev/ttyS0", O_RDWR);
      if (fd < 0) {
          // 错误处理
      }
    • 对于文件或套接字,同样是在打开或创建后获得其文件描述符。
  • 关键点: ioctl 的操作是针对这个 fd 所代表的特定资源的,驱动程序通过这个 fd 内部关联到对应的设备实例,从而知道要对哪个设备执行操作。

unsigned long request (请求码)

  • 类型: unsigned long

  • 含义: 请求码,这是 ioctl 的灵魂,它是一个唯一的数字,告诉驱动程序你想要执行哪种具体的操作,设置波特率、获取MAC地址、清空缓冲区等。

  • 重要性: 请求码必须由驱动程序和用户空间程序预先约定好,如果用户空间程序使用了驱动程序不认识的请求码,行为是不可预测的,通常会导致系统调用返回错误 -1 并设置 errnoEINVAL (Invalid argument)。

    linux ioctl函数 参数
    (图片来源网络,侵删)
  • 请求码的构造 (魔数): 为了避免不同驱动的请求码冲突,Linux 内核采用了一套约定俗成的规则来构造请求码,它通常是一个32位的数字,被划分为几个部分:

    | 8 bits (类型) | 8 bits (序号) | 2 bits (数据方向) | 8 bits (大小) |
    • 类型 (Type / "Magic Number"): 8位,一个唯一的数字,用于标识特定的驱动,开发者需要向内核申请一个唯一的类型号,以防止与其他驱动的请求码冲突。'T' (0x54) 常用于tty设备。
    • 序号 (Number / Nr): 8位,驱动程序内部不同命令的编号,从0开始,每个命令一个序号。
    • 数据方向 (Direction / Dir): 2位,指示数据传输方向,这对于内核正确处理参数至关重要。
      • _IOC_NONE (0): 无数据传输,只是触发一个操作。
      • _IOC_READ (2): 从内核(驱动)读取数据到用户空间。
      • _IOC_WRITE (1): 从用户空间写入数据到内核(驱动)。
      • _IOC_READ | _IOC_WRITE (3): 双向传输数据。
    • 大小 (Size / Size): 14位 (在32位系统中是8位,但通常使用14位来对齐),指定与该请求关联的参数数据的大小(以字节为单位),内核会用这个值来验证用户空间缓冲区的大小,防止内存越界访问。
  • 宏定义: 内核提供了几个宏来方便地构造和解析请求码:

    • _IO(type, nr): 创建一个无数据传输的请求码。
    • _IOR(type, nr, datatype): 创建一个从内核读取数据的请求码。datatype 是参数在用户空间的数据类型,内核用它来计算大小。
    • _IOW(type, nr, datatype): 创建一个向内核写入数据的请求码。
    • _IOWR(type, nr, datatype): 创建一个双向数据传输的请求码。

    示例: 假设我们为驱动 'A' (0x41) 定义一个命令 GET_DEV_INFO (序号1),它返回一个 struct dev_info 类型的数据。

    // 在驱动程序中定义
    struct dev_info {
        int version;
        char name[32];
    };
    #define MYIOC_TYPE 'A'
    #define GET_DEV_INFO _IOR(MYIOC_TYPE, 1, struct dev_info)

    在用户空间,我们直接使用 GET_DEV_INFO 这个宏作为 request 参数。

(可变参数 - 通常是 void *arg)

  • 类型: (可变参数),但实际使用时,它几乎总是被转换为 void * (指向任意类型的指针)。
  • 含义: 参数指针,它指向了用户空间的一个缓冲区,用于向 ioctl 传递数据或从 ioctl 获取数据。
  • 如何使用:
    • 向驱动写入数据: 用户空间程序分配一个缓冲区,填入数据,然后将该缓冲区的地址作为 arg 传给 ioctl,驱动程序从该地址读取数据。
    • 从驱动读取数据: 用户空间程序分配一个缓冲区(可以先清零),然后将该缓冲区的地址作为 arg 传给 ioctl,驱动程序向该地址写入数据。
    • 无参数: 对于不需要数据传输的 request,这个参数可以传 NULL0
  • 关键点:
    • 指针是用户空间的地址: ioctl 内部会处理这个从用户空间到内核空间的地址转换,驱动程序绝不能直接解引用这个指针,而必须使用内核提供的专用函数,如 copy_from_user()copy_to_user(),以确保安全和正确性。
    • 大小匹配: 请求码中的 size 字段会与 arg 指向的缓冲区大小进行比较,如果大小不匹配,ioctl 会失败并返回 EINVAL

完整示例

假设我们有一个虚拟的字符设备 /dev/mychardev,它的驱动程序支持以下操作:

  1. DEV_RESET ('M', 0): 无参数,重置设备。
  2. DEV_SET_LED ('M', 1): 写入一个 int 值来控制LED (0=关, 1=开)。
  3. DEV_GET_STATUS ('M', 2): 读取一个 int 值获取设备状态。

驱动程序中的宏定义 (概念性)

// 在驱动代码中
#define MYDEV_IOC_MAGIC 'M'
#define DEV_RESET       _IO(MYDEV_IOC_MAGIC, 0)
#define DEV_SET_LED     _IOW(MYDEV_IOC_MAGIC, 1, int)
#define DEV_GET_STATUS  _IOR(MYDEV_IOC_MAGIC, 2, int)

用户空间程序示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
// 使用与驱动程序中相同的宏定义
#define MYDEV_IOC_MAGIC 'M'
#define DEV_RESET       _IO(MYDEV_IOC_MAGIC, 0)
#define DEV_SET_LED     _IOW(MYDEV_IOC_MAGIC, 1, int)
#define DEV_GET_STATUS  _IOR(MYDEV_IOC_MAGIC, 2, int)
void print_usage(const char *prog_name) {
    printf("Usage: %s <reset|set_led <0|1>|get_status>\n", prog_name);
}
int main(int argc, char *argv[]) {
    if (argc < 2) {
        print_usage(argv[0]);
        return 1;
    }
    int fd = open("/dev/mychardev", O_RDWR);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    if (strcmp(argv[1], "reset") == 0) {
        // 调用无参数的ioctl
        if (ioctl(fd, DEV_RESET) < 0) {
            perror("ioctl DEV_RESET");
        } else {
            printf("Device reset successfully.\n");
        }
    } else if (strcmp(argv[1], "set_led") == 0) {
        if (argc < 3) {
            print_usage(argv[0]);
            close(fd);
            return 1;
        }
        int led_state = atoi(argv[2]);
        // 调用写入int参数的ioctl
        if (ioctl(fd, DEV_SET_LED, &led_state) < 0) {
            perror("ioctl DEV_SET_LED");
        } else {
            printf("LED set to %d.\n", led_state);
        }
    } else if (strcmp(argv[1], "get_status") == 0) {
        int status = 0;
        // 调用读取int参数的ioctl,传入一个缓冲区地址
        if (ioctl(fd, DEV_GET_STATUS, &status) < 0) {
            perror("ioctl DEV_GET_STATUS");
        } else {
            printf("Device status: %d\n", status);
        }
    } else {
        print_usage(argv[0]);
    }
    close(fd);
    return 0;
}

替代方案与注意事项

ioctl 的缺点

由于其设计的复杂性(请求码魔数、参数处理不安全等),ioctl 被认为是一种“反模式”,在现代 Linux 驱动开发中,有更好的替代方案:

  1. sysfs: 通过 /sys 文件系统暴露设备的属性,用户空间程序通过标准的文件读写操作(open, read, write, scanf, printf 等)来配置或查询设备,这是最推荐的方式,因为它简单、直观、符合 Unix 文件哲学。
  2. configfs: 类似于 sysfs,但提供了一种更动态的、为复杂配置设计的机制。
  3. Netlink 套接字: 专门用于内核与用户空间进行复杂的、异步的网络相关通信(如路由、防火墙规则)。
  4. /dev 上的普通文件: 对于简单的设备,直接将其映射到用户空间内存(如 mmap),或者通过读写文件描述符来传递命令和数据,比 ioctl 更清晰。
参数 类型 描述
fd int 文件描述符,指向要操作的设备或文件。
request unsigned long 请求码,唯一标识要执行的操作,由类型、序号、数据方向和大小组成,是驱动和用户空间的契约。
arg void * 指向用户空间缓冲区的指针,用于传递数据,必须使用 copy_from_user/copy_to_user 在驱动中安全访问。

尽管 ioctl 仍然广泛存在于许多遗留系统和内核子系统中(如终端、存储、网络设备),但对于新项目,应优先考虑使用 sysfs 等更现代、更安全的接口。

-- 展开阅读全文 --
头像
surface pro 2拆机教程
« 上一篇 2025-12-31
智能手机返回键失灵怎么修?
下一篇 » 2025-12-31

相关文章

取消
微信二维码
支付宝二维码

最近发表

标签列表

目录[+]