日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux驱动编程 step-by-step (七) 并发 竞态 (信号量与自旋锁)

發布時間:2024/9/21 linux 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux驱动编程 step-by-step (七) 并发 竞态 (信号量与自旋锁) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

并發 競態 (信號量與自旋鎖)

代碼傳至并發競態控制

并發進程 導致競態的一個例子

前面所述的字符驅動都是沒有考慮并發竟態的情況,想象一下

一個進程去讀一個字符設備,另一個進程在同一時間向這個設備寫入(完全有這種情況)

原來設備中存有 A B C D 要想設備寫入1 2 3 4 每次讀寫一個字節

t1t2t3t4t5t6t7t8
RWWRWRRW
A12233D4
R: read
W:write
所以最后讀出了A23D不是原來的ABCD
而如果兩個進程同時寫入一個設備則寫入的值可能既不是A進程想要的又不是B進程想要的。

并發與競態

并發是指 多個進程同時訪問相同一段代碼(不僅限于內核空間的代碼)

競態 是對共享資源訪問的結果, 并發進程訪問了相同的數據或結構(硬件資源)等。

解決競態的思想 主要有兩種

1、所有的代碼都不使用全局變量

2、使用鎖機制確保一次只有一個進程使用共享資源

顯然第一種方式是不可能的,我們只能盡量少的使用全局變量,而不能完全避免它
下邊介紹 兩種鎖機制

信號量

這個概念對我們來說應該不是很陌生,至少聽說過,它有兩種操作:通常叫做P操作與V操作

希望進入臨界區的進程調用P操作,檢測當前信號量,如果信號量大于0,則信號量減1,進入臨界區,否則進程等待其他進程釋放此信號量,信號量的釋放通過一個V操作增加信號量的值,在某些情況下會去喚醒等待此信號量的進程。

以上說到了一個 臨界區這個名詞 簡單來講就是內部操作了共享數據的代碼

Linux 內核中信號量的實現

因為從LLD3(2.6.10)版本出來到現在(3.1)內核函數有了很多的變化,許多書上介紹的函數都已經不存在了,也能幾版新kernel之后現在我說的函數也會有變化了。

信號量結構以及相關函數的定義在<linux/semaphore.h>中

創建信號量

[cpp]?view plaincopy
  • void?sema_init(struct?semaphore?*sem,?int?val)??
  • sem:?? 信號量結構體

    val :??? 初始化信號量的值(代表了資源數)【val 為1時 表示代碼在同一時間只允許一個進程訪問 稱之為互斥鎖】


    [cpp]?view plaincopy
  • void?init_MUTEX();??
  • void?init_MUTEX_LOCKED();??
  • 兩個定義互斥鎖的函數 ( 最新的內核中已經沒有這兩個函數了,所以不要用
    [cpp]?view plaincopy
  • struct?simple_dev{??
  • ????char?*data;??
  • ????loff_t?count;??
  • ????struct?cdev?cdev;??
  • ????struct?semaphore?semp;??
  • };??
  • ??
  • static?__init?int?simple_init(void)??
  • {??
  • ????...??
  • ????for(?index?=?0?;?index?<?DEV_COUNT?;?++index?)??
  • ????{??
  • ????????sema_init(&char5_dev[index].semp,1);??
  • ????????//init_MUTEX(&(char5_dev[index].semp));??
  • ????????init_waitqueue_head(&(char5_dev[index].queue));??
  • ????}??
  • ????...??
  • ??????
  • }??

  • P操作 [cpp]?view plaincopy
  • void?down(struct?semaphore?*sem);??
  • int?__must_check?down_interruptible(struct?semaphore?*sem);??
  • int?__must_check?down_killable(struct?semaphore?*sem);??
  • int?__must_check?down_trylock(struct?semaphore?*sem);??
  • int?__must_check?down_timeout(struct?semaphore?*sem,?long?jiffies)??
  • 第一個函數在信號量大于0時直接將信號量的值減一程序繼續運行,在信號量為0時進程睡眠只有等到有其他進程釋放信號量時候才被喚醒( 不推薦使用

    第二個函數 在第一個操作的基礎上,如果進程因為沒有得到信號量睡眠,在別的進程釋放信號量或者發成中斷的情況下都會被喚醒,在被中斷信號喚醒時候返回-EINTR,成功返回0

    第三個函數 如果沒有另外的任務會獲取此信號量,則可以調用此函數,在收到中斷信號時會返回-EINTR

    第四個函數 試圖去獲取信號量,在獲取不到的時候不會睡眠,而是繼續運行,返回0值表示得到了此信號量, 返回1 表示沒能獲取到。

    第五個函數 可以去設置最長睡眠時間, 但是 此函數不可中斷

    V操作

    [cpp]?view plaincopy
  • void?up(struct?semaphore?*sem)??
  • 此操作 首先檢測時候有其他進程等待此信號量,如果有則喚醒此進程,不改變信號量的值,如果沒有則將信號量的值加1。


    [cpp]?view plaincopy
  • static?ssize_t?simple_read(struct?file?*filp,?char?__user?*userstr,?size_t?count,?loff_t?*loff)??
  • {??
  • ??????
  • ????struct?simple_dev?*dev?=?NULL;??
  • ????int?data_remain?=?0;??
  • ????int?err;??
  • ????D("[%s:]?In?%s?\n",current->comm,?__func__);??
  • ??????
  • ????dev?????????=?filp->private_data;??
  • ??????
  • ????err?=?down_interruptible(&dev->semp);??
  • ????if(err)??
  • ????{??
  • ????????if(err?==?-EINTR)??
  • ????????????WAR("return?by?an?interrupt?signal\n");??
  • ????????else??
  • ????????????printk(KERN_ERR?"an?error?occured?in?down_interruptible\n");??
  • ????????return?err;??
  • ????}??
  • ??????
  • ????else??
  • ????{??
  • ????????D("have?get?the?mutex?%d\n",?__LINE__);??
  • ????}??
  • ????/*******************************************?
  • ????*???????臨界區代碼?
  • ????********************************************/??
  • ??????
  • ????up(&dev->semp);??
  • ????return?count;??
  • }??

  • 自旋鎖

    自旋鎖是另一種鎖機制,信號量會引起進程的休眠,而在不能睡眠的代碼中我們就需要使用自旋鎖。

    自旋鎖也是一個互斥的概念,有“鎖定”與“解鎖”兩個操作,當程序需要鎖定時候,則先檢測鎖是否可用,如果可用則獲得鎖,程序進入臨界區,否則進入忙等待重復檢測這個鎖是否可用(這就是自旋),臨界區代碼操作完成則解鎖。

    信號量實現中其實也用到了自旋鎖機制(有興趣的刻一看內核源碼,這邊不展開,等以后寫內核時候再介紹)

    因為自旋鎖在得不到鎖的時候會“自旋” 即不會讓出CPU ,所以我們的臨界區執行速度應該盡量的快,最好使用原子操作(不會睡眠)。這也是使用自旋鎖的核心規則,在多數情況下我們做不到這一點,所以自旋鎖在驅動程序中使用的不如信號量頻繁。

    初始化

    [cpp]?view plaincopy
  • spin_lock_init(_lock)??
  • 鎖定

    [cpp]?view plaincopy
  • static?inline?void?spin_lock(spinlock_t?*lock)??
  • static?inline?int?spin_trylock(spinlock_t?*lock)??
  • static?inline?void?spin_lock_irq(spinlock_t?*lock)??
  • spin_lock_irqsave(lock,?flags)??
  • static?inline?void?spin_lock_bh(spinlock_t?*lock)??
  • 第一個函數是 不去禁止中斷 直接鎖定

    第二個函數 會嘗試加鎖,檢測返回之判斷是否鎖定,在不能鎖定時候程序也繼續運行

    第三個函數 禁止中斷,不保存保存原先的中斷狀態

    第四個函數 在禁止中斷之前,保存原先的中斷狀態,

    第五個函數 表示只禁止軟件中斷而保持硬件中斷的打開

    因為自旋鎖本質上要不會被中斷,所以調用時候建議使用包含有禁止中斷的函數

    解鎖

    [cpp]?view plaincopy
  • static?inline?void?spin_unlock(spinlock_t?*lock)???
  • //static?inline?void?spin_unlock(spinlock_t?*lock)??
  • static?inline?void?spin_unlock_irq(spinlock_t?*lock)??
  • static?inline?void?spin_unlock_irqrestore(spinlock_t?*lock,?unsigned?long?flags)??
  • static?inline?void?spin_unlock_bh(spinlock_t?*lock)??
  • 對應于鎖定的五個函數,分別解鎖,(對應于鎖定的第二個操作,在加鎖成功時候需要使用spin_unlock來解鎖)

    死鎖

    上邊只提到了使用鎖的好處,以及如何使用鎖,但是引入鎖機制也會帶來風險,那就是死鎖,進程死鎖最明顯的表現就是死機。

    如上圖所示,A進程占有鎖1,并在持有鎖1的時候需要得到鎖2程序才能繼續進行

    B進程占有鎖2, 并在保持鎖2的同時去獲取鎖1程序才能繼續運行,這樣A, B 進程就卡在這里,互不相讓,這就導致了死鎖。

    亦或 在一個進程內同時試圖獲取同樣的鎖

    死鎖的名詞解釋

    是指兩個或兩個以上的進程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處于死鎖狀態或系統產生了死鎖

    解決死鎖的方法:

    1、加解鎖順序一致

    在必須使用多個鎖的時候,應該始終以相同的順序獲取,也最好以獲取鎖順序逆序解鎖。

    2、使用原子變量
    原子變量不會被多個進程并發操作,內核提供了一種原子類型(atomic_t <asm/stomic.h>中)

    3、設置加鎖timeout值
    在一定時間內不能獲得鎖,就放棄,并釋放已占有的鎖

    總結

    以上是生活随笔為你收集整理的Linux驱动编程 step-by-step (七) 并发 竞态 (信号量与自旋锁)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。