/** * 태스크 2 * 자신의 ID를 참고하여 특정 위치에 회전하는 바람개비를 출력 */ void kTestTask2( void ) { int i = 0, iOffset; CHARACTER* pstScreen = ( CHARACTER* ) CONSOLE_VIDEOMEMORYADDRESS; TCB* pstRunningTask; char vcData[ 4 ] = { '-', '\\', '|', '/' }; // 자신의 ID를 얻어서 화면 오프셋으로 사용 pstRunningTask = kGetRunningTask(); iOffset = ( pstRunningTask->stLink.qwID & 0xFFFFFFFF ) * 2; iOffset = CONSOLE_WIDTH * CONSOLE_HEIGHT - ( iOffset % ( CONSOLE_WIDTH * CONSOLE_HEIGHT ) ); while( 1 ) { // 회전하는 바람개비를 표시 pstScreen[ iOffset ].bCharactor = vcData[ i % 4 ]; // 색깔 지정 pstScreen[ iOffset ].bAttribute = ( iOffset % 15 ) + 1; i++; // 다른 태스크로 전환 kSchedule(); } }
// Task 2 // Along ID, print rotating character void kTestTask2(void) { int i = 0; int iOffset; CHARACTER * pstScreen = (CHARACTER *) CONSOLE_VIDEOMEMORYADDRESS; TCB * pstRunningTask; char vcData[4] = {'-', '\\', '|', '/'}; // Get ID and use is as offset of monitor pstRunningTask = kGetRunningTask(); iOffset = (pstRunningTask->stLink.qwID & 0xFFFFFFFF) * 2; iOffset = CONSOLE_WIDTH * CONSOLE_HEIGHT - (iOffset % (CONSOLE_WIDTH * CONSOLE_HEIGHT)); while (1) { // Set rotating character pstScreen[iOffset].bCharactor = vcData[i % 4]; // Set Color pstScreen[iOffset].bAttribute = (iOffset % 15) + 1; i++; // Switch task kSchedule(); } }
/** * 태스크 1 * 화면 테두리를 돌면서 문자를 출력 */ void kTestTask1( void ) { BYTE bData; int i = 0, iX = 0, iY = 0, iMargin; CHARACTER* pstScreen = ( CHARACTER* ) CONSOLE_VIDEOMEMORYADDRESS; TCB* pstRunningTask; // 자신의 ID를 얻어서 화면 오프셋으로 사용 pstRunningTask = kGetRunningTask(); iMargin = ( pstRunningTask->stLink.qwID & 0xFFFFFFFF ) % 10; // 화면 네 귀퉁이를 돌면서 문자 출력 while( 1 ) { switch( i ) { case 0: iX++; if( iX >= ( CONSOLE_WIDTH - iMargin ) ) { i = 1; } break; case 1: iY++; if( iY >= ( CONSOLE_HEIGHT - iMargin ) ) { i = 2; } break; case 2: iX--; if( iX < iMargin ) { i = 3; } break; case 3: iY--; if( iY < iMargin ) { i = 0; } break; } // 문자 및 색깔 지정 pstScreen[ iY * CONSOLE_WIDTH + iX ].bCharactor = bData; pstScreen[ iY * CONSOLE_WIDTH + iX ].bAttribute = bData & 0x0F; bData++; // 다른 태스크로 전환 kSchedule(); } }
// Task 1 // Print character around monitor void kTestTask1(void) { BYTE bData; int i = 0, iX = 0, iY = 0, iMargin; CHARACTER * pstScreen = (CHARACTER *)CONSOLE_VIDEOMEMORYADDRESS; TCB * pstRunningTask; // Get ID and use is as offset of monitor pstRunningTask = kGetRunningTask(); iMargin = (pstRunningTask->stLink.qwID & 0xFFFFFFFF) % 10; while (1) { switch (i) { case 0: iX++; if ( iX >= (CONSOLE_WIDTH - iMargin) ) { i = 1; } break; case 1: iY++; if ( iY >= (CONSOLE_HEIGHT - iMargin) ) { i = 2; } break; case 2: iX--; if ( iX < iMargin ) { i = 3; } break; case 3: iY--; if ( iY < iMargin ) { i = 0; } break; } // Set characture and color pstScreen[iY * CONSOLE_WIDTH + iX].bCharactor = bData; pstScreen[iY * CONSOLE_WIDTH + iX].bAttribute = bData & 0x0F; bData++; // Switch task kSchedule(); } }
/** * 태스크를 종료 */ BOOL kEndTask( QWORD qwTaskID ) { TCB* pstTarget; BYTE bPriority; BOOL bPreviousFlag; // 임계 영역 시작 bPreviousFlag = kLockForSystemData(); // 현재 실행중인 태스크이면 EndTask 비트를 설정하고 태스크를 전환 pstTarget = gs_stScheduler.pstRunningTask; if( pstTarget->stLink.qwID == qwTaskID ) { pstTarget->qwFlags |= TASK_FLAGS_ENDTASK; SETPRIORITY( pstTarget->qwFlags, TASK_FLAGS_WAIT ); // 임계 영역 끝 kUnlockForSystemData( bPreviousFlag ); kSchedule(); // 태스크가 전환 되었으므로 아래 코드는 절대 실행되지 않음 while( 1 ) ; } // 실행 중인 태스크가 아니면 준비 큐에서 직접 찾아서 대기 리스트에 연결 else { // 준비 리스트에서 태스크를 찾지 못하면 직접 태스크를 찾아서 태스크 종료 비트를 // 설정 pstTarget = kRemoveTaskFromReadyList( qwTaskID ); if( pstTarget == NULL ) { // 태스크 ID로 직접 찾아서 설정 pstTarget = kGetTCBInTCBPool( GETTCBOFFSET( qwTaskID ) ); if( pstTarget != NULL ) { pstTarget->qwFlags |= TASK_FLAGS_ENDTASK; SETPRIORITY( pstTarget->qwFlags, TASK_FLAGS_WAIT ); } // 임계 영역 끝 kUnlockForSystemData( bPreviousFlag ); return TRUE; } pstTarget->qwFlags |= TASK_FLAGS_ENDTASK; SETPRIORITY( pstTarget->qwFlags, TASK_FLAGS_WAIT ); kAddListToTail( &( gs_stScheduler.stWaitList ), pstTarget ); } // 임계 영역 끝 kUnlockForSystemData( bPreviousFlag ); return TRUE; }
/** * getch() 함수의 구현 */ BYTE kGetCh( void ) { KEYDATA stData; // 키가 눌러질때까지 대기함 while( 1 ) { // 키 큐에 데이터가 수신될 때까지 대기 while( kGetKeyFromKeyQueue( &stData ) == FALSE ) { kSchedule(); } // 키가 눌렸다는 데이터가 수신되면 ASCII 코드를 반환 if( stData.bFlags & KEY_FLAGS_DOWN ) { return stData.bASCIICode; } } }
/** * 태스크 사이에서 사용하는 데이터를 위한 잠금 함수 */ void kLock( MUTEX* pstMutex ) { // 이미 잠겨 있다면 내가 잠갔는지 확인하고 잠근 횟수를 증가시킨 뒤 종료 if( kTestAndSet(&( pstMutex->bLockFlag ), 0, 1 ) == FALSE ) { // 자신이 잠갔다면 횟수만 증가시킴 if( pstMutex->qwTaskID == kGetRunningTask()->stLink.qwID ) { pstMutex->dwLockCount++; return ; } // 자신이 아닌 경우는 잠긴 것이 해제될 때까지 대기 while( kTestAndSet( &( pstMutex->bLockFlag ), 0, 1 ) == FALSE ) { kSchedule(); } } // 잠김 설정, 잠김 플래그는 위의 kTestAndSet() 함수에서 처리함 pstMutex->dwLockCount = 1; pstMutex->qwTaskID = kGetRunningTask()->stLink.qwID; }
// 태스크 사이에서 사용하는 데이터를 위한 잠금 함수 void kLock(MUTEX* pstMutex) { BYTE bCurrentAPICID; BOOL bInterruptFlag; // 인터럽트를 비활성화 bInterruptFlag = kSetInterruptFlag(FALSE); // 현재 코어의 로컬 APIC ID를 확인 bCurrentAPICID = kGetAPICID(); // 이미 잠겨 있다면 내가 잠갔는지 확인하고 잠근 횟수를 증가시킨 뒤 종료 if(kTestAndSet(&(pstMutex->bLockFlag),0,1)==FALSE) { // 자신이 잠겼다면 횟수만 증가시킴 if(pstMutex->qwTaskID==kGetRunningTask(bCurrentAPICID)->stLink.qwID) { // 인터럽트를 복원 kSetInterruptFlag(bInterruptFlag); pstMutex->dwLockCount++; return; } // 자신이 아닌 경우는 잠긴 것이 해제될 때까지 대기 while(kTestAndSet(&(pstMutex->bLockFlag),0,1)==FALSE) { kSchedule(); } } // 잠금 설정, 잠긴 플래그는 위의 kTestAndSet() 함수에서 처리함 pstMutex->dwLockCount =1; pstMutex->qwTaskID=kGetRunningTask(bCurrentAPICID)->stLink.qwID; // 인터럽트를 복원 kSetInterruptFlag(bInterruptFlag); }
/** * 태스크를 종료 */ BOOL kEndTask( QWORD qwTaskID ) { TCB* pstTarget; BYTE bPriority; BYTE bAPICID; // 태스크가 포함된 코어의 로컬 APIC ID를 찾은 후, 스핀락을 잠금 if( kFindSchedulerOfTaskAndLock( qwTaskID, &bAPICID ) == FALSE ) { return FALSE; } // 현재 실행중인 태스크이면 EndTask 비트를 설정하고 태스크를 전환 pstTarget = gs_vstScheduler[ bAPICID ].pstRunningTask; if( pstTarget->stLink.qwID == qwTaskID ) { pstTarget->qwFlags |= TASK_FLAGS_ENDTASK; SETPRIORITY( pstTarget->qwFlags, TASK_FLAGS_WAIT ); // 임계 영역 끝 kUnlockForSpinLock( &( gs_vstScheduler[ bAPICID ].stSpinLock ) ); // 현재 스케줄러에서 실행중인 태스크의 경우만 아래를 적용 if( kGetAPICID() == bAPICID ) { kSchedule(); // 태스크가 전환 되었으므로 아래 코드는 절대 실행되지 않음 while( 1 ) { ; } } return TRUE; } // 실행 중인 태스크가 아니면 준비 큐에서 직접 찾아서 대기 리스트에 연결 // 준비 리스트에서 태스크를 찾지 못하면 직접 태스크를 찾아서 태스크 종료 비트를 // 설정 pstTarget = kRemoveTaskFromReadyList( bAPICID, qwTaskID ); if( pstTarget == NULL ) { // 태스크 ID로 직접 찾아서 설정 pstTarget = kGetTCBInTCBPool( GETTCBOFFSET( qwTaskID ) ); if( pstTarget != NULL ) { pstTarget->qwFlags |= TASK_FLAGS_ENDTASK; SETPRIORITY( pstTarget->qwFlags, TASK_FLAGS_WAIT ); } // 임계 영역 끝 kUnlockForSpinLock( &( gs_vstScheduler[ bAPICID ].stSpinLock ) ); return TRUE; } pstTarget->qwFlags |= TASK_FLAGS_ENDTASK; SETPRIORITY( pstTarget->qwFlags, TASK_FLAGS_WAIT ); kAddListToTail( &( gs_vstScheduler[ bAPICID ].stWaitList ), pstTarget ); // 임계 영역 끝 kUnlockForSpinLock( &( gs_vstScheduler[ bAPICID ].stSpinLock ) ); return TRUE; }
/** * 유휴 태스크 * 대기 큐에 삭제 대기중인 태스크를 정리 */ void kIdleTask( void ) { TCB* pstTask, * pstChildThread, * pstProcess; QWORD qwLastMeasureTickCount, qwLastSpendTickInIdleTask; QWORD qwCurrentMeasureTickCount, qwCurrentSpendTickInIdleTask; QWORD qwTaskID, qwChildThreadID; int i, iCount; void* pstThreadLink; BYTE bCurrentAPICID; BYTE bProcessAPICID; // 현재 코어의 로컬 APIC ID를 확인 bCurrentAPICID = kGetAPICID(); // 프로세서 사용량 계산을 위해 기준 정보를 저장 qwLastSpendTickInIdleTask = gs_vstScheduler[ bCurrentAPICID ].qwSpendProcessorTimeInIdleTask; qwLastMeasureTickCount = kGetTickCount(); while( 1 ) { // 현재 상태를 저장 qwCurrentMeasureTickCount = kGetTickCount(); qwCurrentSpendTickInIdleTask = gs_vstScheduler[ bCurrentAPICID ].qwSpendProcessorTimeInIdleTask; // 프로세서 사용량을 계산 // 100 - ( 유휴 태스크가 사용한 프로세서 시간 ) * 100 / ( 시스템 전체에서 // 사용한 프로세서 시간 ) if( qwCurrentMeasureTickCount - qwLastMeasureTickCount == 0 ) { gs_vstScheduler[ bCurrentAPICID ].qwProcessorLoad = 0; } else { gs_vstScheduler[ bCurrentAPICID ].qwProcessorLoad = 100 - ( qwCurrentSpendTickInIdleTask - qwLastSpendTickInIdleTask ) * 100 /( qwCurrentMeasureTickCount - qwLastMeasureTickCount ); } // 현재 상태를 이전 상태에 보관 qwLastMeasureTickCount = qwCurrentMeasureTickCount; qwLastSpendTickInIdleTask = qwCurrentSpendTickInIdleTask; // 프로세서의 부하에 따라 쉬게 함 kHaltProcessorByLoad( bCurrentAPICID ); // 대기 큐에 대기중인 태스크가 있으면 태스크를 종료함 if( kGetListCount( &( gs_vstScheduler[ bCurrentAPICID ].stWaitList ) ) > 0 ) { while( 1 ) { // 임계 영역 시작 kLockForSpinLock( &( gs_vstScheduler[ bCurrentAPICID ].stSpinLock ) ); pstTask = kRemoveListFromHeader( &( gs_vstScheduler[ bCurrentAPICID ].stWaitList ) ); // 임계 영역 끝 kUnlockForSpinLock( &( gs_vstScheduler[ bCurrentAPICID ].stSpinLock ) ); if( pstTask == NULL ) { break; } if( pstTask->qwFlags & TASK_FLAGS_PROCESS ) { // 프로세스를 종료할 때 자식 스레드가 존재하면 스레드를 모두 // 종료하고, 다시 자식 스레드 리스트에 삽입 iCount = kGetListCount( &( pstTask->stChildThreadList ) ); for( i = 0 ; i < iCount ; i++ ) { // 임계 영역 시작 kLockForSpinLock( &( gs_vstScheduler[ bCurrentAPICID ].stSpinLock ) ); // 스레드 링크의 어드레스에서 꺼내 스레드를 종료시킴 pstThreadLink = ( TCB* ) kRemoveListFromHeader( &( pstTask->stChildThreadList ) ); if( pstThreadLink == NULL ) { // 임계 영역 끝 kUnlockForSpinLock( &( gs_vstScheduler[ bCurrentAPICID ].stSpinLock ) ); break; } // 자식 스레드 리스트에 연결된 정보는 태스크 자료구조에 있는 // stThreadLink의 시작 어드레스이므로, 태스크 자료구조의 시작 // 어드레스를 구하려면 별도의 계산이 필요함 pstChildThread = GETTCBFROMTHREADLINK( pstThreadLink ); // 다시 자식 스레드 리스트에 삽입하여 해당 스레드가 종료될 때 // 자식 스레드가 프로세스를 찾아 스스로 리스트에서 제거하도록 함 kAddListToTail( &( pstTask->stChildThreadList ), &( pstChildThread->stThreadLink ) ); qwChildThreadID = pstChildThread->stLink.qwID; // 임계 영역 끝 kUnlockForSpinLock( &( gs_vstScheduler[ bCurrentAPICID ].stSpinLock ) ); // 자식 스레드를 찾아서 종료 kEndTask( qwChildThreadID ); } // 아직 자식 스레드가 남아있다면 자식 스레드가 다 종료될 때까지 // 기다려야 하므로 다시 대기 리스트에 삽입 if( kGetListCount( &( pstTask->stChildThreadList ) ) > 0 ) { // 임계 영역 시작 kLockForSpinLock( &( gs_vstScheduler[ bCurrentAPICID ].stSpinLock ) ); kAddListToTail( &( gs_vstScheduler[ bCurrentAPICID ].stWaitList ), pstTask ); // 임계 영역 끝 kUnlockForSpinLock( &( gs_vstScheduler[ bCurrentAPICID ].stSpinLock ) ); continue; } // 프로세스를 종료해야 하므로 할당 받은 메모리 영역을 삭제 else { // 유저 레벨 프로세스라면 메모리를 할당 받았을 것이므로 할당 // 받은 메모리를 삭제 if( pstTask->qwFlags & TASK_FLAGS_USERLEVEL ) { kFreeMemory( pstTask->pvMemoryAddress ); } } } else if( pstTask->qwFlags & TASK_FLAGS_THREAD ) { // 스레드라면 프로세스의 자식 스레드 리스트에서 제거 pstProcess = kGetProcessByThread( pstTask ); if( pstProcess != NULL ) { // 프로세스 ID로 프로세스가 속한 스케줄러의 ID를 찾고 스핀락 잠금 if( kFindSchedulerOfTaskAndLock( pstProcess->stLink.qwID, &bProcessAPICID ) == TRUE ) { kRemoveList( &( pstProcess->stChildThreadList ), pstTask->stLink.qwID ); kUnlockForSpinLock( &( gs_vstScheduler[ bProcessAPICID ].stSpinLock ) ); } } } // 여기까지 왔다면 태스크가 정상적으로 종료된 것이므로, // 태스크 자료구조(TCB)와 스택을 반환 qwTaskID = pstTask->stLink.qwID; // 스택을 반환 kFreeMemory( pstTask->pvStackAddress ); // 태스크 자료구조(TCB)를 반환 kFreeTCB( qwTaskID ); kPrintf( "IDLE: Task ID[0x%q] is completely ended.\n", qwTaskID ); } } kSchedule(); } }
/** * 유휴 태스크 * 대기 큐에 삭제 대기중인 태스크를 정리 */ void kIdleTask( void ) { TCB* pstTask; QWORD qwLastMeasureTickCount, qwLastSpendTickInIdleTask; QWORD qwCurrentMeasureTickCount, qwCurrentSpendTickInIdleTask; BOOL bPreviousFlag; QWORD qwTaskID; // 프로세서 사용량 계산을 위해 기준 정보를 저장 qwLastSpendTickInIdleTask = gs_stScheduler.qwSpendProcessorTimeInIdleTask; qwLastMeasureTickCount = kGetTickCount(); while( 1 ) { // 현재 상태를 저장 qwCurrentMeasureTickCount = kGetTickCount(); qwCurrentSpendTickInIdleTask = gs_stScheduler.qwSpendProcessorTimeInIdleTask; // 프로세서 사용량을 계산 // 100 - ( 유휴 태스크가 사용한 프로세서 시간 ) * 100 / ( 시스템 전체에서 // 사용한 프로세서 시간 ) if( qwCurrentMeasureTickCount - qwLastMeasureTickCount == 0 ) { gs_stScheduler.qwProcessorLoad = 0; } else { gs_stScheduler.qwProcessorLoad = 100 - ( qwCurrentSpendTickInIdleTask - qwLastSpendTickInIdleTask ) * 100 /( qwCurrentMeasureTickCount - qwLastMeasureTickCount ); } // 현재 상태를 이전 상태에 보관 qwLastMeasureTickCount = qwCurrentMeasureTickCount; qwLastSpendTickInIdleTask = qwCurrentSpendTickInIdleTask; // 프로세서의 부하에 따라 쉬게 함 kHaltProcessorByLoad(); // 대기 큐에 대기중인 태스크가 있으면 태스크를 종료함 if( kGetListCount( &( gs_stScheduler.stWaitList ) ) >= 0 ) { while( 1 ) { // 임계 영역 시작 bPreviousFlag = kLockForSystemData(); pstTask = kRemoveListFromHeader( &( gs_stScheduler.stWaitList ) ); if( pstTask == NULL ) { // 임계 영역 끝 kUnlockForSystemData( bPreviousFlag ); break; } qwTaskID = pstTask->stLink.qwID; kFreeTCB( qwTaskID ); // 임계 영역 끝 kUnlockForSystemData( bPreviousFlag ); kPrintf( "IDLE: Task ID[0x%q] is completely ended.\n", qwTaskID ); } } kSchedule(); } }
/** * 유휴 태스크 * 대기 큐에 삭제 대기중인 태스크를 정리 */ void kIdleTask( void ) { TCB* pstTask, * pstChildThread, * pstProcess; QWORD qwLastMeasureTickCount, qwLastSpendTickInIdleTask; QWORD qwCurrentMeasureTickCount, qwCurrentSpendTickInIdleTask; int i, iCount; QWORD qwTaskID; void* pstThreadLink; // 프로세서 사용량 계산을 위해 기준 정보를 저장 qwLastSpendTickInIdleTask = gs_stScheduler.qwSpendProcessorTimeInIdleTask; qwLastMeasureTickCount = kGetTickCount(); while( 1 ) { // 현재 상태를 저장 qwCurrentMeasureTickCount = kGetTickCount(); qwCurrentSpendTickInIdleTask = gs_stScheduler.qwSpendProcessorTimeInIdleTask; // 프로세서 사용량을 계산 // 100 - ( 유휴 태스크가 사용한 프로세서 시간 ) * 100 / ( 시스템 전체에서 // 사용한 프로세서 시간 ) if( qwCurrentMeasureTickCount - qwLastMeasureTickCount == 0 ) { gs_stScheduler.qwProcessorLoad = 0; } else { gs_stScheduler.qwProcessorLoad = 100 - ( qwCurrentSpendTickInIdleTask - qwLastSpendTickInIdleTask ) * 100 /( qwCurrentMeasureTickCount - qwLastMeasureTickCount ); } // 현재 상태를 이전 상태에 보관 qwLastMeasureTickCount = qwCurrentMeasureTickCount; qwLastSpendTickInIdleTask = qwCurrentSpendTickInIdleTask; // 프로세서의 부하에 따라 쉬게 함 kHaltProcessorByLoad(); // 대기 큐에 대기중인 태스크가 있으면 태스크를 종료함 if( kGetListCount( &( gs_stScheduler.stWaitList ) ) >= 0 ) { while( 1 ) { // 임계 영역 시작 kLockForSpinLock( &( gs_stScheduler.stSpinLock ) ); pstTask = kRemoveListFromHeader( &( gs_stScheduler.stWaitList ) ); if( pstTask == NULL ) { // 임계 영역 끝 kUnlockForSpinLock( &( gs_stScheduler.stSpinLock ) ); break; } if( pstTask->qwFlags & TASK_FLAGS_PROCESS ) { // 프로세스를 종료할 때 자식 스레드가 존재하면 스레드를 모두 // 종료하고, 다시 자식 스레드 리스트에 삽입 iCount = kGetListCount( &( pstTask->stChildThreadList ) ); for( i = 0 ; i < iCount ; i++ ) { // 스레드 링크의 어드레스에서 꺼내 스레드를 종료시킴 pstThreadLink = ( TCB* ) kRemoveListFromHeader( &( pstTask->stChildThreadList ) ); if( pstThreadLink == NULL ) { break; } // 자식 스레드 리스트에 연결된 정보는 태스크 자료구조에 있는 // stThreadLink의 시작 어드레스이므로, 태스크 자료구조의 시작 // 어드레스를 구하려면 별도의 계산이 필요함 pstChildThread = GETTCBFROMTHREADLINK( pstThreadLink ); // 다시 자식 스레드 리스트에 삽입하여 해당 스레드가 종료될 때 // 자식 스레드가 프로세스를 찾아 스스로 리스트에서 제거하도록 함 kAddListToTail( &( pstTask->stChildThreadList ), &( pstChildThread->stThreadLink ) ); // 자식 스레드를 찾아서 종료 kEndTask( pstChildThread->stLink.qwID ); } // 아직 자식 스레드가 남아있다면 자식 스레드가 다 종료될 때까지 // 기다려야 하므로 다시 대기 리스트에 삽입 if( kGetListCount( &( pstTask->stChildThreadList ) ) > 0 ) { kAddListToTail( &( gs_stScheduler.stWaitList ), pstTask ); // 임계 영역 끝 kUnlockForSpinLock( &( gs_stScheduler.stSpinLock ) ); continue; } // 프로세스를 종료해야 하므로 할당 받은 메모리 영역을 삭제 else { // TODO: 추후에 코드 삽입 } } else if( pstTask->qwFlags & TASK_FLAGS_THREAD ) { // 스레드라면 프로세스의 자식 스레드 리스트에서 제거 pstProcess = kGetProcessByThread( pstTask ); if( pstProcess != NULL ) { kRemoveList( &( pstProcess->stChildThreadList ), pstTask->stLink.qwID ); } } qwTaskID = pstTask->stLink.qwID; kFreeTCB( qwTaskID ); // 임계 영역 끝 kUnlockForSpinLock( &( gs_stScheduler.stSpinLock ) ); kPrintf( "IDLE: Task ID[0x%q] is completely ended.\n", qwTaskID ); } } kSchedule(); } }