Linux驱动IO篇——ioctl设备操作

简介: Linux驱动IO篇——ioctl设备操作

应用程序如果想要设置/获取驱动层的数据,一般是驱动提供一个ioclt接口,然后应用层调用。因此,学会在驱动中实现ioctl接口是必要的一项技能。

ioctl命令编码规则

想要定义一个自己的ioctl命令,必须要遵从ioctl的编码规则。

一个ioctl命令由32比特位表示,每个比特位都有不同的含义,不同版本的内核定义可能有些差异,具体参考文档“Documentation/ioctl/ioctl-deconding.txt”.

比特位 含义
31-30 00 - 命令不带参数
01 - 命令需要把数据写入驱动,写方向
10 - 命令需要从驱动中获取数据,读方向
11 - 命令既要写入数据又要获取数据,读写方向
29-16 如果命令带参数,则指定参数所占用的内存空间大小
15-8 每个驱动全局唯一的幻数(魔数)
7-0 命令码

在内核include/uapi/asm-generic/ioctl.h头文件中提供了一组宏来定义、提取命令中的字符信息:

#define _IOC(dir,type,nr,size) \
 (((dir)  << _IOC_DIRSHIFT) | \
  ((type) << _IOC_TYPESHIFT) | \
  ((nr)   << _IOC_NRSHIFT) | \
  ((size) << _IOC_SIZESHIFT))
#ifndef __KERNEL__
#define _IOC_TYPECHECK(t) (sizeof(t))
#endif
/* 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)))
#define _IOR_BAD(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW_BAD(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR_BAD(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
/* 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)

构造ioctl命令

  • _IO(type,nr):用于构造无参数的命令号
  • _IOR(type,nr,datetype):用于构造从驱动程序中读取数据的命令号
  • _IOW(type,nr,datatype):用于构造向驱动程序写入数据的命令号
  • _IORW(type,nr,datatype):用于构造双向传输的命令号

解析ioctl命令

  • _IOC_DIR(cmd):获得传输方向位段的值
  • _IOC_TYPE(cmd):获得类型的值
  • _IOC_NR(cmd):获得编号的值
  • _IOC_SIZE(cmd):获得大小的值

具体示例

向驱动写入数据,假设定义幻数为'a',命令码为0,传入的数据为结构体struct option

#define VS_MAGIC 's'//幻数
#define VS_SET_FFMT _IOW(VS_MAGIC, 0, struct option)

从驱动获得数据,将命令码修改为1,_IOW换成_IOR:

#define VS_MAGIC 's'//幻数
#define VS_GET_FFMT _IOR(VS_MAGIC, 1, struct option)

这里要注意,理想情况下定义的幻数在一种体系结构下应该是全局唯一的,虽然很难做到,但我们还是应该遵从内核所规定的这种定义形式。

ioctl系统调用过程

ioctl本质是一个系统调用,在应用层使用ioctl函数时,会使得系统从用户态trap到内核态,即调用到内核态的sys_ioctl函数。

sys_ioctl函数的定义位于内核源码fs/ioctl.c中:

SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
{
 int error;
 struct fd f = fdget(fd);
 if (!f.file)
  return -EBADF;
 error = security_file_ioctl(f.file, cmd, arg);
 if (!error)
  error = do_vfs_ioctl(f.file, fd, cmd, arg);
 fdput(f);
 return error;
}

SYSCALL_DEFINE3是一个系统调用宏,3代表这个系统调用需要3个参数,具体的SYSCALL_DEFINE宏定义可以参考include/linux/syscalls.h。这里不对系统调用展开讨论,只需要知道ioctl是一个系统调用就可以了。

展开之后,这个函数名字其实就是sys_ioctlsys_ioctl函数首先调用了security_file_ioctl,然后调用了do_vfs_ioctl

do_vfs_ioctl函数是需要关注的,定义如下:

int do_vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd,
      unsigned long arg)
{
 int error = 0;
 int __user *argp = (int __user *)arg;
 struct inode *inode = file_inode(filp);
 switch (cmd) {
 case FIOCLEX:
  set_close_on_exec(fd, 1);
  break;
 case FIONCLEX:
  set_close_on_exec(fd, 0);
  break;
 case FIONBIO:
  error = ioctl_fionbio(filp, argp);
  break;
 case FIOASYNC:
  error = ioctl_fioasync(fd, filp, argp);
  break;
 case FIOQSIZE:
  if (S_ISDIR(inode->i_mode) || S_ISREG(inode->i_mode) ||
      S_ISLNK(inode->i_mode)) {
   loff_t res = inode_get_bytes(inode);
   error = copy_to_user(argp, &res, sizeof(res)) ?
     -EFAULT : 0;
  } else
   error = -ENOTTY;
  break;
 case FIFREEZE:
  error = ioctl_fsfreeze(filp);
  break;
 case FITHAW:
  error = ioctl_fsthaw(filp);
  break;
 case FS_IOC_FIEMAP:
  return ioctl_fiemap(filp, arg);
 case FIGETBSZ:
  return put_user(inode->i_sb->s_blocksize, argp);
 case FICLONE:
  return ioctl_file_clone(filp, arg, 0, 0, 0);
 case FICLONERANGE:
  return ioctl_file_clone_range(filp, argp);
 case FIDEDUPERANGE:
  return ioctl_file_dedupe_range(filp, argp);
 default:
  if (S_ISREG(inode->i_mode))
   error = file_ioctl(filp, cmd, arg);
  else
   error = vfs_ioctl(filp, cmd, arg);
  break;
 }
 return error;
}

可以看到,do_vfs_ioctl函数会对一些特殊的命令进行处理,因此,我们在定义自己的ioctl命令时,要避免和这些已有的命令冲突

只有非特殊命令的情况,才会进入switch的default,vfs_ioctl函数最终会调用驱动中实现的unlocked_ioctl函数:

long vfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
 int error = -ENOTTY;
 if (!filp->f_op->unlocked_ioctl)
  goto out;
 error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
 if (error == -ENOIOCTLCMD)
  error = -ENOTTY;
 out:
 return error;
}

驱动层实现

在驱动中,我们只需要实现struct file_operations结构体里的unlocked_ioctl函数即可,这个函数用于处理ioctl命令,基本结构如下:

static long vser_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    switch(cmd) {
        case VS_SET_BAUD:
             vsdev.baud = arg;
             break;
        case VS_SET_FFMT:
             if (copy_from_user(&vsdev.opt, (struct option __user *)arg, sizeof(struct option)))
                    return -EFAULT; 
             break;
        default:
             break;
 };
 return 0;
}
static struct file_operations vser_ops = {
 .owner = THIS_MODULE,
    .unlocked_ioctl = vser_ioctl,
};

cmd就是传入ioctl命令,一般在unlocked_ioctl函数的实现中,通过switch语句判断不同ioctl命令。第三个参数arg代表输入的数据,如果传入的是一个指针,可以对arg进行强制类型转换

vser.c

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/device.h>
static struct class *class;
struct option{
 unsigned int datab;
    unsigned int parity;
    unsigned int stopb;
};
#define VS_MAGIC 's'
#define VS_SET_BAUD _IOW(VS_MAGIC, 0, unsigned int)
#define VS_GET_BAUD _IOR(VS_MAGIC, 1, unsigned int)
#define VS_SET_FFMT _IOW(VS_MAGIC, 2, struct option)
#define VS_GET_FFMT _IOR(VS_MAGIC, 3, struct option)
#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 1
#define VSER_DEV_NAME "vser"
struct vser_dev{
 unsigned int baud;
    struct option opt;
    struct cdev cdev;
};
DEFINE_KFIFO(vsfifo, char, 32);
static struct vser_dev vsdev;
static int vser_open(struct inode *inode, struct file *filp)
{
 return 0;
}
static int vser_release(struct inode *inode, struct file *flip)
{
 return 0;
}
static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
 int ret;
    unsigned int copied = 0;
    ret = kfifo_to_user(&vsfifo, buf, count, &copied);
    return ret == 0 ? copied : ret;
}
static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
 int ret;
    unsigned int copied = 0;
    ret = kfifo_from_user(&vsfifo, buf, count, &copied);
    return ret == 0? copied : ret;
}
static long vser_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    if (_IOC_TYPE(cmd) != VS_MAGIC)
        return -ENOTTY;
    switch(cmd) {
        case VS_SET_BAUD:
             vsdev.baud = arg;
             break;
        case VS_GET_BAUD:
             arg = vsdev.baud;
             break;
        case VS_SET_FFMT:
             if (copy_from_user(&vsdev.opt, (struct option __user *)arg, sizeof(struct option)))
                    return -EFAULT; 
             break;
        case VS_GET_FFMT:
             if (copy_to_user((struct option __user *)arg, &vsdev.opt, sizeof(struct option)))
     return -EFAULT;
             break;
        default:
             break;
 };
 return 0;
}
static struct file_operations vser_ops = {
 .owner = THIS_MODULE,
    .open = vser_open,
    .release = vser_release,
    .read = vser_read,
    .write = vser_write,
    .unlocked_ioctl = vser_ioctl,
};
static int __init vser_init(void)
{
 int ret;
    dev_t dev;
    dev = MKDEV(VSER_MAJOR, VSER_MINOR);
    ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
    if (ret)
         goto reg_err;
    cdev_init(&vsdev.cdev, &vser_ops);
    vsdev.cdev.owner = THIS_MODULE;
    vsdev.baud = 115200;
    vsdev.opt.datab = 8;
    vsdev.opt.parity = 0;
    vsdev.opt.stopb = 1;
    ret = cdev_add(&vsdev.cdev, dev, VSER_DEV_CNT);
    if (ret)
        goto add_err;
    /* 自动创建设备节点 */
    class = class_create(THIS_MODULE, "my_ioctl");  /* /sys/class/my_ioctl */
 device_create(class, NULL, dev, NULL, "vser0"); /* /dev/vser0 */
    return 0;
add_err:
    unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:
    return ret;
}
static void __exit vser_exit(void)
{
 dev_t dev;
 dev = MKDEV(VSER_MAJOR, VSER_MINOR);
    cdev_del(&vsdev.cdev);
    device_destroy(class, dev);
 class_destroy(class);
    unregister_chrdev_region(dev, VSER_DEV_CNT);
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");

应用层示例

test.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
struct option{
 unsigned int datab;
    unsigned int parity;
    unsigned int stopb;
};
#define VS_MAGIC 's'
#define VS_SET_BAUD _IOW(VS_MAGIC, 0, unsigned int)
#define VS_GET_BAUD _IOR(VS_MAGIC, 1, unsigned int)
#define VS_SET_FFMT _IOW(VS_MAGIC, 2, struct option)
#define VS_GET_FFMT _IOR(VS_MAGIC, 3, struct option)
int main(int argc, char *argv[])
{
 int fd;
    int ret;
    unsigned int baud;
    struct option opt = {8,1,1};
    fd = open("/dev/vser0", O_RDWR);
    if (fd == -1)
        goto fail;
    baud = 9600;
    ret = ioctl(fd, VS_SET_BAUD, baud);
    if (ret == -1)
        goto fail;
    ret = ioctl(fd, VS_GET_BAUD, baud);
    if (ret == -1)
        goto fail;
    ret = ioctl(fd, VS_SET_FFMT, &opt);
    if (ret == -1)
        goto fail;
 ret = ioctl(fd, VS_GET_FFMT, &opt);
    if (ret == -1)
        goto fail;
    printf("baud rate:%d\n", baud);
    printf("frame format: %d%d%d\n", opt.datab, opt.parity, opt.stopb);
 close(fd);
    exit(EXIT_SUCCESS);
fail:
     perror("ioctl test");
     exit(EXIT_FAILURE);
}

运行结果:

end

猜你喜欢

一文搞懂内核模块依赖

不敲一行代码,实现Linux下的LED驱动!

一个Linux驱动工程师必知的内核模块知识

Linux内核中常用的数据结构和算法

Linux内核中常用的C语言技巧

Linux内核基础篇——常用调试技巧汇总

Linux内核基础篇——动态输出调试

Linux内核基础篇——printk调试

Linux内核基础篇——initcall

相关文章
|
7月前
|
安全 Linux 网络安全
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
201 0
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
|
8月前
|
数据采集 编解码 运维
一文讲完说懂 WowKey -- WowKey 是一款 Linux 类设备的命令行(CLT)运维工具
WowKey 是一款面向 Linux 类设备的命令行运维工具,支持自动登录、批量执行及标准化维护,适用于企业、团队或个人管理多台设备,显著提升运维效率与质量。
|
9月前
|
监控 Linux 开发者
理解Linux操作系统内核中物理设备驱动(phy driver)的功能。
综合来看,物理设备驱动在Linux系统中的作用是至关重要的,它通过与硬件设备的紧密配合,为上层应用提供稳定可靠的通信基础设施。开发一款优秀的物理设备驱动需要开发者具备深厚的硬件知识、熟练的编程技能以及对Linux内核架构的深入理解,以确保驱动程序能在不同的硬件平台和网络条件下都能提供最优的性能。
486 0
|
11月前
|
安全 Ubuntu Linux
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
452 0
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
608 34
|
运维 安全 Linux
试试Linux设备命令行运维工具——Wowkey
WowKey 是一款专为 Linux 设备设计的命令行运维工具,提供自动化、批量化、标准化、简单化的运维解决方案。它简单易用、高效集成且无依赖,仅需 WIS 指令剧本文件、APT 账号密码文件和 wowkey 命令即可操作。通过分离鉴权内容与执行内容,WowKey 让运维人员专注于决策,摆脱繁琐的交互与执行细节工作,大幅提升运维效率与质量。无论是健康检查、数据采集还是配置更新,WowKey 都能助您轻松应对大规模设备运维挑战。立即从官方资源了解更多信息:https://atsight.top/training。
|
数据采集 运维 安全
Linux设备命令行运维工具WowKey问答
WowKey 是一款用于 Linux 设备运维的工具,可通过命令行手动或自动执行指令剧本,实现批量、标准化操作,如健康检查、数据采集、配置更新等。它简单易用,只需编写 WIS 指令剧本和 APT 帐号密码表文件,学习成本极低。支持不同流派的 Linux 系统,如 RHEL、Debian、SUSE 等,只要使用通用 Shell 命令即可通吃Linux设备。
|
SQL druid Java
【YashanDB知识库】YashanDB JDBC驱动查询时抛出io fail:Read timed out异常
【YashanDB知识库】YashanDB JDBC驱动查询时抛出io fail:Read timed out异常
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
SQL druid Java
【YashanDB知识库】YashanDB JDBC驱动查询时抛出io fail:Read timed out异常
【YashanDB知识库】YashanDB JDBC驱动查询时抛出io fail:Read timed out异常

热门文章

最新文章