似乎每一章介绍的内容比较少,但学习是一个循序渐进的过程,不在于一天学多少,重要的一天能真正的学懂多少,所以我主张一步一步来,从多个渠道去学习知识,实现互补。
本节测试代码传到此处了:char_step1 大家可以下载测试一下。
字符设备的注册与设备节点的自动创建
cdev 结构
内核内部使用struct cdev<linux/cdev.h>来表示一个字符设备
struct cdev {
struct kobject kobj; //kobj设备模型以后介绍
struct module *owner;
const struct file_operations *ops;//文件操作
struct list_head list;
dev_t dev;//设备号
unsigned int count; //设备个数
};
struct kobject kobj; //kobj设备模型以后介绍
struct module *owner;
const struct file_operations *ops;//文件操作
struct list_head list;
dev_t dev;//设备号
unsigned int count; //设备个数
};
注册字符设备
动态初始化:
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_ops;
my_cdev->ops = &my_ops;
静态初始化:
struct cdev my_cdev;
cdev_init(&my_dev, &my_ops);
cdev_init(&my_dev, &my_ops);
向内核注册(添加设备):
int cdev_add(struct cdev *dev, dev_t num, unsigned count);
dev:
指向以初始化的字符设备结构
num: 设备号
count: 所要添加的设备个数
返回值:
成功返回0 失败返回错误码,设备被注册时候可能立即会呗调用,所以在驱动程序还没有准备好处理设备操作时候不要先调用cdev_add
注销字符设备:
在不需要用西设备时候需要注销设备
void cdev_del(struct cdev *);
早期的方法
在2.6的内核之前注册字符设备有一个简单易用的方法,现在也可以使用但是不建议使用,因为到最后这个方法会从内核消失注册字符设备:
static inline int register_chrdev(unsigned int major,
const char *name, const struct file_operations *fops);
major:
主设备号 (当major为0时表示有内核分配一个可用的主设备号并返回)
const char *name, const struct file_operations *fops);
name: 驱动名
fops: 文件操作
缺点: 不能同时注册多个设备, 他是用一种默认的方式去建立一个cdev结构,而这样的话它不能使用大于255的主次设备号
注销设备:
static inline void unregister_chrdev(unsigned int major, const char *name)
设备节点的自动创建
在上一个程序中,虽然能够争取的得到一个设备号,但是并不会在/dev/目录下生成一个设备节点。使用手动创建
mknod /dev/device_name c major minor
device_name:
指设备名,
c: 表述创建字符设备节点(同理b 表示创建块设备文件节点)
major: 主设备号
minor: 次设备号
在做测试时候这样做事可以理解的,而在作为一个模块要发行时候,这样就太不方面了,①要先查看设备是否成功创建,②查看注册设备的主设备号,③输入命令创建文件节点
当然可以写一个shell脚本来完成这些事情。
自动创建设备节点
需要的纤体内核空间支持udev,在内核配置,及busybox配置时候需要指定内核中定义了struct class 与 struct devic结构<linux/device.h>
创建一个class结构
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
owner表示拥有这个模块的指针
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
name 表示设备名
调用此函数后 创建了一个struct class结构放在sysfs下边,而后调用
struct device *device_create(struct class *cls,
struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...)
在/dev目录下创建设备节点
struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...)
cls : 是上边函数的创建的struct class结构指针
parent: 指向设备的父节点设备(如果没有填NULL
devt: 被添加设备的设备号
drvdata: 回调函数参数
fmt ,args:一个或者多个设备名
至此我们就自动创建了一个设备节点
删除设备节点
在设备不需要使用时候我们需要删除设备节点:
void device_destroy(struct class *class, dev_t devt);//删除设备节点
void class_destroy(struct class *cls);//删除sysfs下得struct class结构
至于深层的一些实现机制,没有细细研究,大家干兴趣可以参考相关资料
void class_destroy(struct class *cls);//删除sysfs下得struct class结构
实例:
初始化 添加设备到内核
- simple_cdev = cdev_alloc(); //动态初始化字符设备
- if(simple_cdev != NULL)
- {
- simple_cdev->ops = &simple_fops; //文件操作
- simple_cdev->owner = THIS_MODULE;
- }
- else
- {
- printk(KERN_ERR "alloc cdev err no memory");
- unregister_chrdev_region(dev, DEV_COUNT); //如果分配资源出错则释放设备号
- return -ENOMEM;
- }
- err = cdev_add(simple_cdev, dev, DEV_COUNT); //向内核添加设备
- if(err < 0)
- {
- printk(KERN_ERR "add cdev err \n");
- goto error1;
- }
- else
- {
- #if SIMPLE_DEBUG
- printk(KERN_INFO "add char dev OK!\n");
- #endif
- }
自动创建设备节点
- simple_class = class_create(THIS_MODULE, SIMPLE_NAME);
- if(simple_class == NULL)
- {
- printk(KERN_ERR "create simple class error\n");
- goto error2;
- }
- else
- {
- #if SIMPLE_DEBUG
- printk(KERN_INFO "create simple class OK!\n");
- #endif
- }
- simple_dev = device_create(simple_class, NULL, dev, NULL, SIMPLE_NAME);
- if(simple_dev == NULL)
- {
- printk(KERN_ERR "create device error");
- goto error3;
- }
- else
- {
- #if SIMPLE_DEBUG
- printk(KERN_INFO "create simple device OK!\n");
- #endif
- }
删除设备节点,注销字符设备
- dev = MKDEV(simple_major, simple_minor); //计算出设备号
- device_destroy(simple_class, dev); //删除设备节点
- class_destroy(simple_class);//删除并释放class结构
- cdev_del(simple_cdev);//注销设备