鸿蒙轻内核A核源码分析系列二 数据结构-位图操作 原创 精华
鸿蒙轻内核A核源码分析系列二 数据结构-位图操作
在进一步分析之前,本文我们先来熟悉下OpenHarmony
鸿蒙轻内核提供的位操作模块,在互斥锁等模块对位操作有使用。位操作是指对二进制数的bit
位进行操作。程序可以设置某一变量为状态字,状态字中的每一bit
位(标志位)可以具有自定义的含义。
本文中所涉及的源码,以OpenHarmony LiteOS-A
内核为例,均可以在开源站点https://gitee.com/openharmony/kernel_liteos_a 获取。
1 位操作的宏定义
位操作模块提供对32位无符号整数数值的bit
位进行操作,bit
位取值为0-31,以0开始计算,从左向右,第0位,第1位。。。第31位等。⑴处定义的宏OS_BITMAP_MASK
如下,也就是十进制31。如果传入的比特位pos
大于31,会通过逻辑与运算截断(pos & OS_BITMAP_MASK)
,只取低5位,确保不会大于31,避免溢出。⑵处定义的位图掩码全是1。
在文件kernel\include\los_bitmap.h
中定义了常用的位操作相关的宏。宏BITMAP_WORD
根据参数x
计算出需要操作第几个状态字,由于计算状态字的使用的是UINTPTR
,状态字可以是32位、也可以是64位。后文,我们默认以32位进行讲解。宏BITMAP_FIRST_WORD_MASK
传入的参数是位操作的开始bit
位数,用于计算需要进行位操作的掩码,从开始位全部是1,宏BITMAP_LAST_WORD_MASK
传入的参数是位操作的结束bit
位数,用于计算需要进行位操作的掩码,结束位之前全部是1。宏BITMAP_NUM_WORDS
传入位数,计算状态字的数量。
2 位操作常用功能
OpenHarmony
鸿蒙轻内核的位操作模块提供标志位的置1和清0操作,可以改变标志位的内容,同时还提供获取状态字中标志位为1的最高位和最低位的功能。用户也可以对系统的寄存器进行位操作。位操作提供了7个API
,进行置1、清0、获取为1的最高、最低位等操作,如下:
接口名 | 描述 |
---|---|
LOS_BitmapSet | 对状态字的某一标志位进行置1操作 |
LOS_BitmapClr | 对状态字的某一标志位进行清0操作 |
LOS_HighBitGet | 获取状态字中为1的最高位 |
LOS_LowBitGet | 获取状态字中为1的最低位 |
LOS_BitmapSetNBits | 对状态字的连续标志位进行置1操作 |
LOS_BitmapClrNBits | 对状态字的连续标志位进行清0操作 |
LOS_BitmapFfz | 获取从最低有效位开始的第一个0的bit位 |
下面,我们剖析下位操作的源代码。
2.1 LOS_BitmapSet()对状态字的某一标志位进行置1操作
对状态字的某一标志位进行置1操作。我们先看看传入的参数,需要的2个参数分别是:需要改变bit
位内容的状态字UINT32 *bitmap
,需要改变的bit
位位数UINT16 pos
。
代码很简单,首先进行基础的校验,如果状态字为空,则返回。然后计算pos & OS_BITMAP_MASK
,只取二进制的低5位,最大位值为31,避免左移的时候发生溢出。1U << (pos & OS_BITMAP_MASK)
就是需要改变内容的状态字的bit
位,通过按位或运算设置状态字UINT32 *bitmap
的指定bit
位的内容为1。
2.2 LOS_BitmapClr()对状态字的某一标志位进行清0操作
对状态字的某一标志位进行清0操作,代码和置1操作对应,比较简单,~(1U << (pos & OS_BITMAP_MASK))
表示需要改变内容的状态字的bit
位为0,其余位为1,然后通过按位与运算设置状态字UINT32 *bitmap
的指定bit
位的内容为0。
2.3 LOS_HighBitGet()获取状态字中为1的最高位
代码中CLZ(bitmap)
是宏,展开为(__builtin_clz(bitmap))
,这是编译器内置的高效位运算的库函数,clz
是count leading zeros
的缩写,就是统计二进制数值中高位区开头的全是0的数目。使用OS_BITMAP_MASK
减去该值,结果就是状态字中的1的最高位。
2.4 LOS_LowBitGet()获取状态字中为1的最低位
代码其中CTZ(bitmap)
是宏,展开为(__builtin_ctz(value))
,这是编译器内置的高效位运算的库函数,ctz
是count trailing zeros
的缩写,就是统计二进制数值中低位区结尾的全是0的数目,该结果就是状态字中的1的最低位。
2.5 LOS_BitmapSetNBits()对状态字的连续标志位进行置1操作
可以使用LOS_BitmapSetNBits()
函数对状态字的连续比特位进行置1操作,第一个参数是需要改变bit
位内容的状态字UINT32 *bitmap
,第二个参数是需要置1的bit
位开始数start
,第三个参数是需要置1的数量numsSet
。由于bit
位开始数start
并没有限制在[0,31],所以实际上设置的可能是UINT32 *bitmap
状态字后面的状态字,需要根据业务实际情况进行设置,避免覆写其他内存。同样,需要置1的数量numsSet
也可能跨多个状态字。如图所示:
我们看下代码,⑴处计算出需要操作的状态字,其中BITMAP_WORD(start)
计算相对状态字bitmap
需要偏移的数量,如果start处于区间[0,31],BITMAP_WORD(start)
等于0,操作的就是状态字bitmap
。如果start处于区间[32,63],BITMAP_WORD(start)
等于1,操作的就是状态字bitmap
后面的第一个状态字,以此类推。⑵处size
可以和bit
位开始数start
结合来理解,size
就是需要置1的bit
位结束位数。⑶处需要置1操作的bit
位的位数,⑷是对应需要置1操作的bit
位的掩码。
⑸处如果条件成立,说明需要置1操作需要跨多个状态字进行操作,代码会一个状态字处理完毕,再去处理下一个状态字。⑹处把当前状态字的相应的bit
位进行置1操作,然后执行⑺把剩余需要置1的位数减去已经置1的位数。⑻处更新bitsToSet
和maskToSet
,然后指针p
指向下一个状态字。⑼处如果需要置1的位数大于0,并且此时已经可以在一个状态字内完成操作,执行⑽处计算需要置1操作的掩码,从bit
开始位到结束位需要进行置1。⑾处代码执行置1操作,完成对状态字的连续标志位进行置1操作。
2.6 LOS_BitmapClrNBits()对状态字的连续标志位进行清0操作
可以使用LOS_BitmapClrNBits()
函数对状态字的连续比特位进行清0操作,第一个参数是需要改变bit
位内容的状态字UINT32 *bitmap
,第二个参数是需要清0的bit
位开始数start
,第三个参数是需要清0的数量numsClear
。该函数是函数LOS_BitmapSetNBits()
的反向操作,代码解释可以参考函数LOS_BitmapSetNBits()
。
2.8 LOS_BitmapFfz()获取从最低有效位开始的第一个0的bit位
可以使用LOS_BitmapFfz()
函数获取从最低有效位开始的第一个0的bit
位位数,第一个参数是需要改变bit
位内容的状态字UINT32 *bitmap
,第二个参数numBits
表示最大的位数,对返回值进行限制,需要在指定的位数内找到符合条件的位数,否则返回-1。
在看函数代码之前,先了解下Ffz()
函数,如下:调用内嵌函数__builtin_ffsl()
可以获取一个unsigned long类型数字的二进制形式的从左开始的第一个1的位数,这个位数从1开始计数。比如对于二进制数字0110,该函数会返回2。在下面的函数中,给函数__builtin_ffsl()
传入的参数进行了取反,并减去了1,所以Ffz()
函数返回一个数字从左开始的第一个0的位数,这个位数从0开始计数。
我们接着看下函数LOS_BitmapFfz()
的代码。⑴处根据位数numBits
计算出对应的状态字的数量,然后依次循环每一个状态字,⑵处如果状态字全为1,则继续循环,否则执行⑶。执行到⑶说明,,前面有i
个状态字的各个位全为1。i * BITMAP_BITS_PER_WORD + Ffz(bitmap[i])
就表示各个状态字的二进制位中,从左到右第一个0的位置。⑷处如果获取的位数小于第二个参数,则返回获取的位数,否则返回-1。如下图所示:
源代码如下:
小结
本文带领大家一起剖析了鸿蒙轻内核的位操作模块的源代码。后续也会陆续推出更多的分享文章,敬请期待,也欢迎大家分享学习、使用鸿蒙轻内核的心得,有任何问题、建议,都可以留言给我们: https://gitee.com/openharmony/kernel_liteos_a/issues 。为了更容易找到鸿蒙轻内核代码仓,建议访问 https://gitee.com/openharmony/kernel_liteos_a ,关注Watch
、点赞Star
、并Fork
到自己账户下,谢谢。
跟着楼主学源码
如果对您有用 我会特别开心。。。如果有错误 请指正 谢谢 ^_^