register_chrdev_region函数源码分析

2010年9月21日 由 edsionte 留言 »

如何找到一个有效的切入点去深入分析内核源码,这是一个令人深思的问题。本文以前文中未详细说明的函数为切入点,深入分析char_dev.c文件的代码。如果你已经拥有了C语言基础和一些数据结构基础,那么还等什么?Let’s go!

在《字符设备驱动分析》一文中,我们说到register_chrdev_region函数的功能是在已知起始设备号的情况下去申请一组连续的设备号。不过大部分驱动书籍都没有去深入说明此函数,可能是因为这个函数内部封装了__register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name)函数的原因。不过我们不用苦恼,这正好促使我们去分析这个函数。

194int register_chrdev_region(dev_t from, unsigned count, const char *name)
 195{
 196        struct char_device_struct *cd;
 197        dev_t to = from + count;
 198        dev_t n, next;
 199
 200        for (n = from; n <\ to; n = next) {
 201                next = MKDEV(MAJOR(n)+1, 0);
 202                if (next >\ to)
 203                        next = to;
 204                cd = __register_chrdev_region(MAJOR(n), MINOR(n),
 205                               next - n, name);
 206                if (IS_ERR(cd))
 207                        goto fail;
 208        }
 209        return 0;
 210fail:
 211        to = n;
 212        for (n = from; n <\ to; n = next) {
 213                next = MKDEV(MAJOR(n)+1, 0);
 214                kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
 215        }
 216        return PTR_ERR(cd);
 217}

首先值得我们注意的是,这个函数每次分配的是一组设备编号。其中from参数是这组连续设备号的起始设备号,count是这组设备号的大小(也是次设备号的个数),name参数处理本组设备的驱动名称。另外,当次设备号数目过多(count过多)的时候,次设备号可能会溢出到下一个主设备。因此我们在for语句中可以看到,首先得到下一个主设备号(其实也是一个设备号,只不过此时的次设备号为0)并存储于next中。然后判断在from的基础上再追加count个设备是否已经溢出到下一个主设备号。如果没有溢出(next小于to),那么整个for语句就只执行个一次__register_chrdev_region函数;否则当设备号溢出时,会把当前溢出的设备号范围划分为几个小范围,分别调用__register_chrdev_region函数。

如果在某个小范围调用__register_chrdev_region时出现了失败,那么会将此前分配的设备号都释放。

其实register_chrdev_region函数还没有完全说清除设备号分配的具体过程,因为具体某个小范围的设备号是由__register_chrdev_region函数来完成的。可能你已经注意到在register_chrdev_region函数源码中出现了struct char_device_struct结构,我们首先来看这个结构体:

  50static struct char_device_struct {
  51        struct char_device_struct *next;
  52        unsigned int major;
  53        unsigned int baseminor;
  54        int minorct;
  55        char name[64];
  56        struct cdev *cdev;              /* will die */
  57} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

在register_chrdev_region函数中,在每个字符设备号的小范围上调用__register_chrdev_region函数,都会返回一个struct char_device_struct类型的指针。因此我们可以得知,struct char_device_struct类型对应的并不是每一个字符设备,而是具有连续设备号的一组字符设备。从这个结构体内部的字段也可以看出,这组连续的设备号的主设备号为major,次设备号起始为baseminor,次设备号范围为minorct,这组设备号对应的设备驱动名称为name,cdev为指向这个字符设备驱动的指针。

这里要特别说明的是,内核中所有已分配的字符设备编号都记录在一个名为chrdevs散列表里。该散列表中的每一个元素是一个 char_device_struct结构,这个散列表的大小为255(CHRDEV_MAJOR_HASH_SIZE),这是因为系统屏蔽了12位主设备号的前四位。既然说到散列表,那么肯定会出现冲突现象,因此next字段就是冲突链表中的下一个元素的指针。

接下来我们详细来析__register_chrdev_region函数。首先为cd变量分配内存并用零来填充(这就是用kzalloc而不是kmalloc的原因)。接着通过P操作使得后续要执行的语句均处于临界区。

  92static struct char_device_struct *
  93__register_chrdev_region(unsigned int major, unsigned int baseminor,
  94                           int minorct, const char *name)
  95{
  96        struct char_device_struct *cd, **cp;
  97        int ret = 0;
  98        int i;
  99
 100        cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
 101        if (cd == NULL)
 102                return ERR_PTR(-ENOMEM);
 103
 104        mutex_lock(&chrdevs_lock);

如果major为0,也就是未指定一个具体的主设备号,需要动态分配。那么接下来的if语句就在整个散列表中为这组设备寻找合适的位置,即从散列表的末尾开始寻找chrdevs[i]为空的情况。若找到后,那么i不仅代表这组设备的主设备号,也代表其在散列表中的关键字。当然,如果主设备号实现已指定,那么可不去理会这部分代码。

 105
 106        /* temporary */
 107        if (major == 0) {
 108                for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
 109                        if (chrdevs[i] == NULL)
 110                                break;
 111                }
 112
 113                if (i == 0) {
 114                        ret = -EBUSY;
 115                        goto out;
 116                }
 117                major = i;
 118                ret = major;
 119        }

接着对将参数中的值依次赋给cd变量的对应字段。当主设备号非零,即事先已知的话,那么还要通过major_to_index函数对其进行除模255运算,因此整个散列表关键字的范围是0~254。

 120
 121        cd->major = major;
 122        cd->baseminor = baseminor;
 123        cd->minorct = minorct;
 124        strlcpy(cd->name, name, sizeof(cd->name));
 125
 126        i = major_to_index(major);

至此,我们通过上面的代码会得到一个有效的主设备号(如果可以继续执行下面代码的话),那么接下来还不能继续分配。正如你所知的那样,散列表中的冲突是在所难免的。因此我们得到major的值后,我们要去遍历冲突链表,为当前我们所述的char_device_struct类型的变量cd去寻找正确的位置。更重要的是,我们要检查当前的次设备号范围,即baseminor~baseminor+minorct,是否和之前的已分配的次设备号(前提是major相同)范围有重叠。

下面的for循环就是在冲突链表中查找何时的位置,当出现以下三种情况时,for语句会停止。

(1)如果冲突表中正被遍历的结点的主设备号(*(cp)->major)大于我们所分配的主设备号(major),那么就可以跳出for语句,不再继续查找。此时应该说设备号分配成功了,那么cd结点只需等待被插到冲突链表当中(*cp节点之前)。

(2)如果(*cp)结点和cd结点的主设备号相同,但是前者的次设备号起点比cd结点的大,那么跳出for语句,等待下一步的范围重叠的检测。

(3)如果(*cp)结点和cd结点的主设备号相同,但是cd结点的次设备号起点小于(*cp)结点的次设备号的终点,那么会跳出for语句。此时很可能两个范围的次设备号发生了重叠。

由上面的分析可以看出,冲突表中是按照设备号递增的顺序排列的。

 127
 128        for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
 129                if ((*cp)->major > major ||
 130                    ((*cp)->major == major &&
 131                     (((*cp)->baseminor >= baseminor) ||
 132                      ((*cp)->baseminor + (*cp)->minorct > baseminor))))
 133                        break;

接下来检测当主设备号相同时,次设备范围是否发生了重叠。首先依次计算出新老次设备号的范围,接着进行范围判断。第一个判断语句是检测新范围的终点是否在老范围的之间;第二个判断语句是检测新范围的起点是否在老范围之间。

 134
 135        /* Check for overlapping minor ranges.  */
 136        if (*cp && (*cp)->major == major) {
 137                int old_min = (*cp)->baseminor;
 138                int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
 139                int new_min = baseminor;
 140                int new_max = baseminor + minorct - 1;
 141
 142                /* New driver overlaps from the left.  */
 143                if (new_max >= old_min && new_max <= old_max) {
 144                        ret = -EBUSY;
 145                        goto out;
 146                }
 147
 148                /* New driver overlaps from the right.  */
 149                if (new_min <= old_max && new_min >= old_min) {
 150                        ret = -EBUSY;
 151                        goto out;
 152                }
 153        }

当一切都正常后,就将char_device_struct描述符插入到中途链表中。至此,一次小范围的设备号分配成功。并且此时离开临界区,进行V操作。如果上述过程中有任何失败,则会跳转到out处,返回错误信息。

 154
 155        cd->next = *cp;
 156        *cp = cd;
 157        mutex_unlock(&chrdevs_lock);
 158        return cd;
 159out:
 160        mutex_unlock(&chrdevs_lock);
 161        kfree(cd);
 162        return ERR_PTR(ret);
 163}

至此,我们已经分析完了字符设备号分配函数。

广告位

7 条评论

  1. peter说道:

    谢谢,很不错,分析的到位,期待着续集。。。。。。

    [回复一下]

    edsionte 回复:

    @peter, 你是linux小组的成员?

    [回复一下]

    peter 回复:

    @edsionte,
    暂时还不是, 只是对linux感兴趣和工作需要,一直在关注你的文章,都写的很不错。。。。。。

    [回复一下]

    edsionte 回复:

    @peter, 我随便问问的。只要喜欢就成。呵呵。一起加油!

    [回复一下]

  2. Jak.Ding说道:

    楼主好:
    楼主分析的很透彻,呵呵。

    如果没有溢出(next小于to),那么整个for语句就只执行个一次__register_chrdev_region函数。。。。
    上面这句话是否应该应该改成:
    如果没有溢出(next大于to),那么整个for语句就只执行个一次__register_chrdev_region函数。。。。

    [回复一下]

  3. habwtl说道:

    学长分析的很透彻啊,受益匪浅.
    发现个typo, i = major_to_index(major);下面分析
    “正如你所知的那样,散列表中的冲突是在所难免的。因此我们得到major的值后,我们要去”–>便利<–"冲突链表,",应该是遍历吧?

    [回复一下]

    edsionte 回复:

    @habwtl, 是的,该死的*狗输入法!

    [回复一下]

发表回复

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key