Linux 内核学习笔记系列,GCC 扩展语法和内核数据结构部分,简单介绍 Linux 内核位图。
内核位图的使用#
下面只列举常用的内核位图函数/宏,头文件为 include/linux/bitops.h
。
创建位图#
1
| DECLARE_BITMAP(name,bits);
|
定义一个名称为 name
且至少包含 bits
位的位图。
设置位#
1
| void set_bit(unsigned int nr, volatile unsigned long *addr);
|
清除位#
1
| void clear_bit(int nr, volatile unsigned long *addr);
|
改变位#
1
| void change_bit(int nr, volatile unsigned long *addr);
|
测试并设置位#
1
| int test_and_set_bit(int nr, volatile unsigned long *addr);
|
若被测试位为 0
,返回 0
,反之返回非 0
值。
测试并清除位#
1
| int test_and_clear_bit(int nr, volatile unsigned long *addr);
|
若被测试位为 0
,返回 0
,反之返回非 0
值。
测试并改变位#
1
| int test_and_change_bit(int nr, volatile unsigned long *addr);
|
若被测试位为 0
,返回 0
,反之返回非 0
值。
测试位#
1
| int test_bit(int nr, const volatile unsigned long *addr);
|
若被测试位为 0
,返回 0
,反之返回非 0
值。
内核位图的实现#
定义内核位图的宏位于 include/linux/types.h
。内核位操作的实现分为两部分,体系结构无关的实现位于 include/linux/bitops.h
,x86 体系结构相关的部分位于 arch/x86/include/asm/bitops.h
。
创建位图的实现#
include/linux/types.h
:
1
2
| #define DECLARE_BITMAP(name,bits) \
unsigned long name[BITS_TO_LONGS(bits)]
|
include/linux/bitops.h
:
1
2
| #define BITS_PER_BYTE 8
#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long))
|
辅助宏的实现#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| #if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 1)
/* Technically wrong, but this avoids compilation errors on some gcc
versions. */
#define BITOP_ADDR(x) "=m" (*(volatile long *) (x))
#else
#define BITOP_ADDR(x) "+m" (*(volatile long *) (x))
#endif
#define ADDR BITOP_ADDR(addr)
/*
* We do the locked ops that don't return the old value as
* a mask operation on a byte.
*/
#define IS_IMMEDIATE(nr) (__builtin_constant_p(nr))
#define CONST_MASK_ADDR(nr, addr) BITOP_ADDR((void *)(addr) + ((nr)>>3))
#define CONST_MASK(nr) (1 << ((nr) & 7))
|
IS_IMMEDIATE
宏判断 nr
是否是整数常量。如果把位图看成 8
列的二维数组,那么 CONST_MASK_ADDR
宏负责计算行号,CONST_MASK
宏负责计算列号。
设置位的实现#
arch/x86/include/asm/bitops.h
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| /**
* set_bit - Atomically set a bit in memory
* @nr: the bit to set
* @addr: the address to start counting from
*
* This function is atomic and may not be reordered. See __set_bit()
* if you do not require the atomic guarantees.
*
* Note: there are no guarantees that this function will not be reordered
* on non x86 architectures, so if you are writing portable code,
* make sure not to rely on its reordering guarantees.
*
* Note that @nr may be almost arbitrarily large; this function is not
* restricted to acting on a single-word quantity.
*/
static __always_inline void
set_bit(unsigned int nr, volatile unsigned long *addr)
{
if (IS_IMMEDIATE(nr)) {
asm volatile(LOCK_PREFIX "orb %1,%0"
: CONST_MASK_ADDR(nr, addr)
: "iq" ((u8)CONST_MASK(nr))
: "memory");
} else {
asm volatile(LOCK_PREFIX "bts %1,%0"
: BITOP_ADDR(addr) : "Ir" (nr) : "memory");
}
}
|
LOCK_PREFIX
的实现原理涉及锁,见 TODO。
查阅相关手册,可以知道这里 q
修饰符代表使用 8 位寄存器(例如 al
、bl
等)。
bts
指令(Bit Test and Set)是位测试并置位指令,此处 bts %1,%0
即先测试 %0
内存处的 %1
位的值,存入 CF
标志寄存器,再将该位置 1
。
清除位的实现#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| /**
* clear_bit - Clears a bit in memory
* @nr: Bit to clear
* @addr: Address to start counting from
*
* clear_bit() is atomic and may not be reordered. However, it does
* not contain a memory barrier, so if it is used for locking purposes,
* you should call smp_mb__before_clear_bit() and/or smp_mb__after_clear_bit()
* in order to ensure changes are visible on other processors.
*/
static __always_inline void
clear_bit(int nr, volatile unsigned long *addr)
{
if (IS_IMMEDIATE(nr)) {
asm volatile(LOCK_PREFIX "andb %1,%0"
: CONST_MASK_ADDR(nr, addr)
: "iq" ((u8)~CONST_MASK(nr)));
} else {
asm volatile(LOCK_PREFIX "btr %1,%0"
: BITOP_ADDR(addr)
: "Ir" (nr));
}
}
|
btr
指令(Bit Test and Reset)是位测试并复位指令。
改变位的实现#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| /**
* change_bit - Toggle a bit in memory
* @nr: Bit to change
* @addr: Address to start counting from
*
* change_bit() is atomic and may not be reordered.
* Note that @nr may be almost arbitrarily large; this function is not
* restricted to acting on a single-word quantity.
*/
static inline void change_bit(int nr, volatile unsigned long *addr)
{
if (IS_IMMEDIATE(nr)) {
asm volatile(LOCK_PREFIX "xorb %1,%0"
: CONST_MASK_ADDR(nr, addr)
: "iq" ((u8)CONST_MASK(nr)));
} else {
asm volatile(LOCK_PREFIX "btc %1,%0"
: BITOP_ADDR(addr)
: "Ir" (nr));
}
}
|
btc
指令(Bit Test and Complement)是位测试并取反指令。
测试并设置位的实现#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| /**
* test_and_set_bit - Set a bit and return its old value
* @nr: Bit to set
* @addr: Address to count from
*
* This operation is atomic and cannot be reordered.
* It also implies a memory barrier.
*/
static inline int test_and_set_bit(int nr, volatile unsigned long *addr)
{
int oldbit;
asm volatile(LOCK_PREFIX "bts %2,%1\n\t"
"sbb %0,%0" : "=r" (oldbit), ADDR : "Ir" (nr) : "memory");
return oldbit;
}
|
这里很巧妙地借助了 sbb
指令执行带借位减法。如果 CF
标志被置位,函数返回 -1
,反之返回 0
。
测试并清除位的实现#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| /**
* test_and_clear_bit - Clear a bit and return its old value
* @nr: Bit to clear
* @addr: Address to count from
*
* This operation is atomic and cannot be reordered.
* It also implies a memory barrier.
*/
static inline int test_and_clear_bit(int nr, volatile unsigned long *addr)
{
int oldbit;
asm volatile(LOCK_PREFIX "btr %2,%1\n\t"
"sbb %0,%0"
: "=r" (oldbit), ADDR : "Ir" (nr) : "memory");
return oldbit;
}
|
测试并改变位的实现#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| /**
* test_and_change_bit - Change a bit and return its old value
* @nr: Bit to change
* @addr: Address to count from
*
* This operation is atomic and cannot be reordered.
* It also implies a memory barrier.
*/
static inline int test_and_change_bit(int nr, volatile unsigned long *addr)
{
int oldbit;
asm volatile(LOCK_PREFIX "btc %2,%1\n\t"
"sbb %0,%0"
: "=r" (oldbit), ADDR : "Ir" (nr) : "memory");
return oldbit;
}
|
测试位的实现#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| static __always_inline int constant_test_bit(unsigned int nr, const volatile unsigned long *addr)
{
return ((1UL << (nr % BITS_PER_LONG)) &
(((unsigned long *)addr)[nr / BITS_PER_LONG])) != 0;
}
static inline int variable_test_bit(int nr, volatile const unsigned long *addr)
{
int oldbit;
asm volatile("bt %2,%1\n\t"
"sbb %0,%0"
: "=r" (oldbit)
: "m" (*(unsigned long *)addr), "Ir" (nr));
return oldbit;
}
#if 0 /* Fool kernel-doc since it doesn't do macros yet */
/**
* test_bit - Determine whether a bit is set
* @nr: bit number to test
* @addr: Address to start counting from
*/
static int test_bit(int nr, const volatile unsigned long *addr);
#endif
#define test_bit(nr, addr) \
(__builtin_constant_p((nr)) \
? constant_test_bit((nr), (addr)) \
: variable_test_bit((nr), (addr)))
|
bt
指令(Bit Test)是位测试指令。