Linux bit_spin_lock位自旋锁与wait_on_bit等待

bit_spin_lock是Linux内核中一种极轻量级的同步机制,它利用一个内存地址中的单个bit来模拟自旋锁,结合wait_on_bit/wake_up_bit实现睡眠等待.这种机制特别适合管理内存中的标志位、缓存行状态和文件系统元数据.

一、bit_spin_lock的核心思想

位自旋锁使用unsigned long数组中的一个bit作为锁标志.锁状态通过原子化的test_and_set_bit操作控制:

```c
/* bit 0 = locked, bit 0 = 0 unlocked */

/* 自旋锁获取 */
static inline void bit_spin_lock(int bitnum, unsigned long *addr)
{
/*
* 原子测试并设置bit:
* - 如果bit原来为0(未锁定): 设置bit并返回0, 获得锁
* - 如果bit原来为1(已锁定): 设置bit并返回1, 自旋等待
*/
while (unlikely(test_and_set_bit_lock(bitnum, addr)))
cpu_relax();
}

/* 自旋锁释放 */
static inline void bit_spin_unlock(int bitnum, unsigned long *addr)
{
/*
* 清除bit, 释放锁
* 使用smp_mb__before_atomic确保临界区所有操作在解锁前完成
*/
smp_mb__before_atomic();
clear_bit_unlock(bitnum, addr);
}

/* 尝试获取, 不等待 */
static inline int bit_spin_trylock(int bitnum, unsigned long *addr)
{
return !test_and_set_bit_lock(bitnum, addr);
}
```

二、位自旋锁的实现依赖

test_and_set_bit_lock依赖于CPU架构提供的原子位操作:

```c
/* x86架构实现 */
static __always_inline int test_and_set_bit_lock(long nr,
volatile unsigned long *addr)
{
char oldbit;
asm volatile(LOCK_PREFIX "bts %2,%1\n\t"
"sbb %0,%0"
: "=r" (oldbit), "+m" (*addr)
: "Ir" (nr) : "memory");
return oldbit;
}

/* ARM64架构实现: 使用LDXR/STXR */
static inline int test_and_set_bit_lock(unsigned int nr,
volatile unsigned long *p)
{
unsigned long oldval, newval, tmp;
unsigned long *addr = (unsigned long *)p + BIT_WORD(nr);

asm volatile("// test_and_set_bit_lock\n"
"1: ldxr %0, %3\n"
" orr %1, %0, %4\n"
" stxr %w2, %1, %3\n"
" cbnz %w2, 1b\n"
" dmb ishld\n"
: "=&r" (oldval), "=&r" (newval), "=&r" (tmp),
"+Q" (*addr)
: "r" (BIT_MASK(nr))
: "memory");

return (oldval & BIT_MASK(nr)) != 0;
}
```

三、位自旋锁的应用: 页面标志位管理

位自旋锁最典型的应用是管理struct page的标志位(PG_locked等):

```c
/* 内存管理中struct page的PG_xxx标志位定义 */
enum pageflags {
PG_locked, /* bit 0: 页面锁定标志 */
PG_error, /* bit 1 */
PG_referenced, /* bit 2 */
PG_uptodate, /* bit 3 */
PG_dirty, /* bit 4 */
PG_lru, /* bit 5 */
PG_active, /* bit 6 */
PG_private, /* bit 11 */
PG_writeback, /* bit 12 */
...
};

/* 使用bit_spin_lock保护页状态 */
static inline void lock_page(struct page *page)
{
might_sleep();

/* 尝试获取页锁(PG_locked位自旋锁) */
while (test_and_set_bit_lock(PG_locked, &page->flags))
/* 自旋等待 */
cpu_relax();

/* 持有PG_locked位, 可以安全操作page */
}

static inline void unlock_page(struct page *page)
{
smp_mb__before_atomic();
clear_bit_unlock(PG_locked, &page->flags);
}
```

四、wait_on_bit: 睡眠等待位

bit_spin_lock仅自旋,在等待时间较长时效率低.wait_on_bit提供睡眠等待机制,让调用者在位被清除前睡眠:

```c
/**
* wait_on_bit - 等待指定位被清除
* @word: 包含目标位的unsigned long字
* @bit: 位号
* @mode: 睡眠模式(TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE)
*
* 当指定位为1时,当前任务进入睡眠.
* 当位被清除时,任务被唤醒.
*/
static inline int
wait_on_bit(unsigned long *word, int bit, unsigned mode)
{
might_sleep();
if (!test_bit(bit, word))
return 0;
return out_of_line_wait_on_bit(word, bit, mode, NULL);
}

/* 内核调度函数: 处理等待和唤醒的核心 */
static int
out_of_line_wait_on_bit(unsigned long *word, int bit,
unsigned mode, wait_bit_key_f *key_f)
{
struct wait_bit_queue wait_q;
wait_bit_key_f key;
int ret = 0;

/* 初始化等待队列节点 */
init_wait(&wait_q.wait);
wait_q.key.bit_nr = bit;
wait_q.key.flags = 0;
wait_q.key.flags &= ~BITMAP_LAST_WORD_MASK(0);
wait_q.key.flags |= (unsigned long) word;
wait_q.bit_num = bit;

/* 进入等待循环 */
for (;;) {
prepare_to_wait(__var_waitqueue(word), &wait_q.wait, mode);

if (test_bit(bit, word)) {
/* 位仍被设置, 调度出去睡眠 */
if (mode == TASK_INTERRUPTIBLE &&
signal_pending(current)) {
ret = -EINTR;
break;
}
schedule();
} else {
/* 位已被清除, 退出等待 */
break;
}
}

finish_wait(__var_waitqueue(word), &wait_q.wait);
return ret;
}
```

五、wake_up_bit: 唤醒等待者

当位被清除时,调用wake_up_bit唤醒所有等待该位的任务:

```c
/**
* wake_up_bit - 唤醒等待特定位的任务
* @word: 包含目标位的unsigned long字
* @bit: 位号
*/
static inline void
wake_up_bit(unsigned long *word, int bit)
{
/* 构造wait_queue_head_t的伪地址 */
wait_queue_head_t *wq = bit_waitqueue(word, bit);

/* 唤醒等待在此队列上的所有任务 */
__wake_up(wq, TASK_NORMAL, 1, &word);
}

/*
* bit_waitqueue: 计算位等待队列的哈希位置
* 使用位地址和位号的哈希,减少队列冲突
*/
wait_queue_head_t *bit_waitqueue(const void *word, int bit)
{
const int size = BIT_WAIT_TABLE_SIZE;
const int shift = BITS_PER_LONG == 32 ? 5 : 6;

/* 哈希: word的地址 >> shift + bit */
return &bit_wait_table[hash_long((unsigned long)word >> shift + bit,
size)];
}

/* 全局位等待队列哈希表 */
static wait_queue_head_t bit_wait_table[WAIT_TABLE_SIZE] __cacheline_aligned;
```

六、文件系统中的位等待示例

```c
/* 等待页回写完成: 使用wait_on_bit */
static inline int wait_on_page_writeback(struct page *page)
{
/* 等待PG_writeback位被清除 */
return wait_on_bit(&page->flags, PG_writeback,
TASK_UNINTERRUPTIBLE);
}

/* 回写完成后的唤醒 */
void end_page_writeback(struct page *page)
{
smp_mb__before_atomic();
clear_bit_unlock(PG_writeback, &page->flags);
smp_mb__after_atomic();

/* 唤醒等待PG_writeback的任务 */
wake_up_bit(&page->flags, PG_writeback);
}
```

七、wait_on_bit的高级变体

内核提供多种位等待接口:

```c
/* 可中断版本 */
int wait_on_bit_lock(unsigned long *word, int bit, unsigned mode);

/* 带超时版本 */
int wait_on_bit_timeout(unsigned long *word, int bit,
unsigned mode, unsigned long timeout);

/* 带动作的位等待: 位被设置时执行action函数 */
int wait_on_bit_action(unsigned long *word, int bit,
wait_bit_action_f *action, unsigned mode);
```

这些变体分别对应不同等待语义:不可中断(Uninterruptible)、可中断(Interruptible,返回-EINTR)、杀死(Killable)等.

八、bit_spin_trylock与bit_spin_is_locked

```c
/* 非阻塞尝试获取位锁 */
static inline int bit_spin_trylock(int bitnum, unsigned long *addr)
{
return !test_and_set_bit_lock(bitnum, addr);
}

/* 检查位锁是否被持有 */
static inline int bit_spin_is_locked(int bitnum, unsigned long *addr)
{
return test_bit(bitnum, addr);
}
```

九、性能与适用场景

bit_spin_lock的优势:
1. 空间效率: 仅使用一个bit,而非一个完整的spinlock_t结构(4-8字节)
2. cache友好: 与数据在同一cache line中,减少缓存未命中
3. 适合标志位密集型操作: struct page有大量PG_xxx标志位,不可能为每个标志位分配独立spinlock

bit_spin_lock的限制:
1. 无公平性保证: 多个竞争者自旋,不保证FIFO顺序
2. 不支持调试: 没有lockdep支持(通过CONFIG_DEBUG_BIT_SPINLOCK可启用基本调试)
3. 仅适合非嵌套场景: 同一个字的不同bit不能同时作为位锁(位操作不是原子的复合操作)

位自旋锁是Linux内核中"从实践中生长出来"的同步机制,它用一个bit替代了完整的spinlock结构,在struct page等需要大量锁对象的场景中大幅节省了内存开销.结合wait_on_bit提供的睡眠等待能力,形成了完整且高效的同步解决方案.

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐