一、ioctl操作实现
已知成员的地址获得所在结构体变量的地址:
container_of(成员地址,结构体类型名,成员在结构体中的名称)
long xxx_ioctl (struct file *filp, unsigned int cmd, unsigned long arg);
功能:对相应设备做指定的控制操作(各种属性的设置获取等等)
参数:
filp:指向open产生的struct file类型的对象,表示本次ioctl对应的那次open
cmd:用来表示做的是哪一个操作
arg:和cmd配合用的参数
返回值:成功为0,失败-1
cmd组成
-
dir(direction),ioctl 命令访问模式(属性数据传输方向),占据 2 bit,可以为 IOC_NONE、IOC_READ、IOC_WRITE、IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据;
-
type(device type),设备类型,占据 8 bit,在一些文献中翻译为 “幻数” 或者 “魔数”,可以为任意 char 型字符,例如 ‘a’、’b’、’c’ 等等,其主要作用是使 ioctl 命令有唯一的设备标识;
-
nr(number),命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增;
-
size,涉及到 ioctl 函数 第三个参数 arg ,占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度,如果在驱动的 ioctl 实现中不检查,通常可以忽略该参数;
#define _IOC(dir,type,nr,size) (((dir)<<_IOC_DIRSHIFT)| \ ((type)<<_IOC_TYPESHIFT)| \ ((nr)<<_IOC_NRSHIFT)| \ ((size)<<_IOC_SIZESHIFT)) /* used to create numbers */ // 定义不带参数的 ioctl 命令 #define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0) //定义带读参数的ioctl命令(copy_to_user) size为类型名 #define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size))) //定义带写参数的 ioctl 命令(copy_from_user) size为类型名 #define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size))) //定义带读写参数的 ioctl 命令 size为类型名 #define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size))) /* used to decode ioctl numbers */ #define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK) #define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK) #define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK) #define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
对ioctl的使用起始就是熟练使用这些宏定义,在最后奉上一个基本的ioctl实现
二、多个次设备的支持
每一个具体设备(次设备不一样的设备),必须有一个struct cdev来代表它
cdev_init
cdev.owner赋值
cdev_add
以上三个操作对每个具体设备都要进行
三、ioctl的基本实现
.h文件:
#ifndef MY_CHAR_H
#define MY_CHAR_H
#include <asm/ioctl.h>
#define MY_CHAR_MAGIC 'a' //定义的幻数,可以使任意字母,用来做标识
#define MYCHAR_IOCTL_GET_MAXLEN _IOR(MY_CHAR_MAGIC,1,int *) //ioctl所实现的功能,获取我们定义的设备中一个数组的最大长度
#define MYCHAR_IOCTL_GET_CURLEN _IOR(MY_CHAR_MAGIC,2,int *)//获取这个数组当前的长度
#endif
.c文件:
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<asm/uaccess.h>
#include"mychar.h"
#define BUF_LEN 100
int major = 11;//主设备号
int minor = 0; //次设备号
int mychar_num = 1;//设备数量
struct mychar_dev{
struct cdev mydev;//自定义字符驱动结构体
char mydev_buf[BUF_LEN];//自定义的数组
int curlen ;/*被读取区域字节数量,也就是数组当前长度*/
}gmydev;
//驱动设备被打开后执行的程序
int mychar_open(struct inode *pnode,struct file *pfile){
pfile->private_data = (void *)(container_of(pnode->i_cdev,struct mychar_dev,mydev));
printk("open \n");
return 0;
}
int mychar_close(struct inode *pnode,struct file *pfile){
pfile->private_data = (void *)(container_of(pnode->i_cdev,struct mychar_dev,mydev));
printk("close \n");
return 0;
}
//字符驱动设备的读操作,在此程序中实现读取自定义的数组中的内容
ssize_t mychar_read(struct file *pfile, char __user *puser,size_t count,loff_t *p_pos){
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
int size = 0;
int ret = 0;
if(pmydev->curlen < count)
size = pmydev->curlen;
else
size = count;
ret = copy_to_user(puser,pmydev->mydev_buf,size);
if(ret){
printk("copy_to_user failed\n");
return -1;
}
memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen - size);
pmydev->curlen -= size;
return size;
}
//向自定义的数组中写数据
ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count,loff_t *p_pos){
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
int size = 0;
if (count > BUF_LEN - pmydev->curlen)
size = BUF_LEN - pmydev->curlen;
else
size = count;
int ret = 0;
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);
if(ret){
printk("copy_from_user failed\n");
return -1;
}
pmydev->curlen += size;
return size;
}
long mychar_ioctl(struct file *pfile,unsigned int cmd,unsigned long arg){
int __user *pret = (int *)arg;
int maxlen = BUF_LEN;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
switch(cmd){
case MYCHAR_IOCTL_GET_MAXLEN:
ret = copy_to_user(pret,&maxlen,sizeof(int));
if(ret){
printk("copy_to_user failed\n");
return -1;
}
break;
case MYCHAR_IOCTL_GET_CURLEN:
ret = copy_to_user(pret,&pmydev->curlen,sizeof(int));
if(ret){
printk("copy_to_user failed\n");
return -1;
}
break;
default:
printk("cmd unknow\n");
return -1;
}
}
struct file_operations myops = {
.owner = THIS_MODULE,//该结构体对象属于哪个内核模块
.open = mychar_open,//打开设备
.release = mychar_close,//关闭设备
.read = mychar_read,//读操作
.write = mychar_write,//写操作
.unlocked_ioctl = mychar_ioctl, //ioctl操作
};
int __init mychar_init(void){
/* 将主设备号和次设备号组合成32位完整的设备号*/
dev_t devno = MKDEV(major,minor);
int ret = 0;
//申请设备号,方式为手动申请
ret = register_chrdev_region(devno,mychar_num,"mychar");
//手动申请失败则使用动态申请
if(ret != 0){
ret = alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
if(ret != 0){
printk("get chrdev failed\n");
return -1;
}
major = MAJOR(devno);//自动申请的主设备号需要重新赋值
}
//给struct cdev对象制定操作函数集
cdev_init(&gmydev.mydev,&myops);
//将其添加到内核对应的数据结构里
gmydev.mydev.owner = THIS_MODULE;
//将指定的字符设备添加到内核中
cdev_add(&gmydev.mydev,devno,mychar_num);
}
void __exit mychar_exit(void){
//获取设备的32位设备号
dev_t devno = MKDEV(major,minor);
//移除对应的设备
cdev_del(&gmydev.mydev);
//将申请的设备号释放
unregister_chrdev_region(devno,mychar_num);
}
module_init(mychar_init);
module_exit(mychar_exit);