欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Linux用戶層和內(nèi)核層鎖的實(shí)現(xiàn)方式

 更新時(shí)間:2025年07月11日 09:42:27   作者:小嵌同學(xué)  
futex通過用戶態(tài)CAS與內(nèi)核阻塞協(xié)作,提升高并發(fā)性能,用戶態(tài)鎖與內(nèi)核鎖基于不同機(jī)制,ARM64原子操作依賴相同硬件指令,互斥鎖適配長(zhǎng)臨界區(qū),自旋鎖適合短臨界區(qū),需根據(jù)場(chǎng)景選擇

一、系統(tǒng)調(diào)用futex介紹

futex(Fast Userspace Mutex)是 Linux 內(nèi)核提供的一種底層同步原語,用于高效實(shí)現(xiàn)用戶空間的鎖(如互斥鎖、信號(hào)量等)。

它的核心思想是通過減少不必要的內(nèi)核態(tài)切換來優(yōu)化性能,特別適用于高并發(fā)場(chǎng)景。

1. 核心機(jī)制

混合模式(用戶態(tài)+內(nèi)核態(tài)協(xié)作)

  • 用戶態(tài)原子操作

線程先嘗試在用戶空間通過原子指令(如 CAS)獲取鎖。若成功,則無需進(jìn)入內(nèi)核,性能極高。

CAS的全稱為Compare And Swap,直譯就是比較交換。是一條CPU的原子指令,其作用是讓CPU先進(jìn)行比較兩個(gè)值是否相等,然后原子地更新某個(gè)位置的值,其實(shí)現(xiàn)方式是基于硬件平臺(tái)的匯編指令

  • 內(nèi)核態(tài)阻塞

若鎖已被占用,線程通過 futex 系統(tǒng)調(diào)用進(jìn)入內(nèi)核態(tài)阻塞(FUTEX_WAIT),直到鎖釋放后被喚醒(FUTEX_WAKE)。

關(guān)鍵系統(tǒng)調(diào)用

#include <linux/futex.h>
#include <sys/syscall.h>

int syscall(SYS_futex, uint32_t *uaddr, int futex_op, uint32_t val, 
            const struct timespec *timeout, uint32_t *uaddr2, uint32_t val3);
  • uaddr:指向用戶空間的一個(gè)整數(shù)(鎖的狀態(tài)標(biāo)志)。
  • futex_op:操作類型(如 FUTEX_WAIT, FUTEX_WAKE)。
  • val:輔助值(如等待時(shí)的預(yù)期值)。

2. 常見操作

操作行為
FUTEX_WAIT檢查 *uaddr == val,若成立則阻塞線程;否則立即返回 EAGAIN。
FUTEX_WAKE喚醒最多 val 個(gè)在 uaddr 上阻塞的線程。
FUTEX_REQUEUE將部分線程從 uaddr 的等待隊(duì)列遷移到 uaddr2 的等待隊(duì)列(避免驚群)。
FUTEX_PRIVATE標(biāo)志位,表示僅限進(jìn)程內(nèi)使用(優(yōu)化性能)。

3. 工作流程示例(互斥鎖)

加鎖

// 用戶態(tài)嘗試原子交換
if (atomic_cas(uaddr, 0, 1) == success) return; // 成功獲得鎖
// 失敗則進(jìn)入內(nèi)核等待
syscall(SYS_futex, uaddr, FUTEX_WAIT, 1, NULL, NULL, 0);

解鎖

atomic_set(uaddr, 0);                   // 先釋放鎖
syscall(SYS_futex, uaddr, FUTEX_WAKE, 1); // 喚醒一個(gè)等待線程

4. 優(yōu)勢(shì)

  • 性能:無競(jìng)爭(zhēng)時(shí)完全在用戶態(tài)運(yùn)行,避免內(nèi)核切換開銷。
  • 靈活性:可構(gòu)建更高級(jí)的同步機(jī)制(如讀寫鎖、條件變量)。
  • 資源節(jié)省:內(nèi)核僅在線程阻塞時(shí)介入,減少調(diào)度負(fù)擔(dān)。

5. 注意事項(xiàng)

  1. 優(yōu)先級(jí)反轉(zhuǎn):需結(jié)合優(yōu)先級(jí)繼承(如 FUTEX_LOCK_PI)解決實(shí)時(shí)性問題。
  2. 驚群效應(yīng):使用 FUTEX_REQUEUEFUTEX_WAKE_OP 分散喚醒壓力。
  3. ABA 問題FUTEX_WAIT 會(huì)驗(yàn)證 *uaddr == val,但需確保狀態(tài)變更邏輯正確。

6. 典型應(yīng)用

  • Glibc 的 pthread 庫:互斥鎖(pthread_mutex_t)、條件變量(pthread_cond_t)、信號(hào)量(sem_t)、讀寫鎖(pthread_rwlock_t)、屏障(pthread_barrier_t)底層使用 futex。
  • 高性能服務(wù)器:Redis、Nginx 等利用 futex 實(shí)現(xiàn)自定義鎖。

?? 提示:直接使用 futex 需謹(jǐn)慎!通常推薦通過高級(jí) API(如 C++11 的 std::mutex)間接使用,避免復(fù)雜的內(nèi)核交互細(xì)節(jié)。

二、Linux中用戶態(tài)的鎖和內(nèi)核的鎖不是同一個(gè)實(shí)現(xiàn)嗎?

Linux 中用戶態(tài)的鎖和內(nèi)核的鎖是完全不同的實(shí)現(xiàn)機(jī)制和層次,但它們之間通過 futex 等機(jī)制實(shí)現(xiàn)了協(xié)作

以下是關(guān)鍵區(qū)別和聯(lián)系:

2.1 本質(zhì)區(qū)別

維度用戶態(tài)鎖 (如 pthread_mutex)內(nèi)核鎖 (如 spinlock_t, mutex_t)
實(shí)現(xiàn)位置用戶空間庫(如 glibc)實(shí)現(xiàn)Linux 內(nèi)核源碼實(shí)現(xiàn)
依賴機(jī)制基于原子指令 + futex 系統(tǒng)調(diào)用基于內(nèi)核原子操作、中斷控制、調(diào)度器
作用范圍用戶進(jìn)程內(nèi)的線程間同步內(nèi)核線程/中斷上下文/多核間的同步
內(nèi)存可見性依賴進(jìn)程的虛擬地址空間依賴物理內(nèi)存和內(nèi)核地址空間
阻塞行為通過 futex 讓內(nèi)核掛起線程直接調(diào)用內(nèi)核調(diào)度器切換任務(wù)

2.2 用戶態(tài)鎖如何工作(以pthread_mutex為例)

// 偽代碼:用戶態(tài)鎖的加鎖流程
void pthread_mutex_lock(pthread_mutex_t *mutex) {
    // 1. 用戶態(tài)快速路徑(無競(jìng)爭(zhēng))
    if (atomic_cas(&mutex->state, UNLOCKED, LOCKED) == success) 
        return;

    // 2. 慢速路徑:通過 futex 請(qǐng)求內(nèi)核協(xié)助
    while (true) {
        if (atomic_cas(&mutex->state, UNLOCKED, LOCKED_WAITERS) == success) 
            return;
        
        // 調(diào)用 futex 讓內(nèi)核阻塞線程
        syscall(SYS_futex, &mutex->state, FUTEX_WAIT_PRIVATE, LOCKED_WAITERS, NULL);
    }
}

關(guān)鍵點(diǎn)

  • 用戶態(tài)鎖在無競(jìng)爭(zhēng)時(shí)完全在用戶空間運(yùn)行(僅需幾條原子指令);
  • 僅在競(jìng)爭(zhēng)時(shí)通過 futex 陷入內(nèi)核掛起線程。

2.3 內(nèi)核鎖的實(shí)現(xiàn)(以mutex_t為例)

// 內(nèi)核源碼(kernel/locking/mutex.c)
void __sched mutex_lock(struct mutex *lock)
{
	might_sleep();

	if (!__mutex_trylock_fast(lock)) // 1. 快速嘗試
		__mutex_lock_slowpath(lock); // 2. 慢路徑:內(nèi)核調(diào)度介入
}

關(guān)鍵點(diǎn)

  • 內(nèi)核鎖全程在內(nèi)核態(tài)執(zhí)行,可直接調(diào)用調(diào)度器、中斷控制等底層原語;
  • 無需系統(tǒng)調(diào)用(因自身就在內(nèi)核)。

2.4 為什么用戶態(tài)鎖需要內(nèi)核參與?

用戶態(tài)程序無法自行掛起線程——這必須由內(nèi)核調(diào)度器完成。 當(dāng)鎖競(jìng)爭(zhēng)時(shí):

  1. 用戶態(tài)通過 futex(FUTEX_WAIT) 主動(dòng)請(qǐng)求內(nèi)核掛起自己;
  2. 解鎖時(shí)通過 futex(FUTEX_WAKE) 請(qǐng)求內(nèi)核喚醒等待線程。

2.5 性能對(duì)比

場(chǎng)景用戶態(tài)鎖內(nèi)核鎖
無競(jìng)爭(zhēng)≈10 ns(純用戶態(tài)原子操作)≈20 ns(內(nèi)核原子操作)
有競(jìng)爭(zhēng)μs 級(jí)(需陷入內(nèi)核)μs 級(jí)(但無模式切換開銷)
適用場(chǎng)景高并發(fā)用戶程序內(nèi)核代碼、驅(qū)動(dòng)開發(fā)

?? 用戶態(tài)鎖在無競(jìng)爭(zhēng)時(shí)性能接近無鎖程序!

2.6 協(xié)作關(guān)系圖解

  用戶程序
     │
     ├── 無競(jìng)爭(zhēng) → 用戶態(tài)原子操作(極快)
     │
     └── 有競(jìng)爭(zhēng) → 調(diào)用 futex 陷入內(nèi)核
                 │
                 ▼
              內(nèi)核調(diào)度器
                 │
                 ├── 掛起線程(FUTEX_WAIT)
                 │
                 └── 喚醒線程(FUTEX_WAKE)

2.7 關(guān)鍵結(jié)論

  1. 不是同一實(shí)現(xiàn):用戶態(tài)鎖由庫(如 glibc)基于原子指令 + futex 實(shí)現(xiàn);內(nèi)核鎖由內(nèi)核用更底層的機(jī)制實(shí)現(xiàn)。
  2. 協(xié)作而非替代:用戶態(tài)鎖依賴內(nèi)核提供阻塞/喚醒能力(通過 futex),形成跨層協(xié)作。
  3. 性能分離:通過 futex 的混合模式,用戶態(tài)鎖在無競(jìng)爭(zhēng)時(shí)完全避開內(nèi)核,實(shí)現(xiàn)高性能。

這種設(shè)計(jì)是 Linux 高并發(fā)性能的關(guān)鍵——既給了用戶態(tài)靈活的同步能力,又通過最小化內(nèi)核介入保障了效率!

三、arm64位app層的原子操作如何實(shí)現(xiàn)?和內(nèi)核實(shí)現(xiàn)的原子操作一樣嗎?

在 ARM64 架構(gòu)的 Linux 系統(tǒng)中,應(yīng)用層(app 層)的原子操作與內(nèi)核層的原子操作在硬件基礎(chǔ)上相同,但實(shí)現(xiàn)層級(jí)和功能目標(biāo)存在本質(zhì)差異。

原子指令和內(nèi)存屏障的關(guān)系:

  • 原子指令 解決操作的原子性問題(怎么做)
  • 內(nèi)存屏障 解決操作的順序和可見性問題(何時(shí)生效)
  • 在復(fù)雜同步場(chǎng)景(如 mutex 或)中,二者缺一不可。

內(nèi)存屏障(Memory Barrier)是計(jì)算機(jī)體系結(jié)構(gòu)中的一種硬件級(jí)同步指令,用于解決多核處理器中內(nèi)存訪問的亂序執(zhí)行可見性問題。其核心作用是限制指令重排確保內(nèi)存一致性,在多線程、鎖實(shí)現(xiàn)、無鎖編程中至關(guān)重要。為什么需要內(nèi)存屏障?

亂序執(zhí)行的根源

  • 編譯器優(yōu)化:編譯器可能調(diào)整指令順序以提升性能。
  • CPU流水線:CPU為提升效率會(huì)亂序執(zhí)行指令(Out-of-Order Execution)。
  • 多級(jí)緩存:不同CPU核心的緩存(L1/L2)數(shù)據(jù)可能不一致。

典型問題場(chǎng)景

// 線程A
data = 42;        // 寫數(shù)據(jù)
flag = 1;         // 寫標(biāo)志位

// 線程B
while (flag != 1); // 等待標(biāo)志位
print(data);       // 讀取數(shù)據(jù)

若沒有內(nèi)存屏障:

  • CPU/編譯器可能交換 data=42flag=1 的順序 → 線程B看到 flag=1data 仍是舊值。
  • 線程B的CPU緩存未更新 data 值 → 讀到 data=0。

內(nèi)存屏障根據(jù)限制程度分為四類(以ARM64為例):

屏障類型作用ARM64指令使用場(chǎng)景
LoadLoad確保后續(xù)讀操作不會(huì)重排到當(dāng)前讀之前ldar (Load-Acquire)讀后需依賴之前讀的結(jié)果
StoreStore確保當(dāng)前寫操作不會(huì)重排到后續(xù)寫之后stlr (Store-Release)寫后需立即被其他線程看到
LoadStore確保后續(xù)寫操作不會(huì)重排到當(dāng)前讀之前包含在dmb ishld讀后需立即寫
StoreLoad確保后續(xù)讀操作不會(huì)重排到當(dāng)前寫之前(最強(qiáng)屏障)dmb ish寫后需立即讀最新值

?? StoreLoad屏障最重:因?yàn)樗枰⑿抡麄€(gè)寫緩沖區(qū)(Write Buffer),通常對(duì)應(yīng) dmb ish(ARM64)或 mfence(x86)。寫操作后放 Release,讀操作前放 Acquire —— 這對(duì)屏障組合可解決 90% 的線程同步問題。

3.1、應(yīng)用層原子操作的實(shí)現(xiàn)原理

3.1.1 硬件指令支撐

ARM64 提供兩類關(guān)鍵指令:

獨(dú)占訪問指令(ARMv8.0)

ldxr x0, [x1]    ; 獨(dú)占加載:標(biāo)記地址 x1 為當(dāng)前 CPU 獨(dú)占
stxr w2, x3, [x1] ; 獨(dú)占存儲(chǔ):若標(biāo)記未失效則寫入 x3 → [x1],結(jié)果狀態(tài)存入 w2

通過循環(huán)重試實(shí)現(xiàn)原子操作(如 CAS):

// 原子比較交換(用戶態(tài)偽代碼)
//stxr僅在標(biāo)記未被破壞時(shí)執(zhí)行寫入,否則失敗并重試。這種“嘗試-檢測(cè)-重試”機(jī)制確保了“讀-改-寫”操作的原子性
bool atomic_cas(uint64_t *ptr, uint64_t old, uint64_t new) {
    uint64_t tmp;
    int status;
    do {
        asm volatile("ldxr %0, [%2]\n"   // 獨(dú)占加載
                     "cmp %0, %3\n"       // 比較舊值
                     "b.ne 1f\n"          // 不相等則跳轉(zhuǎn)
                     "stxr %w1, %4, [%2]\n" // 嘗試存儲(chǔ)新值
                     "1:"
                     : "=&r"(tmp), "=&r"(status)
                     : "r"(ptr), "r"(old), "r"(new));
    } while (status != 0); // 失敗則重試
    return (tmp == old);
}

LSE 指令(ARMv8.1+,大系統(tǒng)擴(kuò)展)

單條指令完成原子操作,避免循環(huán)開銷:

ldaddal x0, x1, [x2]  ; 原子操作:[x2] = [x2] + x0, x1 = 原值

2. 編譯器與標(biāo)準(zhǔn)庫封裝

GCC/Clang 內(nèi)置函數(shù)直接映射到硬件指令:

// 原子加法(用戶態(tài))
__atomic_add_fetch(&counter, 1, __ATOMIC_SEQ_CST);

C++11 原子類型:

std::atomic<int> counter;
counter.fetch_add(1, std::memory_order_relaxed);

3. 內(nèi)存序控制

通過屏障指令保證可見性:

dmb ish  ; 數(shù)據(jù)內(nèi)存屏障(Inner Shareable Domain)

3.2 內(nèi)核層原子操作的實(shí)現(xiàn)

3.2.1 相同硬件基礎(chǔ)

內(nèi)核使用相同的 ARM64 原子指令(ldxr/stxr 或 LSE),例如:

// 內(nèi)核原子加法(源碼片段:arch/arm64/include/asm/atomic_ll_sc.h)
static inline void atomic_add(int i, atomic_t *v) {
    unsigned long tmp;
    asm volatile("http:// atomic_add\n"
        "1: ldxr %w0, %2\n"        // 獨(dú)占加載
        "add %w0, %w0, %w3\n"      // 執(zhí)行加法
        "stxr %w1, %w0, %2\n"      // 條件存儲(chǔ)
        "cbnz %w1, 1b"             // 失敗則重試
        : "=&r" (tmp), "=&r" (tmp2)
        : "Q" (v->counter), "Ir" (i));
}

3.2.2 關(guān)鍵差異:特權(quán)級(jí)擴(kuò)展

內(nèi)核原子操作需處理用戶態(tài)無法觸及的場(chǎng)景:

能力內(nèi)核實(shí)現(xiàn)用戶態(tài)限制
內(nèi)存屏障直接調(diào)用 dmb ish 或 dsb sy只能使用普通屏障指令
中斷控制可關(guān)閉中斷(local_irq_save())避免并發(fā)無權(quán)操作中斷
SMP 核間同步使用 smp_mb() 等跨核屏障僅限當(dāng)前 CPU 緩存一致性
調(diào)度器協(xié)作自旋鎖(spin_lock())在爭(zhēng)用時(shí)結(jié)合調(diào)度器無調(diào)度干預(yù)權(quán)限

3.2.3 內(nèi)核獨(dú)占原語

用戶態(tài)無法實(shí)現(xiàn)的原子操作:

  • RCU(Read-Copy-Update):依賴內(nèi)核線程和調(diào)度機(jī)制。
  • 中斷上下文原子操作:內(nèi)核可在中斷處理中安全使用原子變量。

3.3 本質(zhì)對(duì)比:相同硬件,不同使命

維度應(yīng)用層原子操作內(nèi)核層原子操作
硬件基礎(chǔ)相同 ARM64 指令(LSE/LDXR/STXR)相同指令 + 擴(kuò)展屏障/中斷控制
執(zhí)行位置純用戶態(tài)(無特權(quán)指令)內(nèi)核態(tài)(可調(diào)用特權(quán)指令)
內(nèi)存序控制通過標(biāo)準(zhǔn)內(nèi)存序參數(shù)(如 __ATOMIC_ACQ_REL)直接使用硬件屏障指令
阻塞行為非阻塞(自旋重試)可結(jié)合調(diào)度器(如自旋鎖升級(jí)為睡眠)
適用場(chǎng)景細(xì)粒度數(shù)據(jù)操作(計(jì)數(shù)器、標(biāo)志位)內(nèi)核數(shù)據(jù)結(jié)構(gòu)、驅(qū)動(dòng)寄存器訪問

3.4 為什么應(yīng)用層不直接使用內(nèi)核鎖?

性能鴻溝

Atomic Add (user): 28 ns
Futex Lock (uncontended): 1200 ns
  • 用戶態(tài)原子操作:≈10–50 ns(純硬件指令)
  • 內(nèi)核鎖(如 futex):≥1000 ns(系統(tǒng)調(diào)用 + 上下文切換)
  • 實(shí)測(cè)數(shù)據(jù)(Cortex-A78 @2.8GHz):

功能冗余性

  • 內(nèi)核鎖(如互斥鎖)的底層仍依賴原子操作,用戶態(tài)直接使用原子操作可避免陷入內(nèi)核的開銷。

安全邊界

  • 用戶程序直接調(diào)用內(nèi)核鎖會(huì)突破進(jìn)程隔離,需通過系統(tǒng)調(diào)用代理(如 futex),而原子操作無需跨特權(quán)邊界。

四、用戶態(tài)的自旋鎖和互斥鎖

4.1 用戶空間互斥鎖(Mutex)

4.1.1 核心特性:競(jìng)爭(zhēng)時(shí)主動(dòng)讓出CPU

// 偽代碼:用戶態(tài)互斥鎖實(shí)現(xiàn)(基于 futex)
void mutex_lock(mutex_t *m) {
    // 1. 用戶態(tài)快速路徑(無競(jìng)爭(zhēng))
    if (atomic_cas(&m->lock, 0, 1) == success) 
        return;

    // 2. 慢速路徑:標(biāo)記有等待者,并陷入內(nèi)核阻塞
    atomic_set(&m->lock, 2); // 設(shè)置等待標(biāo)志
    syscall(SYS_futex, &m->lock, FUTEX_WAIT_PRIVATE, 2, NULL);
}

void mutex_unlock(mutex_t *m) {
    // 1. 釋放鎖并檢查是否有等待者
    if (atomic_swap(&m->lock, 0) == 2) { // 原值為2表示有等待者
        // 2. 喚醒一個(gè)等待線程
        syscall(SYS_futex, &m->lock, FUTEX_WAKE_PRIVATE, 1);
    }
}

4.1.2 關(guān)鍵設(shè)計(jì):

混合模式優(yōu)化

  • 無競(jìng)爭(zhēng)時(shí):僅需 1次原子CAS操作(≈20ns)
  • 有競(jìng)爭(zhēng)時(shí):通過 futex 陷入內(nèi)核掛起線程,避免CPU空轉(zhuǎn)

內(nèi)核協(xié)作機(jī)制

  • 依賴 futex 系統(tǒng)調(diào)用實(shí)現(xiàn)線程阻塞(FUTEX_WAIT)和喚醒(FUTEX_WAKE

典型行為

  • 鎖被占用時(shí):線程進(jìn)入睡眠狀態(tài),觸發(fā)內(nèi)核調(diào)度
  • 解鎖時(shí):?jiǎn)拘训却?duì)列中的線程

4.2 用戶空間自旋鎖(Spinlock)

4.2.1 核心特性:競(jìng)爭(zhēng)時(shí)忙等待(Busy-Wait)

// 偽代碼:用戶態(tài)自旋鎖(純?cè)硬僮鳎?
void spin_lock(spinlock_t *lock) {
    while (true) {
        // 嘗試獲取鎖:0表示空閑,1表示占用
        if (atomic_exchange(&lock->flag, 1) == 0) 
            return;
        
        // ARM64優(yōu)化:降低CPU功耗
        asm volatile("wfe" ::: "memory"); // Wait For Event
    }
}

void spin_unlock(spinlock_t *lock) {
    atomic_store(&lock->flag, 0);
    asm volatile("sev" ::: "memory"); // Send Event
}

4.2.2 關(guān)鍵設(shè)計(jì):

純用戶態(tài)執(zhí)行

  • 全程無系統(tǒng)調(diào)用,依賴原子指令(如 ldxr/stxr 或 LSE)
  • 解鎖時(shí)通過 sev 指令喚醒其他核心的 wfe 等待

忙等待優(yōu)化

  • 基礎(chǔ)版:循環(huán)執(zhí)行原子檢查(高CPU占用)
  • 優(yōu)化版:插入 wfe 指令讓CPU進(jìn)入低功耗狀態(tài),直到 sev 事件喚醒

典型行為

  • 鎖被占用時(shí):線程在用戶態(tài)循環(huán)檢測(cè)(可能結(jié)合 wfe
  • 解鎖時(shí):直接修改鎖狀態(tài),無內(nèi)核交互

4.3 核心對(duì)比:互斥鎖 vs 自旋鎖

特性互斥鎖 (Mutex)自旋鎖 (Spinlock)
競(jìng)爭(zhēng)策略阻塞線程(睡眠)忙等待(循環(huán)檢測(cè))
內(nèi)核介入依賴 futex 系統(tǒng)調(diào)用無系統(tǒng)調(diào)用
無競(jìng)爭(zhēng)開銷≈20 ns(原子CAS)≈10 ns(原子交換)
高競(jìng)爭(zhēng)開銷微秒級(jí)(上下文切換)浪費(fèi)CPU周期(納秒級(jí)/循環(huán))
線程狀態(tài)睡眠(TASK_INTERRUPTIBLE)運(yùn)行中(RUNNING)
適用場(chǎng)景長(zhǎng)臨界區(qū)(>1μs)或可能阻塞的操作短臨界區(qū)(<1μs)且多核環(huán)境
ARM64優(yōu)化FUTEX_WAIT + FUTEX_WAKEwfe + sev 低功耗等待
饑餓風(fēng)險(xiǎn)公平鎖需額外設(shè)計(jì)(如隊(duì)列)可能饑餓(無排隊(duì)機(jī)制)

4.4 性能臨界點(diǎn):何時(shí)選擇?

通過 臨界區(qū)執(zhí)行時(shí)間(C)上下文切換開銷(S) 決策:

if C < S : 選自旋鎖(避免切換開銷)
if C > S : 選互斥鎖(避免CPU浪費(fèi))

典型值(Linux on ARM64):

  • 上下文切換開銷 S ≈ 1-3 μs
  • 自旋鎖單次循環(huán) ≈5-20 ns

經(jīng)驗(yàn)法則

  • 臨界區(qū) < 1μs(如計(jì)數(shù)器增減):自旋鎖
  • 臨界區(qū) > 2μs(如鏈表操作):互斥鎖
  • 涉及I/O或睡眠操作:必須用互斥鎖

4.5 ARM64 特殊優(yōu)化

4.5.1 自旋鎖低功耗優(yōu)化

// 鎖等待時(shí)進(jìn)入低功耗狀態(tài)
spin_wait:
  wfe         // Wait For Event(暫停CPU流水線)
  b check_lock // 被喚醒后重新檢查鎖狀態(tài)

// 解鎖時(shí)觸發(fā)事件
spin_unlock:
  str wzr, [x0]      // 釋放鎖
  sev         // Send Event(喚醒其他核心的wfe)

4.5.2 互斥鎖適應(yīng)性改進(jìn)

現(xiàn)代 pthread_mutex 在ARM64的實(shí)現(xiàn):

  • 第一階段:用戶態(tài)自旋(約100-200次循環(huán))
  • 第二階段:調(diào)用 futex 睡眠
  • 平衡短等待的性能和長(zhǎng)等待的CPU效率

4.6 錯(cuò)誤使用案例

場(chǎng)景1:在單核系統(tǒng)用自旋鎖

// 錯(cuò)誤!單核忙等待導(dǎo)致死鎖
spin_lock(&lock);
// 若鎖已被占用,當(dāng)前線程永不釋放CPU,持有鎖的線程無法運(yùn)行

場(chǎng)景2:在中斷處理中用互斥鎖

// 內(nèi)核場(chǎng)景(用戶態(tài)無此問題)
void irq_handler() {
    mutex_lock(&lock); // 可能睡眠 → 崩潰!
}

4.7 總結(jié):用戶態(tài)鎖的選擇

場(chǎng)景推薦鎖類型原因
短臨界區(qū) + 多核CPU自旋鎖(帶 wfe)避免上下文切換開銷
長(zhǎng)臨界區(qū)/I/O操作互斥鎖防止CPU空轉(zhuǎn)
需要公平性(如數(shù)據(jù)庫連接池)隊(duì)列互斥鎖解決線程饑餓問題
超高頻計(jì)數(shù)器原子操作(非鎖)完全無鎖,性能極限

終極建議:

  1. 優(yōu)先使用標(biāo)準(zhǔn)庫(如 pthread_mutex_tstd::mutex),其內(nèi)部已做自適應(yīng)優(yōu)化
  2. 僅在極端性能需求時(shí)考慮手寫自旋鎖,并插入 wfe 指令
  3. perf 工具檢測(cè)鎖競(jìng)爭(zhēng)率:perf stat -e L1-dcache-loads,mem_inst_retired.lock_loads

五、內(nèi)核態(tài)的互斥鎖和自旋鎖

在 Linux 內(nèi)核中,互斥鎖(Mutex)自旋鎖(Spinlock) 是兩種最核心的同步原語,其設(shè)計(jì)與用戶態(tài)實(shí)現(xiàn)有本質(zhì)區(qū)別。以下是深度解析(基于 Linux 5.x 內(nèi)核源碼):

5.1 內(nèi)核自旋鎖(Spinlock)

5.1.1 設(shè)計(jì)目標(biāo):非睡眠場(chǎng)景的極速同步

// 典型用法(中斷安全版)
DEFINE_SPINLOCK(my_lock);
unsigned long flags;

spin_lock_irqsave(&my_lock, flags); // 關(guān)中斷 + 拿鎖
/* 臨界區(qū)操作 */
spin_unlock_irqrestore(&my_lock, flags); // 放鎖 + 開中斷

5.1.2 關(guān)鍵特性:

忙等待機(jī)制

  • 通過原子指令(如 ARM64 ldaxr/stlxr)循環(huán)檢測(cè)鎖狀態(tài)
  • 等待時(shí)執(zhí)行 wfe(ARM64)或 pause(x86)降低 CPU 功耗

中斷安全性

變體行為
spin_lock()基礎(chǔ)版本,不保證中斷安全
spin_lock_irq()關(guān)本地中斷
spin_lock_irqsave()關(guān)中斷并保存中斷狀態(tài)
spin_lock_bh()關(guān)軟中斷(Bottom Half)

適用場(chǎng)景

  • 中斷上下文(不可睡眠)
  • 短臨界區(qū)(< 10 μs)
  • 多核競(jìng)爭(zhēng)激烈場(chǎng)景(如網(wǎng)絡(luò)收發(fā)包)

5.2 內(nèi)核互斥鎖(Mutex)

5.2.1 設(shè)計(jì)目標(biāo):可睡眠場(chǎng)景的靈活同步

// 典型用法
DEFINE_MUTEX(my_mutex);

mutex_lock(&my_mutex);  // 可能睡眠
/* 臨界區(qū)(可包含阻塞操作) */
mutex_unlock(&my_mutex);

5.2.2 關(guān)鍵特性:

自適應(yīng)優(yōu)化

內(nèi)核互斥鎖融合自旋與睡眠機(jī)制:

// 加鎖流程偽代碼(kernel/locking/mutex.c)
void mutex_lock(struct mutex *lock) {
    // 1. 快速路徑:用戶態(tài)式原子獲取
    if (atomic_cas(lock->count, 1, 0)) 
        return;
    
    // 2. 中速路徑:短暫自旋(約100循環(huán))
    for (int i = 0; i < 100; i++) {
        if (atomic_cas(lock->count, 1, 0)) 
            return;
        cpu_relax(); // 降低CPU壓力(ARM64: wfe)
    }
    
    // 3. 慢速路徑:真正睡眠
    __mutex_lock_slowpath(lock);
}

高級(jí)特性

特性描述
優(yōu)先級(jí)繼承解決優(yōu)先級(jí)反轉(zhuǎn)(CONFIG_MUTEX_PI)
樂觀自旋持有者運(yùn)行時(shí),等待者在用戶態(tài)自旋避免切換(CONFIG_MUTEX_SPIN_ON_OWNER)
死鎖檢測(cè)CONFIG_DEBUG_MUTEXES 可追蹤鎖依賴

適用場(chǎng)景

  • 進(jìn)程上下文長(zhǎng)臨界區(qū)(> 10 μs)
  • 可能阻塞的操作(如 I/O 等待)
  • 需要避免優(yōu)先級(jí)反轉(zhuǎn)的實(shí)時(shí)任務(wù)

5.2.3 核心對(duì)比:自旋鎖 vs 互斥鎖

維度自旋鎖 (Spinlock)互斥鎖 (Mutex)
等待機(jī)制忙等待(Busy-Wait)可睡眠(Sleep-Wait)
上下文兼容性中斷/進(jìn)程上下文僅進(jìn)程上下文(不可在中斷使用)
臨界區(qū)時(shí)長(zhǎng)短(微秒級(jí))長(zhǎng)(毫秒級(jí))
阻塞行為永不阻塞可能阻塞并觸發(fā)調(diào)度
內(nèi)存開銷4-8 字節(jié)(簡(jiǎn)單狀態(tài))24-40 字節(jié)(含等待隊(duì)列/PI數(shù)據(jù))
ARM64 優(yōu)化wfe + sevl 低功耗等待樂觀自旋(Owner-CPU 檢測(cè))
典型使用場(chǎng)景中斷處理、調(diào)度器、RCU文件系統(tǒng)、驅(qū)動(dòng)長(zhǎng)操作、用戶空間同步
死鎖風(fēng)險(xiǎn)高(需嚴(yán)格關(guān)中斷)中(依賴正確解鎖)

5.2.4 實(shí)現(xiàn)原理深度解析

5.2.4.1 自旋鎖底層(ARM64 示例)

// arch/arm/include/asm/spinlock.h
static inline void arch_spin_lock(arch_spinlock_t *lock)
{
	unsigned long tmp;        // 用于存儲(chǔ) STREX 指令的返回結(jié)果(0表示成功,非0表示失?。?
	u32 newval;               // 計(jì)算后的新鎖值(當(dāng)前鎖值 + 一個(gè) ticket)
	arch_spinlock_t lockval;  // 存儲(chǔ) LDREX 加載的當(dāng)前鎖狀態(tài)

	// 預(yù)取鎖的內(nèi)存到緩存,優(yōu)化后續(xù)訪問速度
	prefetchw(&lock->slock);

	// 通過 LDREX/STREX 原子操作嘗試獲取 ticket(ARM 架構(gòu)原子操作的核心)
	__asm__ __volatile__(
	"1:	ldrex	%0, [%3]\n"        // 加載當(dāng)前鎖值到 %0(lockval.slock)
	"	add	%1, %0, %4\n"        // 計(jì)算新鎖值:當(dāng)前鎖值 + (1 << TICKET_SHIFT)(分配新 ticket)
	"	strex	%2, %1, [%3]\n"    // 嘗試將新鎖值寫回內(nèi)存,結(jié)果存入 %2(tmp)
	"	teq	%2, #0\n"            // 檢查 STREX 是否成功(結(jié)果為0表示成功)
	"	bne	1b"                  // 失敗則跳轉(zhuǎn)到1標(biāo)號(hào)重試
	: "=&r" (lockval), "=&r" (newval), "=&r" (tmp)  // 輸出操作數(shù)(按順序?qū)?yīng) %0/%1/%2)
	: "r" (&lock->slock), "I" (1 << TICKET_SHIFT)   // 輸入操作數(shù)(鎖地址和 ticket 偏移量)
	: "cc");                                        // 破壞的寄存器:條件碼寄存器

	// 等待當(dāng)前線程的 ticket 被輪到(ticket 機(jī)制核心邏輯)
	// 當(dāng) lockval.tickets.next(當(dāng)前線程的 ticket)等于 lockval.tickets.owner(當(dāng)前持有鎖的 ticket)時(shí),獲取鎖成功
	while (lockval.tickets.next != lockval.tickets.owner) {
		wfe();  // 進(jìn)入低功耗等待狀態(tài)(Wait For Event),直到收到 SEV 事件喚醒
		// 重新讀取最新的 owner 值(避免緩存臟數(shù)據(jù),確保獲取最新鎖狀態(tài))
		lockval.tickets.owner = READ_ONCE(lock->tickets.owner);
	}

	// 內(nèi)存屏障:確保加鎖后的操作不會(huì)被重排序到加鎖之前,保證內(nèi)存可見性
	smp_mb();
}

5.2.4.1 互斥鎖狀態(tài)機(jī)(核心狀態(tài))

// include/linux/mutex.h
/*
 * 互斥鎖核心結(jié)構(gòu)體,提供嚴(yán)格的互斥訪問機(jī)制:
 * 成員說明:
 *   owner       - 原子長(zhǎng)整型,記錄當(dāng)前持有鎖的任務(wù)指針(低bit可能包含狀態(tài)標(biāo)志)
 *   wait_lock   - 自旋鎖,用于保護(hù)等待隊(duì)列的并發(fā)訪問
 *   osq         - 樂觀自旋隊(duì)列(MCS鎖),用于實(shí)現(xiàn)自旋優(yōu)化(僅在CONFIG_MUTEX_SPIN_ON_OWNER啟用時(shí)存在)
 *   wait_list   - 等待該鎖的任務(wù)鏈表頭,使用內(nèi)核標(biāo)準(zhǔn)鏈表結(jié)構(gòu)
 *   magic       - 調(diào)試標(biāo)識(shí)指針,用于驗(yàn)證結(jié)構(gòu)體有效性(僅在CONFIG_DEBUG_MUTEXES啟用時(shí)存在)
 *   dep_map     - 鎖依賴跟蹤映射表,用于死鎖檢測(cè)(僅在CONFIG_DEBUG_LOCK_ALLOC啟用時(shí)存在)
 *   ANDROID...  - Android OEM廠商自定義數(shù)據(jù)擴(kuò)展區(qū)
 */
struct mutex {
	atomic_long_t		owner;
	spinlock_t		wait_lock;
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
	struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
	struct list_head	wait_list;
#ifdef CONFIG_DEBUG_MUTEXES
	void			*magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map	dep_map;
#endif
	ANDROID_OEM_DATA_ARRAY(1, 2);
};

狀態(tài)位

  • MUTEX_FLAG_WAITERS(有等待者)
  • MUTEX_FLAG_HANDOFF(優(yōu)先級(jí)繼承傳遞)

5.2.5 錯(cuò)誤使用案例

案例1:在中斷中使用互斥鎖

// 錯(cuò)誤!導(dǎo)致內(nèi)核崩潰
void irq_handler() {
    mutex_lock(&lock);  // 可能觸發(fā)調(diào)度 → 內(nèi)核oops!
}

案例2:未關(guān)閉中斷的自旋鎖

// 死鎖風(fēng)險(xiǎn)!
spin_lock(&lock);
// 若中斷到來并嘗試獲取同一鎖 → 死鎖

案例3:長(zhǎng)臨界區(qū)用自旋鎖

// CPU資源浪費(fèi)
spin_lock(&lock);
msleep(10);  // 睡眠10ms → 其他核空轉(zhuǎn)10ms
spin_unlock(&lock);

5.2.6 性能優(yōu)化實(shí)踐

自旋鎖

  • 減少臨界區(qū)到最?。▋H保護(hù)必要數(shù)據(jù))
  • READ_ONCE()/WRITE_ONCE() 避免編譯器優(yōu)化沖突

互斥鎖

  • 啟用 CONFIG_MUTEX_SPIN_ON_OWNER(默認(rèn)開啟)
  • 避免嵌套鎖(否則破壞樂觀自旋)

替代方案

  • 讀多寫少 → 讀寫鎖(rwlock_tseqlock_t
  • 無鎖編程 → 原子操作或 RCU

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論