• linux内核追踪——find_next_bit函数详详详解


    写在前面

    宗旨:把话说清楚,把道理讲透彻。

    约定:所有代码均来自Linux内核2.6.24版。

    建议:本文介绍得十分详细,但也略显繁琐,读者可以先看“Ⅴ.总结”部分带注释的源码,如果哪里不清楚,再回头看详细解释。

    正文

    预备知识

    位图:

    在Linux下,从数据结构上看,位图本质上是一个数组,数组的每个元素都是long型的(即32bit或64bit)。

    假设在32位系统下,某long型数组有128个元素,那么,从逻辑上看,这个数组就是一个128行×32列的bit阵列,就是所谓的位图,见下面的示意图。

                   

    上图中的数字就是各个bit位的标号,即索引。

    对于位图的操作,也就是对位图中bit位的操作。

    从作用上说,位图通常与其它数据相关联,用位图中的bit位对该数据进行统计或管理。

    例如,在文件系统中,每个进程都有一个元素为file指针的数组(为表述方便,后面称之为数组A),同时,也有一个位图,位图中有效bit位(何为有效bit位,后面会详述)的个数与数组A中元素的个数相同。当进程打开一个文件时,要先在位图中找一个为0的bit位,然后将该位置1,返回该bit位的索引fd。当内核创建了与要打开文件对应的file实例后,会使数组A中索引为fd的元素(注意,前边说过,该元素是一个file类型的指针)指向该file实例。这里的索引fd,就是我们平常所说的文件描述符。

    正题

    Ⅰ.源码

    先给出find_next_bit的源码(源码来自内核2.6.24,位置:/lib/find_next_bit.c)

     1 unsigned long find_next_bit(const unsigned long *addr, unsigned long size,
     2         unsigned long offset)
     3 {
     4     const unsigned long *p = addr + BITOP_WORD(offset);
     5     unsigned long result = offset & ~(BITS_PER_LONG-1);
     6     unsigned long tmp;
     7 
     8     if (offset >= size)
     9         return size;
    10     size -= result;
    11     offset %= BITS_PER_LONG;
    12     if (offset) {
    13         tmp = *(p++);
    14         tmp &= (~0UL << offset);
    15         if (size < BITS_PER_LONG)
    16             goto found_first;
    17         if (tmp)
    18             goto found_middle;
    19         size -= BITS_PER_LONG;
    20         result += BITS_PER_LONG;
    21     }
    22     while (size & ~(BITS_PER_LONG-1)) {
    23         if ((tmp = *(p++)))
    24             goto found_middle;
    25         result += BITS_PER_LONG;
    26         size -= BITS_PER_LONG;
    27     }
    28     if (!size)
    29         return result;
    30     tmp = *p;
    31 
    32 found_first:
    33     tmp &= (~0UL >> (BITS_PER_LONG - size));
    34     if (tmp == 0UL)
    35         return result + size;
    36 found_middle:
    37     return result + __ffs(tmp);
    38 }

    Ⅱ.功能——参数——返回值

    功能:在addr指向的位图中,从索引为offset的bit位(包括该位)开始,找到第一个为1的bit位,返回该位的索引。

    参数:

    @addr:位图(数组)的起始地址。
    @size:位图的大小,即位图中有效bit位的个数。注意,Linux内核实际调用该函数时,该参数的值不一定是32的整数倍(32位系统下)。假设构成位图的数组大小为3,即一共有96个bit,但函数调用时,参数size可           能是90,那么,从逻辑上说,数组最后一个元素的最后6位是不参与构成位图的,即它们不是位图的组成部分,是“无效”的;而前边的90个bit共同构成了位图,它们是“有效”的。注意,后面解释中经常会用             到“有效位”和“无效位”的概念,对此,读者一定要理解清楚。

    @offset:查找起点。即从位图中索引为offset的位(包括该位)开始,查找第一个为1的bit位,offset之前的bit位不在搜索范围之内。“查找起点”这个概念在后面的叙述中经常会用到,希望读者能理解清楚。

    返回值:找到的bit位的索引。

    Ⅲ.BITS_PER_LONG和BITOP_WORD

    3.1 BITS_PER_LONG

    顾名思义,BITS_PER_LONG是指一个long型数据中bit位的个数,看源码

    /*include/asm-x86/types.h*/
    
    #ifdef CONFIG_X86_32
    # define BITS_PER_LONG 32
    #else
    # define BITS_PER_LONG 64
    #endif

    可见,32位系统下,它是32;64位系统下,它是64。

    3.2 BITOP_WORD

    不多说,直接看源码

    /*lib/find_next_bit.c*/
    
    #define BITOP_WORD(nr)        ((nr) / BITS_PER_LONG)

    就是参数nr除以32或64。

    Ⅳ.一句一句解释

    注:在后面的所有解释中,我们按32位系统讲解,即BITS_PER_LONG取32

     a.

    const unsigned long *p = addr + BITOP_WORD(offset);

    该句的作用是使p指向数组中索引为offset的bit位(即查找起点)所在的元素。

    我们知道,位图的0~31位在数组第0个元素中, 32~63位在第1个元素中,等等(如图1)。假设offset=33,则BITOP_WORD(offset)就是33/32,结果为1,那addr + BITOP_WORD(offset)就是addr+1,即指向数组第1个元素,就是索引为33的bit位所在的元素。

    b.

    unsigned long result = offset & ~(BITS_PER_LONG-1);

    BITS_PER_LONG-1就是31,二进制形式就是0001 1111(这里为了表述方便,采用8位二进制),再取反就是1110 0000,即低5位全0,剩下的都是1。假设offset=70D=0100 0110B,那么,

    offset & ~(BITS_PER_LONG-1)就是0100 0110 & 1110 0000 =0100 0000=64。其实,就是将offset的低5位置0,高位保持不变。这个结果有什么意义呢?其实就是:索引为offset的bit位所在的元素前的元素中bit位的个数,如下图所示。很显然,这些bit位都位于查找起点的前面,即它们都不在搜索范围之内,我们可以将它们理解为“已处理”的bit位。请读者牢记,在find_next_bit函数中,result总是表示“已处理”的bit位总数,并且,很显然,它一定是32的整数倍。

                                   

    c.

    if (offset >= size)
        return size;

    如果查找的起始位置offset大于等于位图的总大小,直接返回位图大小。这里解释一下问什么要有等于,假设offset=size=64,但实际上,这种情况下,合法的索引是0~63,所以,值为64的offset不合法。问题的根源在于offset是从0算起的,而size是从1算起的。另外,个人认为这两句应该放在函数体的最前面,这样一来,只要if条件成立,就直接返回,后面的工作就都不用做了。而按照源码的写法,是无论如何都要执行前两句的,但如果此时if条件成立,前边的工作就白做了,这不是一个高效的安排。

    d.

    size -= result;
    offset %= BITS_PER_LONG;

    注意,从这两句开始,size和offset的含义和传参时的含义就不同了

    size-=result就是size=size-result,前边说过,此时的result表示查找起点所在数组元素前的元素中bit位的个数,就是“已处理”的bit位个数,我们要找的bit位一定在这后边;而size表示位图总位数。两者相减的结果,就是“未处理的、待查找的”bit位个数。请读者牢记,此后的代码中,result表示“已处理”的bit位总数,而size表示“待处理”的bit位总数,两者的加和,一定等于传参时size的值。

    offset %= BITS_PER_LONG执行之后的offset也不再表示查找起点的索引,而表示查找起点在它所在的元素中是第几个。假设原来offset=32,则执行该句之后,offset的值变成了0,而从前面的示意图中我们可以看到,索引为32的bit位正是在数组的第1个元素(从0算起)的第0位(从0算起)。所以,我们可以这样说,代码执行前,offset是查找起点在整个位图中的索引;代码执行后,offset是查找起点在它所在的数组元素中的索引。

    e.

    if (offset) {
        tmp = *(p++);
        tmp &= (~0UL << offset);
        if (size < BITS_PER_LONG)
            goto found_first;
        if (tmp)
            goto found_middle;
         size -= BITS_PER_LONG;
         result += BITS_PER_LONG;
        }

    进入这个if的条件是offset不为0,通过前面的分析,就是查找起点不在所在数组元素的首位(我们前边举的例子就是在首位的情况)。好了,开始分析里面的代码,看看这个if做了什么。

    e_1

    tmp = *(p++);

    这句得到的是查找起点所在的数组元素的值,也就是那32个bit。tmp是unsigned long型的,在文章开头给出的源码的第6行定义。注意:

    1.表达式(p++)得到的是p的值(后置++),之后指针p再自增1,指向下一个数组元素。
    2.tmp只是构成位图的数组元素的拷贝而不是数组元素本身,即,对tmp所进行的任何修改,都不会影响到原位图

    e_2

    tmp &= (~0UL << offset);

    首先,0UL就是unsigned long型的数字0,从二进制的角度看,就是32位全0;然后,对它取反,就是32位全1;接着,再左移offset位,假设offset=3,移位后就是1111 1000(为表述方便,这里只写8位);最后,再和tmp(即查找起点所在的数组元素的值)相与,结果就是tmp的低3位置0,其余位保持不变。为什么要这么做呢?因为offset=3,它前面的第2位、第1位、第0位其实都不在搜索范围之内,所以,我们将它们置为0(当然,e_1中就说过,这只是在拷贝上操作,不会影响到原位图),这主要是为后面的工作做准备(读者看到e_4小节就会自然明了)。所以,这句代码的用意是:在查找起点所在的元素中,将查找起点前的bit位置0。代码之所以要将查找起点是否在数组元素的第0位区别对待,就是因为若查找起点不在数组元素首位,需要将查找起点前的bit位置0。

    e_3

    if (size < BITS_PER_LONG)
            goto found_first;

    通过前面的分析,我们知道,此时的size表示的是“待处理”的bit位总数,而现在,这个数字小于32,这说明:

    1.函数调用时传入的参数不是32的整数倍,构成位图的数组的最后一个元素中含有“无效位”,这一点,在讲解函数参数时就详细解释过;
    2.查找起点就在数组的最后一个元素中!

    上面的图2就展示了这种情况,假设函数调用时传入的参数size是86,通过d中的分析,我们知道,此时size的值是86-64=22,满足if中的条件,而显然,这种情况下,查找起点(索引为70的bit位)正是在最后一个数组元素中。
    在这种情况下,代码要转到found_first处。我们继续跟踪,看看found_first干了些什么。

    e_4

    found_first:
        tmp &= (~0UL >> (BITS_PER_LONG - size));
        if (tmp == 0UL)    
            return result + size;    

    首先,我们要知道,代码能走到这里,存在两个前提:

    1.查找起点在数组的最后一个元素中
    2.最后一个元素中存在“无效位”

    通过e_3中的分析,我们知道,此时的size表示的是“待处理”的bit位的个数,同时,它也表示最后一个元素中“有效位”的个数。那么,很显然,BITS_PER_LONG-size就是“无效位”的个数,为了表述方便,我们假设该值位3,那么~0UL >> (BITS_PER_LONG - size)得到的就是这样的32个bit位:最左3位为0,其余位为1。然后,这32个bit位再与tmp(即最后一个数组元素的拷贝)相与,结果就是:将该元素中的“无效位”都置为0了(注意,对32位的long型数据来说,低位、索引小的位在右,高位、索引大的位在左,所以,“无效位“一定是在最左边的)!

    回顾一下e_2,在e_2中,将元素中查找起点之前的位都置成了0,而现在,又将“无效位”都置成了0,如下图

                     

    那么,剩下的是什么呢?剩下的就是查找范围,即我们要在上图中的白色部分找第一个位1的bit位。

    现在,让我们思考这样一个问题:如果此时最后一个数组元素,即tmp的值为0(即if (tmp == 0UL)),说明了什么呢?细思,极恐,这说明查找范围内全是0!因为红色部分和黄色部分早就置为0了,而现在整个元素的值为0,那结论只有一个——白色部分全是0!

    而我们可能会马上想到这样一个不幸事实:这已经是位图中的最后一个数组元素了!

    于是,我们就会得到这样一个令人绝望的结论:以函数参数offset为查找起点,在当前位图中找到一个值为1的bit位,已经不可能了!

    在这种情况下,函数只好无奈地return result + size了,d中说过,result + size的值,一定等于传参时size的值,即数组的总大小。

    这,就是e_4中代码的含义。

    那么,如果我们的运气没那么差,if (tmp == 0UL)没有被执行,也就是说,白色部分有1存在!进而我们就会欣喜地想到:这样,就一定能找到满足条件的bit位!那么,具体怎么找呢?看了最初的源码我们会发现,代码走到了found_middle标签下。好吧,让我们来看看found_middle下有什么。

    e_5

    found_middle:
        return result + __ffs(tmp);

     这里出现了一个新的函数__ffs,它定义在include/asm-generic/bitops/__fss.h中,我们先来看一下这个函数

     1 /**
     2  * __ffs - find first bit in word.
     3  * @word: The word to search
     4  *
     5  * Undefined if no bit exists, so code should check against 0 first.
     6  */
     7 static inline unsigned long __ffs(unsigned long word)
     8 {
     9     int num = 0;
    10 
    11 /*如果BITS_PER_LONG等于64,要先对低32位进行检查;否则(即BITS_PER_LONG等于32),直接对低16位进行检查*/
    12 #if BITS_PER_LONG == 64
    13     if ((word & 0xffffffff) == 0) {   //如果与0xffffffff相与得0,说明word的低32位全为0
    14         num += 32;   //索引加32
    15         word >>= 32;   //word右移32位,把低32位的0移走
    16     }
    17 #endif
    18     if ((word & 0xffff) == 0) {
    19         num += 16;
    20         word >>= 16;
    21     }
    22     if ((word & 0xff) == 0) {
    23         num += 8;
    24         word >>= 8;
    25     }
    26     if ((word & 0xf) == 0) {
    27         num += 4;
    28         word >>= 4;
    29     }
    30     if ((word & 0x3) == 0) {
    31         num += 2;
    32         word >>= 2;
    33     }
    34     if ((word & 0x1) == 0)
    35         num += 1;
    36     return num;
    37 }

    该函数的功能是在参数word中找到第一个值为1的bit位,返回该位的索引。可以看到,该函数没有对参数word=0的情况(这意味着不可能在word中找到值为1的bit位)进行检查,所以我们应该在调用该函数前对传入的参数是否为0进行检查,这就是上面代码中第5行的英文注释的意思,而该函数的调用者find_next_bit在调用该函数前已经进行过检查了,正如我们在e_4中分析的那样。

    该函数的算法还是很巧妙的,它类似于“折半查找”。假设参数word是32位的,先执行word & 0xffff,析取低16位,这时可能出现两种结果:

    1.如果结果为0,说明低16位(0~15位)全为0,那么,可能为1的bit位最小是第16位,所以num += 16,并且,执行word >>= 16,将全0的低16位移走,这样,可能有1存在的高16位变成了低16位;接着,如法炮制,word与0xff相与,对剩下的16位进行“折半查找”。
    2.如果结果不为0,说明低16位中有1存在,由于我们是要找第一个为1的bit位,所以,高16位就不用看了,直接在低16位中寻找,所以,下一步就是执行word & 0xff,对低16位进行“折半查找”。

    可见,无论word & 0xffff的结果是否为0,word & 0xff都是要执行的,只不过,如果word & 0xffff结果为0,需要进行增加计数和右移的工作。

    按照上面的步骤,逐步进行“折半查找”,最终就能得到word中第一个为1的bit位的索引。读者可以自己举个例子,手动执行一下__ffs函数,就更加清楚了,这里不再赘述。

    有一点大家要意识到,find_next_bit在调用__ffs时传入的参数是tmp,而在e_4中我们看到,tmp中查找起点前的bit位和“无效位”都被置为0了,并且,也已经判断出tmp不为0,所以,__ffs一定能找到为1的bit位且保证该bit位在合法的搜索空间(图3的白色区域)内

    现在我们再来看found_middle下的那句return result + __ffs(tmp)。__ffs(tmp)得到的是tmp中自查找起点起第一个为1的bit位的索引,而result表示的是位图中tmp之前的所有元素中bit位的总和,所以,两者相加,就是我们要找的bit位在位图中的索引,即find_next_bit的最终结果,于是,将这个结果return。

    至此,find_next_bit的查找工作就结束了,但是,我们对find_next_bit的分析还没结束。不知读者是否还记得,我们是从e_3中if (size < BITS_PER_LONG)成立这个“路口”进入,一路追踪,才走到这里来的。所以我们还要分析if (size < BITS_PER_LONG)不成立的情况,于是,让我们再次回到最初的源码……

    e_6

    if (tmp)
        goto found_middle;

    如果if (size < BITS_PER_LONG)不成立,就会执行上面的代码。if (size < BITS_PER_LONG)不成立,说明了什么呢?这说明tmp不是数组的最后一个元素,因此就不可能有“无效位”,也就不存在将“无效位”置为0的问题(即不用goto found_first了),又因为,在e_4中,已经将查找起点前的bit位都置0了,所以,这里直接判断tmp是否为0,如不为0,说明在这个tmp中一定能找到为1的bit位,所以,转到found_middle,找到第一个为1的bit位在位图中的索引,然后返回结果。

    那么,如果这里的tmp为0呢,该执行什么样的代码,我们往下看。

    e_7

    size -= BITS_PER_LONG;
    result += BITS_PER_LONG;

    如果tmp为0,说明在当前数组元素中不可能找到为1的bit位,于是需要在下一个数组元素中寻找,在此之前,将“未处理”bit位总数减去32,将“已处理”bit位总数增加32。有读者可能会问:怎么没见指针后移呢?不要忘了,在e_1中,这个工作已经做过了

    至此,整个e中的代码就都分析完了 。但是,革命尚未成功,喘口气,我们还得往下看。

    f

    while (size & ~(BITS_PER_LONG-1)) {
        if ((tmp = *(p++)))
            goto found_middle;
        result += BITS_PER_LONG;
        size -= BITS_PER_LONG;
    }

    首先要明白,有两种情况代码会走到这里:

    1.这个while循环上面的if(offset)(就是e中的代码)条件不成立(即查找起点位于数组元素的第0位),if中的语句体没有被执行。

    2.if(offset)的语句体被执行了,但该语句体内部的两个if条件都不成立,这又分为两种情况:

       1.查找起点所在的数组元素不是数组的最后一个元素,并且,该元素中全是0,没有1。
       2.查找起点是数组最后一个元素,但该元素中没有“无效位”,并且,该元素中全是0,没有1。

    在上面的两种情况下,我们就要执行上面的while循环,在后面的数组元素中依次查找。

    我们先来看一下循环进入条件

    while (size & ~(BITS_PER_LONG-1))

    我们将BITS_PER_LONG换成32,并转换为二进制形式,上面的语句就变成了while(size & 1110 0000),这是什么呢?其实就是while(size>=32)!因为小于32(0010 0000B)的无符号数(注意size是unsigned long型,请看最初的源码)与1110 0000相与的结果都是0000 0000,而大于等于32的无符号数与1110 0000相与的结果都将大于0010 0000。好了,现在我们知道了,上面那句故弄玄虚的while语句其实就是while(size>=32)

    好了,现在我们来看循环体。先看第一句

    if ((tmp = *(p++)))

    这句代码依次做了三件事:

    1.将p指向的数组元素(当然也是我们要进行查找的数组元素)复制到tmp
    2.p指针自增,指向下一元素
    3.判断tmp是否为0

    这里需要解释一下:
    1.如果查找起点在一个数组元素的第0位,那么,e中的if语句体就不会被执行,也就不会执行到if语句体中的tmp = *(p++),所以当第一次进入while循环并执行if ((tmp = *(p++)))时,tmp就是查找起点所在的数组元素的拷贝。
    2.如果查找起点不在一个数组元素的第0位,e中的if语句体就会被执行,当第一次进入while循环并执行if ((tmp = *(p++)))时,tmp就是if语句体中那个tmp对应的元素的下一个元素的拷贝。

    现在让我们在整体看一下这个循环体

    if ((tmp = *(p++)))
        goto found_middle;
    result += BITS_PER_LONG;
    size -= BITS_PER_LONG;

    如果tmp不为0,说明我们要找的为1的bit位一定在这个tmp中,于是转到found_middle,进行查找并返回结果,find_next_bit函数结束;如果tmp等于0,说明该元素中不含1,将“已处理”位数增加32,“待处理”位数减少32,然后判断循环条件(“待查找”位数是否大于32),若条件成立,进入循环体,对下一个数组元素进行查找,否则,退出循环。

    如果上面的while循环是正常退出(即由于size小于32而退出)的,那就说明整个整个while循环都没找到为1的bit位(否则,代码将从循环体转到found_middle,while循环将不会正常退出)。如果是这种情况,接下来该怎么办呢?我们还是继续看源码吧。

    g

        if (!size)
            return result;
        tmp = *p;
    
    found_first:
        tmp &= (~0UL >> (BITS_PER_LONG - size));
        if (tmp == 0UL)
            return result + size;
    found_middle:
        return result + __ffs(tmp);

    首先要明白,代码走到这里,有四种情况:

    1.查找起点位于数组的最后一个元素中,且位于该元素的第0位,同时,该元素含有“无效位”,即传入的参数size不是32的整数倍,并且,执行完d中的代码后,size小于32(这种情况容易被忽略)。在这种情况下,e中的if语句和f中的while循环都不会被执行,代码直接从d处走到g处。
    2.代码执行完e中的if后不再满足while循环的条件,直接走到g中代码处。这种情况是:查找起点在倒数第二个元素中且不位于第0位且该元素32位全是0,同时,最后一个数组元素中有“无效位”(传入参数size不是32的整数倍)。
    3.while循环由于size=0而退出(传入参数恰好是32的整数倍),这种情况下,整个位图都查找过了且并没有找到为1的bit位,换句话说,不可能再找到了。
    4.while循环由于size介于1~31之间而退出,即数组的最后一个元素中有“无效位”(传入参数size不是32的整数倍)。

    下面让我们看看,在这四种情况下,代码是怎么做的:

    首先,代码先判断size是否等于0,若等于0,直接返回result,这正对应了上面的情况3。注意,我们前面就说过,size+result的值总是等于传参时size的值(位图总大小),而此时size=0,所以,返回result,就是返回位图总大小。

    如果size不等于0,那就对应1、2、4三种情况,这三种情况的共同点是:都需要在数组的最后一个元素中查找,并且该元素中含有“无效位”。在这种情形下,find_next_bit函数的处理是:

    1.将最后一个数组元素拷贝到tmp
    2.在found_first中,将“无效位”置0,检验tmp是否为0,若是,返回位图总大小,否则,进入found_middle
    3.在found_middle中,找到tmp中第一个为1的bit位的索引,返回该位在位图中的索引

    至此,find_next_bit函数结束。

    Ⅴ.总结

    至此,我们对find_next_bit函数的分析就全部完成了。我们可以看到:

    1.若函数查找成功,返回自查找起点起第一个为1的bit位的索引;若查找失败,返回位图总大小。
    2.find_next_bit函数只是在位图中进行查找,自始至终都没有对位图进行任何修改

    最后,我们给出find_next_bit函数源码的简要注释版,作为最后的总结梳理

    /*
     *@addr:位图首地址
     *@size:位图总大小
     *@offset:查找起点
     */
    unsigned long find_next_bit(const unsigned long *addr, unsigned long size,
            unsigned long offset)
    {
        /*找到查找起点所在数组元素的地址*/
        const unsigned long *p = addr + BITOP_WORD(offset);
        /*计算查找起点所在数组元素前的数组元素中bit位的总个数,即“已处理”bit位个数*/
        unsigned long result = offset & ~(BITS_PER_LONG-1);
        unsigned long tmp;
    
        /*如果查找起点大于等于位图大小,返回位图大小*/
        if (offset >= size)
            return size;
        /*size:“未处理”bit位个数*/
        size -= result;
        /*offset:查找起点在所在数组元素中的索引*/
        offset %= BITS_PER_LONG;
        /*如果查找起点不在数组元素中的第0位*/
        if (offset) {
            /*拷贝元素*/
            tmp = *(p++);
            /*将查找起点前的bit位都置0*/
            tmp &= (~0UL << offset);
            /*如果查找起点位于最后一个数组元素且该元素含有“无效位”*/
            if (size < BITS_PER_LONG)
                goto found_first;
            /*如果tmp不为0,一定能找到*/
            if (tmp)
                goto found_middle;
            /*“待查找”bit位总数减少32,“已查找”bit位总数增加32*/
            size -= BITS_PER_LONG;
            result += BITS_PER_LONG;
        }
        /*while(size>=32)*/
        while (size & ~(BITS_PER_LONG-1)) {
            /*拷贝元素,若不为0,一定能找到*/
            if ((tmp = *(p++)))
                goto found_middle;
            /*“待查找”bit位总数减少32,“已查找”bit位总数增加32*/
            result += BITS_PER_LONG;
            size -= BITS_PER_LONG;
        }
        /*若size=0,返回位图总大小*/
        if (!size)
            return result;
        /*否则,拷贝最后一个数组元素*/
        tmp = *p;
    
    found_first:
        /*将元素中“无效位”都置为0*/
        tmp &= (~0UL >> (BITS_PER_LONG - size));
        /*若tmp为,不可能再找到,直接返回位图总大小*/
        if (tmp == 0UL)
            return result + size;
    found_middle:
        /*走到这里,就一定能找到,查找,返回结果*/
        return result + __ffs(tmp);
    }

    写在后面

    自认为写得很详细了,但在下才疏学浅,错误疏漏之处在所难免,恳请广大读者批评指正,您的批评指正是在下前进的不竭动力!

  • 相关阅读:
    springboot整合shiro进行权限管理
    多线程基础(一)
    面试杂谈(一)
    Spring循环依赖问题
    记一次电话面试的题目
    Jvm垃圾回收器(终结篇)
    Jvm垃圾回收器(算法篇)
    Jvm垃圾回收器(基础篇)
    Java内存模型探秘
    Jvm类的加载机制
  • 原文地址:https://www.cnblogs.com/zpcdbky/p/5825849.html
Copyright © 2020-2023  润新知