參考資料:
《正點原子Linux驅動教程》
《宋寶華 Linux裝置驅動開發詳解》
原子操作只能對整型變數或者bit位進行保護,但是實際使用中,不可能只有整型變數或者bit位等臨界區
自旋鎖spinlock也是一種典型的對臨界資源進行互斥訪問的手段,其名稱來源自它的工作方式。
當一個執行緒要訪問某個共享資源的時候首先要先獲取相應的鎖,鎖只能被一個執行緒持有,只要此執行緒不釋放持有的鎖,那麼其他的執行緒就不能獲取此鎖。對於自旋鎖而言,如果自旋鎖正在被執行緒 A 持有,執行緒 B 想要獲取自旋鎖,那麼執行緒 B 就會處於忙迴圈-旋轉-等待狀態
自旋鎖簡化定義:
typedef struct { arch_spinlock_t raw_lock; } spinlock_t;
自旋鎖的相關操作:
spinlock_t lock; // 定義自旋鎖 spin_lock_init(lock) // 初始化自旋鎖 spin_lock(lock) // 獲取自旋鎖 spin_unlock(lock) // 釋放自旋鎖
自旋鎖使用demo:
DEFINE_SPINLOCK(lock) /* 定義並初始化一個鎖 */ /* 執行緒 A */ void functionA (){ unsigned long flags; /* 中斷狀態 */ spin_lock_irqsave(&lock, flags) /* 獲取鎖 */ /* 臨界區 */ spin_unlock_irqrestore(&lock, flags) /* 釋放鎖 */ } /* 中斷服務函式 */ void irq() { spin_lock(&lock) /* 獲取鎖 */ /* 臨界區 */ spin_unlock(&lock) /* 釋放鎖 */ }
自旋鎖主要針對SMP或單CPU但核心可搶佔的情況,對於單CPU和核心不支援搶佔的系統,自旋鎖退化為空操作。自旋鎖持有期間核心搶佔將被禁止。在多核SMP的情況下,任何一個核拿到了自旋鎖,該核上的搶佔排程也被暫時禁止了,但是沒有禁止另外的核搶佔排程
儘管用了自旋鎖可以保證臨界區不受別的CPU和本CPU內搶佔程序的打擾,但是得到鎖的程式碼路徑在執行臨界區的時候,還可能收到中斷的影響,為了防止這種影響,就出現了自旋鎖的衍生:
本地中斷:自旋鎖單個核心的中斷
在SMP程式設計中,如果程序和中斷都有可能訪問同一片臨界資源時,一般在程序上下文呼叫spin_lock_irqsave()和spin_lock_irqrestore(),在中斷上下文呼叫spin_lock()和spin_unlock()
編寫驅動程式時,需要注意下面的幾個問題:
1、自旋鎖實際上是忙等待,CPU在等待自旋鎖時不做任何有用的工作,僅僅是等待。因此,只有在佔用鎖的時間極短的情況下,使用自旋鎖才是合理的。當臨界區很大或共享裝置的時候,需要較長時間佔用鎖,這種情況使用自旋鎖會降低系統效能
2、自旋鎖可能導致系統死鎖,引發這個問題最常見的情況是遞迴使用一個自旋鎖,即如果一個已經擁有某個自旋鎖的CPU想第二次獲取到這個自旋鎖,則該CPU將死鎖
3、自旋鎖鎖定期間不能排程會引起程序排程的函式,如果程序獲取自旋鎖之後再阻塞,如使用copy_from_user()、copy_to_user()、kmalloc()和msleep()等函式,則會導致系統的崩潰
spinlock測試demo:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/spinlock.h> #include <linux/kthread.h> #include <linux/delay.h> DEFINE_SPINLOCK(my_spinlock); static int shared_data = 0; static struct task_struct *my_thread1; static struct task_struct *my_thread2; int my_thread_func(void *data) { int i; for (i = 0; i < 5; i++) { spin_lock(&my_spinlock); shared_data++; printk(KERN_INFO "Thread %s: Incrementing shared data - %d\n", (char *)data, shared_data); spin_unlock(&my_spinlock); msleep(1000); // 模擬一些工作 } do_exit(0); return 0; } static int __init demo_init(void) { printk(KERN_INFO "Demo: Initializing driver\n"); // 建立兩個核心執行緒 my_thread1 = kthread_run(my_thread_func, "1", "my_thread1"); my_thread2 = kthread_run(my_thread_func, "2", "my_thread2"); return 0; } static void __exit demo_exit(void) { if (my_thread1) kthread_stop(my_thread1); if (my_thread2) kthread_stop(my_thread2); printk(KERN_INFO "Demo: Exiting driver\n"); } module_init(demo_init); module_exit(demo_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("lethe1203"); MODULE_DESCRIPTION("spinlock demo");