====本文系本站原创,欢迎转载! 转载请注明出处:http://blog.csdn.net/yyplc====
继上篇,本篇结合源码分析
cdev数据结构:
struct cdev { struct kobject kobj; //kobject实体 struct module *owner; const struct file_operations *ops; //大家熟悉的file_operations结构 struct list_head list; //list用于设备管理的链表 dev_t dev; //设备号 unsigned int count; //设备的连续次设备号的数量(范围) };
与cdev相关的函数如下(源码在fs/char_dev.c实现):
void cdev_init(struct cdev *, const struct file_operations *); // struct cdev *cdev_alloc(void); //返回cdev实例 void cdev_put(struct cdev *p); //减少cdev实例中kobject的引用计数,也就是kref的值 int cdev_add(struct cdev *, dev_t, unsigned); // void cdev_del(struct cdev *); //删除一个cdev实例 static int chrdev_open(struct inode *inode, struct file *filp); //打开一个设备时调用,这个函数只是内部使用(static)下面我们选一些对我们有用的函数来了解,什么叫有用?所谓有用就是写驱动时有用的。
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->kobj cdev->ops = fops; //关联cdev的fops }//作用:为系统添加一个cdev实例。ldd3上说"常常 count 是 1, 但是有多个设备号对应于一个特定的设备的情形.
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; }; 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); }kobj_map()会创建一个probe对象,然后将其插入cdev_map中的某一项中,并关联probe->data指向cdev,probe->range = count;
const struct file_operations def_chr_fops = { .open = chrdev_open, };//作用:根据设备号(cdev_map ,inode->i_rdev),通过kobj_lookup()来获取kobject,再通过获取cdev,最后获取关联的fops操作函数
static int chrdev_open(struct inode *inode, struct file *filp);除以上cdev操作函数以外,下面我们看看字符设备相关函数:可以分成两类,注册函数和注销函数
int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops);//根据设备号,注册一段设备号,from是设备号,当为0时就是动态注册,count是“多少个来连续次设备号”
int register_chrdev_region(dev_t from, unsigned count, const char *name);//动态分配设备号,dev是输出的设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)//其实上面三个函数的实现都是调用这个函数的
static struct char_device_struct *__register_chrdev_region(unsigned int major, unsigned int baseminor,int minorct, const char *name)注销函数:
void unregister_chrdev(unsigned int major, const char *name); static struct char_device_struct * __unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct);看看它们的调用过程:
register_chrdev(major, name, fops)--> __register_chrdev_region(major, 0, 256, name)-->cdev = cdev_alloc() -->cdev_add(cdev, MKDEV(cd->major, 0), 256);
alloc_chrdev_region(dev, baseminor, count, name)-->__register_chrdev_region(0, baseminor, count, name)--> *dev = MKDEV(cd->major, cd->baseminor);
register_chrdev_region(from, count, name)--> __register_chrdev_region(MAJOR(from), MINOR(from)), next-from, name);由于以上3个函数都通过调用
__register_chrdev_region(unsigned int major, unsigned int baseminor,int minorct, const char *name)先看字符设备的数据结构:
static struct char_device_struct { struct char_device_struct *next; // 指向散列冲突链表中的下一个元素的指针 unsigned int major; // 主设备号 unsigned int baseminor; // 起始次设备号 int minorct; // 设备编号的范围大小 char name[64]; // 处理该设备编号范围内的设备驱动的名称 struct cdev *cdev; // 指向字符设备驱动程序描述符的指针 } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];定义一个指针chardevs数组,大小为CHRDEV_MAJOR_HASH_SIZE,注意,数组中的元素是一个地址,其中
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,一个char_device_struct内存空间,并初始化为0 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--) {//从254开始,也就是第255个开始 if (chrdevs[i] == NULL) break; } if (i == 0) { //因为主设备号为0,内核定义为UNNAMED_MAJOR,弃之不用 ret = -EBUSY; goto out; } major = i; //找到一个没用过的主设备号来用 ret = major; } //找到major后,给cd赋值 cd->major = major; cd->baseminor = baseminor; cd->minorct = minorct; strlcpy(cd->name, name, sizeof(cd->name)); i = major_to_index(major);//求余:i= major % CHRDEV_MAJOR_HASH_SIZE for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) //根据主设备号,确定正确的次设备号的范围(位置),注意有可能‘回转‘的情况,所以后面会检测 if ((*cp)->major > major || //如果是一个没用过的主设备号,(*cp)->next =NULL ((*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; //指向下一个设备的char设备结构 *cp = cd; //把当前cd的地址保存到chrdevs[i]中 mutex_unlock(&chrdevs_lock); return cd; //返回cd out: mutex_unlock(&chrdevs_lock); kfree(cd); return ERR_PTR(ret); }注销代码:
static struct char_device_struct * __unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct) { struct char_device_struct *cd = NULL, **cp; int i = major_to_index(major); mutex_lock(&chrdevs_lock); for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)//在chardevs[]中找到已注册的cd结构位置 if ((*cp)->major == major && (*cp)->baseminor == baseminor && (*cp)->minorct == minorct) break; if (*cp) {//从chardevs[]中剔除cd结构,并将当前的chardevs[]指向cd的下一个char结构 cd = *cp; *cp = cd->next; } mutex_unlock(&chrdevs_lock); return cd; //返回找到的cd }