linux 内核中的简单 bitmap
作者: 李云鹏([email protected])
bitmap用于实现bool的数组,标识一个事件发生没发生。可以理解为bitmap处理的是有没有的问题。
bitmap将一片连续的空间作为一个数据类型,其中的成员都是1位,长度是bitmap的容量。
此篇文章介绍的是spi驱动中关于分配spi的设备号中用到的bitmap,特点就是从0开始,删除可以随意的删除,再添加时找到最小的没有用到的设备号。需要特别说明的是在后边讲解的时候均采用了没有互斥保护的简单的对位操作的函数进行讲解,此部分函数在 include/asm-generic/bitops/non-atomic.h 中。
DECLARE_BITMAP 宏
#define DECLARE_BITMAP(name,bits) \
unsigned long name[BITS_TO_LONGS(bits)]
#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long))
#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))
#define BITS_PER_BYTE 8
根据上边的定义,可以解析成
在32位系统下:
DECLARE_BITMAP(name, bits) == unsigned long name[(bits+31)/32]
在64位系统下:
DECLARE_BITMAP(name, bits) == unsigned long name[(bits+63)/64]
如果是64位系统,我们带入值看看函数到底实现了啥功能:
nr BITS_TO_LONGS
0 | 0
1 | 1
2 | 1
3 | 1
... | ...
63 | 1
64 | 1
65 | 2
函数的功能如下:
声明一个保存数据类型是 unsigned long 类型的结构体,整个的数组成员,我们看做是保存了 bits 个位的容器。
参数:
name,保存位的 unsigned long 类型的数据保存在内存中的首地址,或者是bitmap的首地址
bits,我们要用到多少位,或者是bitmap中元素个数
假设我们要一个能存储32个bit的bitmap
我们只需要定义一个含一个 unsigned long 类型的数组就可以,即unsigned long name[1],我们声明的时候,只是简单的 DECLARE_BITMAP(xxx, 32);
find_first_zero_bit函数原型:(此处选择分析的是汇编程序,有相应的c程序,但是传入的参数意义不同)
#define find_first_zero_bit(p,sz) _find_first_zero_bit_le(p,sz) /* 对于小端系统 */
ENTRY(_find_first_zero_bit_le) @ 此函数在find_bit.S文件中
teq r1, #0 @ 32 ^ 0 = !0 Z = 0
beq 3f @ Z = 0 条件不成立,不执行
mov r2, #0 @ r2 = 0
1:
ldrb r3, [r0, r2, lsr #3] @ r3 = [r0 + r2>>3] -> r3 = minors[] 首地址指向的下一个字节
eors r3, r3, #0xff @ r3 = r3 ^ 0b1111 1111,如何r3是0,则结果是0xff,z=0,一定要注意的是此处 r3的低8位已经取反了
bne .L_found @ 当 z = 0 ,跳转
add r2, r2, #8 @ r2 = r2 + 8 ,用于控制循环多少次
2: cmp r2, r1 @ 此处限制 什么时候循环退出,此处如果是32,则右移最多4次(32/8)
blo 1b @ if(r2 < r1){bl 1b}
3: mov r0, r1 @ 返回值
mov pc, lr @ 回到c函数
ENDPROC(_find_first_zero_bit_le)
/*
* One or more bits in the LSB of r3 are assumed to be set.
*/
.L_found: @此部分就是找到取反的r3中的第一个1
tst r3, #0x0f @ cpsr = r3 & 0b0000 1111 ,把高4位屏蔽,看低4位是不是全部为 0
addeq r2, r2, #4 @ 如果低四位全部是0,则 r2 = r2 + 4
movne r3, r3, lsl #4 @ 如果上边的结果不等于0,则 r3 = r3 << 4
tst r3, #0x30 @ cpsr = r3 & 0b0011 0000
addeq r2, r2, #2 @ 等于0,r2 = r2 + 2
movne r3, r3, lsl #2 @ 不等0,r3 = r3 << 2
tst r3, #0x40 @ cpsr = r3 & 0b1000 0000
addeq r2, r2, #1 @ 等于0,r2 = r2 + 1
mov r0, r2 @ 不等0,r0 = r2
cmp r1, r0 @ Clamp to maxbit
movlo r0, r1 @ if(r1 < r0){r0 = r1;} 把最小的作为返回值
mov pc, lr
用简单的方式,带入值分析如下:
假设 r3 取反后等于 a、b、c ... 的情况
a. 0b0000 0001
cpsr = 0x01 z = 0
r3 = 0x10
cpsr = 0x0001 0000 z = 0
r3 = 0b0100 0000
cpsr = 0b0100 0000 & 0x0100 0000 = 0x40 z = 0
r0 = 0
返回 r0
b. 0b0000 0010
cpsr = 0b0000 0010 z = 0
r3 = 0b0010 0000
cpsr = 0b0010 0000 z = 0
r3 = 0b1000 0000
cpsr = 0b1000 0000 & 0x0100 0000 = 0x00 z = 1
r0 = 1
返回 r0
c. 0b0000 0100
cpsr = 0b0000 0100 z = 0
r3 = 0b0100 0000
cpsr = 0b0100 0000 & 0b0011 0000 z = 1
r2 = 0 + 2 = 2
cpsr = 0b0100 0000 & 0x0100 0000 = 0b0100 0000 z = 0
r0 = 2
返回 r0
d. 0b0000 1000
r0 = 3 , 返回 r0
e. 0b0000 0110
cpsr = 0b0000 0110 z = 0
r3 = 0b0110 0000
cpsr = 0b0110 0000 & 0b0011 0000 = 0b0010 0000 z = 0
r3 = 0b1000 0000
cpsr = 0b1000 0000 & 0x0100 0000 = 0 z = 1
r0 = 1
返回 r0
f. 0b0000 1100
cpsr = 0b0000 1100 z = 0
r3 = 0b1100 0000
cpsr = 0b1100 0000 & 0b0011 0000 = 0 z = 1
r2 = 2
cpsr = 0b1100 0000 & 0x0100 0000 = 1 z = 0
r0 = 2
返回 r0
g. 0b0001 1000
cpsr = 0b0001 1000 z = 0
r3 = 0b1000 0000 <此处同 d 的情况相同了>
r0 = 3
返回 r0
函数功能如下:
从0位开始,找到第一个值是0的位,返回位置,如果是第1位(位置从0开始计)是0,则返回1
参数:
p,unsigned long name[] 的首地址或者说是bitmap的首地址
sz,bitmap中含有的bit位的总和
图表表示:
如果bitmap中的值是0b0000 0111 的话,则返回的值是3
set_bit函数原型:(此函数的实现由两种,一种是汇编,一种是c实现,当然汇编实现的速度上可能更快,但为了好理解和好分析,我们分析c程序)
除了汇编和c的情况还有是不是要求原子操作(多核的要求原子操作,就是配置了CONFIG_SMP宏的)的(__set_bit函数),要求原子操作(set_bit函数)的我们知道只需要加一个锁就可以,那我们就只来看看不需要原子操作的函数:
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;
}
#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG)) /* 找到在单 unsigned long 元素中的第几位 */
#define BIT_WORD(nr) ((nr) / BITS_PER_LONG) /* 找到操作的位所在的unsigned long数组中的地址 */
此函数的功能:
将bitmap中的第nr位置1(从0开始)
参数:
nr,bitmap中的第多少位(从0开始)
addr,bitmap的首地址
clear_bit函数原型:(函数的命名规则和set_bit相同)
static inline void __clear_bit(int nr, volatile unsigned long *addr)
{
unsigned long mask = BIT_MASK(nr);
unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);
*p &= ~mask;
}
#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG))
#define BIT_WORD(nr) ((nr) / BITS_PER_LONG)
#define BITS_PER_LONG 32 /* 32位系统下 */
此函数的功能:
将bitmap中的第nr位清0(从0开始)
参数:
nr,bitmap中的第多少位(从0开始)
addr,bitmap的首地址
change_bit函数原型:
/**
* __change_bit - 翻转memory中的某位
* @nr: bitmap中的第多少位(从0开始)
* @addr: bitmap中的首地址
*/
static inline void __change_bit(int nr, volatile unsigned long *addr)
{
unsigned long mask = BIT_MASK(nr);
unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);
*p ^= mask;
}
#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG))
#ifdef CONFIG_64BIT
#define BITS_PER_LONG 64
#else
#define BITS_PER_LONG 32
#endif /* CONFIG_64BIT */
【1】此函数中未考虑原子操作的部分
test_bit函数原型:
/**
* test_bit - 测试某一位是不是1
* @nr: 测试哪一位(从0开始)
* @addr: 从哪个地址开始,一般就是bitmap的首地址
*/
static inline int test_bit(int nr, const volatile unsigned long *addr)
{
return 1UL & (addr[BIT_WORD(nr)] >> (nr & (BITS_PER_LONG-1)));
}
#define BIT_WORD(nr) ((nr) / BITS_PER_LONG)
#ifdef CONFIG_64BIT
#define BITS_PER_LONG 64
#else
#define BITS_PER_LONG 32
#endif /* CONFIG_64BIT */
linux内核还为我们提供了一些复合的操作,如下:
/**
* __test_and_set_bit - 设置一个位并且返回它原先的值
* @nr: 要设置的位/bitmap中的哪一位
* @addr: bitmap首地址
*/
static inline int __test_and_set_bit(int nr, volatile unsigned long *addr);
/**
* __test_and_clear_bit - 清空一个位并且返回它的原先的值
* @nr: bitmap中的哪一位
* @addr: bitmap的首地址
*/
static inline int __test_and_clear_bit(int nr, volatile unsigned long *addr);
/**
* __test_and_clear_bit - 取反某一位并返回它的原先的值
* @nr: bitmap中的哪一位
* @addr: bitmap的首地址
*/
static inline int __test_and_change_bit(int nr, volatile unsigned long *addr)