前言
ioctl是linux中一种除read和write之外的数据传递机制
驱动层IOCTL:
int (*ioctl) (struct inode *inode, struct file *fp, unsigned int request, unsigned long args);
以上函数参数的含义如下。
- inode和fp用来确定被操作的设备。
- request就是用户程序下发的命令。
- 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
验证成功!!!文章来源:https://www.toymoban.com/news/detail-488865.html
说明
- arg参数如果是一个整数,可以直接使用,如果是一个指针,需要先确保用户地址的有效性,使用前需要做正确检查,需要检测的函数:__get_user 和 __put_user,不需要检测的函数:copy_from_user & copy_to_user & get_user & put_user
这个就代表正在定义新的cmd文章来源地址https://www.toymoban.com/news/detail-488865.html
- #define MEMDEV_IOCPRTDATA _IO(MEMDEV_IOC_MAGIC,1)
到了这里,关于Ioctl()方式实现与驱动交互简洁框架的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!