세마포어(Semaphore) 정리, uC/OS-II에서의 활용 방법

세마포어(Semaphore) 정리, uC/OS-II에서의 활용 방법

임베디드 RTOS인 uC/OS-II에서 동시성과 자원 관리를 위해 자주 사용되는 동기화 기법 중 하나가 **세마포어(Semaphore)**입니다. 이번 글에서는 세마포어의 구성 요소부터 생성, 삭제, 대기, 반환, 상태 조회까지 핵심 사항을 정리해보았습니다.

1. 세마포어(Semaphore)란?

  • 세마포어는 여러 태스크(Task) 또는 인터럽트(ISR) 간에 공유 자원을 동기화하기 위한 기법입니다.
  • uC/OS-II에서 세마포어는 16비트 부호 없는 정수(0~65,535)의 카운트 값을 가집니다.
  • 카운트 값이 0보다 커질 때를 기다리는 태스크들은 대기 리스트(Wait List)에 등록됩니다.

예시

  • 열쇠(Key) 형태: 공유 자원을 안전하게 접근하기 위해 사용
  • 깃발(Flag) 형태: 특정 이벤트(예: 데이터 수신 완료) 발생을 알릴 때 사용

2. uC/OS-II에서 세마포어 구성 요소

  1. OS_EVENT 구조체
    • OSEventType: 이벤트 종류(이 경우 세마포어)
    • OSEventCnt: 세마포어 카운트(0 ~ 65535)
    • OSEventGrp, OSEventTbl[]: 대기 태스크 관리(Ready Group, Ready Table)
  2. 세마포어 카운트
    • 0이면 자원을 더 이상 사용할 수 없음을 의미
    • 0보다 크면 즉시 자원을 획득할 수 있음
  3. 대기 리스트(Wait List)
    • 세마포어가 0일 때, 자원을 기다리는 태스크들이 등록되는 리스트

3. 세마포어 생성: OSSemCreate()

세마포어를 사용하기 전에 반드시 생성해주어야 합니다.

c복사편집OS_EVENT *OSSemCreate(INT16U cnt) {
    OS_EVENT *pevent;

    // ... (메모리 할당 등 내부 처리)
    pevent->OSEventType = OS_EVENT_TYPE_SEM;
    pevent->OSEventCnt  = cnt;
    OS_EventWaitListInit(pevent);

    return (pevent);
}
  • cnt 파라미터: 세마포어 초기값 설정
    • 0으로 초기화: 이벤트 발생을 기다리는 용도(깃발 역할)
    • 1로 초기화: 단일 공유 자원 보호(열쇠 역할)
    • N으로 초기화: 같은 종류의 자원이 N개 있는 경우

4. 세마포어 삭제: OSSemDel()

세마포어를 삭제하기 전, 해당 세마포어를 사용하는 모든 태스크가 정리되어야 합니다.

c복사편집OS_EVENT *OSSemDel(OS_EVENT *pevent, INT8U opt, INT8U *err) {
    // ...
    if (tasks_waiting == FALSE) {
        // 다른 태스크가 대기 중이 아닐 때만 삭제 가능
        pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
        pevent->OSEventPtr  = OSEventFreeList;
        OSEventFreeList     = pevent;
        *err = OS_NO_ERR;
        return ((OS_EVENT *)0);
    } else {
        // 대기 중인 태스크가 있으면 삭제 불가
    }
    // ...
}

5. 세마포어 대기: OSSemPend()

태스크가 자원을 얻기 위해 세마포어를 기다리는 함수입니다.

c복사편집void OSSemPend(OS_EVENT *pevent, INT16U timeout, INT8U *err) {
    OS_ENTER_CRITICAL();
    if (pevent->OSEventCnt > 0) {
        pevent->OSEventCnt--;
        OS_EXIT_CRITICAL();
        *err = OS_NO_ERR; // 성공적으로 얻음
        return;
    }
    // 세마포어가 0이므로 대기 상태로 전환
    OSTCBCur->OSTCBStat |= OS_STAT_SEM;
    OSTCBCur->OSTCBDly   = timeout;
    OSEventTaskWait(pevent);
    OSSched(); // 스케줄러 호출

    // ...
    if (OSTCBCur->OSTCBStat & OS_STAT_SEM) {
        // 여전히 세마포어 대기 상태라면 시간 초과
        OSEventTO(pevent);
        *err = OS_TIMEOUT;
        return;
    }
}
  • timeout:
    • 0이면 영원히 대기
    • 0이면 지정된 틱(tick) 시간 동안만 대기 후 시간 초과 발생

6. 세마포어 반환: OSSemPost()

태스크나 ISR이 자원 사용을 마치고 세마포어를 되돌려주는 함수입니다.

c복사편집INT8U OSSemPost(OS_EVENT *pevent) {
    OS_ENTER_CRITICAL();
    if (pevent->OSEventGrp != 0x00) {
        // 대기 중인 태스크가 있는 경우
        OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM);
        OS_EXIT_CRITICAL();
        OS_Sched(); // 최상위 대기 태스크를 실행 상태로
        return (OS_NO_ERR);
    }
    if (pevent->OSEventCnt < 65535) {
        pevent->OSEventCnt++; // 카운팅 세마포어 증가
        OS_EXIT_CRITICAL();
        return (OS_NO_ERR);
    }
    OS_EXIT_CRITICAL();
    return (OS_SEM_OVF); // 오버플로우 발생
}
  • 대기 중인 태스크가 있다면 카운트를 증가시키지 않고, 바로 태스크를 깨워줍니다.

7. 대기 없이 세마포어 얻기: OSSemAccept()

태스크가 즉시 자원만 확인하고, 사용할 수 없는 경우에는 기다리지 않고 돌아오는 함수입니다.

c복사편집INT16U OSSemAccept(OS_EVENT *pevent) {
    INT16U cnt;
    OS_ENTER_CRITICAL();
    cnt = pevent->OSEventCnt;
    if (cnt > 0) {
        pevent->OSEventCnt--;
    }
    OS_EXIT_CRITICAL();
    return (cnt); 
}
  • 반환 값이 0보다 크면 성공적으로 세마포어를 얻었음을 의미
  • 0이면 이미 사용 중이므로 대기하지 않고 복귀

8. 세마포어 상태 조회: OSSemQuery()

세마포어(이벤트 컨트롤 블록) 상태를 실시간으로 확인할 수 있는 함수입니다.

c복사편집INT8U OSSemQuery(OS_EVENT *pevent, OS_SEM_DATA *pdata) {
    OS_ENTER_CRITICAL();
    pdata->OSEventGrp = pevent->OSEventGrp;
    // ...
    OS_EXIT_CRITICAL();
    return (OS_NO_ERR);
}
  • pdata 구조체에 현재 대기 중인 태스크 정보, 세마포어 카운트 등 상태가 기록됩니다.

마무리

uC/OS-II 환경에서 세마포어는 태스크 간 공유 자원에 대한 충돌을 방지하고, 특정 이벤트가 발생했음을 태스크나 ISR에 알릴 수 있는 핵심 동기화 도구입니다. 적절한 생성(OSSemCreate()), 대기(OSSemPend()), 반환(OSSemPost()), 상태 조회(OSSemQuery()) 함수를 숙지하면 안정적인 멀티태스킹을 구현할 수 있습니다.

주요 포인트

  1. 세마포어는 uC/OS-II에서 16비트 카운트를 가진 이벤트 구조체로 관리
  2. 대기(Task Wait)는 카운트가 0일 때, 태스크가 블록(Block)되는 방식
  3. 포스트(Post) 시 대기 중인 태스크가 있으면 먼저 태스크를 깨움
  4. 커널 설정 시 OS_SEM_EN이 활성화되어 있어야 세마포어 관련 함수가 생성됨
  5. 삭제(OSSemDel()) 전에는 모든 대기 태스크가 완료되어야 함

이 글이 도움이 되었으면..

Leave a Comment