在Linux2.6内核以前注册字符设备的函数接口是register_chrdev,在2.6中其可继续使用。
register_chrdev大致作用:向内核注册cdev结构体,当在用户空间打开设备文件时内核可以根据设备号快速定位此设备文件的cdev->file_operations结构体,从而调用驱动底层的open,close,read,write,ioctl等函数,当我们在用户空间open字符设备文件时,首先调用def_chr_fops->chrdev_open()函数(所有字符设备都调用此函数),chrdev_open会调用kobj_lookup函数找到设备的cdev->kobject,从而得到设备的cdev,进而获得file_operations.要想通过kobj_lookup找到相应的cdev,必需调用register_chrdev()函数。向内核注册。
现分析如下:
/**
* register_chrdev() - Register a major number for character devices.
* @major: major device number or 0 for dynamic allocation
* @name: name of this range of devices
* @fops: file operations associated with this devices
*
* If @major == 0 this functions will dynamically allocate a major and return
* its number.
*
* If @major > 0 this function will attempt to reserve a device with the given
* major number and will return zero on success.
*
* Returns a -ve errno on failure.
*
* The name of this device has nothing to do with the name of the device in
* /dev. It only helps to keep track of the different owners of devices. If
* your module name has only one type of devices it's ok to use e.g. the name
* of the module here.
*
* This function registers a range of 256 minor numbers. The first minor number
* is 0.
*/
int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
char *s;
int err = -ENOMEM;
/* 内核中用char_device_struct结构体指针数组chrdevs(哈希表)来维护所有的设备号
__register_chrdev_region函数向内核注册major主设备号,其有256个次设备号
在以前内核中用8位来表示次设备号,2.6内核用20位来表示次设备号*/
cd = __register_chrdev_region(major, 0, 256, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc();//2.6内核新接口
if (!cdev)
goto out2;
cdev->owner = fops->owner;
cdev->ops = fops;//底层驱动接口
kobject_set_name(&cdev->kobj, "%s", name);//linux内核驱动模型kobject.
for (s = strchr(kobject_name(&cdev->kobj),'/'); s; s = strchr(s, '/'))
*s = '!';
err = cdev_add(cdev, MKDEV(cd->major, 0), 256);//向内核提交cdev.
if (err)
goto out;
cd->cdev = cdev;
return major ? 0 : cd->major;
out:
kobject_put(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, 0, 256));
return err;
}
下面分析:__register_chrdev_region
点击(此处)折叠或打开
- /*
- * Register a single major with a specified minor range.
- *
- * If major == 0 this functions will dynamically allocate a major and return
- * its number.
- *
- * If major > 0 this function will attempt to reserve the passed range of
- * minors and will return zero on success.
- *
- * Returns a -ve errno on failure.
- */
- 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);//根据major获得哈希chrdevs数组索引
- //chrdevs数组的一项可能对应多个主设备号
- 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);
- }
cdev_add分析:
点击(此处)折叠或打开
- 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);
- }
- 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; //一般情况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++) { //一般n=1
- 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; //如果major在probes哈希表中的索引一样,则依据rang从小到大排列
- *s = p;
- }
- mutex_unlock(domain->lock);
- return 0;
- }
在完成register_chrdev的注册以后就可以对设备文件操作,在chrdev_open函数中通过调用kobj_lookup函数找到cdev内嵌的kobject结构体以后就可以得到cdev结构体本身。
点击(此处)折叠或打开
- 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)
- continue;
- if (p->range - 1 >= best)
- break;
- if (!try_module_get(p->owner))
- continue;
- owner = p->owner;
- data = p->data;
- probe = p->get;
- best = p->range - 1;
- *index = dev - p->dev;
- if (p->lock && p->lock(dev, data) < 0) {
- module_put(owner);
- continue;
- }
- mutex_unlock(domain->lock);
- kobj = probe(dev, index, data);
- /* Currently ->owner protects _only_ ->probe() itself. */
- module_put(owner);
- if (kobj)
- return kobj;
- goto retry;
- }
- mutex_unlock(domain->lock);
- return NULL;
- }
本文中使用2.6.30.4内核源代码。