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

news2024/9/21 4:25:43

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jcchTzIk-1681623278142)(pic/image-20230416130347390.png)]

文章目录

    • ioctl命令编码规则
    • ioctl系统调用过程
    • 驱动层实现
    • 应用层示例

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

ioctl命令编码规则


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

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

比特位含义
31-3000 - 命令不带参数
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);
}

运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8i8S3Uw8-1681623278144)(pic/image-20230416121142842.png)]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/417015.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

每天一道大厂SQL题【Day20】华泰证券真题实战(二)表转置

每天一道大厂SQL题【Day20】华泰证券真题实战(二) 大家好&#xff0c;我是Maynor。相信大家和我一样&#xff0c;都有一个大厂梦&#xff0c;作为一名资深大数据选手&#xff0c;深知SQL重要性&#xff0c;接下来我准备用100天时间&#xff0c;基于大数据岗面试中的经典SQL题&…

SpringBoot配置加载机制

目录 前言 官方文档&#xff1a;SpringBoot配置加载机制 外化配置加载顺序 application.properties默认位置 命名风格 Spring Boot 中的 ConfigurationProperties 定制PropertySource 主要步骤 切入位置 加载自定义properties文件 自定义文件&#xff08;yapf.propert…

PM866-2 3BSE050201R1未来的工业将会更加智能化。

PM866-2 3BSE050201R1未来的工业将会更加智能化。 ​ 从工业到智慧工业&#xff0c;半导体厂商可以做什么&#xff1f; 从第一次工业革命开始到现在&#xff0c;工业领域发生了翻天覆地的变化&#xff0c;现在及未来的工业将会更加智能化。 意法半导体&#xff08;ST&#xf…

南卡全新开放式OE Pro蓝牙耳机震撼上市,行业新标杆,再迎巅峰

近日&#xff0c;Nank南卡全新发布了一款不入耳开放式蓝牙耳机&#xff0c;创新打破无限可能。根据目前透露&#xff0c;新发布的南卡OE Pro不管是音质还是佩戴舒适度都一如既往的好&#xff0c;比市场内其他品牌的开放式耳机要高出几倍&#xff0c;各大媒体猜测&#xff0c;Na…

AI作画生成器,AI作画的优缺点

AI作画生成器&#xff0c;AI作画生成是一种基于人工智能技术的艺术创作方式&#xff0c;其在过去几年中得到了广泛应用和迅速发展。本文将以此为题&#xff0c;从几个方面来探讨AI作画生成。 一.什么是AI作画生成 AI作画生成&#xff0c;顾名思义&#xff0c;指的是使用人工智…

wifi理论数据速率计算

经常看到这样的数据&#xff0c;下面是802.11n协议&#xff0c;2X2天线的理论数据速率。&#xff1a; 下面我们就来了解下这是怎么计算出来的。 影响因素 速率的计算需要考虑的因素包括&#xff1a;调制方式、码率、子载波个数&#xff08;带宽和协议模式不同&#xff0c;子载…

RK3399平台开发系列讲解(基础篇)延迟和定时器管理

🚀返回专栏总目录 文章目录 一、标准定时器1.1、jiffy和HZ1.2、定时器API二、高精度定时器(HRT)三、动态Tick/Tickless内核四、内核中的延迟和睡眠4.1、原子上下文4.2、非原子上下文沉淀、分享、成长,让自己和他人都能有所收获!😄 📢时间是继内存之后常用的资源之一。…

细说synchronized 加锁 this 和 class 的区别

介绍 synchronized 是 Java 语言中处理并发问题的一种常用手段&#xff0c;它也被我们亲切的称之为“Java 内置锁”&#xff0c;由此可见其地位之高。然而 synchronized 却有着多种用法&#xff0c;当它修饰不同对象时&#xff0c;其意义也是不同的&#xff0c;下面我们一起来…

macOS Monterey 12.6.5 (21G531) Boot ISO 原版可引导镜像

本站下载的 macOS 软件包&#xff0c;既可以拖拽到 Applications&#xff08;应用程序&#xff09;下直接安装&#xff0c;也可以制作启动 U 盘安装&#xff0c;或者在虚拟机中启动安装。另外也支持在 Windows 和 Linux 中创建可引导介质。 2023 年 4 月 10 日&#xff08;北京…

VIM编辑常用命令

本贴为学习小甲鱼【VIM快速入门】时的笔记&#xff0c;视频链接&#xff1a;【VIM快速入门】《带你学C带你飞》&#xff08;番外篇&#xff09; VIM是Linux系统下强大的编辑器&#xff0c;能够方便使用者提升效率。 VIM 具有 6 种基本模式和 5 种派生模式。 基本模式&#xff…

获取淘宝商品分类详情API,抓取淘宝全品类目API接口分享(代码展示、参数说明)

商品分类技巧 淘宝店铺分类怎么设置&#xff1f;我们登录卖家账号的时候&#xff0c;我们看到自己的商品&#xff0c;会想要给商品进行分类&#xff0c;一个好的分类可以帮助提高商品的曝光率。那么在给商品分类前&#xff0c;如果您毫无头绪&#xff0c;以下几点可以给您带来…

Vue.js 2.0 状态管理

类 Flux 状态管理的官方实现 由于多个状态分散的跨越在许多组件和交互间各个角落&#xff0c;大型应用复杂度也经常逐渐增长。为了解决这个问题&#xff0c;Vue 提供 vuex&#xff1a; 我们有受到 Elm 启发的状态管理库。vuex 甚至集成到 vue-devtools&#xff0c;无需配置即可…

详解linux多线程——互斥锁、条件变量、读写锁、自旋锁、信号量

一、互斥锁&#xff08;同步&#xff09; 在多任务操作系统中&#xff0c;同时运行的多个任务可能都需要使用同一种资源。这个过程有点类似于&#xff0c;公司部门里&#xff0c;我在使用着打印机打印东西的同时&#xff08;还没有打印完&#xff09;&#xff0c;别人刚好也在此…

华为ADS 2.0智驾助力阿维塔成为智能电动汽车领军品牌

随着科技的不断发展和智能化的迅速崛起&#xff0c;智能电动汽车已经成为当今汽车行业的热点话题。在这一领域&#xff0c;中国汽车品牌阿维塔科技近日引人瞩目地成为了首批搭载华为ADS 2.0智驾系统的品牌。在2023年4月16日举行的华为智能汽车解决方案发布会上&#xff0c;阿维…

HCIA第二次笔记

目录 OSI/RM七层参考模型——开放式的系统互联参考模型 核心——分层 TCP/IP模型——TCP/IP协议簇 应用层 应用层协议 封装与解封装 传输层 TCP协议和UDP协议的区别 TCP的报文 TCP的三次握手 TCP的四次挥手 TCP的四种可靠传输机制 OSI/RM七层参考模型——开放式的系…

单链表经典面试题 (动图解析)

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点!人生格言&#xff1a;当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔&#x1f9be;&am…

Lars bak

&#xff08;1&#xff09;先从smalltalk说起上回书《阿伦凯(Alan Kay)》咱们说到世界上第一个基于语言虚拟机的编程语言Smalltalk&#xff0c;以及它的创造者&#xff1a;Alan kay。今天我再沿着语言虚拟机这条路&#xff0c;再走向系统虚拟机《虚拟机&#xff1a;IBM-S/360-O…

WebServer项目(一)->计网知识补充

WebServer项目->计网知识补充1.网络结构模式C/S结构B/S结构2.MAC 地址3.IP 地址1)IP 地址编址方式2)A类IP地址3)B类IP地址4)C类IP地址5)D类IP地址(了解)6)特殊的网址7)子网掩码4.端口5.网络模型1)OSI 七层参考模型&#xff08;Open System Interconnection&#xff09;2)TCP…

测试开发岗 - 一面复盘

1. 什么是软件测试&#xff0c; 谈谈你对软件测试的了解 软件测试就是验证产品特性是否符合用户需求, 软件测试贯穿于软件的整个生命周期. >>> 那软件测试具体是什么呢 ? 就拿生活中的例子来说, 比如说我们去商场买衣服, 会有以下几个步骤 : 第一步: 我们会走进门店…

SSM版本个人博客系统实现

SSM版本的个人博客系统 文章目录SSM版本的个人博客系统统一的数据返回处理关于前端的一些问题实现注册功能实现登录的功能存储session获取用户的信息获取左侧的个人信息获取右侧的博客列表时间格式化删除操作注销功能&#xff08;退出登录&#xff09;查看文章的详情页排查问题…