提示:驱动地址传参实验,方便理解传参
前言
前面已经了解过驱动传参实验,这里进一步了解驱动地址传参。
参考资料
驱动传参和地址传参简单对比
这里我先把驱动传参和地址传参简单对比写在这里,方便先了解,后面再分析、思考。
特性 | 驱动传参 (Driver Parameter Passing) | 地址传参 (Address Passing) |
---|---|---|
传递内容 | 传递驱动程序需要的配置参数或控制信息 | 传递内存地址(指针) |
安全性 | 相对较高,参数经过验证 | 风险较高,需谨慎处理指针有效性 |
使用场景 | 驱动初始化、配置变更 | 大数据传输、共享内存 |
典型实现 | IOCTL、sysfs、模块参数 | 指针传递、DMA缓冲区传递 |
数据大小 | 通常较小(简单数据类型或小结构体) | 可处理大数据块 |
实验
在驱动传参里面我们已经写了相关实验程序,其实地址传参和驱动传参基本一致,只是传参类型不一样而已,这里以结构体参数为例。
控制程序 ioctltest.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#define CMD_TEST0 _IOW('L', 0, int)
struct args // 定义要传递的结构体
{
int a;
int b;
int c;
};
int main(int argc, char *argv[])
{
int fd; // 定义int类型的文件描述符fd
struct args test;
test.a = 100;
test.b = 200;
test.c = 300;
fd = open("/dev/test", O_RDWR, 0777); // 打开test设备节点
if (fd < 0)
{
printf("file open fail\n");
}
ioctl(fd, CMD_TEST0, &test); // 使用ioctl 函数传递结构体变量test地址
close(fd);
}
代码解读
这里我们打开驱动后, ioctl 控制的是test 结构体,结构体有三个属性。
驱动程序 ioctl.c
驱动程序源码如下:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/atomic.h>
#define CMD_TEST0 _IOW('L', 0, int)
struct args // 定义要传递的结构体
{
int a;
int b;
int c;
};
struct device_test
{
dev_t dev_num; // 定义dev_t类型变量来表示设备号
int major, minor; // 定义int 类型的主设备号和次设备号
struct cdev cdev_test; // 定义字符设备
struct class *class; // 定义结构体变量class 类
struct device *device; // 设备
};
struct device_test dev1;
static long cdev_test_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct args test;
switch (cmd)
{
case CMD_TEST0:
if (copy_from_user(&test, (int *)arg, sizeof(test)) != 0)
{ // 通过copy_to_user向用户空间传递数据
printk("copy_to_user error \n");
}
printk(" kernel a = %d\n", test.a); // 对传递的值进行打印验证
printk("kernel b = %d\n", test.b);
printk("kernel c = %d\n", test.c);
break;
default:
break;
}
return 0;
}
/*打开设备函数*/
static int open_test(struct inode *inode, struct file *file)
{
file->private_data = &dev1; // 设置私有数据
printk("\n this is open_test \n");
return 0;
};
static int release_test(struct inode *inode, struct file *file)
{
printk("\nthis is release_test \n");
return 0;
}
static struct file_operations fops_test = {
.owner = THIS_MODULE, // 将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
.open = open_test, // 将open字段指向chrdev_open(...)函数
.release = release_test, // 将open字段指向chrdev_release(...)函数
.unlocked_ioctl = cdev_test_ioctl,
}; // 定义file_operations结构体类型的变量cdev_test_ops
static int __init timer_dev_init(void) // 驱动入口函数
{
if (alloc_chrdev_region(&dev1.dev_num, 0, 1, "chrdev_name") < 0)
{
printk("alloc_chrdev_region is error\n");
}
printk("alloc_chrdev_region is ok\n");
dev1.major = MAJOR(dev1.dev_num); // 通过MAJOR()函数进行主设备号获取
dev1.minor = MINOR(dev1.dev_num); // 通过MINOR()函数进行次设备号获取
printk("major is %d\n", dev1.major);
printk("minor is %d\n", dev1.minor);
////使用cdev_init()函数初始化cdev_test结构体,并链接到cdev_test_ops结构体
cdev_init(&dev1.cdev_test, &fops_test);
dev1.cdev_test.owner = THIS_MODULE; // 将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
printk("cdev_add is ok\n");
dev1.class = class_create(THIS_MODULE, "test"); // 使用class_create进行类的创建,类名称为class_test
device_create(dev1.class, NULL, dev1.dev_num, NULL, "test"); // 使用device_create进行设备的创建,设备名称为device_test
return 0;
}
static void __exit timer_dev_exit(void) // 驱动出口函数
{
cdev_del(&dev1.cdev_test); // 使用cdev_del()函数进行字符设备的删除
unregister_chrdev_region(dev1.dev_num, 1); // 释放字符驱动设备号
device_destroy(dev1.class, dev1.dev_num); // 删除创建的设备
class_destroy(dev1.class); // 删除创建的类
printk("module exit \n");
}
module_init(timer_dev_init); // 注册入口函数
module_exit(timer_dev_exit); // 注册出口函数
MODULE_LICENSE("GPL v2"); // 同意GPL开源协议
MODULE_AUTHOR("wang fang chen "); // 作者信息
代码解读
驱动程序从字符设备到现在,基本思路、必备技能知识点:
- 结构体封装设备号、主设备号、次设备号、字符设备、类、设备
- 私有数据结构体
- 创建设备号
- 初始化字符设备
- 添加字符设备到内核
- 创建类
- 创建设备
这些基础知识点通过各个案例、实验 不断深化基础,这里不再讲解。
这里其它技能知识点 需要关注两方面:
- .unlocked_ioctl = cdev_test_ioctl, 函数,处理传参的函数的方法回调
- 定义需要传参的结构体,也就是对象,也就是地址。 其实就是一个声明,当需要控制的程序来传参给驱动也是需要按照这个对象、地址 形式来进行传参 实现传参(地址传参)功能。下面简单描述这部分内容
#define CMD_TEST0 _IOW('L', 0, int)
struct args // 定义要传递的结构体
{
int a;
int b;
int c;
};
Makefile 编译文件
#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
obj-m += ioctl.o
KDIR :=/home/wfc123/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
编译脚本
- make 脚本直接编译驱动C程序代码生成 驱动文件:make
- 针对控制程序,编译生成控制脚本
aarch64-linux-gnu-gcc -o ioctl ioctltest.c -static
实际测试验证
驱动程序和控制程序copy 到主板系统目录下面,如下
- 先执行驱动程序,让驱动生成字符设备、节点、设备
- 执行控制程序,查看测试结果
总结
驱动传参和地址传参,实际写代码感觉基本一致。其实就是参数类型不同而已。这里以最简单结构体为例,实际过程中可能地址比较复杂,但终是地址类型,表现形式不一样而已,后续再讨论。