自旋鎖spinlock

lethe1203發表於2024-03-25
參考資料:
《正點原子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內搶佔程序的打擾,但是得到鎖的程式碼路徑在執行臨界區的時候,還可能收到中斷的影響,為了防止這種影響,就出現了自旋鎖的衍生:
0
本地中斷:自旋鎖單個核心的中斷
在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");

相關文章