linux驱动和应用的数据交互

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

一、应用程序中的ioctl接口

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

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

下面我们解释各个参数的含义。
1)fd是文件描述符。当我们的设备作为特殊文件被open()函数打开后,会返回一个文件描述符,通过操作这个文件描述符达到操作设备文件的目的。
2)request是命令码,应用程序通过下发命令码来控制驱动程序完成对应操作。
3)第三个参数“…”是可变参数arg,一些情况下应用程序需要向驱动程序传参,参数就通过arg来传递。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使用的简单实例——整数传参

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

我们使用字符’a’作为幻数,三个命令的作用分别是用户程序让驱动程序打印一句话,用户程序从驱动程序读一个int型数,用户程序向驱动程序写一个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");

#define CH36x_GET_IO_BASE_ADDR			0x00000001
#define CH36x_SET_IO_BASE_ADDR			0x00000002
#define CH36x_GET_MEM_BASE_ADDR			0x00000003
#define CH36x_SET_MEM_BASE_ADDR			0x00000004
#define CH36x_READ_CONFIG_BYTE			0x00000005
#define CH36x_WRITE_CONFIG_BYTE			0x00000006
#define CH36x_GET_INT_LINE				0x00000007
#define CH36x_SET_INT_LINE				0x00000008
#define CH36x_READ_IO_BYTE				0x00000009
#define CH36x_READ_IO_WORD				0x0000000a
#define CH36x_READ_IO_DWORD				0x0000000b
#define CH36x_WRITE_IO_BYTE				0x0000000c
#define CH36x_WRITE_IO_WORD				0x0000000d
#define CH36x_WRITE_IO_DWORD			0x0000000e
#define CH36x_READ_MEM_BYTE				0x0000000f
#define CH36x_READ_MEM_WORD				0x00000010
#define CH36x_READ_MEM_DWORD			0x00000011
#define CH36x_WRITE_MEM_BYTE			0x00000012
#define CH36x_WRITE_MEM_WORD			0x00000013
#define CH36x_WRITE_MEM_DWORD			0x00000014
#define CH36x_READ_MEM_BLOCK			0x00000015
#define CH36x_WRITE_MEM_BLOCK			0x00000016
#define CH36x_READ_IO_BLOCK				0x00000017
#define CH36x_WRITE_IO_BLOCK			0x00000018
#define CH36x_READ_CONFIG_WORD			0x00000019
#define CH36x_WRITE_CONFIG_WORD			0x0000001a
#define CH36x_READ_CONFIG_DWORD			0x0000001b
#define CH36x_WRITE_CONFIG_DWORD		0x0000001c
#define CH36x_ENABLE_INT				0x0000001d
#define CH36x_DISABLE_INT				0x0000001e
#define CH36x_GET_DRV_VERSION			0x000000ff

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;
	switch(cmd) {
		case CH36x_GET_IO_BASE_ADDR:
			printk("cmd 0: no argument.\n");
			rc = 0;
			break;
		case CH36x_READ_MEM_BLOCK:
			printk("cmd 1: ioc read, arg = %d.\n", arg_r);
			arg = arg_r;
			rc = 1;
			break;
		case CH36x_READ_CONFIG_WORD:
			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, CH36x_GET_IO_BASE_ADDR);
	printf("rc = %d.\n", rc);

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

	rc = ioctl(fd, CH36x_READ_CONFIG_WORD, 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

用户层和内核层传递复杂参数

上面的实例只在用户程序和驱动程序中传递了一个简单的整形参数。实际使用中我们可能需要传递更复杂的参数,或者传递多个参数,这时我们就只能传递参数的地址,或者将多个参数打包成一个结构体再传递该结构体的地址。

但是请注意,在Linux系统中,用户空间和内核空间之间是相互隔离开的。驱动程序运行在内核空间中,给出的地址也是在内核空间中的地址,运行在用户空间下的用户程序即使拿到这个地址,也不能访问内核空间。这时,我们需要使用到copy_to_user()函数,将要传递的内容从内核空间拷贝到用户空间,用户程序再访问用户空间中的该内容即可。
(1)内核地址空间和驱动地址空间是隔绝的,不能使用memcpy()函数,必须使用专门的拷贝函数;
(2)在拷贝大量数据时使用copy_to_user()和copy_from_user()函数,拷贝单个数据时,比如某个int型变量,则优先使用put_user()和get_user()函数;

copy_to_user函数的原型如下。

unsigned long copy_to_user(void *to, const void *from, unsigned long n)

参数含义:
1)to:目标地址(用户空间)
2)from:源地址(内核空间)
3)n:需要拷贝的数据的字节数
返回值:成功返回0,失败返回没有拷贝成功的数据字节数。
同理,也有从用户空间向内核空间拷贝的函数copy_from_user(),原型如下。

unsigned long copy_from_user(void *to, const void *from, unsigned long n)

参数含义:
1)to:目标地址(内核空间)
2)from:源地址(用户空间)
3)n:需要拷贝的数据的字节数
返回值:成功返回0,失败返回没有拷贝成功的数据字节数。
下面举个实例,来详细介绍如何在用户空间和内核空间中通过传递地址参数的方法来传递复杂参数。这里我们传递两个参数char arg1和int arg2,将这两个参数打包进一个结构体struct IOC_ARGS中。这个结构体在用户程序和驱动程序中也需要保持一致。
同样我们还需要定义用户程序和驱动程序之间命令码,我们定义两个命令码,分别用来读参数和写参数。
用户程序的头文件user_ioctl.h
用户程序user_ioctl.c。

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

int main()
{
	int rc;
	struct IOC_ARGS args_r;
	struct IOC_ARGS args_w = {'u', 233};
	int fd = open("/dev/test_chr_dev", O_RDWR);

	rc = ioctl(fd, CMD_IOC_0, &args_r);
	if (rc < 0)
		printf("ioctl: %s\n", strerror(errno));
	else
		printf("ioc read arg1 = %c, arg2 = %d.\n", args_r.arg1, args_r.arg2);

	rc = ioctl(fd, CMD_IOC_1, &args_w);
	if (rc < 0)
		printf("ioctl: %s\n", strerror(errno));
	else
		printf("ioc write arg1 = %c, arg2 = %d.\n", args_w.arg1, args_w.arg2);

	close(fd);
	return 0;
}
内核驱动程序ioctl_test.c。

#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;
	struct IOC_ARGS args_r = {'k', 566};
	struct IOC_ARGS args_w;
	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:
			rc = copy_to_user((char __user *)arg, &args_r, sizeof(IOC_ARGS));
			if (rc) {
				pr_err("%s: copy_to_user failed", __func__);
				return rc;
			}
			printk("%s: ioc read arg1 = %c, arg2 = %d", __func__, args_r.arg1, args_r.arg2);
			break;
			break;
		case CMD_IOC_1:
			rc = copy_from_user(&args_w, (char __user *)arg, sizeof(IOC_ARGS));
			if (rc) {
				pr_err("%s: copy_from_user failed", __func__);
				return rc;
			}
			printk("%s: ioc write arg1 = %c, arg2 = %d", __func__, args_w.arg1, args_w.arg2);
			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);

除此之外,还有put_user()函数和get_usr()函数,在用户层和内核层拷贝单个数据时,比如某个int型变量,则优先使用put_user()和get_user()函数;使用方法和copy_from_user,copy_to_user差不多文章来源地址https://www.toymoban.com/news/detail-463595.html

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

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

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

相关文章

  • 【Linux】应用与驱动交互及应用间数据交换

    1. 系统调用接口(System Calls): 应用程序可以通过系统调用,如 open(), read(), write(), ioctl(), 等来与设备驱动进行交互。这些调用最终会通过内核转发到相应的驱动函数。 2. 输入输出控制(ioctl): ioctl() 系统调用是一种特殊的系统调用,它提供了一个进行设备特定操作的方法。

    2024年01月21日
    浏览(63)
  • 探索在云原生环境中构建的大数据驱动的智能应用程序的成功案例,并分析它们的关键要素。

    🎈个人主页:程序员 小侯 🎐CSDN新晋作者 🎉欢迎 👍点赞✍评论⭐收藏 ✨收录专栏:云计算 ✨文章内容:云原生 🤝希望作者的文章能对你有所帮助,有不足的地方请在评论区留言指正,大家一起学习交流!🤗 在云原生环境中构建大数据驱动的智能应用程序已经成为许多

    2024年02月10日
    浏览(32)
  • windows驱动开发7:应用程序和驱动程序的通信

    一、基础介绍 1.1 设备与驱动的关系 设备由驱动去创建,访问一个设备,是首先得访问驱动。如果驱动在卸载的时候没有删除符号,r3下也是不能去访问设备的。 驱动程序和系统其他组件之间的交互是通过给设备发送或者接受发给设备的请求来交互的。换句话说,一个没有任

    2023年04月08日
    浏览(31)
  • Spark 部署与应用程序交互简单使用说明

    本文将讲解 Spark 的部署,并通过三个简单的步骤来编写一个独立应用程序。 我们将使用本地模式,其中所有的处理都是在Spark shell中的一台机器上完成的——这是学习框架的一种简单方法,迭代执行的方式可以及时反馈直接结果。使用Spark shell,可以在编写复杂的Spark应用程

    2024年04月12日
    浏览(26)
  • 【.NET】控制台应用程序的各种交互玩法

    关于控制台交互,大伙伴们也许见得最多的是进度条,就是输出一行但末尾不加 n,而是用 r 回到行首,然后输出新的内容,这样就做出进度条了。不过这种方法永远只能修改最后一行文本。 于是,有人想出了第二种方案——把要输出的文本存起来(用二维数组,啥的都行

    2024年02月04日
    浏览(45)
  • 驱动与应用程序通信

    本文讲述的是 Windows操作系统下驱动程序与应用程序之间的通信 ,说简单点就是相互发送数据。 在应用程序中,可以通过 CreateFile 来打开设备,然后通过 DeviceIoControl 来向驱动发送或接收数据;而驱动程序则需要通过 创建控制设备对象 ,并 创建符号链接 ,通过 分发函数 来

    2024年02月06日
    浏览(82)
  • 微信小程序前后端交互与WXS的应用

    目录 前言 一、后台数据交互 1.数据表 2.后端代码的实现 3.前后端交互 3.1.后端接口URL管理 3.2.发送后端请求 3.3.请求方式的封装 4.前端代码的编写 二、WXS的使用 1、.wxs 文件 2.综合运用 当今社交媒体的普及使得微信小程序成为了一种流行的应用开发形式。微信小程序不仅可以

    2024年02月08日
    浏览(44)
  • 5.2.10.应用程序如何调用驱动 mknod /dev/test c 250 0 创建设备文件,应用app 程序 调用 我们 驱动 壳子

    5.2.10.应用程序如何调用驱动 5.2.10.1、驱动设备文件的创建 (1)何为设备文件     索引驱动 (2)设备文件的关键信息是:设备号 = 主设备号 + 次设备号,使用ls -l去查看设备文件,就可以得到这个设备文件对应的主次设备号。         4颗LED不可能 都占用 主设备号,设备号

    2024年02月16日
    浏览(34)
  • LCD驱动程序——Framebuffer应用编程

    在 Linux 系统中通过 Framebuffer 驱动程序来控制 LCD。 Frame 是帧的意思 , buffer 是缓冲的意思 ,这意味着 Framebuffer 就是一块内存,里面保存着一帧图像。Framebuffer 中保存着一帧图像的每一个像素颜色值,假设 LCD 的分辨率是 1024x768,每一个像素的颜色用 32 位来表示,那么 Frame

    2024年02月06日
    浏览(27)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包