小編給大家分享一下linux驅(qū)動中并發(fā)與竟態(tài)的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
10年積累的成都網(wǎng)站制作、成都網(wǎng)站建設(shè)經(jīng)驗(yàn),可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識你,你也不認(rèn)識我。但先網(wǎng)站制作后付款的網(wǎng)站建設(shè)流程,更有麻陽免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
首先什么是并發(fā)與竟態(tài)呢?并發(fā)(concurrency)指的是多個執(zhí)行單元同時、并行被執(zhí)行。而并發(fā)的執(zhí)行單元對共享資源(硬件資源和軟件上的全局、靜態(tài)變量)的訪問則容易導(dǎo)致競態(tài)(race conditions)??赡軐?dǎo)致并發(fā)和竟態(tài)的情況有:
SMP(Symmetric Multi-Processing),對稱多處理結(jié)構(gòu)。SMP是一種緊耦合、共享存儲的系統(tǒng)模型,它的特點(diǎn)是多個CPU使用共同的系統(tǒng)總線,因此可訪問共同的外設(shè)和存儲器。
中斷。中斷可 打斷正在執(zhí)行的進(jìn)程,若中斷處理程序訪問進(jìn)程正在訪問的資源,則競態(tài)也會發(fā)生。中斷也可能被新的更高優(yōu)先級的中斷打斷,因此,多個中斷之間也可能引起并發(fā)而導(dǎo)致競態(tài)。
內(nèi)核進(jìn)程的搶占。linux是可搶占的,所以一個內(nèi)核進(jìn)程可能被另一個高優(yōu)先級的內(nèi)核進(jìn)程搶占。如果兩個進(jìn)程共同訪問共享資源,就會出現(xiàn)竟態(tài)。
以上三種情況只有SMP是真正意義上的并行,而其他都是宏觀上的并行,微觀上的串行。但其都會引發(fā)對臨界共享區(qū)的競爭問題。而解決競態(tài)問題的途徑是保證對共享資源的互斥訪問,即一個執(zhí)行單元在訪問共享資源的時候,其他的執(zhí)行單元被禁止訪問。那么linux內(nèi)核中如何做到對對共享資源的互斥訪問呢?在linux驅(qū)動編程中,常用的解決并發(fā)與竟態(tài)的手段有信號量與互斥鎖,Completions 機(jī)制,自旋鎖(spin lock),以及一些其他的不使用鎖的實(shí)現(xiàn)方式。下面一一介紹。
信號量其實(shí)就是一個整型值,其核心是一個想進(jìn)入臨界區(qū)的進(jìn)程將在相關(guān)信號量上調(diào)用 P; 如果信號量的值大于零, 這個值遞減 1 并且進(jìn)程繼續(xù). 相反,,如果信號量的值是 0 ( 或更小 ), 進(jìn)程必須等待直到別人釋放信號量. 解鎖一個信號量通過調(diào)用 V 完成; 這個函數(shù)遞增信號量的值,,并且, 如果需要, 喚醒等待的進(jìn)程。而當(dāng)信號量的初始值為1的時候,就變成了互斥鎖。
信號量的典型使用形式:
//聲明信號量 struct semaphore sem; //初始化信號量 void sema_init(struct semaphore *sem, int val) //常用下面兩種形式 #define init_MUTEX(sem) sema_init(sem, 1) #define init_MUTEX_LOCKED(sem) sema_init(sem, 0) //以下是初始化信號量的快捷方式,最常用的 DECLARE_MUTEX(name) //初始化name的信號量為1 DECLARE_MUTEX_LOCKED(name) //初始化信號量為0 //常用操作 DECLARE_MUTEX(mount_sem); down(&mount_sem); //獲取信號量 ... critical section //臨界區(qū) ... up(&mount_sem); //釋放信號量
常用的down操作還有
// 類似down(),因?yàn)閐own()而進(jìn)入休眠的進(jìn)程不能被信號打斷,而因?yàn)閐own_interruptible()而進(jìn)入休眠的進(jìn)程能被信號打斷, // 信號也會導(dǎo)致該函數(shù)返回,此時返回值非0 int down_interruptible(struct semaphore *sem); // 嘗試獲得信號量sem,若立即獲得,它就獲得該信號量并返回0,否則,返回非0.它不會導(dǎo)致調(diào)用者睡眠,可在中斷上下文使用 int down_trylock(struct semaphore *sem);
完成量(completion)提供了一種比信號量更好的同步機(jī)制,它用于一個執(zhí)行單元等待另一個執(zhí)行單元執(zhí)行完某事。
// 定義完成量 struct completion my_completion; // 初始化completion init_completion(&my_completion); // 定義和初始化快捷方式: DECLEAR_COMPLETION(my_completion); // 等待一個completion被喚醒 void wait_for_completion(struct completion *c); // 喚醒完成量 void cmplete(struct completion *c); void cmplete_all(struct completion *c);
若一個進(jìn)程要訪問臨界資源,測試鎖空閑,則進(jìn)程獲得這個鎖并繼續(xù)執(zhí)行;若測試結(jié)果表明鎖扔被占用,進(jìn)程將在一個小的循環(huán)內(nèi)重復(fù)“測試并設(shè)置”操作,進(jìn)行所謂的“自旋”,等待自旋鎖持有者釋放這個鎖。自旋鎖與互斥鎖類似,但是互斥鎖不能用在可能睡眠的代碼中,而自旋鎖可以用在可睡眠的代碼中,典型的應(yīng)用是可以用在中斷處理函數(shù)中。自旋鎖的相關(guān)操作:
// 定義自旋鎖 spinlock_t spin; // 初始化自旋鎖 spin_lock_init(lock); // 獲得自旋鎖:若能立即獲得鎖,它獲得鎖并返回,否則,自旋,直到該鎖持有者釋放 spin_lock(lock); // 嘗試獲得自旋鎖:若能立即獲得鎖,它獲得并返回真,否則立即返回假,不再自旋 spin_trylock(lock); // 釋放自旋鎖: 與spin_lock(lock)和spin_trylock(lock)配對使用 spin_unlock(lock); 自旋鎖的使用: // 定義一個自旋鎖 spinlock_t lock; spin_lock_init(&lock); spin_lock(&lock); // 獲取自旋鎖,保護(hù)臨界區(qū) ... // 臨界區(qū) spin_unlock(); // 解鎖
自旋鎖持有期間內(nèi)核的搶占將被禁止。自旋鎖可以保證臨界區(qū)不受別的CPU和本CPU內(nèi)的搶占進(jìn)程打擾,但是得到鎖的代碼路徑在執(zhí)行臨界區(qū)的時候還可能受到中斷和底半部(BH)的影響。為防止這種影響,需要用到自旋鎖的衍生:
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_unlock_irqrestore() = spin_unlock() + local_irq_restore() spin_lock_bh() = spin_lock() + local_bh_disable() spin_unlock_bh() = spin_unlock() + local_bh_enable()
以上是linux驅(qū)動編程中經(jīng)常用到的鎖機(jī)制,下面講一些內(nèi)核中其他的一些實(shí)現(xiàn)。
有時, 你可以重新打造你的算法來完全避免加鎖的需要.。許多讀者/寫者情況 -- 如果只有一個寫者 -- 常常能夠在這個方式下工作.。如果寫者小心使數(shù)據(jù)結(jié)構(gòu),由讀者所見的,是一直一致的,,有可能創(chuàng)建一個不加鎖的數(shù)據(jù)結(jié)構(gòu)。在linux內(nèi)核中就有一個通用的無鎖的環(huán)形緩沖實(shí)現(xiàn),具體內(nèi)容參考
原子操作指的是在執(zhí)行過程中不會被別的代碼路徑所中斷的操作。原子變量與位操作都是原子操作。以下是其相關(guān)操作介紹。
// 設(shè)置原子變量的值 void atomic_set(atomic_t *v, int i); // 設(shè)置原子變量的值為i atomic_t v = ATOMIC_INIT(0); // 定義原子變量v,并初始化為0 // 獲取原子變量的值 atomic_read(atomic_t *v); // 返回原子變量的值 // 原子變量加/減 void atomic_add(int i, atomic_t *v); // 原子變量加i void atomic_sub(int i, atomic_t *v); // 原子變量減i // 原子變量自增/自減 void atomic_inc(atomic_t *v); // 原子變量增加1 void atomic_dec(atomic_t *v); // 原子變量減少1 // 操作并測試:對原子變量進(jìn)行自增、自減和減操作后(沒有加)測試其是否為0,為0則返回true,否則返回false int atomic_inc_and_test(atomic_t *v); int atomic_dec_and_test(atomic_t *v); int atomic_sub_and_test(int i, atomic_t *v); // 操作并返回: 對原子變量進(jìn)行加/減和自增/自減操作,并返回新的值 int atomic_add_return(int i, atomic_t *v); int atomic_sub_return(int i, atomic_t *v); int atomic_inc_return(atomic_t *v); int atomic_dec_return(atomic_t *v); 位原子操作: // 設(shè)置位 void set_bit(nr, void *addr); // 設(shè)置addr地址的第nr位,即將位寫1 // 清除位 void clear_bit(nr, void *addr); // 清除addr地址的第nr位,即將位寫0 // 改變位 void change_bit(nr, void *addr); // 對addr地址的第nr位取反 // 測試位 test_bit(nr, void *addr); // 返回addr地址的第nr位 // 測試并操作:等同于執(zhí)行test_bit(nr, void *addr)后再執(zhí)行xxx_bit(nr, void *addr) int test_and_set_bit(nr, void *addr); int test_and_clear_bit(nr, void *addr); int test_and_change_bit(nr, void *addr);
使用seqlock鎖,讀執(zhí)行單元不會被寫執(zhí)行單元阻塞,即讀執(zhí)行單元可以在寫執(zhí)行單元對被seqlock鎖保護(hù)的共享資源進(jìn)行寫操作時仍然可以繼續(xù)讀,而不必等待寫執(zhí)行單元完成寫操作,寫執(zhí)行單元也不需要等待所有讀執(zhí)行單元完成讀操作才去進(jìn)行寫操作。寫執(zhí)行單元之間仍是互斥的。若讀操作期間,發(fā)生了寫操作,必須重新讀取數(shù)據(jù)。seqlock鎖必須要求被保護(hù)的共享資源不含有指針。
// 獲得順序鎖 void write_seqlock(seqlock_t *sl); int write_tryseqlock(seqlock_t *sl); write_seqlock_irqsave(lock, flags) write_seqlock_irq(lock) write_seqlock_bh() // 釋放順序鎖 void write_sequnlock(seqlock_t *sl); write_sequnlock_irqrestore(lock, flags) write_sequnlock_irq(lock) write_sequnlock_bh() // 寫執(zhí)行單元使用順序鎖的模式如下: write_seqlock(&seqlock_a); ... // 寫操作代碼塊 write_sequnlock(&seqlock_a); 讀執(zhí)行單元操作: // 讀開始:返回順序鎖sl當(dāng)前順序號 unsigned read_seqbegin(const seqlock_t *sl); read_seqbegin_irqsave(lock, flags) // 重讀:讀執(zhí)行單元在訪問完被順序鎖sl保護(hù)的共享資源后需要調(diào)用該函數(shù)來檢查,在讀訪問期間是否有寫操作。若有寫操作,重讀 int read_seqretry(const seqlock_t *sl, unsigned iv); read_seqretry_irqrestore(lock, iv, flags) // 讀執(zhí)行單元使用順序鎖的模式如下: do{ seqnum = read_seqbegin(&seqlock_a); // 讀操作代碼塊 ... }while(read_seqretry(&seqlock_a, seqnum));
讀取-拷貝-更新(RCU) 是一個高級的互斥方法,在合適的時候可以取得非常高的效率。RCU可以看作讀寫鎖的高性能版本,相比讀寫鎖,RCU的優(yōu)點(diǎn)在于既允許多個讀執(zhí)行單元同時訪問被保護(hù)的數(shù)據(jù),又允許多個讀執(zhí)行單元和多個寫執(zhí)行單元同時訪問被保護(hù)的數(shù)據(jù)。但是RCU不能替代讀寫鎖,因?yàn)槿绻麑懕容^多時,對讀執(zhí)行單元的性能提高不能彌補(bǔ)寫執(zhí)行單元導(dǎo)致的損失。由于平時應(yīng)用較少,所以不做多說。
以上是“l(fā)inux驅(qū)動中并發(fā)與竟態(tài)的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!