linux驱动---字符设备的注册register_chrdev说起
首先我们在注册函数里面调用了register_chrdev(MEM_MAJOR,"mem",&memory_fops),向内核注册了一个字符设备。
第一个参数是主设备号,0代表动态分配,这里的MEM_MAJOR是1。第二个参数是设备的名字,第三个参数是文件操作指针。
每个设备文件对应有两个设备号:一个是主设备号,标识该设备的种类,也标识了该设备所使用的驱动程序;另一个是次设备号,标识使用同一设备驱动程序的不同硬件设备。设备文件的主设备号必须与设备驱动程序在登录该设备时申请的主设备号一致,否则用户进程将无法访问到设备驱动程序。
一般说来,PCI卡通常都属于字符设备
完成注册后,在/proc/devices中的第一个字符设备我们就看到了:1 mem。
1.前面提到了注册,那这个字符设备到底注册到哪里去了呢?这是要弄明白的第一个问题。
其实是注册到一个存放字符设备的链表中了:
fs/char_dev.c
- 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];
这里还是一点需要注意的是这里分配的是一个指针数组。
- cd->next = *cp;
- *cp = cd;
首先*cp代表的是当前相同的主设备号中最后面的一个,当然这这里的*cp是指向NULL的,然后把*cp更新为cd。
可以注意到这里好像并没有struct cdev什么事情,下面就对其进行初始化。
2.cdev_add第二个任务,add a char device to the system,make it live immediately
先介绍两个遇到的结构体:
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;
- };
- void cdev_init(struct cdev *, const struct file_operations *);
- struct cdev *cdev_alloc(void);
- void cdev_put(struct cdev *p);
- int cdev_add(struct cdev *, dev_t, unsigned);
- void cdev_del(struct cdev *);
- void cd_forget(struct inode *);
- struct kobject {
- const char *name;
- struct list_head entry;
- struct kobject *parent;
- struct kset *kset;
- struct kobj_type *ktype;
- struct sysfs_dirent *sd;
- struct kref kref;
- unsigned int state_initialized:1;
- unsigned int state_in_sysfs:1;
- unsigned int state_add_uevent_sent:1;
- unsigned int state_remove_uevent_sent:1;
- unsigned int uevent_suppress:1;
- };
主要的操作有:
- extern void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
- extern void kobject_del(struct kobject *kobj);
- extern struct kobject *kobject_get(struct kobject *kobj);
- extern void kobject_put(struct kobject *kobj);extern void kobject_put(struct kobject *kobj);
cdev_add里面只调用了一个函数:kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
cdev_map是fs/char_dev.h里定义的一个结构体变量,而kobj_map的作用就是初始化它。- static struct kobj_map *cdev_map;
- struct kobj_map {
- struct probe {
- struct probe *next;
- dev_t dev;
- unsigned long range;
- struct module *owner;
- kobj_probe_t *get;
- int (*lock)(dev_t, void *);
- void *data;
- } *probes[255];
- struct mutex *lock;
- };
kobj_map:
内核中所有都字符设备都会记录在一个 kobj_map 结构的 cdev_map 变量中。这个结构的变量中包含一个散列表用来快速存取所有的对象。kobj_map() 函数就是用来把字符设备编号和 cdev 结构变量一起保存到 cdev_map 这个散列表里。当后续要打开一个字符设备文件时,通过调用 kobj_lookup() 函数,根据设备编号就可以找到 cdev 结构变量,从而取出其中的 ops 字段。
- 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(sizeof(struct probe) * n, 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;
- }
- 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;
- }