Linux 内核之bitmap

1.Bit 简介

  bit 中文翻译 ‘位’,即代表计算机里最小的计数单位。bit 在计算机里可用于表示 ‘0’ 和 ‘1’ 两个值,由于表征数字信号高低电平,为计算机提供了最基础的数据基础。 计算机中,多个 bit 的集合构成了固定长度不同的数据类型,比如字节,字,双字等 数据类型;多个 bit 也可以构成长度不同的位图 (bitmap), 因此位图就是包含了多个 bit 的数组。

  由于 bit 的值只包含了 ‘0’ 和 ‘1’ 两种结果,因此在计算机中,bit 经常用于标识 数据的状态。例如使用 bit 标识外围硬件开关的状态等。bit 从低位 (LSB) 开始编号 到高位 (MSB) 将 bit 定义为有序的 bit 序列,程序可以通过特定的算法,有序的管理 每个 bit 的使用,从而构造复杂的数据结构。

2.Bit 框架

  Linux 内核广泛使用 bit 作为其基础数据结构,包括 bitmap (位图), bitops, bitmask, bit find 等,基于这些基础数据结构,将 bit 运用到复杂的数据结构 中,例如 Bit,虚拟文件系统,进程管理等。因此 bit 是内核运行必不可少的部分。

2.1.bitmap 位图

  Linux 定义了位图 bitmap 为 bit 的数组,数组的成员就是独立的 bit。内核提供 了 bitmap 的通用框架和接口函数,以此便捷使用 bitmap,其定义如下:

  • lib/bitmap.c :文件包含了 bitmap 的核心实现以及通用接口。
  • include/linux/bitmap.h :bitmap 头文件,包含了 bitmap 相关的宏以及内嵌函数接口
  • Documentation/core-api/kernel-api.rst bitmap :内核文档

2.2.bitops

  Linux 提供了一套用于操作与机器位宽相同的位操作函数,这套函数用于操作 bitmap 长度在 BITS_PER_LONG 以内的 bit 集合,包括了 bit 的置位,清零,以及 翻转操作。bitops 的函数实现与体系架构有关,因此不同的体系结构有不同的 实现方式,但实现的效果都是一致,因此 bitops 又分为通用部分和与体系相关 部分。Linux 提供了一套通用框架接口函数,其定义基本如下:

  • include/asm-generic/bitops/non-atomic.h: bitops 函数最通用实现
  • arch/arm/include/asm/bitops.h: 与 ARM 相关的 bitops 函数实现

2.2.1.位操作__set_bit函数为例来分析其原理:

include/asm-generic/bitops/non-atomic.h
 * __set_bit - Set a bit in memory
 * @nr: the bit to set
 * @addr: the address to start counting from
 *
 * Unlike set_bit(), this function is non-atomic and may be reordered.
 * If it's called on the same region of memory simultaneously, the effect
 * may be that only one operation succeeds.
 */
static inline void __set_bit(int nr, volatile unsigned long *addr)
{
    unsigned long mask = BIT_MASK(nr);
    unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);

    *p |= mask;
}

宏定义如下:

include/linux/bitops.h:
#define BIT(nr)            (1UL << (nr))
#define BIT_MASK(nr)        (1UL << ((nr) % BITS_PER_LONG))
#define BIT_WORD(nr)        ((nr) / BITS_PER_LONG)

arch/arm/include/asm/types.h:
#define BITS_PER_LONG 32

__set_bit函数内容等价表示如下:

static inline void __set_bit(int nr, volatile unsigned long *addr)
{
    addr[nr/32] |= (1UL << (nr % 32)); 
    //或者addr[nr >> 5] |= (1UL << (nr & 31));
}

  addr是一个类型为unsigned long(32 bits)的数组,通过nr/32得到要设置的比特位nr位于该数组中的哪一个unsigned long,然后通过( nr%32 )得到该unsigned long整数中是哪一位(第0位、第1位、…还是第31位?)需要设置;最后,通过 addr[nr/32] |= (1UL << (nr % 32)) 设置该unsigned long整数中相应的比特位。

  理解了上述代码,则其它比特位操作API就容易懂了。

__clear_bit: 将addr所指的地址处的值第nr位清0,方法 addr[nr/32] & 11111011111- __change_bit: 将addr所指的地址处的值第nr位取反,方法addr[nr/32] ^ 00000100000
__test_and_set_bit:将addr所指的地址处的值第nr位置1,返回该bit原始值(0或1);
__test_and_clear_bit:将addr所指的地址处的值第nr位清0,返回该bit原始值(0或1);
__test_and_change_bit:将addr所指的地址处的值第nr位取反,返回该bit原始值(0或1);
test_bit:即测试nr位是否被置位,置位返回1;

2.3.bitmask

  Linux 提供了一套用于掩码操作的函数。由于 bit 是内核的基础组成部分,多种 复杂的数据结构都也基于 bit 实现,为了实现多个 bit 的掩码操作,内核也提供 多个函数以及宏用于便捷实现其功能。bitmask 的实现与机器的位宽实现密切相关, 其实现与 BITS_PER_LONG 的实现有关。Linux 提供了一套通过的框架接口,其 定义如下:

  • linux/include/linux/bitmap.h 包含了 bitmap 掩码相关的宏以及内嵌函数接口
  • linux/include/bits.h 包含了 bit 基础的掩码宏。

2.4.bit find

  由于 bitmap 包含了多个 bit,需要在 bit 集合中找到符合要求的 bit, 由于 bit 序与大小端有关系,并与体系结构也有关系,因此内核提供了一套 用于查找 bit 的函数接口,其定义如下:

  • linux/lib/find_bit.c: bit 查找的通用接口函数
  • linux/arch/arm/include/asm/bitops.h: ARM 相关的 bit find 接口。
  • linux/lib/find_bit_benchmark.c: bit 查找测试接口函数

3.Bitmap 在内核中的应用

3.1.Bitmap 与、或、非、异或、与非操作

  提供了一套基础的算术逻辑,包括与运算、或运算、非运算、异或运算、与非 运算等,这将大大增加 bitmap 的可用性,其函数接口及实践接口如下:

  • bitmap_and: bitmap 与运算
  • bitmap_or: bitmap 或运算
  • bitmap_xor: bitmap 异或运算
  • bitmap_complement: bitmap 非运算
  • bitmap_andnot: bitmap 与非运算

3.1.1.bitmap_and

static inline int bitmap_and(unsigned long *dst, const unsigned long *src1,
                        const unsigned long *src2, unsigned int nbits)
{
        if (small_const_nbits(nbits))
                return (*dst = *src1 & *src2 & BITMAP_LAST_WORD_MASK(nbits)) != 0;
        return __bitmap_and(dst, src1, src2, nbits);
}

  函数用于 bitmap 的 AND 操作。参数 dst 存储 AND 结果;参数 src1 指向第一个 bitmap;参数 src2 指向第二个 bitmap;参数 nbits 代表相与的位数。 函数首先调用 small_const_nbits() 函数判断 nbits 参数是不是一个常数,并且 nbits 大于 0 小于 BITS_PER_LONG。如果满足上面这些条件,那么函数就将两个 bitmap 相与, 结果与 BITMAP_LAST_WORD_MASK(nbits) 掩码相与,生成的结果存储在 dst 里; 如果不符合,要么 nbits 的位数超过 BITS_PER_LONG 或者 nbits 不是一个常量, 那么函数就调用 __bitmap_and() 函数指向两个 bitmap 的相与操作。

3.2.Bitmap 置位、清零、填充操作

  Bitmap 提供了一套用于置位、清零、填充的函数接口,使用这些接口可以便捷的对特定 位进行操作,其函数接口及实践入口如下:

  • bitmap_set: bitmap 置位操作
  • bitmap_clear: bitmap 清零操作
  • bitmap_fill: bitmap 填充操作

3.3.Bitmap 转换操作

  在 Linux 中,由于不同的位宽导致 bitmap 所包含的 bit 数目不一样,其与 BITS_PER_LONG 有直接的关系,32-bit 系统中,BITS_PER_LONG 代表一个 long 变量中包含 32 个 bit;在 64-bit 系统中,BIST_PER_LONG 代表一个 long 变量中包含 64 个 bit。由于体系位宽的 差异,内核也为 bitmap 提供了一套用于转换的函数,函数接口及实践入口如下:

  • bitmap_from_arr32: 将 32bit 的数组转换成一个 bitmap
  • bitmap_to_arr32: 将 bitmap 的数组转换成一个 32 位数组
  • bitmap_from_u64: 将 64 位变量转换成 bitmap
  • bitmap_parse: 将字符串转换成 bitmap

3.4.Bitmap 位移操作

  Bitmap 在特定的需求中需要将其左移或右移多个 bit,因此内核提供了专门用于 bitmap 位移的函数,函数接口及实践入口如下:

  • bitmap_shift_left: bitmap 左移操作
  • bitmap_shift_right: bitmap 右移操作

3.5.Bitmap 查找操作

  Bitmap 同样提供了一套用于查找特定 bit 的函数接口,以便在 bitmap 中找到指定 的 bit,函数接口及实践入口如下:

  • bitmap_find_next_zero_area_off: bitmap 中找到整块零区域
  • find_first_bit: bitmap 中找到第一个置位的位置
  • find_first_zero_bit: bitmap 中找到第一个清零的位置
  • find_last_bit: bitmap 中找到最后一个置位的位置

3.6.Bitmap 掩码操作

  Bitmap 也提供了一套用于掩码的操作,和 bitops 的掩码相比,bitmap 提供的掩码 涉及的 bit 范围更广阔,可以超出 BITS_PER_LONG 的限制,函数接口及实践入口如下:

  • BITMAP_FIRST_WORD_MASK: bitmap 低位指定个 bit 为零,其余全 1 的掩码
  • BITMAP_LAST_WORD_MASK: bitmap 低位指定个 bit 为 1,其余全 0 的掩码

3.7.Bitmap 遍历操作

  Bitmap 提供了一套函数用于遍历 bitmap 中置位或者清零的位置,这些函数中可以从 头还是遍历,也可以从指定位置遍历,函数接口及实践入口如下:

  • for_each_clear_bit_from: 从指定位置遍历 bitmap 清零 bit
  • for_each_clear_bit: 从头开始遍历 bitmap 清零 bit
  • for_each_set_bit: 从头开始遍历 bitmap 置位 bit
  • for_each_set_bit_from: 从指定位置开始遍历 bitmap 置位 bit

3.8.Bitmap 其余操作

  Bitmap 还提供了其余特定需求的函数,函数接口及实践入口如下:

  • bitmap_copy: 复制 bitmap
  • bitmap_copy_clear_tail: 复制 bitmap 低位
  • bitmap_empty: 判断 bitmap 是否为空
  • bitmap_equal: 判断 bitmap 是否相等
  • bitmap_weight: 获得 bitmap 中 1 的个数

refer to

  • https://biscuitos.github.io/blog/BITMAP/
发布了161 篇原创文章 · 获赞 15 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_41028621/article/details/105725077
今日推荐