前面我们学习了杂项设备驱动模型、早期经典字符设备驱动模型,这一小节来讲解Linux中的标准字符设备驱动。
目录
- (一)为什么引入标准字符设备驱动模型
- (二)相关接口
- (三)注册流程
- (四)程序示例
(一)为什么引入标准字符设备驱动模型
前面讲解了杂项设备驱动模型和早期经典设备驱动模型,但是他们都存在不足之处
杂项设备存在的不足: 主设备号固定为10,最多设备为0-255个次设备号,设备号有限
早期经典驱动模型不足: 早期经典的字符设备主设备号为0-255除10外,但是申请一次主设备号,该设备号下的所有次设备号(0-255)均属于同一设备,且不会在申请同时创建节点文件,需要手动创建
出于以上的不足之处,内核中对驱动模型进行了升级,引入了标准字符设备驱动模型
标准字符设备驱动模型: 标准字符设备驱动模型中对设备号进行了数据的规定,规定设备号为一个32位的无符号整型数据
(二)相关接口
(1)int cdev_add(struct cdev *p, dev_t dev, unsigned count)这个函数是添加到字符设备系统中,注册标准字符设备驱动。
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{p->dev = dev;p->count = count;return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}//第一个参数结构体原型
struct cdev {struct kobject kobj; //不关心struct module *owner; //一般赋值为THIS_MODULEconst struct file_operations *ops; //文件操作结构体struct list_head list; //dev_t dev; //设备号unsigned int count; //表示申请设备号的数量
};
(2)struct cdev 结构体可以手动静态初始化,但是我们通常使用动态申请,本篇文章主要介绍动态相关的函数。
struct cdev *cdev_alloc(void);//动态申请struct cdev结构体 的内存void cdev_init(struct cdev *, const struct file_operations *);//初始化struct cdev结构体
(3)删除标准字符设备接口
void cdev_del(struct cdev *);
(4)设备号合成相关的接口
1.自己指定主次设备号,通过MKDEV合成dev_t dev=MKDEV(major,minor);2.静态申请int register_chrdev_region(dev_t from, unsigned count, const char *name)3.动态申请
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
{struct char_device_struct *cd;cd = __register_chrdev_region(0, baseminor, count, name);if (IS_ERR(cd))return PTR_ERR(cd);*dev = MKDEV(cd->major, cd->baseminor);return 0;
}
(5)创建设备节点 device_create
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
{va_list vargs;struct device *dev;va_start(vargs, fmt);dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);va_end(vargs);return dev;
}
(6)设备类struct class 创建
#define class_create(owner, name) \
({ \static struct lock_class_key __key; \__class_create(owner, name, &__key); \
})
(7)标准字符设备驱动注销cdev_del
void cdev_del(struct cdev *p)
{cdev_unmap(p->dev, p->count);kobject_put(&p->kobj);
}
(8)注销设备号unregister_chrdev_region()
void unregister_chrdev_region(dev_t from, unsigned count)
{dev_t to = from + count;dev_t n, next;for (n = from; n < to; n = next) {next = MKDEV(MAJOR(n)+1, 0);if (next > to)next = to;kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));}
}
(9)销毁设备类:
void class_destroy(struct class *cls)
{if ((cls == NULL) || (IS_ERR(cls)))return;class_unregister(cls);
}
(10)销毁设备节点: device_destroy(struct class *class, dev_t devt)
void device_destroy(struct class *class, dev_t devt)
{struct device *dev;dev = class_find_device(class, NULL, &devt, __match_devt);if (dev) {put_device(dev);device_unregister(dev);}
}
(11)释放cdev空间:
static inline void kfree(void *p)
{free(p);
}
(三)注册流程
(1)定义struct cdev 结构体指针
struct cdev *cdev =NULL
(2)申请cdev结构体空间并初始化
cdev = cdev_alloc();//动态申请空间
cdev_init(cdev,&fop);//fop为struct file_operations类型的结构体
(3)申请设备号–动态
dev_t dev=0;
int ret =alloc_chrdev_region(&dev,0, CDEVCOUNT, CDEVNAME);
(4)将字符设备添加到系统
ret =cdev_add(cdev,dev, CDEVCOUNT);
(5)创建设备类
struct class * cdevclass =NULL;//定义设备节点类接构体cdevclass = class_create(THIS_MODULE, INODENAME)
(6)添加设备节点
reate(cdevclass, NULL, dev, NULL, "mydevice",);
(四)程序示例
chrdev.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>#define CDEVCOUNT 5
#define CDEVNAME "cdevdevice"
#define INODENAME "mycdev"
int i=0;
dev_t dev=0;
struct cdev * cdev =NULL;
struct class * cdevclass =NULL;int cdev_open (struct inode *node, struct file *file)
{printk("cdev_open is install\n");return 0;
}
ssize_t cdev_read (struct file *fp, char __user *buf, size_t size, loff_t *offset)
{printk("cdev_read is install\n");return 0;
}
ssize_t cdev_write (struct file *fp, const char __user * buf, size_t size, loff_t *offset)
{printk("cdev_write is install\n");return 0;
}
int cdev_release (struct inode *node, struct file *fp)
{printk("cdev_release is install\n");return 0;
}
struct file_operations fop={.open=cdev_open,.read=cdev_read,.write=cdev_write,.release=cdev_release,
};void mycdev_add()
{//1.申请设备号--动态int ret =alloc_chrdev_region(&dev,0, CDEVCOUNT, CDEVNAME);if(ret)return ;//初始化cdev结构体cdev = cdev_alloc();if(!cdev){goto out; }cdev_init(cdev,&fop);//添加字符设备到系统中ret =cdev_add(cdev,dev, CDEVCOUNT);if(ret){goto out1;}//创建设备类cdevclass = class_create(THIS_MODULE, INODENAME);if(IS_ERR(cdevclass)){goto out2;}
for (i=0;i<CDEVCOUNT;i++)device_create(cdevclass, NULL, dev+i, NULL, "mydevice%d",i);out:unregister_chrdev_region(dev,CDEVCOUNT); return ;out1:unregister_chrdev_region(dev,CDEVCOUNT);kfree(cdev);return ;
out2:cdev_del(cdev);unregister_chrdev_region(dev,CDEVCOUNT);kfree(cdev);return ;
}static int __init dev_module_init(void)
{mycdev_add();printk("this is dev_module_init \n");return 0;
}static void __exit dev_module_cleanup(void)
{device_destroy(cdevclass, dev);class_destroy(cdevclass);cdev_del(cdev);unregister_chrdev_region(dev, CDEVCOUNT);kfree(cdev);printk("this is dev_module_cleanup\n");
}module_init(dev_module_init);
module_exit(dev_module_cleanup);
MODULE_LICENSE("GPL");
Makefile
CFLAG =-C
TARGET = chrdev
TARGET1 = chr_app
KERNEL = /mydriver/linux-3.5
obj-m += $(TARGET).oall:make $(CFLAG) $(KERNEL) M=$(PWD)arm-linux-gcc -o $(TARGET1) $(TARGET1).c
clean:make $(CFLAG) $(KERNEL) M=$(PWD) clean
chr_app.c
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main(int argc, char *argv[])
{int fd= open(argv[1],O_RDWR);if(fd== -1){perror("open");return -1;}write(fd,"hell",4);close(fd);return 0;
}
关于上面的驱动程序都是建立在前面的模块化编程的基础上编写的,不懂的往前看前面的博客就行了,演示一下效果
本文章仅供学习交流用禁止用作商业用途,文中内容来水枂编辑,如需转载请告知,谢谢合作
微信公众号:zhjj0729
微博:文艺to青年