分类: LINUX linux cdev详解 谨以此文纪念过往的岁月 一.前言 以前对于cdev仅仅是知其然,而不知其所以然。在本文中,将深入理解cdev的架构以及具体的实现。 二.真实的cdev 2.1 设备号 搞驱动的都应该知道的东西,在写gpio驱动时,往往会用到以下两个函数。 alloc_chrdev_region --自动分配设备号 register_chrdev_region --分配以设定的设备号。 上面两个函数的调用很简单,当时却没有深入去理解其实现的原理,只知道其采用了hash表,但是具体怎么实现的却不知道。在这里来好好理解一下。上面两个函数的核心是__register_chrdev_region;那来看源码的实现。 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]; --CHRDEV_MAJOR_HASH_SIZE = 255 你看这个结构体会发现,其定义了一个指针数组,大小为255. static struct char_device_struct * __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) { --当调用alloc_chrdev_region时,传入的major为0,从表面上看调用alloc_chrdev_region会自动分配设备,但是有一个缺点就是其 分配的设备号只能在255内,而且并没有使用hash表,从而大大减少了linux所支持的设备号数。 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; } --以下以register_chrdev_region为例即传入major不为0。 cd->major = major; cd->baseminor = baseminor; cd->minorct = minorct; strncpy(cd->name,name, 64); i = major_to_index(major); -- major % CHRDEV_MAJOR_HASH_SIZE 这里采用除留余数法来产生地址。 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; --这里是将cd插入链表中。 *cp = cd; mutex_unlock(&chrdevs_lock); return cd; out: mutex_unlock(&chrdevs_lock); kfree(cd); return ERR_PTR(ret); } 上面是分配设备号的核心函数,上面的描述比较空洞,那来看一个例子。 在两个驱动文件中都采用register_chrdev_region来分配设备号。 A文件. dev_t devt = MKDEV(506,0); register_chrdev_region(devt,1,'A'); B文件. dev_t devt = MKDEV(506,1); register_chrdev_region(devt,1,'B'); 也许有人会奇怪这两个module怎么会用同一个主设备号,有人会认为是在同一个主设备号下有两个从设备,所以会采用同一个主设备号,其实不然,在命令行中输入cat /proc/devices 时会发现竟然有两个主设备号为506的设备,并且这设备号竟然大于255。到此时,你在回去去看cdev 设备号的hash表实现就不太难懂了。其实对于hash表的地址为506%255 = 251,而B的cd 等于 A的cd->next.就是hash出现地址冲突时采用拉链法来解决冲突的。 OK,到此对于cdev的设备号是如何分配的应该很清楚了吧。 2.2 cdev的初始化和注册。 一般使用cdev的过程如下 struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; }; cdev_alloc->cdev_init->cdev_add 那就按照上面的顺序来分别解释。 cdev_alloc和cdev_init都比较简单,这里就不说了。在cdev_add中有一个很重要的函数kobj_map。 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); } 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; }; 其传入的参数cdev_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; --当rang小于1<<20时,n=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 = &next; p->next = *s; *s = p; } mutex_unlock(domain->lock); return 0; } 上面还是一个链表型的数组,用于保存module的信息。至于这些信息的作用哪儿有用,不急,且听我慢慢道来。 三.cdev打开 话说每一个设备在打开的时候都会使用open,而每一个cdev open的时候都会调用chrdev_open这个函数,而上面所说的cdev_add中的一些某明奇妙的东东都会在这里看到。 static int chrdev_open(struct inode *inode, struct file *filp) { struct cdev *p; struct cdev *new = NULL; int ret = 0; spin_lock(&cdev_lock); p = inode->i_cdev; --以此时p=NULL为例。 if (!p) { struct kobject *kobj; int idx; spin_unlock(&cdev_lock); kobj = kobj_lookup(cdev_map, inode->i_rdev, --查找kobject。这里面就知道kobj_mmap为什么那么做了。 if (!kobj) return -ENXIO; new = container_of(kobj, struct cdev, kobj); --根据kobj找到cdev spin_lock(&cdev_lock); p = inode->i_cdev; if (!p) { inode->i_cdev = p = new; inode->i_cindex = idx; list_add(&inode->i_devices, list); new = NULL; } else if (!cdev_get(p)) ret = -ENXIO; } else if (!cdev_get(p)) ret = -ENXIO; spin_unlock(&cdev_lock); cdev_put(new); if (ret) return ret; ret = -ENXIO; filp->f_op = fops_get(p->ops); --将file的f_op用cdev自己的ops代替。 if (!filp->f_op) goto out_cdev_put; if (filp->f_op->open) { --这里将会调用设备自己的open。 ret = filp->f_op->open(inode,filp); if (ret) goto out_cdev_put; } return 0; out_cdev_put: cdev_put(p); return ret; } struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index) { struct kobject *kobj; struct probe *p; unsigned long best = ~0UL; retry: mutex_lock(domain->lock); for (p = domain->probes[MAJOR(dev) % 255]; p; p = p->next) { --从链表头开始查询 struct kobject *(*probe)(dev_t, int *, void *); struct module *owner; void *data; if (p->dev > dev || p->dev + p->range - 1 < dev) --还记否上面是根据什么来将probe节点插入到以probes[x]为头的链表中的。 continue; if (p->range - 1 >= best) --我感觉这个错误时不会发生的。 break; if (!try_module_get(p->owner)) --判断module是否在内核中,在就增加计数。否则返回0表示module不在kernel中。 continue; owner = p->owner; --这里就是cdev->owner data = p->data; --data = cdev probe = p->get; --在cdev_add中就说明是exact_match best = p->range - 1; --就是cdev_add中的参数count。 *index = dev - p->dev; if (p->lock lock(dev, data) < 0) { --p->lock = exact_lock module_put(owner); continue; } mutex_unlock(domain->lock); kobj = probe(dev, index, data); --返回时cdev的kobject static struct kobject *exact_match(dev_t dev, int *part, void *data) { struct cdev *p = data; return kobj; } /* Currently ->owner protects _only_ ->probe() itself. */ module_put(owner); if (kobj) return kobj; goto retry; } mutex_unlock(domain->lock); return NULL; } 四.总结 到此cdev的核心算是理解了,其理解有错之处请各位多多指教。 阅读(4542) | 评论(0) | 转发(3) |
|
|