linux字符设备解析
字符设备的表示当然是指这个struct cdev结构。let us see。
struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; };
kobj用来表示sys下的目录,owner模块使用者指针,一般赋值为THIS_MODULE,第三个参数很关键file_operations,是用户层调用open、release、write、read函数时。传到内核层处理,第四个结构list表示的是一个链表节点。第五个是设备号,没什么讲的。第六个是count
怎么分配结构体cdev呢,有两种方式,一种是静态定义一个 struct cdev my_cdev,然后在使用cdev_init函数去初始化该结构,第二种方式是利用cdev_alloc函数去动态申请。
有的人会先使用cdev_alloc函数去动态分配,在使用函数cdev_init去释放,其实这种用法是不太正确的,为什么呢?且看下面分析。
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; } 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; }
看出相同之处和不同之处了吗,yes kobject_init这个函数是关键,关键在于ktype类型不同、
static struct kobj_type ktype_cdev_default = { .release = cdev_default_release, }; static struct kobj_type ktype_cdev_dynamic = { .release = cdev_dynamic_release, };
很明显,再看
static void cdev_default_release(struct kobject *kobj) { struct cdev *p = container_of(kobj, struct cdev, kobj); cdev_purge(p); } static void cdev_dynamic_release(struct kobject *kobj) { struct cdev *p = container_of(kobj, struct cdev, kobj); cdev_purge(p); kfree(p); }
对,多了一个kfree,也就是说alloc申请出来的cdev占用的内存空间可以在设备被卸载时自动释放。
下面来看linux 内核怎么管理字符设备号。
内核提供了两种方式,一种是alloc_chrdev_region另一个是register_chrdev_region,两个的底层实现如出一辙。下面来分析alloc_chrdev_region。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) { struct char_device_struct *cd; cd = __register_chrdev_region(0, baseminor, count, name); if (IS_ERR(cd)) return PTR_ERR(cd); *dev = MKDEV(cd->major, cd->baseminor); return 0; }
主要的函数是__register_chrdev_region(0, baseminor, count, name);
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) {//动态申请 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); }
分析到这里,就该看一个有意思的数据结构了,也就是所谓的哈希表。
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];
这就是哈希表结构,chrdevs是一个指针数组,每一个元素都存储着一个字符设备,一看是static类型的,就知道这个chrdevs的每一项初始化后都为NULL,当一个新的设备来了之后通过主设备号来挂接到相应的chrdevs里面,主设备号就是哈希表的表头,而冲突域就用next指针来挂接。比如我注册了一个major = 254 baseminor= 3 的一个设备和major = 254 baseminor= 6的一个设备,这时候chrdevs[254]就应该指向major= 254 baseminor= 3的char_device_struct结构然后该结构的next指针又指向 major= 254 baseminor= 6的char_device_struct结构。