#include <linux/module.h>
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/errno.h>
#include<linux/sched.h>
#include<linux/init.h>
#include<linux/cdev.h>
#include<asm/io.h>
#include<asm/uaccess.h>
#include<linux/timer.h>
#include<asm/atomic.h>
#include<linux/slab.h>
#include<linux/device.h>
#include<asm/ioctl.h>
#define CDEVDEMO_MAJOR 255 //一个宏定义 主设备号
static int cdevdemo_major = CDEVDEMO_MAJOR;
static struct class *cdevdemo_class; //为了在release中都destory,所以这里将其定义为一个全局变量
int private_data = 0;
struct cdevdemo_dev //这里将structr cdev放在了另外的一个结构体中,是为了迎合最新写法。也可以不这样做,直接定义为structcdev
{
struct cdev cdev;
};
struct cdevdemo_dev *cdevdemo_devp; //定义了一个指针变量,后面要用kmalloc动态分配内存
int cdevdemo_open(struct inode *inode,struct file *filp) //打开一个字符设备,并传入参数inode和filp;在/dev下每个节点都有一个inode,file在
系统空间调用open时就自动创建,并且传递给每一个操作函数;
{
filp->private_data=&private_data; //通过filp中的private_data来保存自己的data;
printk(KERN_NOTICE "cdevdemo_open");
return 0;
}
int cdevdemo_release(struct inode *inode,struct file *filp) //release函数
{
printk(KERN_NOTICE "cdevdemo_release");
return 0;
}
static ssize_t cdevdemo_read(struct file *filp,char *buf,size_t count,loff_t *ppos) //传入filp ,用户控件的buf地址,读数据的长度
{
int *data = filp->private_data;
if(copy_to_user(buf,data,sizeof(int))) //用户空间从内核空间读数据通过这个函数,从内核空间读一个int长度的data存入到用户空间的buf中
{
return -EFAULT;
}
printk(KERN_NOTICE"cdevdemo_read");
return sizeof(int); //返回读取的字节个数
}
static ssize_t cdevdemo_write(struct file *filp,char *buf,size_t count,loff_t *ppos) //传入filp,用户空间的buf以及写的字节数
{
int *data = filp->private_data;
if(copy_from_user(data,buf,sizeof(int))) //向内核空间写数据只能通过这个函数,从用户空间的buf中向内核空间写一个int长度的data
{
return -EFAULT;
}
printk(KERN_NOTICE"cdevdemo_write");
return sizeof(int);//返回写的字节数
}
static long cdevdemo_ioctl(struct file *filp,unsigned int cmd,unsigned long arg) //ioctl函数 通过用户空间传入的cmd来判断执行哪种操作
{
int *data = filp->private_data;
switch(cmd)
{
case 0:printk("ioctl:CDEV_HELLO\n");
break;
case 1:printk("ioctl:CDEV_BYE\n");
break;
case 2:*data=(int)arg;
break;
case 3:return *data;
break;
default:printk("unknow command\n");
}
}
static const struct file_operations cdevdemo_fops= //内核就是通过这个结构体来找到具体的函数操作
{
.owner = THIS_MODULE, //意思是这个file_operation指向这个模块 几乎都是这种写法
.open = cdevdemo_open,
.release = cdevdemo_release,
.read = cdevdemo_read,
.write = cdevdemo_write,
.unlocked_ioctl=cdevdemo_ioctl,
};
//下面这个函数中基本完成了对cdev的接口调用 从cdev_init-->cdev_add;(注册-->添加),在cdev_init之前要进行cdev内存的分配(在cdevdemo_init中完成)
static void cdevdemo_setup_cdev(struct cdevdemo_dev *dev,int index) //传入含有cdev结构体的指针,以及从设备号;
{
printk(KERN_NOTICE"cdevdemo_setup_cdev 1");
int err,devno = MKDEV(cdevdemo_major,index); //通过MKDEV(主设备号,从设备号)来获取设备号
printk(KERN_NOTICE"cdevdemo_setup_cdev 2");
cdev_init(&dev->cdev,&cdevdemo_fops); //cdev初始化,完成注册
printk(KERN_NOTICE"cdevdemo_setup_cdev 3");
dev->cdev.owner = THIS_MODULE; //指向本模块
dev->cdev.ops = &cdevdemo_fops; //将file_operation的指针赋给cdev结构体的ops
printk(KERN_NOTICE"cdevdemo_setup_cdev 4");
err = cdev_add(&dev->cdev,devno,1); //向内核空间添加cdev,注意传入cdev指针和设备号;添加成功后,可以通过设备号找到cdev,
从而找到file_operation;
printk(KERN_NOTICE "cdevdemo_setup_cdev 5");
if(err)
{
printk(KERN_NOTICE "Error %d add cdevdemo %d",err,index);
}
}
int cdevdemo_init(void)
{
printk(KERN_NOTICE "cdevdemo_init");
int ret;
dev_t devno = MKDEV(cdevdemo_major,0); //获取设备号
if(cdevdemo_major) //判断主设备号是否被占用,若没占用就静态注册设备号,若占用就动态注册设备号
{
printk(KERN_NOTICE"cdevdemo_init 1");
ret = register_chrdev_region(devno,1,"cdevdemo"); //若静态注册失败(可能被占用),再动态注册
}
else
{
printk(KERN_NOTICE"cdevmo_init 2");
ret = alloc_chrdev_region(&devno,0,1,"cdevdemo"); //静态注册失败,动态注册
cdevdemo_major = MAJOR(devno); //获取动态注册设备号的主设备号
}
if(ret < 0)//判断设备号是否注册失败
{
printk(KERN_NOTICE"cdevdemo_init 3");
return ret;
}
cdevdemo_devp = kmalloc(sizeof(struct cdevdemo_dev),GFP_KERNEL); //动态分配结构体的内存(前面定义的时指针变量)
if(!cdevdemo_devp) //判断是否分配失败
{
ret = -ENOMEM;
printk(KERN_NOTICE"Error add cdevdemo");
goto fail_malloc;
}
memset(cdevdemo_devp,0,sizeof(struct cdevdemo_dev)); //内存空间初始化,初始化结构体里的每一个变量
printk(KERN_NOTICE"cdevdemo_init 3");
cdevdemo_setup_cdev(cdevdemo_devp,0);
cdevdemo_class = class_create(THIS_MODULE,"cdevdemoo"); //创建一个类
device_create(cdevdemo_class,NULL,MKDEV(cdevdemo_major,0),NULL,"cdevdemoo"); //创建一个节点
printk(KERN_NOTICE"cdevdemo_init 4");
return 0;
fail_malloc:
unregister_chrdev_region(devno,1);
cdev_del(&cdevdemo_devp->cdev);
}
static void cdevdemo_exit(void)//退出时进行一些资源的释放
{
printk(KERN_NOTICE"End cdevdemo");
dev_t devno = MKDEV(cdevdemo_major,0);
cdev_del(&cdevdemo_devp->cdev); //释放字符设备结构体
device_destroy(cdevdemo_class,devno); //销毁节点
class_destroy(cdevdemo_class);// 销毁class类
kfree(cdevdemo_devp); // 释放结构
unregister_chrdev_region(MKDEV(cdevdemo_major,0),1); //释放设备号
}
MODULE_LICENSE("Dual BSD/GPL");
module_param(cdevdemo_major,int,S_IRUGO);
module_init(cdevdemo_init); //驱动入口 动态加载这个函数时 第一个执行的函数
module_exit(cdevdemo_exit);//模块卸载时 最后执行的函数
总结:首先申请主设备号(动态申请或静态申请),通过主设备号和次设备号产生设备号。再通过cdev_init函数完成cdev的注册,再向内核添加注册过的cdev的指针和设备号。通过 device_create函数创建一个节点,创建节点时要将设备号传入。这样以来,创建了节点就能在/dev目录下找到该设备节点,由于设备节点和设备号绑定,cdev结构体和设备号绑定,通过设备节点能找到设备号,再从设备号能找到cdev结构体,cdev结构里定义了file_operations指针,从而完成相应的读写工作。
Makefile文件
ifneq ($(KERNELRELEASE),)
obj-m := mydecice.o
else
KERNELDIR ?= /usr/src/kernels/3.10.0-514.16.1.el7.x86_64
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm -rf *.o *~core .depend .*.cmd *.ko *.mod.c .tmp_version modules.order Module.symvers
测试程序
int main()
{
int fd;
int old_data;
int new_data;
if(-1!=(fd=open("/dev/cdevdemoo",O_RDWR)))
{
if(-1==read(fd,&old_data,sizeof(int)))
{
printf("read error\n");
return -1;
}
else
{
printf("old data is %d\n",old_data);
}
new_data = ++ old_data %3;
if(-1==write(fd,&new_data,sizeof(int)))
{
printf("write error\n");
return -1;
}
if(-1==read(fd,&new_data,sizeof(int)))
{
printf("read error\n");
return -1;
}
else
{
printf("new data is %d\n",new_data);
}
ioctl(fd,0,0);
ioctl(fd,1,0);
ioctl(fd,2,90);
new_data = ioctl(fd,3,0);
printf("ioctl : new data is %d\n",new_data);
ioctl(fd,4,0);
}
else
{
perror("open error\n");
return -1;
}
close(fd);
return 0;
}
测试
1 make(生成ko文件)
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/errno.h>
#include<linux/sched.h>
#include<linux/init.h>
#include<linux/cdev.h>
#include<asm/io.h>
#include<asm/uaccess.h>
#include<linux/timer.h>
#include<asm/atomic.h>
#include<linux/slab.h>
#include<linux/device.h>
#include<asm/ioctl.h>
#define CDEVDEMO_MAJOR 255 //一个宏定义 主设备号
static int cdevdemo_major = CDEVDEMO_MAJOR;
static struct class *cdevdemo_class; //为了在release中都destory,所以这里将其定义为一个全局变量
int private_data = 0;
struct cdevdemo_dev //这里将structr cdev放在了另外的一个结构体中,是为了迎合最新写法。也可以不这样做,直接定义为structcdev
{
struct cdev cdev;
};
struct cdevdemo_dev *cdevdemo_devp; //定义了一个指针变量,后面要用kmalloc动态分配内存
int cdevdemo_open(struct inode *inode,struct file *filp) //打开一个字符设备,并传入参数inode和filp;在/dev下每个节点都有一个inode,file在
系统空间调用open时就自动创建,并且传递给每一个操作函数;
{
filp->private_data=&private_data; //通过filp中的private_data来保存自己的data;
printk(KERN_NOTICE "cdevdemo_open");
return 0;
}
int cdevdemo_release(struct inode *inode,struct file *filp) //release函数
{
printk(KERN_NOTICE "cdevdemo_release");
return 0;
}
static ssize_t cdevdemo_read(struct file *filp,char *buf,size_t count,loff_t *ppos) //传入filp ,用户控件的buf地址,读数据的长度
{
int *data = filp->private_data;
if(copy_to_user(buf,data,sizeof(int))) //用户空间从内核空间读数据通过这个函数,从内核空间读一个int长度的data存入到用户空间的buf中
{
return -EFAULT;
}
printk(KERN_NOTICE"cdevdemo_read");
return sizeof(int); //返回读取的字节个数
}
static ssize_t cdevdemo_write(struct file *filp,char *buf,size_t count,loff_t *ppos) //传入filp,用户空间的buf以及写的字节数
{
int *data = filp->private_data;
if(copy_from_user(data,buf,sizeof(int))) //向内核空间写数据只能通过这个函数,从用户空间的buf中向内核空间写一个int长度的data
{
return -EFAULT;
}
printk(KERN_NOTICE"cdevdemo_write");
return sizeof(int);//返回写的字节数
}
static long cdevdemo_ioctl(struct file *filp,unsigned int cmd,unsigned long arg) //ioctl函数 通过用户空间传入的cmd来判断执行哪种操作
{
int *data = filp->private_data;
switch(cmd)
{
case 0:printk("ioctl:CDEV_HELLO\n");
break;
case 1:printk("ioctl:CDEV_BYE\n");
break;
case 2:*data=(int)arg;
break;
case 3:return *data;
break;
default:printk("unknow command\n");
}
}
static const struct file_operations cdevdemo_fops= //内核就是通过这个结构体来找到具体的函数操作
{
.owner = THIS_MODULE, //意思是这个file_operation指向这个模块 几乎都是这种写法
.open = cdevdemo_open,
.release = cdevdemo_release,
.read = cdevdemo_read,
.write = cdevdemo_write,
.unlocked_ioctl=cdevdemo_ioctl,
};
//下面这个函数中基本完成了对cdev的接口调用 从cdev_init-->cdev_add;(注册-->添加),在cdev_init之前要进行cdev内存的分配(在cdevdemo_init中完成)
static void cdevdemo_setup_cdev(struct cdevdemo_dev *dev,int index) //传入含有cdev结构体的指针,以及从设备号;
{
printk(KERN_NOTICE"cdevdemo_setup_cdev 1");
int err,devno = MKDEV(cdevdemo_major,index); //通过MKDEV(主设备号,从设备号)来获取设备号
printk(KERN_NOTICE"cdevdemo_setup_cdev 2");
cdev_init(&dev->cdev,&cdevdemo_fops); //cdev初始化,完成注册
printk(KERN_NOTICE"cdevdemo_setup_cdev 3");
dev->cdev.owner = THIS_MODULE; //指向本模块
dev->cdev.ops = &cdevdemo_fops; //将file_operation的指针赋给cdev结构体的ops
printk(KERN_NOTICE"cdevdemo_setup_cdev 4");
err = cdev_add(&dev->cdev,devno,1); //向内核空间添加cdev,注意传入cdev指针和设备号;添加成功后,可以通过设备号找到cdev,
从而找到file_operation;
printk(KERN_NOTICE "cdevdemo_setup_cdev 5");
if(err)
{
printk(KERN_NOTICE "Error %d add cdevdemo %d",err,index);
}
}
int cdevdemo_init(void)
{
printk(KERN_NOTICE "cdevdemo_init");
int ret;
dev_t devno = MKDEV(cdevdemo_major,0); //获取设备号
if(cdevdemo_major) //判断主设备号是否被占用,若没占用就静态注册设备号,若占用就动态注册设备号
{
printk(KERN_NOTICE"cdevdemo_init 1");
ret = register_chrdev_region(devno,1,"cdevdemo"); //若静态注册失败(可能被占用),再动态注册
}
else
{
printk(KERN_NOTICE"cdevmo_init 2");
ret = alloc_chrdev_region(&devno,0,1,"cdevdemo"); //静态注册失败,动态注册
cdevdemo_major = MAJOR(devno); //获取动态注册设备号的主设备号
}
if(ret < 0)//判断设备号是否注册失败
{
printk(KERN_NOTICE"cdevdemo_init 3");
return ret;
}
cdevdemo_devp = kmalloc(sizeof(struct cdevdemo_dev),GFP_KERNEL); //动态分配结构体的内存(前面定义的时指针变量)
if(!cdevdemo_devp) //判断是否分配失败
{
ret = -ENOMEM;
printk(KERN_NOTICE"Error add cdevdemo");
goto fail_malloc;
}
memset(cdevdemo_devp,0,sizeof(struct cdevdemo_dev)); //内存空间初始化,初始化结构体里的每一个变量
printk(KERN_NOTICE"cdevdemo_init 3");
cdevdemo_setup_cdev(cdevdemo_devp,0);
cdevdemo_class = class_create(THIS_MODULE,"cdevdemoo"); //创建一个类
device_create(cdevdemo_class,NULL,MKDEV(cdevdemo_major,0),NULL,"cdevdemoo"); //创建一个节点
printk(KERN_NOTICE"cdevdemo_init 4");
return 0;
fail_malloc:
unregister_chrdev_region(devno,1);
cdev_del(&cdevdemo_devp->cdev);
}
static void cdevdemo_exit(void)//退出时进行一些资源的释放
{
printk(KERN_NOTICE"End cdevdemo");
dev_t devno = MKDEV(cdevdemo_major,0);
cdev_del(&cdevdemo_devp->cdev); //释放字符设备结构体
device_destroy(cdevdemo_class,devno); //销毁节点
class_destroy(cdevdemo_class);// 销毁class类
kfree(cdevdemo_devp); // 释放结构
unregister_chrdev_region(MKDEV(cdevdemo_major,0),1); //释放设备号
}
MODULE_LICENSE("Dual BSD/GPL");
module_param(cdevdemo_major,int,S_IRUGO);
module_init(cdevdemo_init); //驱动入口 动态加载这个函数时 第一个执行的函数
module_exit(cdevdemo_exit);//模块卸载时 最后执行的函数
总结:首先申请主设备号(动态申请或静态申请),通过主设备号和次设备号产生设备号。再通过cdev_init函数完成cdev的注册,再向内核添加注册过的cdev的指针和设备号。通过 device_create函数创建一个节点,创建节点时要将设备号传入。这样以来,创建了节点就能在/dev目录下找到该设备节点,由于设备节点和设备号绑定,cdev结构体和设备号绑定,通过设备节点能找到设备号,再从设备号能找到cdev结构体,cdev结构里定义了file_operations指针,从而完成相应的读写工作。
Makefile文件
ifneq ($(KERNELRELEASE),)
obj-m := mydecice.o
else
KERNELDIR ?= /usr/src/kernels/3.10.0-514.16.1.el7.x86_64
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm -rf *.o *~core .depend .*.cmd *.ko *.mod.c .tmp_version modules.order Module.symvers
测试程序
int main()
{
int fd;
int old_data;
int new_data;
if(-1!=(fd=open("/dev/cdevdemoo",O_RDWR)))
{
if(-1==read(fd,&old_data,sizeof(int)))
{
printf("read error\n");
return -1;
}
else
{
printf("old data is %d\n",old_data);
}
new_data = ++ old_data %3;
if(-1==write(fd,&new_data,sizeof(int)))
{
printf("write error\n");
return -1;
}
if(-1==read(fd,&new_data,sizeof(int)))
{
printf("read error\n");
return -1;
}
else
{
printf("new data is %d\n",new_data);
}
ioctl(fd,0,0);
ioctl(fd,1,0);
ioctl(fd,2,90);
new_data = ioctl(fd,3,0);
printf("ioctl : new data is %d\n",new_data);
ioctl(fd,4,0);
}
else
{
perror("open error\n");
return -1;
}
close(fd);
return 0;
}
测试
1 make(生成ko文件)
2 insmod (动态加载模块)
3 lsmod | less (查看加载的模块)
4 cd /dev 可以查看到创建的节点(cdevdemo)
5 dmesg 产看内核打印信息
6 编译测试程序 直接gcc即可
7 执行二进制测试程序
可以看到已经完成了 read、write、ioctl操作。
8 dmesg产看内核消息
9 rmmod 卸载内核模块