🔥 推薦:《Yocto項目實戰教程:高效定制嵌入式Linux系統》
京東正版促銷,歡迎支持原創!
鏈接:https://item.jd.com/15020438.html
一、為什么需要同步機制?
Linux 是一個支持 多核并發 + 搶占式調度 的操作系統,多個內核線程可能同時訪問同一份內存區域,例如:驅動共享某個全局變量、文件系統緩存讀寫、網絡緩沖區處理等等。
如果沒有同步機制,就會產生競態條件(Race Condition),導致數據錯亂、內核崩潰甚至安全漏洞。
因此,Linux 內核實現了一整套高效的同步機制,用來協調不同 CPU、不同執行上下文之間對共享資源的訪問。
二、Linux 中的主要同步機制
同步機制 | 是否可睡眠 | 適用場景 | 常見用法 | 內核位置 |
---|---|---|---|---|
原子變量 | 否 | 簡單計數/標志 | 狀態標志、自增自減 | include/linux/atomic.h |
自旋鎖 | 否 | 中斷處理、短臨界區 | 硬件寄存器、短時間保護 | include/linux/spinlock.h |
信號量 | 是 | 多資源管理 | 限制并發訪問數 | include/linux/semaphore.h |
互斥鎖 | 是 | 臨界區保護 | 普通數據結構保護 | include/linux/mutex.h |
RCU | 部分讀者無鎖 | 讀多寫少的表結構 | 任務列表、網絡表項 | kernel/rcu/ |
三、五種同步機制逐一講解
3.1 原子變量
? 場景:狀態標志、自增計數器
atomic_t count;
atomic_set(&count, 0);atomic_inc(&count); // ++count
atomic_dec(&count); // --countif (atomic_read(&count) == 0) {// 說明資源清空或條件滿足
}
- 原子變量適合簡短邏輯,如計數器、標志位。
- 不適合保護復雜數據結構。
? 面試考點:
- 原子變量是否需要鎖?→ 否。
- 原子操作能否用于中斷上下文?→ 可以。
3.2 自旋鎖(Spinlock)
? 場景:短時間保護、中斷上下文
spinlock_t my_lock;
spin_lock_init(&my_lock);spin_lock(&my_lock);
/* 訪問共享資源 */
spin_unlock(&my_lock);
🔥 推薦:《Yocto項目實戰教程:高效定制嵌入式Linux系統》
京東正版促銷,歡迎支持原創!
鏈接:https://item.jd.com/15020438.html
? 中斷安全版本:
unsigned long flags;
spin_lock_irqsave(&my_lock, flags);
/* 臨界區 */
spin_unlock_irqrestore(&my_lock, flags);
? 特點:
- 獲取不到鎖會 原地忙等(自旋),適合快速操作;
- 不可用于睡眠上下文,否則死鎖;
- 常用于中斷處理函數、底半部等高優先級邏輯。
? 面試考點:
- spin_lock 與 mutex 有什么區別?
- 中斷上下文是否可以使用 mutex?→ 否。
3.3 信號量(Semaphore)
? 場景:控制多個線程對有限資源訪問
struct semaphore sem;
sema_init(&sem, 3); // 最多允許3個并發/* 請求資源 */
down(&sem); // 若資源不足,將睡眠/* 使用資源 *//* 釋放資源 */
up(&sem);
- 適合表示資源池,如“3臺打印機”、“10個緩存槽位”;
- 已逐漸被 mutex 替代。
? 面試考點:
- down()/up() 會阻塞線程嗎?→ 是。
- 信號量適合中斷中使用嗎?→ 否(會睡眠)。
3.4 互斥鎖(Mutex)
? 場景:線程間對共享數據的互斥訪問
struct mutex my_mutex;
mutex_init(&my_mutex);mutex_lock(&my_mutex);
/* 臨界區代碼 */
mutex_unlock(&my_mutex);
- 獲取不到鎖會讓當前線程 掛起等待,資源釋放后再調度回來;
- 不適合中斷處理。
? 特點對比:
比較項 | spinlock | mutex |
---|---|---|
可否睡眠 | 否 | 是 |
場景 | 中斷/底半部 | 普通線程 |
獲取失敗 | 自旋等待 | 睡眠等待 |
? 面試考點:
- mutex 和 semaphore 區別?→ semaphore 是計數,mutex 僅一人。
3.5 RCU(Read-Copy-Update)
? 場景:讀多寫少,如任務列表、網絡表項
讀操作(不加鎖):
rcu_read_lock();
my_ptr = rcu_dereference(global_ptr);
/* 安全讀取數據 */
rcu_read_unlock();
寫操作(復制更新):
new_ptr = kmalloc(...);
/* 修改副本 */
rcu_assign_pointer(global_ptr, new_ptr);
synchronize_rcu();
/* 釋放舊數據 */
kfree(old_ptr);
? 特點:
- 讀性能極高,不加鎖;
- 寫復雜,需注意數據生命周期;
- 常用于鏈表、哈希表等結構。
? 面試考點:
- RCU 是否支持多讀多寫?→ 多讀 + 單寫 + 延遲銷毀。
- RCU 與普通鎖最大區別?→ 讀時不阻塞。
四、實戰示例:共享計數器保護
? 錯誤示例:未加鎖
static int count = 0;void do_work(void) {count++; // 多線程訪問存在競態!
}
? 正確方式:使用原子變量
static atomic_t count = ATOMIC_INIT(0);void do_work(void) {atomic_inc(&count);
}
? 或使用自旋鎖
static int count = 0;
static spinlock_t lock;void do_work(void) {spin_lock(&lock);count++;spin_unlock(&lock);
}
? 或使用 mutex(若可睡眠)
static int count = 0;
static struct mutex my_mutex;void do_work(void) {mutex_lock(&my_mutex);count++;mutex_unlock(&my_mutex);
}
五、面試常見問題匯總(含答案)
-
spinlock 和 mutex 有何區別?
- spinlock 不可睡眠、適用于中斷上下文;mutex 會睡眠、適合線程間同步。
-
原子變量需要加鎖嗎?
- 不需要,已具備原子性。
-
哪些鎖不能用于中斷?
- mutex、semaphore 會睡眠,不能用于中斷處理。
-
RCU 為什么性能高?
- 讀操作無鎖,不阻塞任何線程,極高并發性。
-
如何避免死鎖?
- 保持鎖獲取順序一致;禁止在持鎖時調用睡眠函數(如 mutex + msleep());中斷上下文避免調用可睡眠接口。
六、小結:如何選用同步機制?
場景 | 建議同步方式 |
---|---|
標志位、計數器 | 原子變量 |
中斷處理、底半部 | 自旋鎖 |
用戶進程臨界區 | 互斥鎖 mutex |
限制訪問數量(N個資源) | 信號量 semaphore |
讀多寫少表結構 | RCU |
七、結束語
Linux 內核的同步機制是驅動開發和內核編程中的“必修課”,掌握好它不僅能寫出正確的代碼,更是邁入高級內核開發的關鍵一步。
📚 🔥 推薦:《Yocto項目實戰教程:高效定制嵌入式Linux系統》
京東正版促銷,歡迎支持原創!
鏈接:https://item.jd.com/15020438.html
🎥 視頻教程請關注 B 站:“嵌入式 Jerry”