《Linux Device Drivers》第五章 併發和競態——note

  • 併發及其管理
    • 競態通常作爲對資源的共享訪問結果而產生
    • 當兩個執行線程需要訪問相同的數據結構(或硬件資源)時,併發的可能性就永遠存在
    • 只要可能就應該避免資源的共享,但共享通常是必須的,硬件本質上就是共享的
    • 訪問管理的常見技術稱爲“鎖定”或者“互斥”
  • 信號量和互斥體
    • 建立臨界區:在任意給定的時刻,代碼只能被一個線程執行
    • 可以使用一種鎖定機制,當進程在等待對臨界區的訪問時,此機制可讓進程進入休眠狀態
    • 一個信號量本質上是一個整數值,它和一對函數聯合使用,這一對函數通常稱爲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);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章