1. 程式人生 > >linux驅動中的互斥途徑三:自旋鎖

linux驅動中的互斥途徑三:自旋鎖

1. 自旋鎖

1.1 概念:

自旋鎖是一種典型的對臨界資源進行互斥訪問的手段,其名稱來源於它的工作方式。為了獲得一個自旋鎖,在某 CPU 上執行的程式碼需先執行一個原子操作,該操作測試並設定某個記憶體變數,如果測試結果表示鎖已經空閒,則程式獲得這個自旋鎖並繼續執行,如果測試結果表明鎖仍被佔用,程式將在一個小的迴圈內重複這個"測試並設定"操作。當然當鎖的持有者通過重置該變數釋放這個鎖後,某個等待的操作向其使用者報告鎖已釋放。

理解鎖最簡單的方法是把它作為一個變數看待。

使用的時候需要注意一下的問題:

  1. 自旋鎖是忙等鎖。使用時要求短平快
  2. 可能導致系統死鎖。如果一個已經擁有某個自旋鎖的 CPU 想第二次獲得這個自旋鎖,該CPU 將鎖死。
  3. 鎖定期間不能呼叫可能引起程序排程的函式。如果程序獲得自旋鎖之後再阻塞(如呼叫copy_from_user()、copy_to_user()、kmalloc()、msleep等函式)可能導致核心崩潰。

1. 2 函式:

定義
spinlock_t lock;
初始化
spin_lock_init(lock);
這是一個巨集,用於動態的初始化一個鎖 獲得鎖
spin_lock(lock);

功能:

獲得自旋鎖,

獲得,返回

不能獲得,自旋在那裡

spin_trylock(lock);

嘗試獲得自旋鎖,

能立即獲得鎖,獲得鎖返回 真

不能獲得,返回 假

釋放鎖
spin_unlock(lock);

自旋鎖主要針對 SMP 或單 CPU 單核心支援可搶佔的情況,使臨界區不受別的 CPU 和本 CPU 內的搶佔程序打擾,但是得到鎖的程式碼路徑在執行臨界區的時候,還可能受到中斷和底半部的影響。解決辦法是:

spin_lock_irq() = spin_lock() + local_irq_disable() /* 關中斷加鎖 */

spin_unlock_irq() = spin_unlock() + local_irq_enable()

spin_lock_irqsave() = spin_lock() + local_irq_save()

spin_lock_irqrestore() = spin_unlock() + local_irq_restore()

spin_lock_bh() = spin_lock() + local_bh_disable() /* 關底半部加鎖 */

spin_unlock_bh() = spin_unlock() + local_bh_enable()

1.3 例子:

使用自旋鎖實現一個裝置只能被一個程序開啟:

int flag = 0;

struct hello_device
{
    char data[128];
    spinlock_t lock;
    struct cdev cdev;
} hello_device;


static int hello_open (struct inode *inode, struct file *file)
{
    spin_lock(&hello_device.lock);
    if ( flag )
    {
        spin_unlock(&hello_device.lock);
        return -EBUSY;
    }
    flag++;
    spin_unlock(&hello_device.lock);

    printk (KERN_INFO "Hey! device opened\n");

    return 0;
}

static int hello_release (struct inode *inode, struct file *file)
{
    spin_lock(&hello_device.lock);
    flag--;
    spin_unlock(&hello_device.lock);
    printk (KERN_INFO "Hmmm... device closed\n");

    return 0;
}
【1】程式的思路是在驅動中宣告一個變數初始化為0,當第一次開啟時,加1(此部分用自旋鎖保護一下),在open函式中檢查flag,如果是1,則返回。在release函式中flag減1(此部分也要加鎖保護下)

2. 讀寫自旋鎖:

解決的問題:
對共享資源併發訪問時,多個執行單元同時讀取是沒有問題的,只要保證寫的時候只能有一個寫程序就行。

2.1定義和初始化

rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* 靜態初始化 */

rwlock_t my_rwlock;

rwlock_init(&my_rwlock); /* 動態初始化 */

2.2讀鎖定

void read_lock(rwlock_t *lock);

void read_lock_irqsave(rwlock_t *lock, unsigned long flags);

void read_lock_irq(rwlock_t *lock);

void read_lock_bh(rwlock_t *lock);

2.3讀解鎖

void read_unlock(rwlock_t *lock);

void read_unlock_irqresore(rwlock_t *lock, unsigned long flags);

void read_unlock_irq(rwlock_t *lock);

void read_unlock_bh(rwlock_t *lock);

2.4寫鎖定

void write_lock(rwlock_t *lock);

void write_lock_irqsave(rwlock_t *lock, unsigned long flags);

void write_lock_irq(rwlock_t *lock);

void write_lock_bh(rwlock_t *lock);

void write_trylock(rwlock_t *lock);

2.5寫解鎖

void write_unlock(rwlock_t *lock);

void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);

void write_unlock_irq(rwlock_t *lock);

void write_unlock_bh(rwlock_t *lock);

在對共享資源進行寫之前,應該先呼叫寫鎖定函式,完成後應呼叫寫解鎖函式

3. 順序鎖: -- 解決的是讀寫同時進行的小的概率情況

循序鎖是對讀寫鎖的一種優化,讀執行單元不會被寫執行單元阻塞,但是寫執行單元與寫執行單元之間是互斥的:
  • 如果有寫執行單元在進行寫操作,其他寫執行單元必須自旋在那裡,直到寫執行單元釋放了順序鎖
  • 如果讀執行單元在讀操作期間,寫執行單元已經發生了寫操作,那麼,讀執行單元必須重新讀取資料
限制
要求被保護的共享資源不含指標,因為寫執行單元可能使得指標失效,但讀執行單元如果正在訪問該指標,將導致 oops。

3.1 寫執行單元:

獲取順序鎖:
void write_seqlock(seqlock *sl); void write_tryseqlock(seqlock *sl); write_seqlock_irqsave(lock, flags); = local_irq_save() + write_seqlock() write_seqlock_irq(lock, flags); = local_irq_disable() + write_seqlock() write_seqlock_bh(lock, flags); = local_bh_disable() + write_seqlock()
釋放順序鎖:
void write_sequnlock(seqlock *sl); write_sequnlock_irqrestore(lock, flags); = write_sequnlock() + local_irq_restore() write_sequnlock_irq(lock, flags); = write_sequnlock() + local_irq_enable() write_sequnlock_bh(lock, flags); = write_sequnlock() + local_bh_enable()

3.2 讀執行單元:

讀開始
unsigned read_seqbegin(const seqlock_t *sl); read_seqbegin_irqsave(lock, flags); = local_irq_save() + read_seqbegin()
讀執行單元在被順序鎖 sl 保護的共享資源進行訪問需要呼叫該函式,該函式僅返回順序鎖 sl 的當前順序號 重讀
int read_seqretry(const seqlock_t *sl, unsigned iv); read_seqretry_irqrestore(lock, iv, flags); = read_seqretry() + local_irq_restore()
讀執行單元在訪問完被順序鎖 sl 保護的共享資源後需要呼叫該函式來檢查,在讀訪問期間是否有寫操作。如果有寫操作,讀執行單元需要重新進行讀操作。 使用順序鎖的格式:
do{
seqnum = read_seqbegin(&seqlock_a);
/* 讀操作程式碼塊 */
...
}while (read_seqretry(&seqlock_a, seqnum));

4. RCU:

RCU 是 Read-Copy Update 的縮寫,讀-拷貝-更新,他是基於其原理命名的,對於被 RCU 保護的共享資源,讀執行單元不需要獲得任何鎖就可以訪問它,不使用原子執行,而且在除alpha的所有架構上也不需要記憶體屏障,因此不會導致鎖競爭、記憶體延遲以及流水線停滯。

使用 RCU 的寫執行單元在訪問他前需要首先拷貝一個副本,然後對副本進行修改,最後使用一個回撥機制在適當的時機吧指向原來資料的指標重新指向新的被修改的資料,這個時機就是所有引用該資料 CPU 都退出對共享資料的操作的時候,讀執行單元沒有任何的同步開銷,寫執行單元的同步開銷則取決於使用的寫執行單元之間同步機制。

相關推薦

linux驅動互斥途徑

1. 自旋鎖 1.1 概念: 自旋鎖是一種典型的對臨界資源進行互斥訪問的手段,其名稱來源於它的工作方式。為了獲得一個自旋鎖,在某 CPU 上執行的程式碼需先執行一個原子操作,該操作測試並設定某個記憶

Linux環境程式設計之同步()讀寫

概述 相互排斥鎖把試圖進入我們稱之為臨界區的全部其它執行緒都堵塞住。該臨界區通常涉及對由這些執行緒共享一個或多個數據的訪問或更新。讀寫鎖在獲取讀寫鎖用於讀某個資料和獲取讀寫鎖用於寫直接作差別。 讀寫鎖的分配規則例如以下: 1、僅僅要沒有執行緒持有某個給定的讀寫鎖用於寫。那麼

Java的種類以及辨析(二)的其他種類

作者:山雞 鎖作為併發共享資料,保證一致性的工具,在JAVA平臺有多種實現(如 synchronized 和 ReentrantLock等等 ) 。這些已經寫好提供的鎖為我們開發提供了便利,但是鎖的具體性質以及型別卻很少被提及。本系列文章將分析JAVA下常見的鎖名稱以及特性,為大家答疑解惑。

java的種類以及辨析(一)

作者:山雞 鎖作為併發共享資料,保證一致性的工具,在JAVA平臺有多種實現(如 synchronized 和 ReentrantLock等等 ) 。這些已經寫好提供的鎖為我們開發提供了便利,但是鎖的具體性質以及型別卻很少被提及。本系列文章將分析JAVA下常見的鎖名稱以及特性,為大家答疑解惑。

java 的型別和性質(一)

作為併發共享資料,保證一致性的工具,在java平臺有多種實現(如 synchronized 和 ReentrantLock等等 ) 。這些已經寫好提供的鎖為我們開發提供了便利,但是鎖的具體性質以及型別卻很少被提及。本系列文章將分析java下常見的鎖名稱以及特性,為大家答疑

linux驅動互斥途徑一二中斷遮蔽和原子操作

1. 中斷遮蔽: 1.1 說明: 在單 CPU 範圍內避免競態的簡單而省事的方法是在進入臨界區之前遮蔽系統的中斷 優點是:當中斷遮蔽的時候,核心搶佔程序之間的併發也得以避免了缺點是:由於linux的非同步I/O、程序排程等很多重要操作都依賴於中斷,這些功能在中斷遮蔽期間將不

Linux驅動入門篇(基本的字符設備模塊(2)

連接 truct ace alloc orm 負數 -s tabs idt   上一節中介紹了設備號的申請和釋放,這一節開始了解字符設備的相關操作。   首先定位到<linux/cdev.h>文件,查看內核提供給字符設備的接口。 cdev結構 str

Java 15種的介紹公平,可重入,獨享互斥,樂觀,分段等等

Java 中15種鎖的介紹 在讀很多併發文章中,會提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內容如下: 公平鎖 / 非公平鎖 可重入鎖 / 不可重入鎖 獨享鎖 / 共享鎖 互斥鎖 / 讀寫鎖 樂觀鎖 / 悲觀鎖 分段鎖

android的對話方塊之定義對話方塊

首先看下效果圖 下面講一下具體的實現: 1.修改系統預設的Dialog樣式(風格、主題) 2.自定義Dialog佈局檔案 3.可以自己封裝一個類,繼承自Dialog或者直接使用Dialog類來實現,為了方便以後重複使用,建議自己封裝一個Dialog類 ==

Linux裝置驅動的併發控制之五(

7.5 自旋鎖7.5.1 自旋鎖的使用自旋鎖(Spin Lock)是一種典型的對臨界資源進行互斥訪問的手段,名稱來源於它的工作方式。為了獲得一個自旋鎖,在某CPU上執行的程式碼需先執行一個原子操作,該操作測試並設定(Test-And-Set)某個記憶體變數。由於它是原子操作,

CSS學習筆記定義單選框,復選框,開關

sla checked 移動 transform 第一個 16px 位移 block back 一點一點學習CCS,這次學習了如何自定義單選框,復選框以及開關。 一、單選框 1、先寫好body裏面的樣式,先寫幾個框 1 <body> 2 <d

Linux驅動常用的宏

常用 linux drive pan div linux驅動 bsp return class 1.module_i2c_driver(adxl34x_driver)展開為 static int __int adxl34x_driver_init(void) { ret

[Doctrine Migrations] 數據庫遷移組件的深入解析定義數據字段類型

con 組件 extends arr TP value ctr ets field 自定義type 根據官方文檔,新建TinyIntType類,集成Type,並重寫getName,getSqlDeclaration,convertToPHPValue,getBindingT

ROS學習筆記()定義話題的程式設計

前言:ros給我們提供了眾多的訊息結構,但是更多時候我們需要根據自己的研發需求定義自己的訊息結構。 一、檢視ros自帶的訊息結構 我們最常用的一個訊息結構就是std_msgs,那麼怎麼檢視這個訊息結構支援可以定義哪些資料型別呢? 我們使用roscd std_msgs/這個命令開啟該訊息結

linux驅動讀寫硬體暫存器(例如__raw_writel)

   __iomem原始碼位置:include/linux/compiler.h # define __force    __attribute__((force)) //變數可以進行強制轉換 # define __nocast &

介面測試系列工作所用(__read_config.py檔案)

import os from common import fileUtil def __read_config(): base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) settings_file

Linux系統DNS服務之 反向解析!

DNS的反向解析:將IP地址轉化為域名!     開始實驗! 實驗環境: @虛擬機器server作為DNS伺服器(172.25.254.48) @真機和虛擬機器desktop作為測試端使用   服務端: 注:前面我們已經做過了DNS的

LINUX共享型伺服器之iSCSI伺服器

什麼是iSCSi??? 如果你的系統需要大量的磁碟容量,但是身邊卻沒有NAS或外接的儲存裝置,僅有個人計算機時,那該怎麼辦呢???此時,通過網路的SCSI磁碟就能夠提供幫助。什麼是iSCSI??   SCSI(Internet SCSI) 支援從客戶端 ( 發起端 ) 通過 IP 向遠

spring security起步定義登入配置與form-login屬性詳解

在上一篇部落格spring security起步二:自定義登入頁中我們實現瞭如何自定義登入頁,但是還存在很多問題: 1.spring security如何對登入請求進行攔截 2.登入成功後如何跳轉 3.登入失敗後如何跳轉 form-login屬性詳解

ARM平臺linux驅動怎樣刷cache

怎樣刷cache 在涉及到DMA的驅動中當你對DMA的buffer進行了修改後,通常需要刷cache(當然有不需要刷cache的情況,不過我不分析它)。怎樣刷cache —— 呼叫dma_sync_sg_for_device或它的兄弟姐妹(仔細看一下那個標頭檔案,自然之道需