Linux设备驱动系列(八)——ioctl系统调用

2,010 阅读4分钟

关注微信公众号:Linux内核拾遗

文章来源:mp.weixin.qq.com/s/lbhCd6gri…

Linux内核将虚拟内存空间划分成相互隔离的两个部分:内核空间和用户空间。内核空间只能用于运行内核代码、内核扩展以及大部分的设备驱动程序。相反,用户空间的内存可以被所有运行的用户态程序使用,并且必要的时候用户空间的内存会被交换到磁盘中。

为了实现内核空间和用户空间之间的通信,Linux提供了ioctl、procfs、sysfs、configfs、debugfs、sysctl、UDP sockets以及Netlink sockets等通信机制。本文将重点介绍基于ioctl系统调用的通信机制。

1 ioctl介绍

IOCTL(Input and Output Contol)全称”输入和输出控制“,它通常用来与设备驱动程序通信。

系统调用作为通用的接口,不太可能满足所有的设备操作需求,这时候可以通过实现设备驱动的ioctl系统调用,处理一些系统调用没有实现的设备特定操作。

2 ioctl实现步骤

下面介绍一下,如果要在Linux设备驱动程序中实现一个ioctl系统调用,通常要涉及的步骤。

2.1 设备驱动创建ioctl命令

"linux/ioctl.h"头文件中提供了创建ioctl命令的宏:

#define "ioctl name" __IOX("magic number","command number","argument type")

其中IOX可以是"IO"、"IOW"、"IOR"和"IOWR",分别表示ioctl命令不带参数、携带写入参数、携带读取参数以及同时携带读写参数。

Magic number是新创建ioctl命令集合的唯一标识,用于区分其他的ioctl命令,通常设置被设备的主设备号,而command number则是表示当前ioctl命令在命令集合中的唯一标识。

例如:

#include <linux/ioctl.h>

#define WR_VALUE _IOW(1, 1, int32_t *)
#define RD_VALUE _IOR(1, 2, int32_t *)

2.2 设备驱动实现ioctl函数

接下来就是实现设备驱动的ioctl调用,其函数原型如下:

int  ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);

其中cmd表示当前ioctl调用接收到的ioctl命令,即前面创建的ioctl命令。

ioctl调用函数需要实现前面创建的所有ioctl命令,通常使用switch来处理,最后将实现好的ioctl函数设置到设备文件的fops->unlocked_ioctl上。

static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
  switch(cmd) {
    case WR_VALUE:
      return copy_from_user(&value ,(int32_t*) arg, sizeof(value));
    case RD_VALUE:
      return copy_to_user((int32_t*) arg, &value, sizeof(value));
  }
  return 0;
}

static struct file_operations fops =
{
  .owner          = THIS_MODULE,
  ...
  .unlocked_ioctl = my_ioctl,
};

2.3 用户程序创建ioctl命令

用户程序创建ioctl命令和设备驱动中的类似:

#define WR_VALUE _IOW(1, 1, int32_t *)
#define RD_VALUE _IOR(1, 2, int32_t *)

2.4 用户程序使用ioctl系统调用

"sys/ioctl.h"头文件中提供了ioctl()系统调用函数,可以通过它向设备驱动发送ioctl命令:

long ioctl(fd, cmd, args);

例如:

ioctl(fd, WR_VALUE, (int32_t *)&value); 
ioctl(fd, RD_VALUE, (int32_t *)&value);

3 设备驱动ioctl示例

说明一下:随着知识点的深入,后面的示例代码行数会越来越多,为了节省篇幅突出重点,从本文开始,后续的示例代码将简化异常路径的处理以及其他的一些次要代码,但这种做法在实际代码开发中是非常不推荐的。

3.1 设备驱动代码

kernle_driver.c

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/ioctl.h>
#include <linux/err.h>

#define WR_VALUE _IOW(1, 1, int32_t *)
#define RD_VALUE _IOR(1, 2, int32_t *)

int32_t value = 0;

dev_t dev = 0;
static struct class *dev_class;
static struct cdev my_cdev;

static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    switch (cmd)
    {
    case WR_VALUE:
        return copy_from_user(&value, (int32_t *)arg, sizeof(value));
    case RD_VALUE:
        return copy_to_user((int32_t *)arg, &value, sizeof(value));
    }
    return 0;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = my_ioctl,
};

static int __init my_driver_init(void)
{
    if ((alloc_chrdev_region(&dev, 0, 1, "my_dev")) < 0)
        return -1;

    cdev_init(&my_cdev, &fops);

    if ((cdev_add(&my_cdev, dev, 1)) < 0)
        goto r_class;

    if (IS_ERR(dev_class = class_create(THIS_MODULE, "my_class")))
        goto r_class;

    if (IS_ERR(device_create(dev_class, NULL, dev, NULL, "my_device")))
        goto r_device;

    return 0;

r_device:
    class_destroy(dev_class);
r_class:
    unregister_chrdev_region(dev, 1);
    return -1;
}

static void __exit my_driver_exit(void)
{
    device_destroy(dev_class, dev);
    class_destroy(dev_class);
    cdev_del(&my_cdev);
    unregister_chrdev_region(dev, 1);
}

module_init(my_driver_init);
module_exit(my_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("feifei <feifei@gmail.com>");
MODULE_DESCRIPTION("Simple Linux device driver");
MODULE_VERSION("1.5");

3.2 用户程序代码

user_prog.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>

#define WR_VALUE _IOW(1, 1, int32_t *)
#define RD_VALUE _IOR(1, 2, int32_t *)

int main()
{
    int fd;
    int32_t value, number;

    fd = open("/dev/my_device", O_RDWR);
    if (fd < 0)
    {
        printf("Failed to open /dev/my_device\n");
        return -1;
    }

    printf("Enter the Value to send: \n");
    scanf("%d", &number);
    ioctl(fd, WR_VALUE, (int32_t *)&number);
    ioctl(fd, RD_VALUE, (int32_t *)&value);
    printf("Value read from Driver: %d\n", value);

    close(fd);
    return 0;
}

3.3 代码演示

image-20240430131954447

关注微信公众号:Linux内核拾遗

文章来源:mp.weixin.qq.com/s/lbhCd6gri…