[Linux内核驱动]ioctl

ioctl()命令

更多详细内容在我的github

ioctl 是设备驱动程序中设备控制接口函数,一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,在一些需要细分的情境下,如果需要扩展新的功能,通常以增设 ioctl() 命令的方式实现。

Linux建议以下面的格式定义ioctl()命令:

设备类型 序列号 方向 数据尺寸
8位 8位 2位 13/14位
  • 设别类型(type):是一个幻数,可以是0-0xff中的任意值。内核中的ioctl-number.txt给出了已经被使用的幻数。
  • 序列号(nr):可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增
  • 方向(dir):数据传送的方向,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据
  • 数据尺寸(size):占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度

在内核中,我们可以使用下面几个宏来定义 ioctl 命令:

c 复制代码
// 分别对应不同的访问模式
#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)))

在ioctl()函数的实现中,可以使用下面的宏对命令进行检查:

c 复制代码
_IOC_TYPE(unsigned int cmd)     // 获取幻数
_IOC_NR(unsigned int cmd)       // 获取序列号
_IOC_DIR(unsigned int cmd)      // 获取命令方向

代码

ioctl.c

c 复制代码
/*
 * @Date: 2024-05-07 15:45:31
 * @author: lidonghang-02 2426971102@qq.com
 * @LastEditTime: 2024-05-29 16:21:05
 */
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/device.h>

#include <linux/semaphore.h>
#include <linux/mutex.h>

#include "ioctl.h"

#define LOCK_USE 1

#define IOCTL_SIZE 0x1000

#define IOCTL_MAJOR 0
#define IOCTL_MINOT 0
#define IOCTL_NR_DEVS 1

static int ioctl_major = IOCTL_MAJOR;
static int ioctl_minor = IOCTL_MINOT;
static int ioctl_nr_devs = IOCTL_NR_DEVS;

struct ioctl_dev
{
  struct cdev cdev;
  struct device *class_dev;
  unsigned int len;
  unsigned char buf[IOCTL_SIZE];

  struct semaphore sema;
  struct mutex mutex;
};
static struct class *ioctl_cls;
static struct ioctl_dev *ioctl_devp;

static int ioctl_open_func(struct inode *inode, struct file *filp)
{
  struct ioctl_dev *dev = container_of(inode->i_cdev, struct ioctl_dev, cdev);
  filp->private_data = dev;
  printk(KERN_INFO "ioctl_open\n");
  return 0;
}

static int ioctl_release_func(struct inode *inode, struct file *filp)
{
  printk(KERN_INFO "ioctl_release\n");
  return 0;
}

static ssize_t ioctl_read_func(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
  struct ioctl_dev *dev = filp->private_data;
  int ret = 0;
  if (*f_pos >= dev->len)
  {
    ret = -ENOMEM;
    printk(KERN_INFO "read beyond end of device\n");
    goto out_err_1;
  }
  if (count > dev->len - *f_pos)
    count = dev->len - *f_pos;

#if (LOCK_USE == 0)
  if (down_interruptible(&dev->sema))
    return -ERESTARTSYS;
#endif
#if (LOCK_USE == 1)
  if (mutex_lock_interruptible(&dev->mutex))
    return -ERESTARTSYS;
#endif

  if (copy_to_user(buf, dev->buf + *f_pos, count))
    ret = -EFAULT;
  else
  {
    *f_pos += count;
    ret = count;
  }

#if (LOCK_USE == 0)
  up(&dev->sema);
#endif
#if (LOCK_USE == 1)
  mutex_unlock(&dev->mutex);
#endif

out_err_1:
  return ret;
}

static ssize_t ioctl_write_func(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
  struct ioctl_dev *dev = filp->private_data;
  int ret = 0;

  if (*f_pos >= dev->len)
  {
    printk(KERN_INFO "write beyond end of device\n");
    ret = -ENOMEM;
    goto out_err_1;
  }
  if (count > dev->len - *f_pos)
    count = dev->len - *f_pos;

#if (LOCK_USE == 0)
  if (down_interruptible(&dev->sema))
    return -ERESTARTSYS;
#endif
#if (LOCK_USE == 1)
  if (mutex_lock_interruptible(&dev->mutex))
    return -ERESTARTSYS;
#endif

  if (copy_from_user(dev->buf + *f_pos, buf, count))
    ret = -EFAULT;
  else
  {
    *f_pos += count;
    ret = count;
  }

#if (LOCK_USE == 0)
  up(&dev->sema);
#endif
#if (LOCK_USE == 1)
  mutex_unlock(&dev->mutex);

#endif

out_err_1:
  return ret;
}

static long ioctl_ioctl_func(struct file *filp, unsigned int cmd, unsigned long arg)
{
  struct ioctl_dev *dev = filp->private_data;
  int ret = 0;
  unsigned int tmp = 0;

  // 检查幻数(返回值POSIX标准规定,也用-EINVAL)
  if (_IOC_TYPE(cmd) != IOCTL_CHR_MAGIC)
    return -ENOTTY;
  // 检查命令编号
  if (_IOC_NR(cmd) > IOCTL_MAXNR)
    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 IOCTL_CLEAN:
    memset(dev->buf, 0, dev->len);
    printk(KERN_INFO "clean ioctl_dev\n");
    break;
  case IOCTL_GET_LEN:
    if (put_user(dev->len, (unsigned int __user *)arg))
      ret = -EFAULT;
    printk(KERN_INFO "get ioctl_dev len %u\n", dev->len);
    break;
  case IOCTL_SET_LEN:
    if (!capable(CAP_SYS_ADMIN))
      return -EPERM;
    if (get_user(tmp, (unsigned int __user *)arg))
    {
      printk(KERN_INFO "get_user failed\n");
      ret = -EFAULT;
    }
    else
    {
      if (tmp > IOCTL_SIZE)
        tmp = IOCTL_SIZE;
      dev->len = tmp;
    }
    printk(KERN_INFO "set ioctl_dev len to %u\n", dev->len);
    break;
  default:
    return -ENOTTY;
  }

  return ret;
}

struct file_operations ioctl_fops = {
    .owner = THIS_MODULE,
    .open = ioctl_open_func,
    .release = ioctl_release_func,
    .read = ioctl_read_func,
    .write = ioctl_write_func,
    .unlocked_ioctl = ioctl_ioctl_func,
};

static int __init ioctl_init_module(void)
{
  int ret = 0;
  dev_t devno = MKDEV(ioctl_major, 0);
  int i;

  // 注册设备号
  if (ioctl_major)
    ret = register_chrdev_region(devno, ioctl_nr_devs, "ioctl");
  else
  {
    ret = alloc_chrdev_region(&devno, 0, ioctl_nr_devs, "ioctl");
    ioctl_major = MAJOR(devno);
  }

  if (ret < 0)
    return ret;

  ioctl_devp = kzalloc(sizeof(struct ioctl_dev) * ioctl_nr_devs, GFP_KERNEL);
  if (!ioctl_devp)
  {
    printk(KERN_WARNING "alloc mem failed");
    ret = -ENOMEM;
    goto out_err_1;
  }
  // 创建一个类
  ioctl_cls = class_create(THIS_MODULE, "ioctl");
  if (IS_ERR(ioctl_cls))
  {
    printk(KERN_WARNING "Error creating class for ioctl");
    goto out_err_2;
  }

  for (i = 0; i < ioctl_nr_devs; i++)
  {
    cdev_init(&ioctl_devp[i].cdev, &ioctl_fops);
    ioctl_devp[i].cdev.owner = THIS_MODULE;
    ret = cdev_add(&ioctl_devp[i].cdev, MKDEV(ioctl_major, i), 1);
    if (ret)
      printk(KERN_WARNING "fail add hc_dev%d", i);
    else
    {
      ioctl_devp[i].len = 0;
      ioctl_devp[i].class_dev = device_create(ioctl_cls, NULL, MKDEV(ioctl_major, i), NULL, "ioctl%d", i);
      if (IS_ERR(ioctl_devp[i].class_dev))
      {
        printk(KERN_NOTICE "Error creating device for ioctl%d", i);
      }
    }
#if (LOCK_USE == 0)
    sema_init(&ioctl_devp[i].sema, 1); // 初始化信号量
#elif (LOCK_USE == 1)
    mutex_init(&ioctl_devp[i].mutex); // 初始化互斥量
#endif
  }

  printk(KERN_INFO "ioctl module init\n");
  return 0;
out_err_2:
  kfree(ioctl_devp);
out_err_1:
  unregister_chrdev_region(devno, ioctl_nr_devs);
  return ret;
}

static void __exit ioctl_exit_module(void)
{
  int i;
  for (i = 0; i < ioctl_nr_devs; i++)
  {
    device_destroy(ioctl_cls, MKDEV(ioctl_major, i));
    cdev_del(&ioctl_devp[i].cdev);
  }
  class_destroy(ioctl_cls);

  kfree(ioctl_devp);
  unregister_chrdev_region(MKDEV(ioctl_major, 0), ioctl_nr_devs);
  printk(KERN_INFO "ioctl module exit\n");
}

module_param(ioctl_major, int, S_IRUGO);
module_param(ioctl_minor, int, S_IRUGO);
module_param(ioctl_nr_devs, int, S_IRUGO);

module_init(ioctl_init_module);
module_exit(ioctl_exit_module);

MODULE_AUTHOR("lidonghang-02");
MODULE_LICENSE("GPL");

ioctl.h

c 复制代码
#ifndef _IOCTL_CHR_H_
#define _IOCTL_CHR_H_

#define IOCTL_CHR_MAGIC 'c'

#define IOCTL_CLEAN _IO(IOCTL_CHR_MAGIC, 0)
#define IOCTL_GET_LEN _IOR(IOCTL_CHR_MAGIC, 1, unsigned int)
#define IOCTL_SET_LEN _IOW(IOCTL_CHR_MAGIC, 2, unsigned int)

#define IOCTL_MAXNR 2

#endif // _IOCTL_CHR_H_

test.c

c 复制代码
/*
 * @Date: 2024-05-07 16:54:27
 * @author: lidonghang-02 2426971102@qq.com
 * @LastEditTime: 2024-05-19 19:54:51
 */
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <errno.h>
#include "ioctl.h"
int main(int argc, char* argv[])
{
  unsigned int n;
  int fd;
  fd = open("/dev/ioctl0", O_RDWR);
  switch (argv[1][0])
  {
  case '0':
    ioctl(fd, IOCTL_CLEAN);
    printf("rclean mem\n");
    break;
  case '1':
    ioctl(fd, IOCTL_GET_LEN, &n);
    printf("get lens value = %u\n", n);
    break;
  case '2':
    n = atoi(argv[2]);
    ioctl(fd, IOCTL_SET_LEN, &n);
    printf("set lens value = %u\n", n);
    break;
  }
  close(fd);

  return 0;
}
相关推荐
LIZHUOLONG18 分钟前
linux 设备初始化
linux·运维·服务器
雪霁清寒20 分钟前
麒麟V10用MobaXterm远程连接SSH偶尔卡顿的问题
linux·ssh
ylscode23 分钟前
Linux CIFSwitch 内核新漏洞允许攻击者获得 root 权限
linux·运维·服务器
芯岭技术郦1 小时前
集成 2.4G 射频收发器、MCU 及丰富外设的XL2417D透传模组
单片机·嵌入式硬件
诸葛务农1 小时前
共沸脱水技术及其在光刻胶用PGMEA纯化中的应用(中)
linux·数据库·人工智能
lld9510272 小时前
(二)从验证到执行:策略实时运行全链路
linux·服务器·数据库
zlinear数据采集卡2 小时前
定时器电路深度解析:从经典555到STM32定时器,从ZLinear采集卡的工程化设计实战
stm32·单片机·嵌入式硬件·fpga开发·自动化
坤昱2 小时前
cfs调度类深入解刨——最新内核细节分析5
linux·分布式·cfs调度·eevdf调度·linux调度·linux技术·kernel最新版本内容
阿洛学长2 小时前
Kali Linux 虚拟机安装(VMware Workstation 17)
java·linux·服务器
H Journey2 小时前
source命令、.bashrc 、.bash_profile、/etc/profile配置文件详解
linux·.bashrc