linux驱动开发(四):ioctl()函数

这篇具有很好参考价值的文章主要介绍了linux驱动开发(四):ioctl()函数。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前文中我们介绍了应用程序通过使用虚拟文件系统VFS提供的接口,来控制字符驱动程序,完成字符驱动设备的open、close、read、write操作。但是如果我们想进行除此以外的其他操作,拓展一些file_operations给出的接口中没有的自定义功能,则需要使用到ioctl()函数。

一、应用程序中的ioctl接口

首先,我们需要规定一些命令码,这些命令码在应用程序和驱动程序中需要保持一致。应用程序只需向驱动程序下发一条指令码,用来通知它执行哪条命令。如何解读这条指令和怎么实现相关操作,就是驱动程序自己要做的事。

应用程序的接口函数为ioctl,参考官方文档,函数原型为

#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);

下面我们解释各个参数的含义。
1)fd是文件描述符。当我们的设备作为特殊文件被open()函数打开后,会返回一个文件描述符,通过操作这个文件描述符达到操作设备文件的目的。

2)request是命令码,应用程序通过下发命令码来控制驱动程序完成对应操作。

3)第三个参数“…”是可变参数arg,一些情况下应用程序需要向驱动程序传参,参数就通过ag来传递。ioctl函数中的“…”只能传递一个参数,但内核不会检查这个参数的类型。那么,就有两种传参方式:只传一个整数,传递一个指针。

如果ioctl执行成功,它的返回值就是驱动程序中ioctl接口给的返回值,驱动程序可以通过返回值向用户程序传参。但驱动程序最好返回一个非负数,因为用户程序中的ioctl运行失败时一定会返回-1并设置全局变量errorno。

errono不同的值代表的含义如下:

EBADF:fd是一个无效的文件描述符。
EFAULT:在arg是指针的前提下,argp指向一个不可访问的内存空间。
EINVAL:request或argp是无效的。
ENOTTY:fd没有关联到一个字符特殊设备,或该request不适用于文件描述符fd引用的对象类型。(说人话就是fd没有指向一个字符设备,或fd指向的文件不支持ioctl操作)

因此,在用户空间调用ioctl时,可以使用如下的错误判断处理。包括的两个头文件,string.h声明了strerror函数,errno.h定义了错误码errno。

#include <string.h>
#include <errno.h>

int ret;
ret = ioctl(fd, MYCMD);
if (ret == -1)
    printf("ioctl: %s\n", strerror(errno));

二、驱动程序中的ioctl接口

在驱动程序的ioctl函数体中,实现了一个switch-case结构,每一个case对应一个命令码,case内部是驱动程序实现该命令的相关操作。

ioctl的实现函数要传递给file_operations结构体中对应的函数指针,函数原型为

#include <linux/ioctl.h>
long (*unlocked_ioctl) (struct file * fp, unsigned int request, unsigned long args);
long (*compat_ioctl) (struct file * fp, unsigned int request, unsigned long args);

unlocked_ioctl在无大内核锁(BKL)的情况下调用。64位用户程序运行在64位的kernel,或32位的用户程序运行在32位的kernel上,都是调用unlocked_ioctl函数。

compat_ioctl是64位系统提供32位ioctl的兼容方法,也在无大内核锁的情况下调用。即如果是32位的用户程序调用64位的kernel,则会调用compat_ioctl。如果驱动程序没有实现compat_ioctl,则用户程序在执行ioctl时会返回错误Not a typewriter。

另外,如果32位用户态和64位内核态发生交互时,第三个参数的长度需要保持一致,否则交互协议会出错。

int (*ioctl) (struct inode *inode, struct file *fp, unsigned int request, unsigned long args);

在2.6.35.7及以前的内核版本中,file_operations还定义了ioctl()接口,与unlocked_ioctl是等价的。但是在2.6.36以后就不再支持这个接口,全部使用unlocked_ioctl了。

以上函数参数的含义如下。
1)inode和fp用来确定被操作的设备。
2)request就是用户程序下发的命令。
3)args就是用户程序在必要时传递的参数。

返回值:可以在函数体中随意定义返回值,这个返回值也会被直接返回到用户程序中。通常使用非负数表示正确的返回,而返回一个负数系统会判定为ioctl调用失败。

三、用户与驱动之间的ioctl协议构成

也就是request或cmd,本质上就是一个32位数字,理论上可以是任何一个数,但为了保证命令码的唯一性,linux定义了一套严格的规定,通过计算得到这个命令吗数字。linux将32位划分为四段,如下图。
ioctl函数,Linux系统,linux,驱动程序
含义如下。

1)dir,即direction,表示ioctl命令的访问模式,分为无数据(_IO)、读数据(_IOR)、写数据(_IOW)、读写数据(_IOWR)四种模式。

2)type,即device type,表示设备类型,也可翻译成“幻数”或“魔数”,可以是任意一个char型字符,如’a’、‘b’、‘c’等,其主要作用是使ioctl命令具有唯一的设备标识。不过在内核中’w’、‘y’、'z’三个字符已经被使用了。

3)nr,即number,命令编号/序数,取值范围0~255,在定义了多个ioctl命令的时候,通常从0开始顺次往下编号。

4)size,涉及到ioctl的参数arg,占据13bit或14bit,这个与体系有关,arm使用14bit。用来传递arg的数据类型的长度,比如如果arg是int型,我们就将这个参数填入int,系统会检查数据类型和长度的正确性。

在上面的四个参数都需要用户自己定义,linux系统提供了宏可以使程序员方便的定义ioctl命令码。

include/uapi/asm-generic/ioctl.h
--------------------------------------------
/* 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)))

分别对应了四个dir:
_IO(type, nr):用来定义不带参数的ioctl命令。
_IOR(type,nr,size):用来定义用户程序向驱动程序写参数的ioctl命令。
_IOW(type,nr,size):用来定义用户程序从驱动程序读参数的ioctl命令。
_IOWR(type,nr,size):用来定义带读写参数的驱动命令。

当然了,系统也定义反向解析ioctl命令的宏。

include/uapi/asm-generic/ioctl.h
--------------------------------------------
/* 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使用的简单实例——整数传参

本例中,我们让ioctl传递三个命令,分别是一个无参数、写参数、读参数三个指令。首先我们需要确定两个头文件,命名为ioctl_test.h和user_ioctl.h,用来分别定义内核空间和用户空间下的命令码协议。两个头文件中除了引用不同的头文件外,其他内容需要完全一致,以保证协议的一致性。

我们使用字符’a’作为幻数,三个命令的作用分别是用户程序让驱动程序打印一句话,用户程序从驱动程序读一个int型数,用户程序向驱动程序写一个int型数。

ioctl_test.h

#include <linux/ioctl.h>

#define CMD_IOC_MAGIC	'a'
#define CMD_IOC_0		_IO(CMD_IOC_MAGIC, 0)
#define CMD_IOC_1		_IOR(CMD_IOC_MAGIC, 1, int)
#define CMD_IOC_2		_IOW(CMD_IOC_MAGIC, 2, int)

user_ioctl.h

#include <sys/ioctl.h>

#define CMD_IOC_MAGIC	'a'
#define CMD_IOC_0		_IO(CMD_IOC_MAGIC, 0)
#define CMD_IOC_1		_IOR(CMD_IOC_MAGIC, 1, int)
#define CMD_IOC_2		_IOW(CMD_IOC_MAGIC, 2, int)

编写驱动程序ioctl_test.c。核心函数是demo_ioctl,使用一个switch-case完成用户程序下发的指令。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include "ioctl_test.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zz");

static dev_t devno;

static int demo_open(struct inode *ind, struct file *fp)
{
	printk("demo open\n");
	return 0;
}

static int demo_release(struct inode *ind, struct file *fp)
{
	printk("demo release\n");
	return 0;
}

static long demo_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
{
	int rc = 0;
	int arg_w;
	const int arg_r = 566;
	if (_IOC_TYPE(cmd) != CMD_IOC_MAGIC) {
		pr_err("%s: command type [%c] error.\n", __func__, _IOC_TYPE(cmd));
		return -ENOTTY;
	}

	switch(cmd) {
		case CMD_IOC_0:
			printk("cmd 0: no argument.\n");
			rc = 0;
			break;
		case CMD_IOC_1:
			printk("cmd 1: ioc read, arg = %d.\n", arg_r);
			arg = arg_r;
			rc = 1;
			break;
		case CMD_IOC_2:
			arg_w = arg;
			printk("cmd 2: ioc write, arg = %d.\n", arg_w);
			rc = 2;
			break;
		default:
			pr_err("%s: invalid command.\n", __func__);
			return -ENOTTY;
	}
	return rc;
}

static struct file_operations fops = {
	.open = demo_open,
	.release = demo_release,
	.unlocked_ioctl = demo_ioctl,
};

static struct cdev cd;

static int demo_init(void)
{
	int rc;
	rc = alloc_chrdev_region(&devno, 0, 1, "test");
	if(rc < 0) {
		pr_err("alloc_chrdev_region failed!");
		return rc;
	}
	printk("MAJOR is %d\n", MAJOR(devno));
	printk("MINOR is %d\n", MINOR(devno));

	cdev_init(&cd, &fops);
	rc = cdev_add(&cd, devno, 1);
	if (rc < 0) {
		pr_err("cdev_add failed!");
		return rc;
	}
	return 0;
}

static void demo_exit(void)
{
	cdev_del(&cd);
	unregister_chrdev_region(devno, 1);
	return;
}

module_init(demo_init);
module_exit(demo_exit);

编写用户程序user_ioctl.c,主要作用就是打开设备节点,依次下发三条指令,打印参数和ioctl的返回值,关闭设备节点。

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include "user_ioctl.h"

int main()
{
	int rc;
	int arg_r;
	const int arg_w = 233;
	int fd = open("/dev/test_chr_dev", O_RDWR);
	if (fd < 0) {
		printf("open file failed!\n");
		return -1;
	}

	rc = ioctl(fd, CMD_IOC_0);
	printf("rc = %d.\n", rc);

	rc = ioctl(fd, CMD_IOC_1, arg_r);
	printf("ioc read arg = %d, rc = %d.\n", arg_r, rc);

	rc = ioctl(fd, CMD_IOC_2, arg_w);
	printf("ioc write arg = %d, rc = %d.\n", arg_w, rc);

	close(fd);
	return 0;
}

编写Makefile。

ifneq ($(KERNELRELEASE),)
	obj-m := ioctl_test.o
else
	KDIR    := /lib/modules/$(shell uname -r)/build
	PWD     := $(shell pwd)
all:
	make -C $(KDIR) M=$(PWD) modules
	gcc user_ioctl.c -o user
clean:
	make -C $(KDIR) M=$(PWD) clean
	rm -rf user
endif

运行结果。首先make编译。
ioctl函数,Linux系统,linux,驱动程序
进入root模式,安装模块,打印内核信息查看系统分配给设备的设备号。
ioctl函数,Linux系统,linux,驱动程序
可以看到系统分配了236给该模块。创建设备节点,设备节点的名称为/dev/test_chr_dev,与user_ioctl.c中的保持一致。
ioctl函数,Linux系统,linux,驱动程序
运行用户程序,打印结果如下。
ioctl函数,Linux系统,linux,驱动程序
打印一下内核输出信息,结果如下。
ioctl函数,Linux系统,linux,驱动程序
可以看到结果是正确的。删除设备节点,移除模块。
ioctl函数,Linux系统,linux,驱动程序文章来源地址https://www.toymoban.com/news/detail-794746.html

到了这里,关于linux驱动开发(四):ioctl()函数的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • linux tty 驱动之ioctls 函数

    在 struct tty_driver 中的 ioctl 函数被 tty 核心调用当 ioctl(2) 被在设备节点上 调用. 如果这个 tty 驱动不知道如何处理传递给它的 ioctl 值, 它应当返回 - ENOIOCTLCMD 来试图让 tty 核心实现一个通用的调用版本. 2.6 内核定义了大约 70 个不同的 tty ioctls, 可被用来发送给一个 tty 驱动. 大部

    2024年01月19日
    浏览(7)
  • 嵌入式Linux驱动开发 02:将驱动程序添加到内核中

    嵌入式Linux驱动开发 02:将驱动程序添加到内核中

    在上一篇文章 《嵌入式Linux驱动开发 01:基础开发与使用》 中我们已经实现了最基础的驱动功能。在那篇文章中我们的驱动代码是独立于内核代码存放的,并且我们的驱动编译后也是一个独立的模块。在实际使用中将驱动代码放在内核代码中,并将驱动编译到内核中也是比较

    2023年04月09日
    浏览(19)
  • Linux 驱动开发基础知识——LED 模板驱动程序的改造:设备树(十一)

    Linux 驱动开发基础知识——LED 模板驱动程序的改造:设备树(十一)

     个人名片: 🦁作者简介:学生 🐯个人主页:妄北y 🐧个人QQ:2061314755 🐻个人邮箱:2061314755@qq.com 🦉个人WeChat:Vir2021GKBS 🐼 本文由妄北y原创,首发CSDN 🎊🎊🎊 🐨座右铭:大多数人想要改造这个世界,但却罕有人想改造自己。 专栏导航: 妄北y系列专栏导航: C/C++的基

    2024年02月21日
    浏览(9)
  • 在windows通过VS Code开发Linux内核驱动程序

    在windows通过VS Code开发Linux内核驱动程序

    最近在看Linux设备驱动程序第三版,为了在windows系统上练手操作,先是下载VMware Workstation安装了Linux系统虚拟机。然后在vscode上编写简单的示例程序,通过ftp把源文件发送到Linux虚拟机后,再在虚拟机上make编译测试内核驱动程序。这样即使是在内核日志中打印个简单的hello w

    2024年02月06日
    浏览(13)
  • Linux设备驱动开发学习笔记(等待队列,锁,字符驱动程序,设备树,i2C...)

    container_of函数可以通过结构体的成员变量检索出整个结构体 函数原型: 内核开发者只实现了循环双链表,因为这个结构能够实现FIFO和LIFO,并且内核开发者要保持最少代码。 为了支持链表,代码中要添加的头文件是linux/list.h。内核中链表实现核心部分的数据结构 是struct li

    2024年01月22日
    浏览(12)
  • 【Linux】按键驱动程序

    【Linux】按键驱动程序

    【Linux】按键驱动程序 前言: 一、按键驱动程序的背景知识 1.1 查询方式 1.2 休眠-唤醒方式 1.3 poll方式 1.4 异步通知  1.5 总结  二、按键驱动程序的框架 三、按键驱动程序实战 3.1 头文件(button_drv.h) 3.2 驱动程序(button_drv.c) 3.3 驱动程序(button_100ask_imx6ull.c) 3.4 Makefil

    2024年02月10日
    浏览(13)
  • Linux设备驱动程序(一)——设备驱动简介

    这一部分主要是用来介绍 Linux 设备驱动程序的一些基本概念,包括:Linux 设备驱动程序的作用、内核功能的划分、设备和模块的分类以及版本编号。 设备驱动程序就像一个个的“黑盒子”,使某个特定硬件响应一个定义良好的内部编程接口,这些操作完全隐藏了设备的工作

    2024年02月05日
    浏览(11)
  • Linux驱动(一)之最简单的驱动程序

    Linux驱动(一)之最简单的驱动程序

    为什么要有驱动?为了防止像我等小菜程序员写应用程序的时候权限过高直接去操作底层设备,给设备造成不可挽回的损失,所以要过度一下,让大牛们将底层封装好,应用开发工程师只需要通过特定的接口来完成特定的功能就可以了。 通常情况下,应用开发只需要open一个

    2024年02月17日
    浏览(13)
  • linux驱动和应用的数据交互ioctl函数和copy_from_user、copy_to_user

    首先,我们需要规定一些命令码,这些命令码在应用程序和驱动程序中需要保持一致。应用程序只需向驱动程序下发一条指令码,用来通知它执行哪条命令。如何解读这条指令和怎么实现相关操作,就是驱动程序自己要做的事。 应用程序的接口函数为ioctl,参考官方文档,函

    2024年02月07日
    浏览(9)
  • Linux 设备驱动程序(四)

    Linux 设备驱动程序(四)

    Linux 内核设计与实现 深入理解 Linux 内核 Linux 设备驱动程序(一) Linux 设备驱动程序(二) Linux 设备驱动程序(三) Linux 设备驱动程序(四) Linux设备驱动开发详解 深入理解Linux虚拟内存管理     ⇐ ⇒ ⇔ ⇆ ⇒ ⟺ ①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳㉑㉒㉓㉔㉕

    2024年02月16日
    浏览(15)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包