字符设备驱动程序(ioctl)
-
ioctl函数模型
int (*ioctl)(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg){}
返回值:- ioctl函数的实现通常是根据命令执行的switch语句,
- 但是命令号不能匹配到任何一个设备时,通常返回-EINVAL(非法参数)
使用参数:
- 参数如果是一个整数,可以直接使用,
- 如果是一个指针,需要先确保用户地址的有效性,使用前需要做正确检查
- 需要检测的函数:__get_user 和 __put_user
- 不需要检测的函数:copy_from_user & copy_to_user & get_user & put_user
参数检查:
int access_ok(int type,const void *addr,unsigned long size){}
- 第一个参数:VERIFY_READ or VERIFY_WRITE
- 用来表示读用户内存或者写用户内存
- 第二个参数:addr是要操作的用户内存地址
- 第三个参数:size是操作的长度,如果ioctl需要从用户空间读一个整数,size=sizeof(int)
- 返回值:access_ok返回一个布尔值,1是成功,0是失败,如果返回失败,ioctl应当返回-EFAULT
//ioctl读取数据,需要向用户空间写入数据 if(_IOC_DIR(cmd) & _IOC_READ){ err = !access_ok(VERIFY_WRITE,(void __user *)arg,_IOC_SIZE(cmd)); } //ioctl设置数据,需要向用户空间读取数据 else if(_IOC_DIR(cmd) & _IOC_WRITE){ err = !access_ok(VERIFY_READ,(void __user *)arg,_IOC_SIZE(cmd)); } if(err){ return -EFAULT; }
定义命令
#define MEM_IOC_MAGIC 'm' //定义幻数
#define MEM_IOCSET _IOW(MEM_IOC_MAGIC,o,int) //set数据到驱动中
#define MEM_IOCGQSET _IOR(MEM_IOC_MAGIC,1,int) //从驱动中读数据
关于ioctl函数模型
-
在2.6.36以后ioctl函数已经不存在了,用unlocked_ioctl和compat_ioctl两个函数代替。参数去除了原来ioctl中的struct inode参数,返回值也发生了改变。
-
在应用程序设计中还是采用ioctl实现访问,而并不是unlocked_ioctl函数,因此我们还可以称之为ioctl函数的实现。
- 操作集定义
- 函数原型
- 应用程序调用
- 操作集定义
详细步骤
-
编译Makefile
-
将字符设备驱动模块加载到内核中
-
查看主设备号
cat /proc/devices
-
去到/dev目录下,创建设备文件memdev_ioctl0
cd /dev
-
关于创建设备文件参数解释见上一篇博客(连接:创建设备文件参数解释)
sudo mknod memdev_ioctl0 c 240 0
-
编译应用程序文件
gcc memdev_ioctl_app.c -o memdev_ioctl_app
-
执行可执行文件
sudo ./memdev_ioctl_app
执行成功!!!!!!!!!
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 "memdev_ioctl"
#define MEMDEV_SIZE 4096
#define MEMDEV_IOC_MAGIC 'j' //定义幻数
#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 memdev{
char *data;
unsigned long size;
};
struct cdev *cdevp = NULL;
struct memdev *memdevp = NULL;
int mem_open (struct inode *inode,struct file *filp){
//printk(KERN_INFO "---%s---%s---%d---\n",__FILE__,__func__,__LINE__);
struct memdev *mdev = NULL;
int num = MINOR(inode->i_rdev);
if(num >= COUNT){
return -ENODEV;
}
mdev = &memdevp[num];
filp->private_data = mdev;
return 0;
}
int mem_release (struct inode *inode,struct file *filp){
printk(KERN_INFO "---%s---%s---%d---\n",__FILE__,__func__,__LINE__);
return 0;
}
static long mem_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(VERIFY_WRITE,(void *)arg,_IOC_SIZE(cmd));
}
//ioctl设置数据,需要向用户空间读取数据
else if(_IOC_DIR(cmd) & _IOC_WRITE){
err = !access_ok(VERIFY_READ,(void *)arg,_IOC_SIZE(cmd));
}
if(err){
return -EFAULT;
}
switch(cmd){
case MEMDEV_IOCPRTDATA:
printk("CMD MEMDEV_IOCPRTDATA DONE!!!!!\n");
break;
case MEMDEV_IOCSETDATA:
ret = __get_user(ioarg,(int *)arg);
printk("CMD MEMDEV_IOCGETDATA IS %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,
.open = mem_open,
.release = mem_release,
.unlocked_ioctl = mem_ioctl
};
static int __init mem_init(void)
{
int ret;
int i;
/***********************************************************************/
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;
}
/***********************************************************************/
//为memdev设备结构体分配内存
memdevp = kmalloc(COUNT * sizeof(struct memdev),GFP_KERNEL);
if(NULL == memdevp){
printk(KERN_ERR "memdevp kmalloc failed...\n");
ret = -ENOMEM;
goto err2;
}
memset(memdevp,0,sizeof(struct memdev));
//上一步申请的是一片空间,这一片空间里是COUNT个设备的总空间,需要为每一个设备分配空间
for(i = 0;i<COUNT;i++){
memdevp[i].data = kmalloc(MEMDEV_SIZE,GFP_KERNEL);
memdevp[i].size = MEMDEV_SIZE;
memset(memdevp[i].data,0,MEMDEV_SIZE);
}
/***********************************************************************/
printk(KERN_INFO "---%s---%s---%d---\n",__FILE__,__func__,__LINE__);
return 0;
err2:
unregister_chrdev_region(devno,COUNT); //释放申请到的设备号资源
err1:
return ret;
}
static void __exit mem_exit(void)
{
cdev_del(cdevp);
unregister_chrdev_region(devno,COUNT); //释放申请到的设备号资源
printk(KERN_INFO "---%s---%s---%d---\n",__FILE__,__func__,__LINE__);
}
module_init(mem_init);
module_exit(mem_exit);
MODULE_LICENSE("GPL");
ioctl_app源码资源!!!!!!!
#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;
char buf[4096];
int cmd;
int arg = 0;
fd = open("/dev/memdev_ioctl0",O_RDWR);
if(fd < 0){
printf("open memdev0 failed!!!\n");
return -1;
}
//调用MEMDEV_IOCPRTDATA
printf("调用MEMDEV_IOCPRTDATA ing!!!!\n");
cmd = _IO('j',1);
if(ioctl(fd,cmd,&arg) < 0){
printf("调用MEMDEV_IOCPRTDATA failed!!!!\n");
return -1;
}
//调用MEMDEV_IOCSETDATA
printf("调用MEMDEV_IOCSETDATA ing!!!!\n");
cmd = _IOW('j',3,int);
arg = 2007;
if(ioctl(fd,cmd,&arg) < 0){
printf("调用MEMDEV_IOCSETDATA failed!!!!\n");
return -1;
}
//调用MEMDEV_IOCGETDATA
printf("调用MEMDEV_IOCGETDATA ing!!!!\n");
cmd = _IOR('j',2,int);
if(ioctl(fd,cmd,&arg) < 0){
printf("调用MEMDEV_IOCGETDATA failed!!!!\n");
return -1;
}
printf("调用MEMDEV_IOCGETDATA is %d!!!!\n",arg);
close(fd);
return 0;
}
Makefile源码资源!!!!!!!
KERNDIR = /lib/modules/4.15.0-107-generic/build
PWD = $(shell pwd)
obj-m:=memdev_ioctl.o
all:
make -C $(KERNDIR) M=$(PWD) modules
clean:
make -C $(KERNDIR) M=$(PWD) clean