競合と同期(1)

5828 ワード

カーネルで処理される競合状態は、主に、信号量(反発量)、スピンロック、読み書き信号量、読み書きスピンロック、待機キュー、完了量によって処理される.
  • 信号量(反発量)
  • // ,val 
    void sema_init(struct semaphore *sem, int val);
    // , 1, 
    void down (&sem);
    // down , 
    int down_interruptible(&sem);
    // , , 0
    int down_trylock(&sem);
    // 
    void up(&sem);
    

    上記の取得関数では、down_interruptibleとdown_trylockにはいずれも戻り値があり、0に戻ると取得信号量が成功したことを示し、0でないと取得されなかったことを示す.対down_interruptibleはスリープ待ちで中断されましたdown_trylockは、信号量がすべて占有されていることを示す.この2つのdownを使用するには、必ず戻り値を常にチェックします.反発量は信号量を1に初期化する場合であり、最もよく使われる場合でもある.
    DECLARE_MUTEX(name);
    DECLARE_MUTEX_LOCKED(name)
    // 
    void init_MUTEX(struct semaphore *sem);
    void init_MUTEX_LOCKED(struct semaphore *sem);
    

    example:
    // 
    static DECLARE_MUTEX(mutex);
    static xxx_open(struct inode *inode, struct file *filp)
    {
        ......
        if(0 != down_trylock(&mutex)) {
            // 
            return -EBUSY;
        }
        ......
        // , 
        return 0;
    }
    static xxx_release(struct inode *inode, struct file *filp)
    {
        //close 
        up(&mutex);
        return 0;
    }
    

    ***デバイスをシャットダウンするときは必ずup!!***
  • スピンロックスピンロックも反発装置であり、スレッドがロックを取得した後、あるリソースを使用し、使用後にロックを解放することができる.解放されていない場合、別のスレッドがロックを申請すると、スピン、すなわち、待機中にロックがテストされ、使用可能になるとロックされます.SMPおよびpreemptiveのシングルプロセッサは、non-preemptiveのシングルプロセッサのスピンロックについては何もする必要はありません.スレッド実行中にプリエンプトされることはありません.あるスレッドがスピンに陥ると、待機しているロックを解放する他のスレッドはありません.

  • スピンロックの実装の説明:
    do {
        preempt_disable(); __acquire(lock); (void)(lock);
    }while(0)
    

    プリエンプトをオフにする->ロックを要求する->ロック(セット)
    // 
    spinlock_t  lock = SPIN_LOCK_UNLOCKED;
    // 
    void spinlock_init(spinlock_t *lock);
    // 
    void spin_lock(spinlock_t *lock);
    // 、 、 
    void spin_lock_irqsave(&lock, unsigned long flag);
    // 、 
    void spin_lock_irq(&lock);
    // 、 、 
    void spin_lock_bh(&lock)
    //---------------------------------------------------
    // 
    void spin_unlock(spinlock_t *lock);
    // lock unlock
    void spin_unlock_irqrestore(&lock, unsigned long falg);
    void spin_unlock_irq(&lock);
    void spin_unlock_bh(&iock);
    //-------------------------------------------------
    //try  , 0
    int spin_trylock(&lock);
    int spin_trylock_bh(&lock);
    

    スピンロックの取得中のコードセグメントはスリープ禁止です.ここでは、copy_を含むスリープを引き起こす可能性のある関数の使用を禁止することに注意してください.to_user, copy_from_user,kmalloc等
  • 読み書き信号量の基本的なメカニズムは、複数の読取者が信号量を取得することを許可するが、1人の書き込み者のみを許可し、1人の書き込み者がロックを取得すると、他の読取/書き込み者が信号量を取得することを禁止し、すなわち完全にロックされることである.
  • void init_rwsem(struct rw_semaphore *sem);
    // 
    void down_read(&sem);
    int dow_read_trylock(&sem);
    void up_read(&sem);
    // 
    void down_write(&sem);
    int down_write_trylock(&sem);
    void up_write(&sem);
    // 
    void downgrade_write(&sem);
    

    downgrade_writeの意味:読み書き信号量における書き込み者の優先度が読み取り者より高く、読み取り者を排斥することで、書き込みが頻繁な場合に、読み取り者が長時間ロックを取得できない可能性がある.したがって、ある書き込み者がロックを解除した後、downgrade_を呼び出すことができます.writeは、リソースを保護して読み取り専用になるまで、書き込みをしばらく禁止できます.
  • 読み書きスピンロック使用はスピンロックAPIと類似しており、以下の通りである:
  • void rwlock_init(rwlock_t *lock);
    void read_lock(&lock);
    void read_lock_irqsave(&lock);
    void read_lock_irq(&lock);
    void read_lock_bh(&lock);
    // 
    // try , try 
    int lock_write_rrylock(&lock);
    

    スピンロックは読み取り者が飢えていることをあまり気にしていないようだ.
  • 完了量&待機キュー信号量とスピンロックは、同時時のリソース保護を処理するために使用され、待機キューと完了量はカーネルスレッド間の同期に使用されます.完了量は同期をうまく実現でき、インタフェースも簡潔です:
  • // 
    DECLARE_COMPLETION(cmp);
    // , 
    void init_completion(struct completion *cmp);
    // , , 
    void wait_for_completion(&cmp);
    // 
    // 
    void complete(&cmp);
    // 
    void complete_all(&cmp);
    

    待機キューはさらに古く、完了量はそれに基づいてカプセル化されており、待機キューでスリープしているスレッドを呼び覚ますには2つの条件が必要です.1つはconditionが真で、もう1つは対応するwake_です.upが呼び出されます.基本原理はスレッド呼び出しwait_even_xxx後、スレッドはTASK_INTERUPTIBLE(またはUNITERRUPTIBLE)は、スケジューリングされます.呼び出しwake_upは、これらのスレッドを準備キューに戻します.スレッドが再スケジューリングされた後,まずconditionが真であるか否かを判断し,そうでなければ待機を繰り返す.
    #define wait_event(wq, condition)                   \  
    do {                                    \  
       if (condition)                          \  
           break;                          \  
       __wait_event(wq, condition);                    \  
    } while (0) 
    //-------------------------------------------------------------------------
    #define __wait_event(wq, condition)                     \  
    do {                                    \  
       DEFINE_WAIT(__wait);                        \  
                                       \  
       for (;;) {                          \  
           prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);    \  
           if (condition)                      \  
               break;                      \  
           schedule();                     \  
       }                               \  
       finish_wait(&wq, &__wait);                  \  
    } while (0) 
    

    次の手順に従います.
    // 
     DECLARE_WAIT_QUEUE_HEAD(queue_head);  
    init_waitqueue_head(wait_queue_head_t *queue_head);
    // 
    wait_event(queue_head, condition)
    wait_event_interruptible(queue_head, condition);
    wait_event_timeout(queue_head, condition, timeout);
    wait_event_interruptible_timeout(queue_head, condition, timeout);
    // 
    void wake_up(&queue_head);
    void wake_up_interruptible(&queue_head);
    

    もう1つの方法は、待機キューに直接タスクを追加し、プログラムのスケジューリングを制御し、実際には手動でwait_を制御することです.eventの過程は、明らかに面倒だった.
    /* */
    // 
    DECLARE_WAITQUEUE(queue, tsk);
    //  / 
     void fastcall add_wait_queue(&queue_head, &queue);
     void fastcall remove_wait_queue(&queue_head, &queue);
    /* */
    DECLEARE_WAITQUEUE(queue,current); // current 
    add_wait_queue(&queue_head, &queue);
    while(!conditon)
    {
    set_current_state(TASK_INTERRUPTIBLE);
    if(signal_pending(currrent))
    /* */
    schedule();
    }
    set_current_state(TASK_RUNNING);
    remove_wait_queue(q,&wait);
    

    参照wait_event_interruptibleの使用方法