Zephyr入門(排他制御:その他)


その他の排他制御

前回はmutexの実装を読みました。
mutexはユーザーアプリからシステムコールとして利用可能です。
Zephyrにはmutex以外にもには排他制御の機能があります。
これらはドライバから使用可能です。

割込みの制御

クリティカルセクション実行中で割り込まれたくない場合などに割込みの制御を行います。
これまで同様にUP (Uni-Processor)向けを対象とし、アーキテクチャはArm Cortex M向けコードを見ることにします。

irq_lock ( )

その時の割込み状態を退避し、割込みを禁止するirq_lock ( )について見てみます。

include/irq.h
#define irq_lock() _arch_irq_lock()

上記_arch_irq_lock ( )は以下の通りです。

include/arch/arm/cortex_m/asm_inline_gcc.h
static ALWAYS_INLINE unsigned int _arch_irq_lock(void)
{
1        unsigned int key;

#if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE)
2        __asm__ volatile("mrs %0, PRIMASK;"
3                "cpsid i"
4                : "=r" (key)
5                :
6                : "memory");
#elif defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE)
7        unsigned int tmp;

8        __asm__ volatile(
9                "mov %1, %2;"
10                "mrs %0, BASEPRI;"
11                "msr BASEPRI, %1"
12                : "=r"(key), "=r"(tmp)
13                : "i"(_EXC_IRQ_DEFAULT_PRIO)
14                : "memory");
#else
#error Unknown ARM architecture
#endif /* CONFIG_ARMV6_M_ARMV8_M_BASELINE */

15        return key;
}

Armv6の場合はmrs命令でその時点のcpsrの値を保持した後に3行目のcpsid命令で割込みを禁止しています。
Armv7、Armv8の場合はmrs命令でBASEPRIレジスタの値をkeyに退避した後、msr命令を用いて禁止しています。このとき、下記に示しますが、_EXC_IRQ_DEFAULT_PRIOを用いてコンフィグで設定した優先度以下 (0~2の範囲)の割込みを禁止にします。

include/arch/arm/cortex_m/exc.h
#define _EXC_IRQ_DEFAULT_PRIO _EXC_PRIO(_IRQ_PRIO_OFFSET)
#define _EXC_PRIO(pri) (((pri) << (8 - CONFIG_NUM_IRQ_PRIO_BITS)) & 0xff)

#define _IRQ_PRIO_OFFSET \
        (_ZERO_LATENCY_IRQS_RESERVED_PRIO + _EXCEPTION_RESERVED_PRIO)

#ifdef CONFIG_ZERO_LATENCY_IRQS
#define _ZERO_LATENCY_IRQS_RESERVED_PRIO 1
#else
#define _ZERO_LATENCY_IRQS_RESERVED_PRIO 0
#endif

#if defined(CONFIG_CPU_CORTEX_M_HAS_PROGRAMMABLE_FAULT_PRIOS) || \
        defined(CONFIG_CPU_CORTEX_M_HAS_BASEPRI)
#define _EXCEPTION_RESERVED_PRIO 1
#else
#define _EXCEPTION_RESERVED_PRIO 0
#endif

irq_unlock ( )

irq_unlock ( )は、引数で渡した値で割込み禁止/許可の状態を復元します。

include/irq.h
#define irq_unlock(key) _arch_irq_unlock(key)

上記_arch_irq_unlock (key)は以下の通りです。

static ALWAYS_INLINE void _arch_irq_unlock(unsigned int key)
{
#if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE)
1        if (key) {
2                return;
3        }
4        __asm__ volatile("cpsie i" : : : "memory");
#elif defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE)
5        __asm__ volatile("msr BASEPRI, %0" :  : "r"(key) : "memory");
#else
6#error Unknown ARM architecture
#endif /* CONFIG_ARMV6_M_ARMV8_M_BASELINE */
}

処理がカーネルコンフィグで分かれていますが、それぞれ以下の通りです。
Armv6-Mプロセッサ、またはArmv8-Mプロセッサ(Baselineサポート)の場合、keyに値があれば即時復帰、値がなければcpsieで割込み許可状態にします。
「keyに値がある = 対になるirq_lock ( )した時点ですでに割込み禁止状態
 keyに値がない = 対になるirq_lock ( )した時点では割込み許可状態」
ということです。

一方、Armv7-Mプロセッサ、またはARMv8-Mプロセッサ(Main Extentionサポート)の場合、keyの値をBASEPRIレジスタに設定し、割込み状態を復元します。

割込み制御の例

irq_lock ( )、irq_unlock ( )を用いて割込みを制御する例を下図に示します。
割込み発生時、割込みハンドラ、他スレッドともに実行不可です。

スケジューラの制御

割込みの制御と同様にスケジューリングの禁止、許可を制御します。
preemptibleスレッドが他の高優先度スレッドにプリエンプションされたくない場合にこのAPIを使用します。(ただし、割込みハンドラは実行されます)

k_sched_lock ( )

kernel/sched.c
void k_sched_lock(void)
{
        _sched_lock();
}

実体は以下です。

kernel/include/ksched.h
static inline void _sched_lock(void)
{
#ifdef CONFIG_PREEMPT_ENABLED
1        __ASSERT(!_is_in_isr(), "");
2        __ASSERT(_current->base.sched_locked != 1, "");

3        --_current->base.sched_locked;

4        compiler_barrier();

5        K_DEBUG("scheduler locked (%p:%d)\n",
                _current, _current->base.sched_locked);
#endif
}

2行目:_currentスレッドのbase.sched_lockが1でない場合、警告メッセージを出力します。
3行目:_currentスレッドのbase.sched_lockをデクリメントします。

2行目から判る通り、
スケジューリングが
アンロック状態:_current->base.sched_lockedが1
ロック状態:_current->base.sched_lockedが0
となります。

k_sched_unlock ( )

kernel/sched.c
void k_sched_unlock(void)
{
#ifdef CONFIG_PREEMPT_ENABLED
1        __ASSERT(_current->base.sched_locked != 0, "");
2        __ASSERT(!_is_in_isr(), "");

3        int key = irq_lock();

4        /* compiler_barrier() not needed, comes from irq_lock() */

5        ++_current->base.sched_locked;

6        K_DEBUG("scheduler unlocked (%p:%d)\n",
                _current, _current->base.sched_locked);

7        _reschedule_threads(key);
#endif
}

3行目:割込みを禁止します。
5行目:_currentスレッドのbase.sched_lockをインクリメントしてアンロック状態にします。
7行目:スケジューリングして実行すべきスレッドをディスパッチします。

スケジューラ制御の例

sched_lock ( )、sched_unlock ( )を用いてスケジューラを制御する例を下図に示します。割込み発生時、割込みハンドラは実行可能です。

内容は薄かったかもしれませんが、Zephyr利用時には知っておく必要があるので。

それでは、また。

前回:Zephyr入門(排他制御:mutex編)
次回:

『各種製品名は、各社の製品名称、商標または登録商標です。本記事に記載されているシステム名、製品名には、必ずしも商標表示((R)、TM)を付記していません。』