char device driver
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了char device driver相关的知识,希望对你有一定的参考价值。
概览:
第一步:注册设备号 信息#tail -f /var/log/message
注册函数:
register_chrdev_region() 或 查看#lsmod
alloc_chrdev_region() 或 查看#cat /proc/devices
register_chrdev()
注销函数:
unregist_chrdev_region() 或
unregister_chrdev()
第二步:初始化cdev并添加到系统
初始化cdev
静态初始化 cdev_init() 或
动态初始化 cdev_alloc()
添加到系统函数
cdev_add()
从系统删除函数
cdev_del()
第三步:创建设备节点
创建类
class_create() 将放于/sysfs 查看#ls /sys/class
删除类
class_destroy()
创建节点
device_create() 或 class_device_create() 将存放于/dev 查看#ls /dev
删除节点
device_destroy() 或 class_device_destroy()
第四步:简单示例
/***************************************************************************************************
第一步:注册设备号
***************************************************************************************************/
Linux内核中所有已分配的字符设备编号都记录在一个名为 chrdevs 散列表里。
该散列表中的每一个元素是一个 char_device_struct 结构,它的定义如下:
- static struct char_device_struct
- {
- struct char_device_struct *next; // 指向散列冲突链表中的下一个元素的指针
- unsigned int major; // 主设备号
- unsigned int baseminor; // 起始次设备号
- int minorct; // 设备编号的范围大小
- char name[64]; // 处理该设备编号范围内的设备驱动的名称
- struct file_operations *fops; // 没有使用
- struct cdev *cdev; // 指向字符设备驱动程序描述符的指针
- }*chrdevs[CHRDEV_MAJOR_HASH_SIZE];
1 每一个主设备有一个会分配一个此结构,可以有多个次设备号。次设备是依次递增的。
2 内核提供了5个函数来来管理字符设备编号。
register_chrdev_region() 指定初始值
alloc_chrdev_region() 动态分配
register_chrdev() 指定设备号
他们都会调用 __register_chrdev_region() 来注册一组设备编号范围(一个char_device_struct结构),我们使用其中一个即可。
unregist_chrdev_region() 释放都用此函数
unregister_chrdev() 都调用了 __unregister_chrdev_region() 来注销设备
注册:
register_chrdev_region(dev_t first,unsigned int count,char *name)
first :要分配的设备编号范围的初始值(次设备号常设为0);
count :连续编号范围.
Name :编号相关联的设备名称. (/proc/devices);
int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);
*dev :存放返回的设备号
firstminor :第一个次设备号的号数,常为0;
int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
major :要注册的设备号, 若为0则自动分配一个
name :设备名
*fops :以后再聊
释放:
void unregister_chrdev(unsigned int major, const char *name);
void unregister_chrdev_region(dev_t from, unsigned count);
3 详细分析
- /**
- * register_chrdev_region() - register a range of device numbers
- * @from: the first in the desired range of device numbers; must include
- * the major number.
- * @count: the number of consecutive device numbers required
- * @name: the name of the device or driver.
- *
- * Return value is zero on success, a negative error code on failure.
- */
- int register_chrdev_region(dev_t from, unsigned count, const char *name)
- {
- struct char_device_struct *cd;
- dev_t to = from + count;
- dev_t n, next;
- /**
- 这个for循环主要是判断次设备号是否超过下一个主设备号。
- 如果超过(next<to)那么就只申请next-n个次设备号,也就是说,只申请一个主设备号对应的最大的次设备号的数量。
- 如果没有超过(next>to),那么就是申请用户穿过来的次设备数量,还是next-n,但是这个next=to的
- 其实这里可以不用for循环写的,这里的for循环也只执行了一次
- 还就是char dev主设备号和次设备号的大小又一个宏来控制,有8|8型也有12|20型
- */
- for (n = from; n < to; n = next) {
- next = MKDEV(MAJOR(n)+1, 0);
- if (next > to)
- next = to;
- cd = __register_chrdev_region(MAJOR(n), MINOR(n),
- next - n, name);
- if (IS_ERR(cd))
- goto fail;
- }
- return 0;
- fail:
- to = n;
- for (n = from; n < to; n = next) {
- next = MKDEV(MAJOR(n)+1, 0);
- kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
- }
- return PTR_ERR(cd);
- }
- /*
- * Register a single major with a specified minor range.
- *
- * If major == 0 this functions will dynamically allocate a major and return
- * its number.
- *
- * If major > 0 this function will attempt to reserve the passed range of
- * minors and will return zero on success.
- *
- * Returns a -ve errno on failure.
- */
- static struct char_device_struct *<span style="font-size:12px;"><span style="font-family:SimSun;"> </span></span>__register_chrdev_region(unsigned int major, unsigned int baseminor,int minorct, const char *name)
- {
- struct char_device_struct *cd, **cp;
- int ret = 0;
- int i;
- cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
- if (cd == NULL)
- return ERR_PTR(-ENOMEM);
- mutex_lock(&chrdevs_lock);
- /* temporary */
- if (major == 0) {
- for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
- if (chrdevs[i] == NULL)
- break;
- }
- if (i == 0) {
- ret = -EBUSY;
- goto out;
- }
- major = i;
- ret = major;
- }
- cd->major = major;
- cd->baseminor = baseminor;
- cd->minorct = minorct;
- strlcpy(cd->name, name, sizeof(cd->name));
- i = major_to_index(major);
- for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
- if ((*cp)->major > major ||
- ((*cp)->major == major &&
- (((*cp)->baseminor >= baseminor) ||
- ((*cp)->baseminor + (*cp)->minorct > baseminor))))
- break;
- /* Check for overlapping minor ranges. */
- if (*cp && (*cp)->major == major) {
- int old_min = (*cp)->baseminor;
- int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
- int new_min = baseminor;
- int new_max = baseminor + minorct - 1;
- /* New driver overlaps from the left. */
- if (new_max >= old_min && new_max <= old_max) {
- ret = -EBUSY;
- goto out;
- }
- /* New driver overlaps from the right. */
- if (new_min <= old_max && new_min >= old_min) {
- ret = -EBUSY;
- goto out;
- }
- }
- cd->next = *cp;
- *cp = cd;
- mutex_unlock(&chrdevs_lock);
- return cd;
- out:
- mutex_unlock(&chrdevs_lock);
- kfree(cd);
- return ERR_PTR(ret);
- }
函数 __register_chrdev_region() 主要执行以下步骤:
1. 分配一个新的 char_device_struct 结构,并用 0 填充。
2. 如果申请的设备编号范围的主设备号为 0,那么表示设备驱动程序请求动态分配一个主设备号。动态分配主设备号的原则是从散列表的最后一个桶向前寻找,那个桶是空的,主设备号就是相应散列桶的序号。所以动态分配的主设备号总是小于 256,如果每个桶都有字符设备编号了,那动态分配就会失败。
3. 根据参数设置 char_device_struct 结构中的初始设备号,范围大小及设备驱动名称。
4. 计算出主设备号所对应的散列桶,为新的 char_device_struct 结构寻找正确的位置。同时,如果设备编号范围有重复的话,则出错返回。
5. 将新的 char_device_struct 结构插入散列表中,并返回 char_device_struct 结构的地址。
所有的字符设备驱动描述都放在chrdevs这个数组中。
static struct char_device_struct {
struct char_device_struct *next;
unsigned int major;
unsigned int baseminor;
int minorct;
char name[64];
struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
4 参考:感谢原著 (有此6个函数的源码及解说)。
http://blog.csdn.net/iLetLet/article/details/6180314
/***************************************************************************************************
第二步:初始化 cdev 并添加到系统
***************************************************************************************************/
1.内核中每个字符设备都对应一个 cdev 结构的变量,定义如下:
linux-2.6.22/include/linux/cdev.h
struct cdev
{
struct kobject kobj; //每个 cdev 都是一个 kobject
struct module *owner; //指向实现驱动的模块
const struct file_operations *ops; //操纵这个字符设备文件的方法
struct list_head list; //与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
dev_t dev; //起始设备编号
unsigned int count; //设备范围号大小
};
2. 初始化cdev :有两种定义初始化方式:
方式1:静态内存定义初始化:
struct cdev my_cdev;
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
方式2:动态内存定义初始化:
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &fops;
my_cdev->owner = THIS_MODULE;
下面是2函数的具体代码:
- struct cdev *cdev_alloc(void) //它主要完成了空间的申请和简单的初始化操作;
- {
- struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
- if (p)
- {
- INIT_LIST_HEAD(&p->list);
- kobject_init(&p->kobj, &ktype_cdev_dynamic);
- }
- return p;
- }
- void cdev_init(struct cdev *cdev, const struct file_operations *fops)
- {
- memset(cdev, 0, sizeof *cdev); //主要是对空间起到一个清零作用并较之cdev_alloc多了一个ops的赋值操作
- INIT_LIST_HEAD(&cdev->list);
- kobject_init(&cdev->kobj, &ktype_cdev_default);
- cdev->ops = fops;
- }
3. 添加cdev到系统
为此可以调用 cdev_add() 函数。传入cdev结构的指针,起始设备编号,以及设备编号范围。
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);
}
释放时使用 cdev_del()函数来释放cdev占用的内存。
void cdev_del(struct cdev *p)
{
cdev_unmap(p->dev, p->count); //释放 cdev_map 散列表中的对象
kobject_put(&p->kobj); //释放 cdev 结构本身。
}
4.关于kobject_init() kobj_map()
内核中所有都字符设备都会记录在一个 kobj_map 结构的 cdev_map 变量中。
这个结构的变量中包含一个散列表用来快速存取所有的对象。
kobj_map() 函数就是用来把字符设备编号和 cdev 结构变量一起保存到 cdev_map 这个散列表里。
当后续要打开一个字符设备文件时,通过调用 kobj_lookup() 函数,根据设备编号就可以找到 cdev 结构变量,从而取出其中的 ops 字段。
/***************************************************************************************************
第三步:创建设备节点
***************************************************************************************************/
方法一:利用mknod命令手动创建设备节点。
方法二:实际上Linux内核为我们提供了一组函数,可以在模块加载的时候在/dev目录下创建相应设备节点,在卸载时可删除该节点。
原理:
1 内核中定义了struct class结构体,它对应一个类。
2 先调用class_create()函数,可以用它来创建一个类,这个类将存放于sysfs下面.
3 再调用device_create()函数,从而在/dev目录下创建相应的设备节点。
4 卸载模块对应的函数是 device_destroy 和 class_destroy()
注:2.6 以后的版本使用device_create(),之前的版本使用的class_device_create()。
详解:
- 1:class结构:
- include/linux/device.h
- struct class
- {
- const char *name;
- struct module *owner;
- struct kset subsys;
- struct list_head devices;
- struct list_head interfaces;
- struct kset class_dirs;
- struct semaphore sem; /* locks children, devices, interfaces */
- struct class_attribute *class_attrs;
- struct device_attribute *dev_attrs;
- int (*dev_uevent) (struct device *dev,struct kobj_uevent_env *env);
- void (*class_release)(struct class *class);
- void (*dev_release) (struct device *dev);
- int (*suspend) (struct device *dev, pm_message_t state);
- int (*resume) (struct device *dev);
- };
- 2:class_create()
- class_create()在/drivers/base/class.c中实现:
- struct class *class_create(struct module *owner, // 指定类的所有者是哪个模块
- const char *name) // 指定类名
- {
- struct class *cls;
- int retval;
- cls = kzalloc(sizeof(*cls), GFP_KERNEL);
- if (!cls)
- {
- retval = -ENOMEM;
- goto error;
- }
- cls->name = name;
- cls->owner = owner;
- cls->class_release = class_create_release;
- retval = class_register(cls);
- if (retval)
- goto error;
- return cls;
- error:
- kfree(cls);
- return ERR_PTR(retval);
- }
- 3:device_create()函数在/drivers/base/core.c中实现:
- struct device *device_create(struct class *class, //指定所要创建的设备所从属的类
- struct devicev *parent, //这个设备的父设备,如果没有就指定为NULL
- dev_t devt, //设备号
- const char *fmt, //设备名称
- ...) //从设备号
- {
- va_list vargs;
- struct device *dev;
- va_start(vargs, fmt);
- dev = device_create_vargs(class, parent, devt, NULL, fmt, vargs);
- va_end(vargs);
- return dev;
- }
/***************************************************************************************************
第四步:例子
***************************************************************************************************/
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/init.h>
- #include <linux/fs.h> 以上是关于char device driver的主要内容,如果未能解决你的问题,请参考以下文章
Linux MTD (Memory Technology Device) subsystem analysis -For Atheros char device