test_and_set_bit 是 Linux 内核中一个经典的原子操作函数,作用是将指定位置 1,并返回该位的旧值。不同架构有不同的实现方式

int test_and_set_bit(int nr, volatile unsigned long *addr);
  • nr:要设置的位编号(从 0 开始)
  • addr:位图起始地址,本质是一个unsigned long类型数组的起始地址
  • 返回值:该位设置前的旧值(0 表示原来未设置,非 0 表示原来已设置)

通用 C 实现(基于自旋锁)

在不支持硬件原子位操作的架构上,通过锁来保证原子性:

1static inline int test_and_set_bit(int nr, volatile unsigned long *addr)
2{
3    unsigned long mask = BIT_MASK(nr);
4    unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);
5    unsigned long old;
6    unsigned long flags;
7
8    _atomic_spin_lock_irqsave(p, flags);
9    old = *p;           // 读取旧值
10    *p = old | mask;    // 设置目标位
11    _atomic_spin_unlock_irqrestore(p, flags);
12
13    return (old & mask) != 0;
14}

addr数组的元素是unsigned long,所以mask需要计算一个unsigned long长度内的mask

p= ((unsigned long *)addr) + BIT_WORD(nr)这一步得到的是addr数组中第(BIT_WORD(nr))个元素的首地址;

其中辅助宏定义为:

1#define BIT_MASK(nr)  (UL(1) << ((nr) % BITS_PER_LONG))
2#define BIT_WORD(nr)  ((nr) / BITS_PER_LONG)
  • BIT_MASK(nr):生成只包含目标位的掩码,因为bitmap数组的每个元素是unsigned long,所以求余的除数是unsigned long的长度。
  • BIT_WORD(nr):计算 nr 位于 addr 数组的第几个 unsigned long 元素中,之所以要这样计算,是因为p和addr都是unsigned long类型的指针,指针的加法是偏移n * sizeof(指针指向的类型),所以上述操作就是把nr转换成bitmap数组里的一个元素里进行操作

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐