Ioctl()方式实现与驱动交互简洁框架

前言

ioctl是linux中一种除read和write之外的数据传递机制

驱动层IOCTL:

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

以上函数参数的含义如下

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

在2.6.36以后ioctl函数已经不存在了,用unlocked_ioctl和compat_ioctl两个函数代替。参数去除了原来ioctl中的struct
inode参数,返回值也发生了改变。

新的代码

#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);

应用层IOCTL:

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

流程梳理:

  • 应用程序调用ioctl函数发送一个数字(cmd)给内核层驱动程序(此时驱动程序已经注册加入到系统里)
  • 驱动程序接收到cmd(一个数字), 将内核数据传递给从应用层传递过来的指针,或者将应用层的数据传递给内核变量。

注意

  • ioctl所发送的数字, 是有一定规则的, 必须符合这个规则,可以直接使用内核提供的API(_IO,_IOR,_IOW,_IOWR)去完成cmd的生成, 驱动层才能正确解析这个数字指令。 这个数字也叫指令码
  • 应用层的ioctl是个变参函数, 但是变参并不意味着它可以传任意多个参数进去,它的意思是第三个参数可传可不传

源码

驱动代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/ioctl.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/unistd.h>
 

#define BASEMINOR 0
#define COUNT     3
#define NAME      "ioctl_test"

#define MEMDEV_IOC_MAGIC 'k'			//定义幻数
#define MEMDEV_IOCPRTDATA _IO(MEMDEV_IOC_MAGIC,1)			//控制驱动打印数据
#define MEMDEV_IOCSETDATA _IOW(MEMDEV_IOC_MAGIC,3,int)			//set数据到驱动中
#define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC,2,int)			//从驱动中读数据
#define MEMDEV_IOCMAXNR 3

dev_t devno;
struct cdev *cdevp = NULL;

static long test_ioctl(struct file *filp,unsigned int cmd,unsigned long arg){
	
	long ret = 0;
	int err = 0;
	int ioarg = 0;
	
	//第一步验证命令cmd参数的有效性
	if(_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC){
	return -EINVAL;}
	if(_IOC_NR(cmd) > MEMDEV_IOCMAXNR){
	return -EINVAL;}
	
	//检测参数空间是否可以正确访问
	//ioctl读取数据,需要向用户空间写入数据
	if(_IOC_DIR(cmd) & _IOC_READ){
		err = !access_ok((void *)arg,_IOC_SIZE(cmd));
	}
	//ioctl设置数据,需要向用户空间读取数据
	else if(_IOC_DIR(cmd) & _IOC_WRITE){
		err = !access_ok((void *)arg,_IOC_SIZE(cmd));
	}
	if(err){
		return -EFAULT;
	}
	
	switch(cmd){
		case MEMDEV_IOCPRTDATA:
			printk("---------打印函数执行成功---------\n");
			break;
		case MEMDEV_IOCSETDATA:
			ret = __get_user(ioarg,(int *)arg);
			printk("从用户空间拿到的值:%d\n",ioarg);
			break;
		case MEMDEV_IOCGETDATA:
			ioarg = 1101;
			ret = __put_user(ioarg,(int *)arg);
			break;
		default:
			return -EINVAL;
	}
	return ret;
}

static const struct file_operations fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = test_ioctl
};

static int __init ioctl_init(void)
{
	int ret;
	ret = alloc_chrdev_region(&devno,BASEMINOR,COUNT,NAME);
	if(ret < 0){
		printk(KERN_ERR "alloc_chrdev_region failed...\n");
		goto err1;
	}
	//设备号申请成功打印出来
	printk(KERN_INFO "major = %d \n",MAJOR(devno));
	cdevp = cdev_alloc();
	if(NULL == cdevp){
		printk(KERN_ERR "cdev_alloc failed...\n");
		ret = -ENOMEM;
		goto err2;
		//cdev结构体申请失败需要将上一步申请到的设备号资源释放。
	}
	cdev_init(cdevp,&fops);
	ret = cdev_add(cdevp,devno,COUNT);
	if(ret < 0){
		printk(KERN_ERR "cdev_add failed...\n");
		goto err2;
	}

	printk(KERN_INFO "---init over :%s---%s---%d---\n",__FILE__,__func__,__LINE__);
	return 0;
err2:
	unregister_chrdev_region(devno,COUNT);			//释放申请到的设备号资源
err1:
	return ret;
}

static void __exit ioctl_exit(void)
{
	cdev_del(cdevp);
	unregister_chrdev_region(devno,COUNT);			//释放申请到的设备号资源
	printk(KERN_INFO "---%s---%s---%d---\n",__FILE__,__func__,__LINE__);
}

module_init(ioctl_init);
module_exit(ioctl_exit);
MODULE_LICENSE("GPL");

编译驱动的Makefile

KERNDIR = /lib/modules/`uname -r`/build
PWD = $(shell pwd)

obj-m:=char_ioctl_driver.o

all:
	make -C $(KERNDIR) M=$(PWD) modules
	
clean:
	make -C $(KERNDIR) M=$(PWD) clean

应用层代码

#include <stdio.h>
#include <string.h>
#include <linux/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>

int main()
{
	int fd = 0;
	int cmd;
	int arg = 0;
	
	fd = open("/dev/ioctl_test",O_RDWR);
	if(fd < 0){
		printf("open memdev0 failed!!!\n");
		return -1;
	}
	
	cmd = _IO('k',1);//利用kernel api构造cmd
	if(ioctl(fd,cmd,&arg) < 0){
		printf("-------打印命令传输失败--------\n");
		return -1;
	}
	
	cmd = _IOW('k',3,int);//利用kernel api构造cmd
	arg = 2007;
	if(ioctl(fd,cmd,&arg) < 0){
		printf("-----------应用层数据copy到驱动失败--------\n");
		return -1;
	}
	
	cmd = _IOR('k',2,int);//利用kernel api构造cmd
	if(ioctl(fd,cmd,&arg) < 0){
		printf("-------驱动数据copy到应用层数据失败---------\n");
		return -1;
	}
	printf("从内核获取到的数据值:%d\n",arg);
	
	close(fd);
	
	return 0;
}

编译与验证流程
驱动make直接编译:

ubuntu:~/Desktop/huangrui/project_1/char_ioctl$ make
make -C /lib/modules/`uname -r`/build M=/home/xj/Desktop/huangrui/project_1/char_ioctl modules
make[1]: Entering directory '/usr/src/linux-headers-5.4.0-149-generic'
  CC [M]  /home/Desktop/huangrui/project_1/char_ioctl/char_ioctl_driver.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC [M]  /home/Desktop/huangrui/project_1/char_ioctl/char_ioctl_driver.mod.o
  LD [M]  /home/Desktop/huangrui/project_1/char_ioctl/char_ioctl_driver.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.4.0-149-generic'

文件罗列

char_ioctl$ ls
char_ioctl_driver.c   char_ioctl_driver.mod    char_ioctl_driver.mod.o  char_ioctl_user    Makefile       Module.symvers
char_ioctl_driver.ko  char_ioctl_driver.mod.c  char_ioctl_driver.o      char_ioctl_user.c  modules.order

驱动加载

sudo insmod char_ioctl_driver.ko

驱动模块查看

project_1/char_ioctl$ lsmod
Module                  Size  Used by
char_ioctl_driver      16384  0
btrfs                1249280  0
xor                    24576  1 btrfs

查看驱动程序生成的设备及其主设备号:(可以作为mknod的参数)

$ cat /proc/devices
Character devices:
240 ioctl_test

创建设备节点:(cd到/dev下)

/dev$ sudo mknod ioctl_test c 240 0

查看设备节点情况

/dev$ ls
ioctl_test

应用层代码编译

$ gcc char_ioctl_user.c -o char_ioctl_user

执行结果

$ sudo ./char_ioctl_user
从内核获取到的数据值:1101

驱动打印

$ dmesg
[1192849.439778] major = 240
[1192849.439781] ---init over :/home/xj/Desktop/huangrui/project_1/char_ioctl/char_ioctl_driver.c---ioctl_init---97---
[1193159.265924] ---------打印函数执行成功---------
[1193159.265925] 从用户空间拿到的值:2007

验证成功!!!

说明

  • arg参数如果是一个整数,可以直接使用,如果是一个指针,需要先确保用户地址的有效性,使用前需要做正确检查,需要检测的函数:__get_user 和 __put_user,不需要检测的函数:copy_from_user & copy_to_user & get_user & put_user

这个就代表正在定义新的cmd

  • #define MEMDEV_IOCPRTDATA _IO(MEMDEV_IOC_MAGIC,1)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值