タスク通知(タスクセマフォ)の設計思想
μC/OS-IIIのアーキテクチャでは、独立したカーネルオブジェクトとして管理する標準セマフォとは別に、各タスクに内在する通知用のカウンターフィールドが備えられています。これは一般的に「タスク通知」または「タスク固有セマフォ」と呼ばれ、タスク制御ブロック(TCB)内に32bit整数として保持されます。
このメカニズムを採用する主目的は、従来のIPC(Inter-Process Communication)オブジェクトと比較して、割り込みからのイベント返信レイテンシを大幅に低減し、RTOS自体が消費するRAM容量を最適化することです。一方で設計上の制約として、特定の宛先タスクのみを対象とする単一配信仕様となっており、複数待機タスクへの同時ブロードキャスト機能は搭載されていません。したがって、一対一のタスク同期やステートマシンの状態遷起トガーとして利用することが推奨されます。
主要APIの実装挙動解析
通知送信: OSTaskSemPost()
この関数は任意のタスクコンテキストやISRから呼び出し可能ですが、内部処理では臨界段保護下でターゲットTCBの待機状態を判定します。もし対象タスクが当該通知に対して
/* タスク通知の発行を処理する内部関数群 */
OS_SEM_CTR OSTaskSemPost (OS_TCB *p_target, OS_OPT opt, OS_ERR *p_err)
{
OS_SEM_CTR ctr;
CPU_TS timestamp;
#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return ((OS_SEM_CTR)0);
}
#endif
#if OS_CFG_ARG_CHK_EN > 0u
switch (opt) {
case OS_OPT_POST_NONE:
case OS_OPT_POST_NO_SCHED: break;
default:
*p_err = OS_ERR_OPT_INVALID;
return ((OS_SEM_CTR)0u);
}
#endif
timestamp = OS_TS_GET();
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u
if (OSIntNestingCtr > (OS_NESTING_CTR)0) {
OS_IntQPost((OS_OBJ_TYPE)OS_OBJ_TYPE_TASK_SIGNAL,
(void *)p_target, (void *)0,
(OS_MSG_SIZE)0, (OS_FLAGS)0,
(OS_OPT)0, timestamp, p_err);
return ((OS_SEM_CTR)0);
}
#endif
ctr = OS_TaskSemPost(p_target, opt, timestamp, p_err);
return (ctr);
}
/* 実際のカウントアップおよび状態遷移ロジック */
OS_SEM_CTR OS_TaskSemPost (OS_TCB *p_target, OS_OPT opt, CPU_TS ts, OS_ERR *p_err)
{
OS_SEM_CTR ctr;
CPU_SR_ALLOC();
OS_CRITICAL_ENTER();
if (p_target == (OS_TCB *)0) {
p_target = OSTCBCurPtr;
}
p_target->TS = ts;
*p_err = OS_ERR_NONE;
switch (p_target->TaskState) {
case OS_TASK_STATE_RDY:
case OS_TASK_STATE_DLY:
case OS_TASK_STATE_SUSPENDED:
case OS_TASK_STATE_DLY_SUSPENDED:
if (sizeof(OS_SEM_CTR) == 4u && p_target->SemCtr == DEF_INT_32U_MAX_VAL) {
OS_CRITICAL_EXIT();
*p_err = OS_ERR_SEM_OVF;
return ((OS_SEM_CTR)0);
}
p_target->SemCtr++;
ctr = p_target->SemCtr;
OS_CRITICAL_EXIT();
break;
case OS_TASK_STATE_PEND:
case OS_TASK_STATE_PEND_TIMEOUT:
case OS_TASK_STATE_PEND_SUSPENDED:
case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
if (p_target->PendOn == OS_TASK_PEND_ON_TASK_SEM) {
OS_Post((OS_PEND_OBJ *)0, p_target, (void *)0,
(OS_MSG_SIZE)0u, ts);
ctr = p_target->SemCtr;
OS_CRITICAL_EXIT_NO_SCHED();
if ((opt & OS_OPT_POST_NO_SCHED) == (OS_OPT)0) {
OSSched();
}
} else {
if (sizeof(OS_SEM_CTR) == 4u && p_target->SemCtr == DEF_INT_32U_MAX_VAL) {
OS_CRITICAL_EXIT();
*p_err = OS_ERR_SEM_OVF;
return ((OS_SEM_CTR)0);
}
p_target->SemCtr++;
ctr = p_target->SemCtr;
OS_CRITICAL_EXIT();
}
break;
default:
OS_CRITICAL_EXIT();
*p_err = OS_ERR_STATE_INVALID;
ctr = (OS_SEM_CTR)0;
break;
}
return (ctr);
}
通知受信: OSTaskSemPend()
現在実行中のタスク自身が保有する通知を受け取るためのエントリポイントです。引数に送信元TCBの指定はなく、暗黙的に現在のタスクコンテキストを参照します。内部カウンターが有効(>0)であればデクリメントして即座に復帰し、無効な場合はオプションに応じてブロッキング待機或いは即時エラーリターンを行います。待機期間中はタスクがレディリストから外され、タイマリストへ移動します。通知取得後、クロック周波数を用いた時間計測データを出力可能です。
OS_SEM_CTR OSTaskSemPend (OS_TICK timeout, OS_OPT opt, CPU_TS *p_ts, OS_ERR *p_err)
{
OS_SEM_CTR ctr;
CPU_SR_ALLOC();
#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return ((OS_SEM_CTR)0);
}
#endif
#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
if (OSIntNestingCtr > (OS_NESTING_CTR)0) {
*p_err = OS_ERR_PEND_ISR;
return ((OS_SEM_CTR)0);
}
#endif
#if OS_CFG_ARG_CHK_EN > 0u
switch (opt) {
case OS_OPT_PEND_BLOCKING:
case OS_OPT_PEND_NON_BLOCKING: break;
default:
*p_err = OS_ERR_OPT_INVALID;
return ((OS_SEM_CTR)0);
}
#endif
if (p_ts != (CPU_TS *)0) *p_ts = (CPU_TS)0;
CPU_CRITICAL_ENTER();
if (OSTCBCurPtr->SemCtr > (OS_SEM_CTR)0) {
OSTCBCurPtr->SemCtr--;
ctr = OSTCBCurPtr->SemCtr;
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
}
#if OS_CFG_TASK_PROFILE_EN > 0u
OSTCBCurPtr->SemPendTime = OS_TS_GET() - OSTCBCurPtr->TS;
if (OSTCBCurPtr->SemPendTimeMax < OSTCBCurPtr->SemPendTime) {
OSTCBCurPtr->SemPendTimeMax = OSTCBCurPtr->SemPendTime;
}
#endif
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_NONE;
return (ctr);
}
if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) {
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_PEND_WOULD_BLOCK;
return ((OS_SEM_CTR)0);
}
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) {
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_SCHED_LOCKED;
return ((OS_SEM_CTR)0);
}
OS_CRITICAL_ENTER_CPU_EXIT();
OS_Pend((OS_PEND_DATA *)0, (OS_PEND_OBJ *)0,
OS_TASK_PEND_ON_TASK_SEM, timeout);
OS_CRITICAL_EXIT_NO_SCHED();
OSSched();
CPU_CRITICAL_ENTER();
switch (OSTCBCurPtr->PendStatus) {
case OS_STATUS_PEND_OK:
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
#if OS_CFG_TASK_PROFILE_EN > 0u
OSTCBCurPtr->SemPendTime = OS_TS_GET() - OSTCBCurPtr->TS;
if (OSTCBCurPtr->SemPendTimeMax < OSTCBCurPtr->SemPendTime) {
OSTCBCurPtr->SemPendTimeMax = OSTCBCurPtr->SemPendTime;
}
#endif
}
*p_err = OS_ERR_NONE;
break;
case OS_STATUS_PEND_ABORT:
if (p_ts != (CPU_TS *)0) *p_ts = OSTCBCurPtr->TS;
*p_err = OS_ERR_PEND_ABORT;
break;
case OS_STATUS_PEND_TIMEOUT:
if (p_ts != (CPU_TS *)0) *p_ts = (CPU_TS)0;
*p_err = OS_ERR_TIMEOUT;
break;
default:
*p_err = OS_ERR_STATUS_INVALID;
break;
}
ctr = OSTCBCurPtr->SemCtr;
CPU_CRITICAL_EXIT();
return (ctr);
}
実践的な適用事例
バイナリセマフォの代替による一対一同期
外部入力を契機としたタスク間の簡易同期シナリオです。キー押下を検出した送信側が通知を発行し、受信側がそれまで待機状態から解除される形式になります。タイムスタンプの差分を用いて応答遅延の計測も実施します。
#include "includes.h"
static OS_TCB evt_tx_tcb;
static OS_TCB evt_rx_tcb;
static CPU_STK stk_tx_buff[800];
static CPU_STK stk_rx_buff[800];
int main(void) {
OS_ERR err;
OSInit(&err);
BSP_Init();
CPU_Init();
OS_CPU_SysTickInit(BSP_CPU_ClkFreq() / OSCfg_TickRate_Hz);
Mem_Init();
OSTaskCreate((OS_TCB *)&evt_tx_tcb, "NotifySender",
(OS_TASK_PTR)sender_entry, 0, 5,
stk_tx_buff, 80, 800, 0, 0, 0,
OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR, &err);
OSTaskCreate((OS_TCB *)&evt_rx_tcb, "NotifyReceiver",
(OS_TASK_PTR)receiver_entry, 0, 4,
stk_rx_buff, 80, 800, 0, 0, 0,
OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR, &err);
OSTaskDel((OS_TCB*)0, &err);
OSStart(&err);
}
void sender_entry(void *arg) {
(void)arg;
OS_ERR err;
uint8_t key_stable_flag = 0;
while(DEF_TRUE) {
if(Key_Scan(macPORT_A, macPIN_1, 1, &key_stable_flag)) {
printf("[TX] イベント発行開始\n");
OSTaskSemPost((OS_TCB*)&evt_rx_tcb, OS_OPT_POST_NONE, &err);
}
OSTimeDlyHMSM(0, 0, 0, 20, OS_OPT_TIME_DLY, &err);
}
}
void receiver_entry(void *arg) {
(void)arg;
OS_ERR err;
CPU_TS ts_acquire;
uint32_t sys_hz = BSP_CPU_ClkFreq();
while(DEF_TRUE) {
OSTaskSemPend(0, OS_OPT_PEND_BLOCKING, &ts_acquire, &err);
CPU_TS ts_now = OS_TS_GET();
float delay_us = ((float)(ts_now - ts_acquire) / sys_hz) * 1000000.0f;
LED_Switch(LED_1, TOGGLE);
printf("[RX] 同期完了。応答遅延: %.2f us\n", delay_us);
OSTimeDlyHMSM(0, 0, 0, 100, OS_OPT_TIME_DLY, &err);
}
}
カウントセマフォの代替による資源管理
駐車場の空枠数を模したケーススタディです。送信タスクが「退場」を表す通知を送るたびに内部カウントが増加し、受信タスクは「入室」を試みます。非ブロッキングでの試行を行い、空きがあれば許可、満車時は失敗ログを出力するロジックを実装します。
#include "includes.h"
static OS_TCB res_mgr_tcb;
static OS_TCB res_usr_tcb;
static CPU_STK mgr_stack[600];
static CPU_STK usr_stack[600];
int main(void) {
OS_ERR err;
OSInit(&err);
BSP_Init();
CPU_Init();
OS_CPU_SysTickInit(BSP_CPU_ClkFreq() / OSCfg_TickRate_Hz);
Mem_Init();
OSTaskCreate((OS_TCB*)&res_mgr_tcb, "ResourceRelease",
(OS_TASK_PTR)release_loop, 0, 3,
mgr_stack, 60, 600, 0, 0, 0,
OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR, &err);
OSTaskCreate((OS_TCB*)&res_usr_tcb, "ResourceAcquire",
(OS_TASK_PTR)acquire_loop, 0, 2,
usr_stack, 60, 600, 0, 0, 0,
OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR, &err);
OSTaskDel(0, &err);
OSStart(&err);
}
void release_loop(void *arg) {
(void)arg;
OS_ERR err;
uint8_t key2_hist = 0;
while(DEF_TRUE) {
if(Key_Scan(macPORT_B, macPIN_2, 1, &key2_hist)) {
OS_SEM_CTR spots_left = OSTaskSemPost((OS_TCB*)&res_usr_tcb, OS_OPT_POST_NONE, &err);
LED_Switch(LED_2, ON);
printf("退場受付済み。利用可能台数: %d\n", spots_left);
}
OSTimeDlyHMSM(0, 0, 0, 20, OS_OPT_TIME_DLY, &err);
}
}
void acquire_loop(void *arg) {
(void)arg;
OS_ERR err;
uint8_t key1_hist = 0;
while(DEF_TRUE) {
if(Key_Scan(macPORT_A, macPIN_1, 1, &key1_hist)) {
OS_SEM_CTR current_count = OSTaskSemPend(0, OS_OPT_PEND_NON_BLOCKING, NULL, &err);
LED_Switch(LED_1, TOGGLE);
if(err == OS_ERR_NONE) {
printf("入庫許可。残り枠: %d\n", current_count);
} else {
printf("満席のため入庫不可です。\n");
}
}
OSTimeDlyHMSM(0, 0, 0, 20, OS_OPT_TIME_DLY, &err);
}
}