세마포어(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에서 세마포어 구성 요소
- OS_EVENT 구조체
OSEventType
: 이벤트 종류(이 경우 세마포어)OSEventCnt
: 세마포어 카운트(0 ~ 65535)OSEventGrp
,OSEventTbl[]
: 대기 태스크 관리(Ready Group, Ready Table)
- 세마포어 카운트
- 0이면 자원을 더 이상 사용할 수 없음을 의미
- 0보다 크면 즉시 자원을 획득할 수 있음
- 대기 리스트(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()
) 함수를 숙지하면 안정적인 멀티태스킹을 구현할 수 있습니다.
주요 포인트
- 세마포어는 uC/OS-II에서 16비트 카운트를 가진 이벤트 구조체로 관리
- 대기(Task Wait)는 카운트가 0일 때, 태스크가 블록(Block)되는 방식
- 포스트(Post) 시 대기 중인 태스크가 있으면 먼저 태스크를 깨움
- 커널 설정 시
OS_SEM_EN
이 활성화되어 있어야 세마포어 관련 함수가 생성됨- 삭제(
OSSemDel()
) 전에는 모든 대기 태스크가 완료되어야 함
이 글이 도움이 되었으면..