- 併發及其管理
- 競態通常作爲對資源的共享訪問結果而產生
- 當兩個執行線程需要訪問相同的數據結構(或硬件資源)時,併發的可能性就永遠存在
- 只要可能就應該避免資源的共享,但共享通常是必須的,硬件本質上就是共享的
- 訪問管理的常見技術稱爲“鎖定”或者“互斥”
- 信號量和互斥體
- 建立臨界區:在任意給定的時刻,代碼只能被一個線程執行
- 可以使用一種鎖定機制,當進程在等待對臨界區的訪問時,此機制可讓進程進入休眠狀態
- 一個信號量本質上是一個整數值,它和一對函數聯合使用,這一對函數通常稱爲P和V
- 當信號量用於互斥時,信號量的值應初始化爲1,這種信號量有時也稱爲“互斥體(mutex)”
- Linux信號量的實現
- <asm/semaphore.h>
- struct semaphore;
- void sema_init(struct semaphore *sem, int val);
- DECLARE_MUTEX(name);
- 稱爲name的信號量變量被初始化爲1
- DECLARE_MUTEX_LOCKED(name);
- 稱爲name的信號量變量被初始化爲0
- void init_MUTEX(struct semaphore *sem);
- void init_MUTEX_LOCKED(struct semaphore *sem);
- void down(struct semaphore *sem);
- 一直等待
- int down_interruptible(struct semaphore *sem);
- 操作是可中斷的
- 如果操作被中斷,該函數會返回非零值
- 通常使用的是可中斷的down版本
- int down_trylock(struct semaphore *sem);
- 永遠不會休眠
- 如果信號量在調用時不可獲得,會立即返回一個非零值
- void up(struct semaphore *sem);
- 讀取者/寫入者信號量
- 一些任務只需要讀取受保護的數據結構,而其他的則必須做出修改
- <linux/rwsem.h>
- struct rw_semaphore;
- void init_rwsem(struct rw_semaphore *sem);
- void down_read(struct rw_semaphore *sem);
- 只讀訪問,可和其他讀取者併發地訪問
- int down_read_trylock(struct rw_semaphore *sem);
- 在授予訪問時返回非零,其他情況下返回零
- void up_read(struct rw_semaphore *sem);
- void down_write(struct rw_semaphore *sem);
- int down_write_trylock(struct rw_semaphore *sem);
- void up_write(struct rw_semaphore *sem);
- void downgrade_write(struct rw_semaphore *sem);
- 最好在很少需要寫訪問且寫入者只會短期擁有信號量的時候使用rwsem
- completion
- 內核編程中常見的一種模式是,在當前線程之外初始化某個活動,然後等待該活動的結束
- <linux/completion.h>
- DECLARE_COMPLETION(my_completion);
- init_completion(struct completion *c);
- void wait_for_completion(struct completion *c);
- 非中斷的等待
- void complete(struct completion *c);
- void complete_all(struct completion *c);
- 一個completion通常是一個單次(one-shot)設備
- 如果沒有使用complete_all,可以重複使用一個complete結構
- 如果使用了complete_all,則必須在重複使用該結構之前重新初始化它
- INIT_COMPLETE(struct completion c);
- void complete_and_exit(struct completion *c, long retval);
- 自旋鎖
- 自旋鎖可在不能休眠的代碼中使用,比如中斷處理例程
- 可提供比信號量更高的性能
- 一個自旋鎖是一個互斥設備,它只能有兩個值:鎖定和解鎖
- 通常實現爲某個整數值中的單個位
- 如果鎖可用,則“鎖定”位被設置,而代碼繼續進入臨界區
- 如果鎖被其他人獲得,則代碼進入忙循環並重複檢查這個鎖,直到該鎖可用爲止,這個循環就是自旋鎖的“自旋”部分
- “測試並設置”的操作必須以原子方式完成
- 在超線程處理器上,還必須仔細處理以避免死鎖,超線程處理器可實現多個虛擬的CPU,它們共享單個處理器核心及緩存
- 自旋鎖API介紹
- <linux/spinlock.h>
- spinlock_t
- spinlock_t my_lock = SPIN_LOCK_UNLOCKED;
- void spin_lock_init(spinlock_t *lock);
- void spin_lock(spinlock_t *lock);
- 不可中斷
- 在獲得鎖之前一直處於自旋狀態
- void spin_unlock(spinlock_t *lock);
- 自旋鎖和原子上下文
- 任何擁有自旋鎖的代碼都必須是原子的,不能休眠,不能因爲任何原因放棄處理器,除了服務中斷之外
- 自旋鎖函數
- void spin_lock(spinlock_t *lock);
- void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
- 在獲得自旋鎖之前禁止中斷,而先前的中斷狀態保存在flags中
- void spin_lock_irq(spinlock_t *lock);
- void spin_lock_bh(spinlock_t *lock);
- 在獲得鎖之前禁止軟件中斷
- void spin_unlock(spinlock_t *lock);
- void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
- flags參數必須是傳遞給spin_lock_irqsave的同一個變量
- void spin_unlock_irq(spinlock_t *lock);
- void spin_unlock_bh(spinlock_t *lock);
- int spin_trylock(spinlock_t *lock);
- 成功時返回非零值,否則返回零
- int spin_trylock_bh(spinlock_t *lock);
- 成功時返回非零值,否則返回零
- 讀取值/寫入者自旋鎖
- <linux/spinlock.h>
- rwlock_t
- rwlock_t my_rwlock = RW_LOCK_UNLOCKED;
- void rwlock_init(rwlock_t * lock);
- 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);
- void read_unlock(rwlock_t *lock);
- void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
- void read_unlock_irq(rwlock_t *lock);
- void read_unlock_bh(rwlock_t *lock);
- 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);
- int write_trylock(rwlock_t *lock);
- 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);
- 讀取者/寫入者鎖可能造成讀取者飢餓
- 鎖陷阱
- 不明確的規則
- 不論是信號量還是自旋鎖,都不允許鎖擁有者第二次獲得這個相同的鎖,如果試圖這樣做,系統將掛起(例如:如果某個獲得鎖的函數,調用其他試圖獲取相同鎖的函數,我們的代碼就會死鎖)
- 鎖的順序規則
- 在必須獲取多個鎖時,應該始終以相同的順序獲得
- 如果必須獲得一個局部鎖以及一個屬於內核更中心位置的鎖,則應該首先獲取自己的局部鎖
- 如果擁有信號量和自旋鎖的組合,則必須首先獲得信號量
- 細粒度鎖和粗粒度鎖的對比
- 細粒度鎖具有良好的伸縮性
- 細粒度鎖將帶來某種程序的複雜性
- 應該在最初使用粗粒度的鎖
- 使用lockmeter工具可度量內核花費在鎖上的時間
- http://oss.sgi.com/projects/lockmeter/
- 不明確的規則
- 除了鎖之外的方法
- 免鎖算法
- 經常用於免鎖的生產者/消費者任務的數據結構之一是循環緩衝區
- 原子變量
- <asm/atomic.h>
- atomic_t
- 一個atomic_t變量保存一個int值,但不能記錄大於24位的整數
- void atomic_set(atomic_t *v, int i);
- atomic_t v = ATOMIC_INIT(0);
- int atomic_read(atomic_t *v);
- void atomic_add(int i, atomic_t *v);
- void atomic_sub(int i, atomic_t *v);
- void atomic_inc(atomic_t *v);
- void atomic_dec(atomic_t *v);
- 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);
- int atomic_add_negative(int i, atomic_t *v);
- 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);
- 需要多個atomic_t變量的操作,仍然需要某種類型的鎖
- 位操作
- <asm/bitops.h>
- nr參數通常被定義爲int,但在少數架構上被定義爲unsigned long
- void set_bit(nr, void *addr);
- void clear_bit(nr, void *addr);
- void change_bit(nr, void *addr);
- test_bit(nr, void *addr);
- int test_and_set_bit(nr, void *addr);
- int test_add_clear_bit(nr, void *addr);
- int test_and_change_bit(nr, void *addr);
- seqlock
- 允許讀取者對資源的自由訪問,但需要讀取者檢查是否和寫入者發生衝突
- <linux/seqlock.h>
- seqlock_t
- seqlock_t lock1 = SEQLOCK_UNLOCKED;
- void seqlock_init(seqlock_t *lock);
- unsigned int read_seqbegin(seqlock_t *lock);
- int read_seqretry(seqlock_t *lock, unsigned int seq);
- unsigned int read_seqbegin_irqsave(seqlock_t *lock, unsigned long flags);
- int read_seqretry_irqrestore(seqlock_t *lock, unsigned int seq, unsigned long flags);
- void write_seqlock(seqlock_t *lock);
- void write_sequnlock(seqlock_t *lock);
- void write_seqlock_irqsave(seqlock_t *lock, unsigned long flags);
- void write_seqlock_irq(seqlock_t *lock);
- void write_seqlock_bh(seqlock_t *lock);
- void write_sequnlock_irqrestore(seqlock_t *lock, unsigned long flags);
- void write_sequnlock_irq(seqlock_t *lock);
- void write_sequnlock_bh(seqlock_t *lock);
- 讀取-複製-更新
- read-copy-update(RCU)也是一種高級的互斥機制
- 很少在驅動程序中使用
- http://www.rdrop.com/users/paulmck/rclock/intro/rclock_intro.html
- 針對經常發生讀取而很少寫入的情形做了優化
- 被保護的資源應該通過指針訪問
- 在需要修改該數據結構時,寫入線程首先複製,然後修改副本,之後用新的版本替代相關指針。當確信老的版本沒有其他引用時,就可釋放老的版本
- <linux/rcupdate.h>
- rcu_read_lock
- rcu_read_unlock
- void call_rcu(struct rcu_head *head, void (*func)(void *arg), void *arg);
- 免鎖算法