字符设备驱动框架应该是我们最常见的一种驱动框架。
废话少说,撸源码。
涉及到的源码路径:
https://elixir.bootlin.com/linux/latest/source/fs/char_dev.c
https://elixir.bootlin.com/linux/latest/source/include/linux/cdev.h
主要涉及到的函数是下面这几个,源码也一并贴出,可以说是相当的简单,注释也相当详细。
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
} __randomize_layout;
/**
* cdev_alloc() - allocate a cdev structure
*
* Allocates and returns a cdev structure, or NULL on failure.
*/
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;
}
/**
* cdev_init() - initialize a cdev structure
* @cdev: the structure to initialize
* @fops: the file_operations for this device
*
* Initializes @cdev, remembering @fops, making it ready to add to the
* system with cdev_add().
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
1.cdev_alloc 函数,主要功能就是为cdev结构体分配一块内存。
2.cdev_init 函数的主要功能就是初始化cdev,从代码看 主要是把结构体成员里的链表初始化 ,fops赋值。
/**
* cdev_add() - add a char device to the system
* @p: the cdev structure for the device
* @dev: the first device number for which this device is responsible
* @count: the number of consecutive minor numbers corresponding to this
* device
*
* cdev_add() adds the device represented by @p to the system, making it
* live immediately. A negative error code is returned on failure.
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
int error;
p->dev = dev;
p->count = count;
error = kobj_map(cdev_map, dev, count, NULL,
exact_match, exact_lock, p);
if (error)
return error;
kobject_get(p->kobj.parent);
return 0;
}
3.看看cdev_add 干了啥,也没干啥,依旧是调函数kobj_map,我们继续追踪kobj_map函数
int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
struct module *module, kobj_probe_t *probe,
int (*lock)(dev_t, void *), void *data)
{
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
unsigned index = MAJOR(dev);
unsigned i;
struct probe *p;
if (n > 255)
n = 255;
p = kmalloc_array(n, sizeof(struct probe), GFP_KERNEL);
if (p == NULL)
return -ENOMEM;
for (i = 0; i < n; i++, p++) {
p->owner = module;
p->get = probe;
p->lock = lock;
p->dev = dev;
p->range = range;
p->data = data; /*!----- 这里是我们传入的cdev 指针*/
}
mutex_lock(domain->lock);
/*! 二级指针有必要吗?,没必要*/
for (i = 0, p -= n; i < n; i++, p++, index++) {
struct probe **s = &domain->probes[index % 255];
while (*s && (*s)->range < range)
s = &(*s)->next;
p->next = *s;
*s = p;
}
mutex_unlock(domain->lock);
return 0;
}
kobj_map函数比较长,但是基本操作就是申请了n个struct probe 结构体,然后全部挂到domin上。这个具体的挂法应该是下面这样的一个形态。(哈希链表法)
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;这个意思非常简单就是当此设备数目超过 设备号范围就进位主设备号,做差值后就是需要的主设备号个数。