歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux基礎知識 >> Linux設備驅動的並發控制

Linux設備驅動的並發控制

日期:2017/3/2 17:14:32   编辑:Linux基礎知識

一、並發與競爭

    並發是指多個 多個執行單元同時執行,而這對對共享的資源,比如硬件的資源、軟件的全局變量、靜態變量 的訪問,很容易導致競態,

1.1、中斷屏蔽

    在單核的  CPU 裡,避免競態的一個簡單有效的方法是,在進入臨界區之前,就屏蔽系統的中斷。也就是說,在進入臨界區之前,中斷被關閉,使得中斷與進程之間的並發不會發生,而且,因為進程的調度器是依賴於中斷來實現的,沒有了中斷,進程就不能被切換,保證了進程之間的並發不會發生。

方法:

local_irq_disable()

XXXXX 臨界區的代碼

local_irq_enable()

    雖然  local_irq_disable() 和 local_irq_enable() 可以關閉本 CPU 的中斷,但是在 多核的  CPU裡面,並不能解決問題,因為 它們只能禁止和使能本 CPU 的中斷,並不能解決多 CPU 引發的競爭,所以在多個的裡面,一般是將關閉中斷的方法與 自旋鎖 結合使用。

    如果指向禁止中斷的底部,在應該使用 locak_bh_disable() ,使能的是, local_bh_enable();

1.2、原子操作

原子操作,可以保證對一個整型數據的修改的排他性,也就是說,原子操作是對一個數據進行操作,操作的過程,是不會被線程的調度機制給打斷,也就是說,一旦開始,是絕對不會睡眠和阻塞的,直到操作結束。對於整形數的操作,分別是對整型數和位進行原子的操作。

1.2.1、整型原子的操作

(1)設置原子變量的值

void atomic_set(atomic_t *v,int i);     // 設置原子變量的值為 i

atomic_t v = ATOMIC_INIT(0)  ;    // 定義原子變量 V,並初始化為 0

(2)獲取原子變量的值

atomic_read(atomic_t *v)    // 返回值就是原子變量的值

(3)原子的操作

void atomic_add(int i, atomic_t *v)    // i + v

void atomic_sub(int i,atomic_t *v ) // v – i

(4)操作並測試

int atomic_inc_an_test(atomic_t *v)   // 自加1,測試是否為0,使得話返回 true

int atomic_dec_and_test(atomic_t *v)  // 自減 1,測試是否為 0,使得話,返回 true

(5)操作與返回

int atomic_add_reture(int i, atomic_t *v);   // V + i,返回新的值

int  atomic _sub_return(int i, atomic_t *v)  // v – i ,返回新的值

int atomic_inc_return(atomic_t *v)   // V + 1

int atomic_dec_return(atomic_t *v) // V – 1

1.2.2、位原子的操作

(1)設置為

void set_bit(nr,void * addr)   // 設置地址 addr 的 第 nr 位 為 1

(2)清除位

void clear_bit(nr, void *addr)   // 設置地址 addr 的 第 nr 位 為 0

(3)改變位

void change_bit(nr, void *addr) // 設置地址 addr 的 第 nr 位 反轉

(4)檢測位

test_bit(nr, void *addr);   // 返回 addr 第  nr 位的  值

(5)測試與操作為

int test_and_set_bit(nr, void *addr);

    先進行測試,之後在進行位的設置。

1.2.3、原子操作的特性

    原子操作的特性,就對原子數的操作的時候,絕對不會被線程的調度器進行打斷,也就說,這個時間段,是沒進程的上下文之間的切換,進一步說,就是絕對不會出現睡眠和阻塞。

1.3、自旋鎖

    自旋鎖 spin  lock,是另外一種對臨界資源進行互斥訪問的手段,它的實現也是借助了 原子操作實現的。自旋鎖的機制是,進程對資源的訪問之前,需要對特性的內存進行讀取,當讀取到值,也就是獲得到鎖的時候,就有權限進入下一步的操作;而其他的進程沒有獲得到鎖的時候,就就如原地的等待,然後再次進行讀取,這個等待、讀取的過程,我們稱之為自旋。

1.3.1、自旋的 API

(1)鎖的定義

spinlock_t lock;

(2)鎖的初始化

spin_lock_init(lock)    // 完成初始化

(3)獲得鎖

spin_lock(lock)

    嘗試去獲得鎖,如果獲得,就馬上返回,沒有獲得的話,就進入自旋的狀態,Linux 也提供非阻塞的方式:

spin_trylock(lock)

    獲得鎖的時候,返回 true,沒有獲得的時候,就返回 false

(4)釋放鎖

spin_unlock(lock)

1.3.2、一般執行方式

spinlock_t lock;

spin_init_lock(lock);

spin_lock(lock);

XXXX 臨界區代碼

spin_unlock(lock);

1.3.3、自旋鎖與中斷

    在單 CPU 和內核可被搶占的系統中,自旋鎖在獲得鎖的期間,內核的搶占就是被禁止的,因此,這個時候,是可以完全保證獲得鎖器件的臨界區操作代碼,不收到其他進程的打擾;但是在 多核的 CPU 中,其中的一個 CPU 獲得了自旋鎖,但是,只是在該 CPU 上的搶占調度被禁止了,其他核心的CPU 是並沒有被禁止搶占調度的,所以為了保證在多核的情況,臨界區不受到本 CPU 和其他的  CPU 搶占進程的打擾。

    因為自旋鎖的底層是借助原子操作實現的,保證了獲得鎖的器件的操作是不會被被本 CPU 和其他 CPU 的進程調度所影響,而且,自旋鎖關閉了搶占系統,但是這些特性並不能保證在得到鎖的的時候,執行臨界區代碼的時候,不受到中斷和底部(BH)的影響,也就是這個時候,本 CPU 的中斷發生的時候,還是會反過來影響臨界區的代碼,所以一般是假如關閉中斷的操作,這個時候就借助了鎖的衍生機制

spin_lock_irq = spin_lock() + local_irq_disable()    // 獲得鎖的同時,關閉中斷

spin_inlock_irq = spin_unlock() + local_irq_enable   // 釋放鎖的同時,打開中斷

spin_lock_irqsave = spin_lock() + locak_irq_save()  // 獲得鎖的同時,關閉中斷,且保存中斷的狀態

spin_lock_irqrestore() = spin_unlock() + locak_irq_restore()   // 釋放鎖的同時,將中斷狀態進行回復

spin_lock_bh() = spin_lock() + local_bh_disable() ;  // 獲得鎖的同時,關閉底部中斷

spin_unlock_bh() = spin_unlock() + local_bh_enable  // 釋放鎖,且恢復底部中斷

    當在自旋鎖內部加了中斷的時候,CPU0 獲得了自旋鎖,進入了原子的操而CPU 1 在 CPU0 還沒有釋放鎖的時候,CPU1 就只等原地等待(浪費

了 CPU);而且,中斷的話,就避免受到  本 CPU 的影響,保證了內核並發的可能。

1.3.4、自旋鎖注意事項

(1)因為在獲得了自旋鎖之後,CPU 就開始了原子的操作,其他的CPU 就會原地一直自旋,等待的過程,並不是睡眠,所以浪費了 N 多的資源,所以,一般上,進程擁有鎖的時間都是非常的短的,這樣才是比較的劃算;如果臨界區的代碼非常大的話,其他的 CPU 就一直原地等待,浪費了資源,導致了系統性能的降低。

(2)進程在擁有一個鎖的期間,不能再嘗試去獲取本鎖,否則導致死鎖。

(3)在獲得自旋鎖的器件,絕對不能出現進程調度的情況,也就是,絕對不能出現進程的睡眠和進程的阻塞,不能出現 copy_tousr或者copy_from_usr,kmalloc、msleep等函數,因為自旋鎖實現就是通過原子操作實現的,原子操作的特性,就是一旦開始執行,絕對不會出現進程的調度,否則會導致內核的崩潰。

1.4、信號量

    信號量,是非常常用的一種同步互斥的手段,類似於燈,當等的個數不為零的時候,進程獲取資源的時候,就將燈拿走一個;當釋放資源的時候,就將燈放回去;當燈為零的時候,這個時候,進程將被掛起,進入睡眠的狀態,直到被喚醒。

1.4.1、API

(1)定義信號量

struct semaphore sem;

(2)初始化信號量

void sema_init(struct semaphore * sem, int val)

    初始化信號量的值為 val

(3)獲得信號量

int down(struct semaphore  * sem)

    獲得信號量,和獲得鎖差不多,當沒有得到信號量的時候,就進入睡眠的狀態,也就是說,不能在中斷裡面被使用,因為中斷全部被關閉了,進程的調度器是依賴中斷實現的,沒有了中斷,當睡眠的進程,擁有沒有進程調度器去喚醒進程,導致程序用死停在那裡。內核也提供了可以被打斷的接口

int down_interruptible(struct semaphore  *sem)

    回去嘗試去獲取信號量,沒有獲得的時候,進入睡眠,但是可以被打斷,,

int dowun_trylock(struct semaphore  *sem)

   即使在沒有活動信號量的情況下,也不會導致睡眠,而是立即返回,所以可以在中斷的上下文進行使用

(4)釋放信號量

void up(struct semaphore *sem)

當信號量初始化為 1 的時候,實現的就是互斥鎖的功能

 

1.5、互斥鎖

   互斥鎖的使用方法,和信號量的幾乎一模一樣 ,只是接口名字不一樣而已。

(1)定義互斥鎖

struct mutex mymutex;

(2)初始化互斥鎖

mutex_init( struct mutex *mymutex)

(3)獲得互斥鎖

void mutex_lock( struct mutex mymutex;)

   當然也有,void mutex_lock_interruptible( struct mutex mymutex;) 和 void mutex_trylock( struct mutex mymutex;)

(4)釋放互斥鎖

void mutex_unlock(struct mutex mymutex)

1.5.1、自旋鎖和互斥鎖的選擇

   (1) 自旋鎖在沒有獲得鎖的時候,就只能是原地的等地,因此開銷就是其他進程獲得鎖執行的時間;而互斥鎖的話,在沒有獲得鎖,就會睡眠當前的進程,鎖帶來的開銷,是進程切換帶來的開銷。

    (2)互斥鎖的過程,會帶來進程的睡眠 ,因此,互斥鎖是絕對不能出現在中斷的上下文;自旋鎖則不然,非常適合與中斷一起配合使用;自旋鎖保護的臨界區,絕對不能出現進程的阻塞以及睡眠。

Copyright © Linux教程網 All Rights Reserved