「Linuxカーネルの設計と実装」リーディングノート-カーネル同期方法

原子操作

アトミック操作は、他の同期方法の基礎です。

カーネルは、整数操作個別ビット操作用の2セットのアトミック操作インターフェイスを提供します

整数に対するアトミック操作は atomic_tタイプ(include \ linux \ types.h)のデータのみを処理できます。

typedef struct {
    volatile int counter;
} atomic_t;

64ビットバージョンもありますが、ほぼ同じです。

ビットに対するアトミック操作のための特別なデータタイプはありません。これは、メモリアドレスポインタとビット番号の2つのパラメータを受け入れます。

ビットアトミック操作機能:

非アトミックビット操作関数もあり、その関数名は上記の表の関数名の前に__追加されています。

整数アトミック操作関数:

 

スピンロック

Linuxカーネルで最も一般的なロックは、スピンロックです。

競合するスピンロックにより、ロックが再び使用可能になるのを待っている間に、スレッドがスピンを要求しているため、プロセッサ時間が無駄になります。

これにより、短期間の軽量な状況でスピンを最大限に活用できます。

スピンロックの実現は、システムと密接に関係しています。一例:

static DEFINE_SPINLOCK(i8259_irq_lock);
inline void
i8259a_enable_irq(unsigned int irq)
{
    spin_lock(&i8259_irq_lock);
    i8259_update_irq_hw(irq, cached_irq_mask &= ~(1 << irq));
    spin_unlock(&i8259_irq_lock);
}

シングルプロセッサマシンでは、コンパイル時にスピンロックは追加されません。カーネルプリエンプションメカニズムが設定されている場合にのみ、スピンコンパイルオプションが考慮されます。

スピンロックは再帰的ではありません。スピンロックを保持した後は、スピンロックを待つことはできません。そうしないと、デッドロックが発生します。

スピンロックは割り込みハンドラーで使用できますが、ロックを取得する前にローカル割り込みを無効にする必要があります。無効にしないと、割り込みハンドラーがロックを保持しているカーネルコードに割り込んで、保持されている競合を表示する可能性があります。いくつかのスピンロック。

カーネルは、ロックを要求している間、割り込みを無効にする機能を提供します。

static long
iommu_arena_alloc(struct device *dev, struct pci_iommu_arena *arena, long n,
          unsigned int align)
{
    unsigned long flags;
    unsigned long *ptes;
    long i, p, mask;
    spin_lock_irqsave(&arena->lock, flags);
    /* 中间略 */
    spin_unlock_irqrestore(&arena->lock, flags);
    return p;
}

下部がプロセスコンテキストとデータを共有する場合、プロセスコンテキストによって中断された共有データを保護する必要があるため、ロック中の下部の実行を禁止する必要があります。

割り込みハンドラーと下半分がデータを共有する場合、割り込みを禁止しながら適切なロックを取得する必要があります。

ロックの目的は、読み取りと書き込みの2つのシナリオに明確に分けることができる場合があります。テストでは、読み取り/書き込みスピンロックを使用できます。場合によってはスピンロックの最適化です。

スピンロックを保持している間はスリープできません。

スピンロック操作機能:

 

信号

ロック時間が長くなく、コードがスリープしない場合は、スピンロックを使用するのが最善の選択です。ロック時間が長い場合や、コードがロックを保持しているときにコードがスリープ状態になる場合は、セマフォを使用してロックを完了するのが最適です。仕事。

セマフォは一種のスリープロックです。

セマフォはスピンロックよりもオーバーヘッドが大きくなります。

セマフォを持ったまま寝ることができます。

セマフォを占有している間、前者は眠ることができますが、後者は眠ることができないため、スピンロックを占有することはできません。

セマフォを保持するコードはプリエンプトできます。

セマフォは、同時に任意の数のロックホルダーを許可できます。最大で1つある場合は相互排他セマフォと呼ばれ、複数ある場合はカウントセマフォと呼ばれます。

通常、相互に排他的なセマフォを使用します。

Semaphoreには2つのアトミック操作(include \ linux \ semaphore.h)があります。

extern void down(struct semaphore *sem);
extern void up(struct semaphore *sem);

down()操作は、セマフォカウントから1を引くことにより、セマフォを要求します。結果が0または0より大きい場合、セマフォロックが取得されます。

up()操作は、セマフォを解放するために使用されます。

セマフォの初期化:

static inline void sema_init(struct semaphore *sem, int val)
{
    static struct lock_class_key __key;
    *sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
    lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}

と同様

#define init_MUTEX(sem)     sema_init(sem, 1)

セマフォは通常、大規模なデータ構造の一部として動的に作成されます。

セマフォの使用例:

int dma_free_channel(DMA_Handle_t handle    /* DMA handle. */
    ) {
    int rc = 0;
    DMA_Channel_t *channel;
    DMA_DeviceAttribute_t *devAttr;
    if (down_interruptible(&gDMA.lock) < 0) {
        return -ERESTARTSYS;
    }
         // ...
out:
    up(&gDMA.lock);
    wake_up_interruptible(&gDMA.freeChannelQ);
    return rc;
}

読み取りおよび書き込みセマフォもあります。

セマフォ操作機能:

 

ミューテックス(ミューテックス)

Mutexは、セマフォよりも単純なスリープロックです。

これは、セマフォの簡略版です。

静的初期化(include \ linux \ mutex.h):

#define __MUTEX_INITIALIZER(lockname) \
        { .count = ATOMIC_INIT(1) \
        , .wait_lock = __SPIN_LOCK_UNLOCKED(lockname.wait_lock) \
        , .wait_list = LIST_HEAD_INIT(lockname.wait_list) \
        __DEBUG_MUTEX_INITIALIZER(lockname) \
        __DEP_MAP_MUTEX_INITIALIZER(lockname) }
#define DEFINE_MUTEX(mutexname) \
    struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)

動的初期化:

# define mutex_init(mutex) \
do {                            \
    static struct lock_class_key __key;     \
                            \
    __mutex_init((mutex), #mutex, &__key);      \
} while (0)

ミューテックスのロックとロック解除:

struct clk *clk_get_sys(const char *dev_id, const char *con_id)
{
    struct clk *clk;
    mutex_lock(&clocks_mutex);
    clk = clk_find(dev_id, con_id);
    if (clk && !__clk_get(clk))
        clk = NULL;
    mutex_unlock(&clocks_mutex);
    return clk ? clk : ERR_PTR(-ENOENT);
}

一度にミューテックスを保持できるタスクは1つだけです。

ミューテックスのロックは、ロックを解除する責任があります。

再帰的にロックおよびロック解除することはできません。

ミューテックスを保持している場合、プロセスは終了できません。

ミューテックスは、割り込みまたは下半分では使用できません。

Mutexは、公式APIを介してのみ管理できます。

セマフォと比較して、ミューテックスを優先的に使用する必要があります。

ミューテックス操作機能:

スピンロックとミューテックスの比較:

 

完了変数

カーネル内のタスクは、イベントが発生したことを別のタスクに通知するために信号を送信する必要があり、完了変数を使用できます。

完了変数には完了があります(include \ linux \ complete.h):

struct completion {
    unsigned int done;
    wait_queue_head_t wait;
};

初期使用:

#define DECLARE_COMPLETION(work) \
    struct completion work = COMPLETION_INITIALIZER(work)

完全な可変操作機能:

 

シーケンシャルロック(seqロック)

シーケンスロックは、共有データを読み書きするための簡単なメカニズムを提供します。

このロックはシーケンスカウンターに依存しています。

共有データが書き込まれると、ロックが取得され、シーケンス値が増加します。データの読み取りの前後で、シーケンス値が読み取られます。読み取りシーケンス番号の値が同じである場合は、読み取り操作が進行中であることを意味します。書き込み操作によって中断されていません。

seqロックの構造表現(include \ linux \ seqlock.h):

typedef struct {
    unsigned sequence;
    spinlock_t lock;
} seqlock_t;

seqロックを定義します。

__cacheline_aligned_in_smp DEFINE_SEQLOCK(xtime_lock);
#define DEFINE_SEQLOCK(x) \
        seqlock_t x = __SEQLOCK_UNLOCKED(x)

書き込みロックの使用:

write_seqlock(&xtime_lock);
do_something();
write_sequnlock(&xtime_lock);

読み取りロックの使用:

u64 get_jiffies_64(void)
{
    unsigned long seq;
    u64 ret;
    do {
        seq = read_seqbegin(&xtime_lock);
        ret = jiffies_64;
    } while (read_seqretry(&xtime_lock, seq));
    return ret;
}

seqロックの選択:

  • あなたのデータの多くの読者がいます。
  • データライターはほとんどいません。
  • 書くことはほとんどありませんが、読む前に書きたいと考えており、読者が作家を飢えさせないようにします。
  • データは単純ですが、原子量は使用できません。

上記のジフィーは一例です。

 

秩序と障壁

プログラムコードは、指定された順序で読み取りおよび書き込み命令を発行する必要がありますが、効率を向上させるために、コンパイラとプロセッサは読み取りおよび書き込み命令のタイプを並べ替えて、例外を引き起こす場合があります。

命令を使用して順序を確認するために、そのような命令はバリアと呼ばれます。

rmb()はメモリバリアを読み取ります。

wmb()書き込みメモリバリア。

読み取り/書き込みバリアも提供するmb()もあります。

x()ローディング動作を呼び出した後に再配置されていない前のMB、同様にxが操作前の呼び出しに再配置されません後にMB()をロードしました。

バリア方式:

 

カーネルプリエンプション関連の機能

共有データが各プロセッサに固有の場合、ロックは必要ない場合があります。

preempt_disable()を使用して、カーネルプリエンプションを無効にすることができます。

 

おすすめ

転載: blog.csdn.net/jiangwei0512/article/details/106148682