1、简介
内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。
(1)register_chrdev 比较老的内核注册的形式 早期的驱动
(2)register_chrdev_region/alloc_chrdev_region + cdev 新的驱动形式
区别:register_chrdev()函数是老版本里面的设备号注册函数,可以实现静态和动态注册两种方法,主要是通过给定的主设备号是否为0来进行区别,为0的时候为动态注册。register_chrdev_region以及alloc_chrdev_region就是将上述函数的静态和动态注册设备号进行了拆分的强化。
1.2 使用流程
字符驱动cdev注册流程
1. 申请并注册主从设备号
2. 初始化已定义的cdev变量,cdev变量指定file_operations接口
3. 添加cdev变量到内核,完成驱动注册,添加cdev时需要一个已申请成功的主从设备号
字符驱动cdev注销流程
4. 删除已添加的cdev
5. 注销申请的主从设备号
1.3 主要方法
//动态分配设备编号
int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);
dev:自动分配的主设备号将保存在dev中
firstminor:第一个次设备号 一般为0
count:要分配的设备数量
name:设备名
//指定一个主设备号和一个起始从设备号,如果成功返回0 失败返回错误码
dev_t first = MKDEV(int major, int minor);
int register_chrdev_region(dev_t first, unsigned int count, char *name);
First :要分配的设备编号范围的初始值, 这组连续设备号的起始设备号, 相当于register_chrdev()中主设备号
Count:连续编号范围. 是这组设备号的大小(也是次设备号的个数)
Name:编号相关联的设备名称. (/proc/devices); 本组设备的驱动名称
//注销设备
void unregister_chrdev_region(dev_t from,unsigned count)
//初始化cdev变量,并设置fops
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
//添加cdev到linux内核,完成驱动注册
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
dev:申请好的主设备号+起始从设备号
count:为该驱动所占用从设备号的数目
//从内核中删除cdev数据
void cdev_del(struct cdev *p)
demo
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
/*声明class_create 和device_create相关信息*/
#include <linux/device.h>
//cdev用到的头文件
#include <linux/cdev.h>
#define DEMO_DEBUG
#ifdef DEMO_DEBUG
#define dem_dbg(fmt, arg...) printk(KERN_WARNING fmt, ##arg)
#else
#define dem_dbg(fmt, arg...) printk(KERN_DEBUG fmt, ##arg)
#endif
#define DEVICE_COUNT 1
//1 定义cdev结构体变量和dev_nr主从设备号变量
static struct cdev demo_cdev;
static dev_t dev_nr;
static int demo_open (struct inode *pnode, struct file *filp)
{
dem_dbg("==>%s major: %d minor: %d\n",
__FUNCTION__, imajor(pnode), iminor(pnode));
return 0;
}
static ssize_t demo_read (struct file *filp, char __user *buf, size_t count, loff_t *offp)
{
unsigned char ary[100] = "read successfully\n";
unsigned long len = min(count, sizeof(ary));
int retval;
printk("==>%s major: %d minor: %d\n",
__FUNCTION__, imajor(filp->f_dentry->d_inode),
iminor(filp->f_dentry->d_inode));
if(copy_to_user(buf, ary, len) != 0){
retval = -EFAULT;
goto cp_err;
}
return len;
cp_err:
return retval;
}
static int demo_release (struct inode *pnode, struct file *filp)
{
dem_dbg("==>%s major: %d minor: %d\n",
__FUNCTION__, imajor(pnode), iminor(pnode));
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.read = demo_read,
.open = demo_open,
.release = demo_release,
};
static struct class *demo_class;
static int __init demo_init(void)
{
int res, i;
struct device *demo_device;
dem_dbg("==>demo_init\n");
//2.1 动态申请主从设备号
res = alloc_chrdev_region(&dev_nr, 0, DEVICE_COUNT, "demo_chrdev");
//2.2 指定主从设备号
//dev_nr = MKDEV(200, 0);
//res = register_chrdev_region(dev_nr, DEVICE_COUNT, "demo_cdev");
if(res){
dem_dbg("==>alloc chrdev region failed!\n");
goto chrdev_err;
}
//3 初始化cdev数据
cdev_init(&demo_cdev, &fops);
//4 添加cdev变量到内核,完成驱动注册
res = cdev_add(&demo_cdev, dev_nr, DEVICE_COUNT);
if(res){
dem_dbg("==>cdev add failed!\n");
goto cdev_err;
}
//创建设备类
demo_class = class_create(THIS_MODULE,"demo_class");
if(IS_ERR(demo_class)){
res = PTR_ERR(demo_class);
goto class_err;
}
for(i=0; i<DEVICE_COUNT; i++){
//创建设备节点
demo_device = device_create(demo_class,NULL, MKDEV(MAJOR(dev_nr), i), NULL,"demo%d",i);
if(IS_ERR(demo_device)){
res = PTR_ERR(demo_device);
goto device_err;
}
}
return 0;
device_err:
while(i--)
device_destroy(demo_class,MKDEV(MAJOR(dev_nr), i));
class_destroy(demo_class);
class_err:
cdev_del(&demo_cdev);
cdev_err:
unregister_chrdev_region(dev_nr, DEVICE_COUNT);
chrdev_err:
//申请主设备号失败
return res;
}
static void __exit demo_exit(void)
{
int i;
dem_dbg("==>demo_exit\n");
//5 删除添加的cdev结构体,并释放申请的主从设备号
cdev_del(&demo_cdev);
unregister_chrdev_region(dev_nr, DEVICE_COUNT);
for(i=0; i<DEVICE_COUNT; i++)
device_destroy(demo_class,MKDEV(MAJOR(dev_nr), i));
class_destroy(demo_class);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("Dual BSD/GPL");