2008년 4월 28일 월요일

uC/OS-II 커널구조(5) - 준비 리스트

각 태스크들은 0부터 OS_LOWEST_PRIO 사이의 유일한 우선순위 값을 갖는다. uC/OS-II가 초기화될 때 OS_LOWEST_PRIO 값은 항상 IDLE 태스크에 할당된다. 주의할 점은 OS_MAX_TASKS 값과 OS_LOWEST_PRIO 값은 별개의 값이라는 사실이다. 예를 들면, 32단계의 우선순위를 사용하면서(OS_LOWEST_PRIO를 31로 설정한 경우) 태스크는 10개만 사용할 수도 있다.

실행할 준비가 되어있는 태스크들을 관리하기 위해, OSRdyGrp와 OSRdyTbl[] 변수로 구성된 준비 리스트에 해당 우선순위의 태스크가 준비상태임을 표시해 둔다. 태스크 우선순위에 대해 8개의 우선순위를 그룹화하여 그룹당 OSRdyGrp의 1비트씩 대응시킨다. 즉 OSRdyGrp에서 각 비트는 해당 우선순위 그룹 내에 하나 이상의 실행 가능한 태스크의 존재 여부를 나타낸다. 태스크가 준비상태가 될 때, 준비 테이블(OSRdyTbl[])의 해당 비트 또한 기록해 둔다.





OSRdyTbl[]의 크기는 OS_LOWEST_PRIO 값에 따라 달라진다. 이런 특징 때문에 응용프로그램에서 사용하는 우선순위 개수가 적을수록 uC/OS-II에 필요한 메모리는 절약된다. uC/OS-II의 스케줄러는 다음 번으로 실행할 태스크를 결정할 때 OSRdyTbl[] 에서 가장 작은 우선순위 값에 해당하는 비트를 찾아내야 한다.



아래 코드는 태스크를 준비 리스트에 삽입하는 부분이다. 여기서 prio는 태스크 우선순위이다.
(태스크를 준비 상태로 만든다.)
OSRdyGrp = OSMapTbl [prio >> 3];
OSRdyTbl [prio >> 3] = OSMapTbl [prio & 0x07];


우선순위 값의 하위 3비트(bit2-bit0)는 OSRdyTbl[]의 항목에 대해 검사해야 할 비트위치를 나타내고, 다음 3비트(bit5-bit3)는 OSRdyTbl[]에서 항목 선택을 위한 인덱스 값으로 사용한다.



태스크는 삽입과정을 거꾸로 수행하여 준비 리스트로부터 삭제된다.
(준비 리스트에서 태스크 삭제)
if ((OSRdyTbl [prio >> 3] &= ~OSMapTbl [prio & 0x07]) == 0)
OSRdyGrp &= ~OSMapTbl [prio >> 3];

이 코드에서는 OSRdyTbl[] 배열에서 삭제하고자 하는 태스크의 우선순위에 해당하는 비트를 0으로 만든다. 이 과정에서 같은 우선순위 그룹 내에 준비상태의 태스크가 하나도 존재하지 않는 상태가 되면 OSRdyGrp의 우선순위 그룹 비트도 0으로 만든다.


준비 리스트에서 가장 우선순위가 높은 태스크를 찾기 위해 OSRdyTbl[0]에서 차례로 검색해 나가는 방법 대신 특별한 방법을 사용한다. 이때 사용하는 배열이 OSUnMapTbl[256]이다. OSRdyTbl[] 배열의 내용인 8비트 값은 실행 가능한 태스크의 우선순위와 1:1로 대응, 해당 우선순위의 태스크가 실행 가능한지 여부를 나타낸다. 0번 비트가 가장 높은 우선순위이다. 이렇게 실행 가능한 우선순위 상태를 나타내는 8비트 값을 그대로 인덱스 값으로 삼아 OSUnMapTbl[] 배열 값을 읽으면, 상황별로 실행 가능한 가장 높은 우선순위 비트 값을 0에서 7사이의 값으로 돌려준다. 이 과정은 아래와 같이 수행된다.
(준비 리스트에서 최상위 태스크 찾기)
y = OSUnMapTbl [OSRdyGrp]; /* OSRdyTbl[]에서 Y 위치를 결정한다. */
x = OSUnMapTbl [OSRdyTbl[y]]; /* OSRdyTbl[Y]에서 X 위치를 결정한다. */
prio = (y <<>
아래 그림과 같이 OSRdyGrp 값이 01101000(2진수) 이면 OSUnMapTbl [OSRdyGrp] 값은 3이 된다. 즉 OSRdyGrp의 3번 비트가 설정돼 있으므로 3번 비트에 대응한 우선순위 그룹이 실행 가능한 가장 높은 우선순위 그룹임을 의미한다. 참고로 이 경우 8비트 값의 가장 오른쪽 비트가 0번 비트이다. 그리고 OSRdyTbl[3]의 값이 11100100(2진수) 라면 OSUnMapTbl[OSRdyTbl[3]] 값은 2가 된다. (2번 비트를 의미) 결과적으로 준비 리스트 내의 가장 높은 우선순위 태스크는 우선순위 값 26(3 * 8 + 2)을 갖고 있음을 알 수 있다. 이제 해당 태스크 컨트롤 블록의 주소는 우선순위 값을 OSTCBPrioTbl[] 배열의 인덱스로 참조해서 얻을 수 있다.


참조> uC/OS-II 실시간 커널...

2008년 4월 24일 목요일

uC/OS-II 커널구조(4) - 태스크 컨트롤 블록 / OS_TCB

태스크 컨트롤 블록은 태스크가 선점될 때 해당 태스크의 상태를 유지하기 위해 사용되는 데이터 구조체다. uC/OS-II는 태스크를 생성할 때 이 태스크 컨트롤 블록을 할당한다. 태스크가 다시 CPU 제어권을 가질 때 태스크 컨트롤 블록에 저장했던 데이터를 이용해서 마지막으로 실행했던 부분부터 코드를 정확히 재개할 수 있다.

typedef struct os_tcb {
OS_STK *OSTTCBStkPtr; - (1)

#if OS_TASK_CREATE_EXT_EN > 0
void *OSTCBExtPtr; - (2)
OS_STK *OSTCBStkBottom; - (3)
INT32U OSTCBStkSize;
- (4)
INT16U OSTCBOpt;
- (5)
INT16U OSTCBId;
- (6)
#endif


struct os_tcb *OSTCBNext; - (7)
struct os_tcb *OSTCBPrev;
- (7)

#if ((OS_Q_EN > 0) && (OS_MAX_QS > 0)) (OS_MBOX_EN > 0) (OS_SEM_EN > 0) (OS_MUTEX_EN > 0) OS_EVENT *OSTCBEventPtr; - (8)
#endif


#if (OS_Q_EN > 0) && (OS_MAX_QS > 0) (OS_MBOX_EN > 0)
void *OSTCBMsg; - (9)
#endif


#if (OS_VERSION >= 251) && (OS_FLAG_EN > 0) && (OS_MAX_FLAGS > 0)
#if OS_TASK_DEL_EN > 0
OS_FLAG_NODE *OSTCBFlagNode; - (10)
#endif

OS_FLAGS OSTCBFlagRdy; - (11)
#endif


INT16U OSTCBDly; - (12)
INT8U OSTCBStat;
- (13)
INT8U OSTCBPrio;
- (14)

INT8U OSTCBX; - (15)
INT8U OSTCBY;
- (15)
INT8U OSTCBBitX;
- (15)
INT8U OSTCBBitY; - (15)

#if OS_TASK
BOOLEAN OSTCBDelReq; - (16)
#endif

} OS_TCB;

(1) .OSTCBStkPtr
현재 태스크가 마지막으로 사용한 스택의 위치를 가리키는 포인터이다. uC/OS-II에서 모든 태스크는 자신이 사용하는 스택을 임의의 크기로 가질 수 있다. .OSTCBStkPtr은 문맥전환 시 어셈블리 코드에서 직접 엑세스해야 하는 유일한 필드이다. 이를 위해 구조체의 첫번째 위치에 있다.

(2) .OSTCBExtPtr
사용자 정의 태스크 컨트롤 블록을 가리키는 포인터이다. 이 필드를 적절히 사용하면 uC/OS-II의 소스코드를 수정하지 않고 태스크 컨트롤 블록을 사용자 용도에 맞게 확장할 수 있다. OSTaskCreateExt() 함수만 .OSTCBExtPtr 필드를 사용한다. 따라서 이 필드를 사용하려면 OS_TASK_CREATE_EXT_EN 매크로를 1로 설정해야 한다. 이 필드를 사용하면 태스크의 이름이나 태스크가 CPU를 할당 받아 받아 실행한 시간, 문맥전환이 일어난 회수 등의 정보를 담는 자료구조를 사용자가 정의해서 사용할 수도 있을 것이다.

(3) .OSTCBStkBottom
태스크가 사용하는 스택영역의 끝을 가리키는 포인터. 스택이 낮은 주소 방향으로 커지는 시스템의 경우라면, 스택으로 할당받은 메모리의 가장 낮은 번지를 가리킨다. 반면에 스택에 데이터를 저장할 때 스택 포인터 값이 커지는 시스템에서는 메모리의 가장 높은 주소를 가리키게 된다. OSTaskStkChk() 함수가 사용된 스택의 크기를 동적으로 조사하기 위해 이 필드를 사용한다. 이 필드를 통해 각 태스크에게 할당한 스택공간 중 사용하지 않는 공간이 얼마나 되는지 알아낼 수 있다. 스택 검사기능은 태스크를 OSTaskCreateExt() 함수로 생성할 경우에만 사용할 수 있다. 따라서 스택 검사기능을 사용하려면 OS_TASK_CREATE_EXT_EN 매크로도 1로 선언되어 있어야 한다.

(4) .OSTCBStkSize
스택의 크기를 바이트 단위가 아니라 항목개수 단위로 나타내는 필드. 즉 32bit 시스템에서 .OSTCBStkSize가 1,000일 경우 실제 스택의 크기는 4,000bytes가 되고, 16bit 시스템일 경우엔 2,000bytes 크기의 스택을 의미한다. .OSTCBStkSize 필드도 OSTaskStkChk() 함수가 사용하는 필드이다. 역시 OS_TASK_CREATE_EXT_EN 매크로가 1로 선언되어 있어야 한다.

(5) .OSTCBOpt
OSTaskCreateExt()를 써서 태스크를 생성할 경우, 옵션에 해당하는 값을 보관하는 필드. 따라서 OS_TASK_CREATE_EXT_EN 매크로가 1로 선언되어 있어야 한다. 현재 uC/OS-II에서는 3개의 옵션을 정의하고 있다.

* OS_TASK_OPT_STK_CHK
생성하는 태스크에 대해 스택 검사 기능을 사용한다는 거을 OSTaskCreateExt()에게 알린다. 스택 점검은 응용프로그램에서 OSTaskStkChk()를 호출해서 수행한다.
* OS_TASK_OPT_STK_CLR
태스크를 생성할 때 스택 공간을 0으로 클리어하라는 것을 의미한다.(수행시간이 길어질 수도 있다. ) 스택 검사기능을 사용할 경우엔 꼭 이 옵션을 활성화해서 스택을 0으로 클리어해야 한다. 이 옵션을 지정하지 않고 태스크의 생성/삭제 과정을 반복한다면 스택 사용량에 대해 잘못된 계산 결과가 나올 것이다. 하지만 일단 생성된 태스크를 삭제하지 않고, 시작코드에서 모든 램 영역을 0으로 클리어 해준다면, 이 옵션을 생략해서 실행시간을 절약할 수 있다.
* OS_TASK_OPT_STK_SAVE_FP
태스크가 부동 소수점 연산기능을 사용할 경우 사용하는 옵션이다. 하드웨어적으로 부동 소수점 연산을 지원하는 코프로세서를 사용한다면, 문맥전환이 발생할 때 코프로세서의 레지스터들도 문맥에 포함하도록 처리해야 한다.

(6) .OSTCBId
태스크의 ID를 나타낸다. 현재 사용하지 않는 필드이며 추후 확장을 위해 정의되어 있다.

(7) .OSTCBNext / .OSTCBPrev
커널 내부에서 생성된 태스크 컨트롤 블록을 이중 연결 리스트로 관리하기 위해 사용하는 필드. 이 연결 리스트는 OSTimeTick()에서 태스크의 .OSTCBDly 값을 갱신하기 위해 사용한다. 태스크가 생성되면 태스크 컨트롤 블록이 할당되어 연결 리스트에 삽입되고, 태스크가 삭제되면 연결 리스트에서도 삭제된다. 이중 연결 리스트를 사용함으로써 빠르게 삽입/삭제 연산을 수행할 수 있다.

(8) .OSTCBEventPtr
이벤트 컨트롤 블록을 가리키는 포인터.

(9) .OSTCBMsg
태스크로 보낸 메시지를 가리키는 포인터.

(10) .OSTCBFlagNode
이벤트 플래그 노드를 가리키는 포인터. 이 필드는 이벤트 플래그 그룹에서 대기 중인 태스크를 삭제할 때 OSTaskDel() 함수가 사용한다. OS_FLAG_EN 매크로를 1로 설정해야 OS_TCB멤버로서 존재한다.

(11) .OSTCBFlagRdy
태스크가 이벤트 플래그 그룹에서 대기 중인 태스크를 준비상태로 만든 이벤트 플래그를 포함한다. OS_FLAG_EN 매크로를 1로 설정해야 OS_TCB멤버로서 존재한다.

(12) .OSTCBDly
태스크를 일정 틱 동안 지연할 필요가 있거나 타임아웃과 함께 이벤트를 기다릴 때 사용하는 필드. 지연 또는 대기 시간 틱 값을 갖는다. 이 값이 0일 경우, 태스크는 지연되지 않는다. 또는 이벤트 발생을 기다리는 경우라면 타임아웃을 사용하지 않은 경우다.

(13) .OSTCBStat
태스크의 상태를 나타내는 필드. 이 값이 OS_STAT_READY이면, 태스크가 실행 준비상태라는 것을 의미한다.

(14) .OSTCBPrio
태스크의 우선순위를 나타내는 필드. 높은 우선순위의 태스크일수록 낮은 .OSTCBPrio 값을 갖는다.

(15) .OSTCBX / .OSTCBY / .OSTCBBitX / .OSTCBBitY
태스크가 실행될 수 있는 상태임을 표시하거나 이벤트를 기다리는 상태라는 것을 판단하는 연산을 빠르게 수행하기 위해 사용하는 필드(실행 시에 이 값을 다시 계산하지 않도록, 미리 계산해서 태스크 컨트롤 블록 안에 저장해둠). 이 값은 태스크를 생성할 때 계산해서 저장하고, 또 태스크의 우선순위를 변경할 때 다시 계산한다.

.OSTCBX = priority >> 3;
.OSTCBY = OSMapTbl[priority >> 3] ;
.OSTCBBitX = priority & 0x07;
.OSTCBBitY = OSMapTbl[priority & 0x07];

(16) .OSTCBDelReq
태스크가 삭제 요청을 받았는지 여부를 나타내는 필드. OS_TASK_DEL_EN 매크로를 1로 설정해야 OS_TCB 맴버로 존재한다.

응용프로그램에서 사용할 수 있는 최대 태스크의 개수(OS_MAX_TASKS)는 OS_CFG.H 파일에 정의한다. 즉 uC/OS-II에서 최대 몇 개의 태스크 컨트롤 블록을 할당하도록 되있느지 OS_MAX_TASKS 값으로 알 수 있다. 이 값을 응용프로그램에서 실제 필요한 태스크의 개수만큼만 정의해서 메모리를 절약할 수 있다.
모든 태스크 컨트롤 블록은 OSTCBTbl[] 배열로 할당된다. uC/OS-II에서는 시스템 내부적인 사용을 위해 OS_N_SYS_TASKS 만큼의 태스크 컨트롤 블록을 더 할당한다. 현재는 IDLE 태스크와 통계 태스크가(OS_CFG.H 파일에 정의된 OS_TASK_STAT_EN이 1로 설정된 경우) 시스템 태스크에 해당한다. uC/OS-II 초기화 과정에서 OSTCBTlb[] 배열 내의 모든 태스크 컨트롤 블록은 자유 리스트에 단일 링크드 리스트 형태로 삽입된다. 태스크 생성을 위해 태스크 컨트롤 블록 할당이 필요할 경우, OSTCBFreeList로 유지되는 자유 리스트에서 태스크 컨트롤 블록 하나를 꺼내서 할당해 주고, 태스크가 삭제되면 해당 컨트롤 블록은 다시 자유 리스트에 삽입된다.

OS_TCB는 태스크 생성 시 OS_TCBInit() 함수가 초기화 한다. OS_TCBInit()은 OSTaskCreate() 또는 OSTaskCreateExt() 함수가 호출한다. OS_TCBInit() 함수는 다음 7개의 전달인자를 받는다.
prio 태스크 우선순위

ptos OSTaskStkInit() 함수가 초기화한 스택 프레임의 시작 위치를 가리키는 포인터.
.OSTCBStkPtr 필드에 저장.

pbos 스택의 끝부분 위치를 가리키는 포인터.
.OSTCBStkBottom 필드에 저장.

id 태스크 식별자.
.OSTCBId 필드에 저장.

stksize 스택의 전체 크기.
.OSTCBExtPtr 필드에 저장.

pext OS_TCB의 .OSTCBExtPtr 필드에 두기 위한 포인터.

opt 선택사항을 알려주는 값.
.OSTCBOpt 필드에 저장.

INT8U OS_TCBInit(INT8U prio, OS_STK *ptos, OS_STK *pbos, INT16U id,
INT32U stk_size, void *pext, INT16U opt)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
OS_TCB *ptcb;

OS_ENTER_CRITICAL();
ptcb = OSTCBFreeList();

if (ptcb != (OS_TCB *) 0) { - (1)
OSTCBFreeList = ptcb -> OSTCBNext;
OS_EXIT_CRITICAL(); - (2)
ptcb -> OSTCBStkPtr = ptos;
ptcb -> OSTCBPrio = (INT8U) prio;
ptcb -> OSTCBStat = OS_STAT_RDY;
ptcb -> OSTCBDly = 0;
#if OS_TASK_CREATE_EXT_EN > 0 - (3)
ptcb -> OSTCBExtPtr = pext;
ptcb -> OSTCBStkSize = stk_size;
ptcb -> OSTCBStkBottom = pbos;
ptcb -> OSTCBOpt = opt;
ptcb -> OSTCBId = id;
#else
pext = pext;
stk_size = stk_size;
pbos = pbos;
opt = opt;
id = id;
#endif

#if OS_TASK_DEL_EN > 0
ptcb -> OSTCBDelReq = OS_NO_ERR; - (4)
#endif
ptcb -> OSTCBY = prio >> 3; - (5)
ptcb -> OSTCBBitY = OSMapTbl [ptcb -> OSTCBY];
ptcb -> OSTCBX = prio & 0x07;
ptcb -> OSTCBBitX = OSMapTbl [ptcb -> OSTCBX];

#if OS_EVENT_EN > 0
ptcb -> OSTCBEventPtr = (OS_EVENT *) 0; -(6)
#endif

#if (OS_VERSION >= 251) && (OS_FLAG_EN > 0) && (OS_MAX_FLAGS > 0) &&
(OS_TASK_DEL_EN > 0)
ptcb -> OSTCBFlagNode = (OS_FLAG_NODE *) 0; -(7)
#endif

#if OS_MBOX_EN (OS_Q_EN && (OS_MAX_QS >= 2))
ptcb -> OSTCBMsg = (void *) 0;
#endif

#if OS_VERSION >= 204
OSTCBInitHook(ptcb);
#endif
OSTaskCreateHook(ptcb);

OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = ptcb; -(8)
ptcb -> OSTCBNext = OSTCBList;
ptcb -> OSTCBPrev = (OS_TCB *) 0;
if (OSTCBList != (OS_TCB *)0) {
OSTCBList -> OSTCBPrev = ptcb;
}
OSTCBList = ptcb;
OSRdyGrp = ptcb -> OSTCBBitX;
OSRdyTbl [ptcb -> OSTCBY] = ptcb -> OSTCBBitX;
OS_EXIT_CRITICAL();
return (OS_NO_ERR); -(9)
}
OS_EXIT_CRITICAL();
return (OS_NO_MORE_TCB);
}

(1) OS_TCBInit() 함수는 우선 자유 OS_TCB 풀로부터 1개의 태스크 컨트롤 블록을 받기 위해 시도한다.

(2) 자유 풀에 사용할 수 있는 태스크 컨트롤 블록이 있을 경우 이 블록을 초기화 한다. 일단 OS_TCB가 할당되면 OS_TCBInit() 함수를 호출한 태스크 생성자가 OS_TCB를 완전히 소유한 것이고, 다른 곳에서 태스크를 생성하려고 하더라도 데이터 구조체가 손상될 염려가 없기 때문에 인터럽트를 활성화할 수 있다는 점을 주목하자. 따라서 OS_TCBInit() 함수는 인터럽트를 활성화 한 상태에서 OS_TCB의 몇몇 필드를 초기화 할 수 있다.

(3) OSTaskCreateExt() 함수에 관련된 코드를 생성하도록 OS_CFG.H의 OS_TASK_CREATE_EXT_EN 매크로를 1로 설정했다면 OS_TCB 추가 필드로 함께 초기화 한다.

(4) OS_CFG.H 파일의 OS_TASK_DEL_EN 매크로 설정에 따라 OS_TCB의 .OSTCBDelReq 필드 존재 여부도 달라진다. 즉 태스크를 삭제할 일이 없다면 이 매크로를 비활성화 해서 .OSTCBDelReq 필드를 제외시켜 OS_TCB 저장영역을 절약할 수 있다.

(5) 스케쥴링 시 계산시간을 절약하기 위해 OS_TCBInit() 함수는 몇몇 필드를 미리 계산해 둔다. OS_TCB의 저장영역은 커지지만 계산 시간을 절약하기 위해 이 필드를 정의한다.

(6) 응용 프로그램이 세마포어, 뮤텍스, 메시지 메일박스, 메시지 큐를 사용하지 않는다면 OS_TCB에 .OSTCBEventPtr 필드를 둘 필요가 없다.

(7) 이벤트 플래그를 활성화 했다면 (OS_CFG.H의 OS_FLAGS_EN = 1) 이벤트 플래그 노드를 가리키는 포인터는 아무것도 가리키지 않도록 초기화된다. 태스크가 처음 생성되면 이벤트 플래그를 기다리지 않기 때문이다.

(8) OS_TCBInit() 함수는 새로 생성한 OS_TCB를 이중 링크드 리스트에 삽입할 때 인터럽트를 비활성화한다. 리스트는 OSTCBList에서 시작하며 새 태스크의 OS_TCB는 항상 리스트 앞에 삽입된다.

(9) 마지막으로 태스크는 실행 준비상태가 된다. 그리고 OS_TCBInit() 함수는 OS_TCB를 할당해서 초기화했다는 코드 값을 가지고 호출자(OSTaskCreate() 또는 OSTaskCreateExt())로 리턴한다.

출처> uC/OS-II 실시간 커널....

2008년 4월 23일 수요일

uC/OS-II 커널구조(3) - 태스크 상태


TASK DORMANT는 태스크가 프로그램 영역(ROM or RAM)에 존재하지만 아직 uC/OS-II에게 태스크로서 등록되지 않은 상태를 의미한다. 태스크는 OSTaskCreate() 또는 OSTaskCreateExt()를 호출해서 uC/OS-II에 등록한다. 이 함수들의 역할은 단순히 uC/OS-II에게 태스크의 시작번지, 우선순위, 태스크가 사용할 스택의 위치와 크기 등을 알려준다. 태스크가 생성과정을 거치면 TASK READY 상태가 되어 실행할 준비를 마친다. 태스크는 멀티태스킹을 시작하기 전이나 이후에 실행 중인 다른 태스크가 동적으로 생성할 수 있다. 실행 중인 태스크가 새롭게 태스크를 생성할 경우, 생성된 태스크의 우선순위가 생성시킨 우선순위보다 높다면, 생성과 동시에 CPU 제어권을 할당받아 실행된다.

OSTaskDel()를 호출해서 자기자신이나 다른 태스크를 수면상태로 만들 수 있다. uC/OS-II에서는 OSStart() 함수를 호출해서 멀티태스킹을 시작한다. 이 함수는 시스템이 시작할 때 오직 한번만 호출할 수 있으며, 초기화 코드가 생성한 태스크 중 가장 우선순위가 높은 태스크를 실행한다. 이 때 최우선순위 태스크는 TASK RUNNING 상태가 된다.

실행중인 태스크는 OSTimeDly() 또는 OSTimeDlyHMSM()을 호출해서 원하는 시간동안 자신의 실행을 지연할 수 있다. 이들 시간지연 함수를 호출한 태스크는 함수를 호출할 때 지정한 시간동안 TASK WAITING 상태에 머문다. 이 두 함수 모두 준비상태의 태스크 중 가장 우선순위가 앞서는 태스크로 문맥전환을 발생시킨다. 지연된 태스크는 설정 시간이 만료되면 OSTimeTick()함수에 의해 TASK WATING 상태가 된다.

실행중인 태스크는 OSFlagPend() / OSSemPend() / OSMutexPend() / OSMboxPend() / OSQPend()를 호출해서 특정 이벤트가 발생할 때까지 실행을 멈출 필요가 있다. 이벤트가 이미 발생하지 않았다면 이 함수를 호출한 태스크는 이벤트가 발생할 때까지 TASK WAITING 상태가 된다. 태스크가 이벤트를 기다리는 상태로 빠지면, 다음 번 우선순위의 태스크가 즉시 CPU의 실행권을 받아 실행한다. 태스크는 이벤트가 발생하거나 대기 시간이 만료하면 준비 상태가 된다. 이벤트는 다른 태스크나 ISR에서 신호를 보낼 때 발생한다. 모든 태스크가 이벤트 발생이나 시간 만료를 기다리고 있을 경우 uC/OS-II는 OS_TaskIdle()라는 내부 태스크를 실행한다.

출처> uC/OS-II 실시간 커널

uC/OS-II 커널구조(2) - 태스크

uC/OS-II에서 태스크는 아래와 같은 전형적인 무한루프 함수이다.

void YourTask (void *pdata) - (1)
{
for (;;) {
/* 사용자 코드 */

uC/OS-II의 서비스 중 하나를 호출
OSFlagPend();
OSMboxPend();
OSMutexPend();
OSQPend();
OSSemPend();
OSTaskDel(OS_PRIO_SELF);
OSTaskSuspend(OS_PRIO_SELF);
OSTimeDly();
OSTimeDlyHMSM();
/* 사용자 코드 */
}
}

(1) - 함수의 리턴과 전달인자는 항상 void형이다. 전달인자가 void이므로 어떤 형태의 데이터(변수, 구조체, 함수의 시작번지)도 넘겨줄 수 있다. 같은 함수를 이용해서 많은 수의 같은 태스크를 생성할 수 있다. 만약 고유의 태스크로 관리하는 4개의 비동기 시리얼 포트가 있다고 가정하고, 태스크 코드또한 실제로 동일하다. 같은 코드를 4번 코딩하는 것보다 데이터 구조체를 가리키는 포인터를 전달인자로 받는 태스크를 한 번만 생성하는 것이 바람직하다. 이 구조체는 시리얼 포트의 설정 인자(Baudrate, I/O address, Interrupt vector number)가 저장된다.

void YourTask (void *pdata)
{
/* 사용자 코드 */
OSTaskDel(OS_PRIO_SELF);
}

태스크가 할 일을 마치면 자신을 삭제할 수 있다. 이는 태스크가 메모리에서 실제로 삭제되는 것이 아니라, uC/OS-II가 이후로는 해당 태스크의 정보를 관리하지 않는다는 것을 의미한다.

uC/OS-II는 태스크를 64개까지 관리할 수 있다. 그러나 uC/OS-II 내부에서 이미 2개의 태스크를 사용한다. 그리고 uC/OS-II에서 차후 버전에서 사용하기 위해 우선순위 0, 1, 2, 3, OS_LOWEST_PRIO-3, OS_LOWEST_PRIO-2, OS_LOWEST_PRIO-1, OS_LOWEST_PRIO를 예약해뒀다. 하지만 응용프로그램을 엄격하게 관리할 수 있다면 OS_LOWEST_PRIO를 제외하고 어떤 우선순위도 사용할 수 있다. 이와 같은 예약된 우선순위를 모두 사용한다면 최대 63개의 응용 태스크를 생성할 수 있고, 보통의 경우는 56개의 태스크를 사용할 수 있다.
우선순위는 낮을수록 더 높은 우선순위를 의미한다. uC/OS-II는 현재 실행 준비상태가 된 태스크 중 가장 높은 우선순위의 태스크를 실행한다. uC/OS-II는 태스크의 우선순위를 태스크 식별자로 사용한다. 즉, OSTaskChangePrio() 나 OSTaskDel()과 같은 시스템 서비스에서 태스크를 식별하기 위해 우선순위를 사용한다.
작성한 태스크를 uC/OS-II가 인식하고 관리하게 해주려면 항상 '생성'과정을 거쳐야 한다. 이 때 태스크의 실행번지와 다른 몇몇 전달인자를 가지고 OSTaskCreate() 서비스나 OSTaskCreateExt() 서비스 함수를 호출하여 태스크를 생성한다.

출처> uC/OS-II 실시간 커널

2008년 4월 22일 화요일

uC/OS-II 커널구조(1) - 크리티컬 섹션

uc/OS-II의 핵심 서비스 함수
OS_ENTER_CRITICAL()
OS_EXIT_CRITICAL()
OSInit()
OSStart()
OSIntEnter()
OSIntExit()
OSSchedLock() -> OS_SCHED_LOCK_EN
OSSchedUnlock() -> OS_SCHED_LOCK_EN
OSVersion()

-----------------------------------------------------------------------------------------
1. OS_ENTER_CRITICAL() / OS_EXIT_CRITICAL()
서로 다른 태스크와 ISR이 크리티컬 섹션에 동시에 진입하는 것을 방지하기 위해 인터럽트를 비활성화/활성화 시키기 위한 함수.
프로세서는 일반적으로 인터럽트를 활성화/비활성화 하는 명령어를 제공한다. 이는 각 컴파일러의 특성에 따라 메커니즘이 다르기 때문에 uC/OS-II 에서는 OS_ENTER_CRITICAL() / OS_EXIT_CRITICAL() 매크로를 사용한다. 이 매크로는 프로세서에 의존적이기 때문에 서로 다른 고유의 OS_CPU.H 파일로 분리되어 있다.
이 매크로들을 이용하여 크리티컬 섹션의 영역을 쌍으로 감싼다.

{
..
OS_ENTER_CRITICAL();
// uC/OS-II 크리티컬 섹션
OS_EXIT_CRITICAL();
..
}

응용 프로그램에서도 이 매크로들을 사용하여 크리티컬 섹션을 보호할 수 있다. 단, 인터럽트를 비활성화시킨 상태에서 OSTimeDly() 같은 함수를 호출하면 시스템이 멈출수 있으므로 주의해야 한다. (태스크가 일정 시간동안 정지상태에로 들어가면서 이 함수를 호출하는데 인터럽트가 비활성화 상태면 타임 틱 인터럽트가 발생할 수 없으므로 문제가 발생한다)
일반적으로 uC/OS-II 서비스를 호출할 때, 인터럽트는 활성화 상태여야 한다.

OS_ENTER_CRITICAL() / OS_EXIT_CRITICAL()은 서로 다른 세가지 방법으로 구현한다. 포트를 만들 때 프로세서와 컴파일러의 기능에 따라 적절한 방법을 선택해야 한다. 실제 사용시는 응용에 사용하고자 하는 포트에 따라 OS_CPU.H에 정의한 OS_CRITICAL_METHOD 상수에 적절한 값을 지정해야 한다.

1) OS_CRITICAL_METHOD == 1
OS_ENTER_CRITICAL() 매크로에 인터럽트를 비활성화는 프로세서 명령을 정의하고,
OS_EXIT_CRITICAL() 매크로에 인터럽트를 활성화하는 프로세서 명령을 정의.
이것의 문제점은 인터럽트 비활성화 상태에서 uC/OS-II 함수를 호출하면, 함수 복귀 시 인터럽트는 활성화 상태가 되지만, uC/OS-II 함수 호출 전에 의도적으로 인터럽트를 비활성화 했다면, 함수 복귀 후에도 인터럽트가 비활성화 되어 있어야 하지만 이런 방법으로는 불가능하다. 특정 프로세서/컴파일러는 이 방법말고는 예외가 없는 경우도 있다.
2) OS_CRITICAL_METHOD == 2
인터럽트 활성화/비활성화 상태를 스택에 저장한 뒤 인터럽트를 비활성화하는 것이다. OS_EXIT_CRITICAL()은 단순히 인터럽트 상태를 스택에서 읽어와 복구하는 방식으로 구현하면 된다. 이 방식을 사용하면, 인터럽트가 비활성화 상태이든 활성화 상태이든 함수 호출 뒤에 이전 상태를 그대로 유지할 수 있다. 단, 인터럽트를 비활성화한 상태에서 uC/OS-II 함수를 호출하면 인터럽트 지연시간이 늘어나서 전체 시스템의 응답성이 떨어지므로 특별한 주의가 필요하다.

#define OS_ENTER_CRITICAL() \
asm("PUSH PSW") \ -> 프로세서 상태워드를 스택에 저장.
asm(" DI") -> 인터럽트 비활성화

#define OS_EXIT_CRITICAL() \
asm("POP PSW") -> 스택에 저장했던 프로세서의 인터럽트 복구

3) OS_CRITICAL_METHOD == 3
어떤 컴파일러는 프로세서 상태 워드의 값을 읽어서 C함수 안에서 선언한 지역변수에 저장하는 기능이 있다. PSW복구를 위해 이 변수를 사용할 수 있다.

void Some_uCOS_II_Service(arguments)
{
OS_CPU_SR cpu_sr;

...
cpu_sr = get_processor_psw();
disable_interrupt();

// 크리티컬 섹션
...
set_processor_psw(cpu_sr);
...
}

출처> uC/OS-II 실시간 커널...