| 項目 | 說明 |
| 助記符 | BSF (Bit Scan Forward) |
| 功能 | 從最低位(bit 0)向高位掃描源操作數,找到第一個值為 1 的位,將其位索引存入目的操作數 |
| 標志影響 | ZF=1 表示源操作數為 0(未找到 1);ZF=0 表示找到,結果在目的寄存器 |
| Intel 語法 | BSF reg16/32/64, reg16/32/64/mem |
| AT&T 語法 | bsfl/bsfq/bsfw src, dest(源、目的順序相反) |
| 配套指令 | BSR (Bit Scan Reverse) 從高位向低位掃描第一個 1 |
經典內核用法:find_first_bit / find_next_bit
Linux 內核 arch/x86/include/asm/bitops.h 中封裝:
static inline unsigned long find_first_bit(const unsigned long *addr, unsigned long size)
{
unsigned long idx;
asm volatile(
"bsfl %1, %0\n\t"
"jne 1f\n\t"
"movl $32, %0\n\t" // 未找到時返回 32(或 64)
"1:"
: "=r" (idx)
: "m" (*(unsigned long *)addr)
: "cc");
return idx;
}
代碼逐行解析
bsfl %1, %0:AT&T 語法,%1 為源(內存操作數 *addr),%0 為目的(輸出寄存器 idx)。執行後,idx = 最低位 1 的索引。
jne 1f:若 ZF=0(找到 1),跳轉到標號 1 繼續。
movl $32, %0:若 ZF=1(源為全 0),將 32 寫入 idx 作為「未找到」標記(32 位模式下)。
1::局部標號,正常流程匯聚點。
實戰場景:__find_next_bit 實現「從指定位偏移開始查找下一個 1」
內核代碼片段(簡化):
unsigned long __find_next_bit(const unsigned long *addr, unsigned long nbits, unsigned long start)
{
unsigned long *p = (unsigned long *)addr + (start >> 5); // 定位到 start 所在的 long
unsigned long bit = start & 31; // start 在 long 內的偏移
unsigned long val = *p >> bit; // 右移丟棄 start 之前的位
val = ~val; // 取反:原本找 0,變為找 1
unsigned long res = find_first_bit(&val, 32); // 調用 BSF 查找
return (res < 32) ? (start + res) : nbits; // 找到返回全局索引,否則返回 nbits(未找到)
邏輯推演
start = 全局起始位索引。
*p = 包含 start 的那個 unsigned long 字。
*p >> bit:將 start 之前的位移出,start 變為新字的 bit 0。
~(*p >> bit):取反,將「找第一個 0」轉化為「找第一個 1」。
bsfl 返回相對索引 res,全局索引 = start + res。
- 若
res == 32,說明該字剩餘位全為 1(原全 0),需繼續查找下一個字(內核完整實現會循環處理)。
性能優勢
- 單指令完成:現代 CPU (Haswell+)
BSF 延遲 3 周期,吞吐 1 周期,遠快於軟件循環。
- 無分支預測失敗:硬件內部並行掃描,不依賴條件跳轉。
- 向量化擴展:AVX2
VPBITSCAN / AVX-512 VPOPCNT 可並行處理 256/512 位。
注意事項
- 源操作數為 0 時結果未定義(舊 CPU):Intel 手冊規定若源為 0,目的寄存器內容未定義,必須檢查 ZF。內核代碼通過
jne 分支保證安全。
- 64 位模式用
BSFQ:操作數為 64 位寄存器/內存,返回 0-63 索引。
- 配合
BTS/BTR/BTC:位圖分配/釋放/翻轉常與 BSF 組合使用(如 test_and_set_bit)。
總結
BSF 是位圖操作的硬件加速基石。理解其「掃描方向、ZF 語義、AT&T 源目的序」三要素,結合內核 find_first_bit 封裝模式,即可在高性能無鎖編程、內存管理器、調度器位圖中靈活運用。