// 시스템 전역에서 사용하는 데이터를 위한 잠금 함수 void kLockForSpinLock(SPINLOCK* pstSpinLock) { BOOL bInterruptFlag; // 인터럽트를 먼저 비활성화 bInterruptFlag = kSetInterruptFlag(FALSE); // 이미 잠겨 있다면 내가 잠갔는지 확인하고 그렇다면 잠금 횟수를 증가시킨 뒤 종료 if(kTestAndSet(&(pstSpinLock->bLockFlag),0,1)==FALSE) { // 자신이 잠갔다면 횟수만 증가시킴 if(pstSpinLock->bAPICID == kGetAPICID()) { pstSpinLock->dwLockCount++; return; } // 자신이 아닌 경우는 잠긴 것이 해제될 때까지 대기 while(kTestAndSet(&(pstSpinLock->bLockFlag),0,1)==FALSE) { // kTestAndSet() 함수르 계속 호출하여 메모리 버스가 Lock 되는 것을 방지 while(pstSpinLock->bLockFlag == TRUE) { kPause(); } } } // 잠김 설정, 잠김 플래그는 위의 kTestAndSet() 함수에서 처리 pstSpinLock->dwLockCount = 1; pstSpinLock->bAPICID = kGetAPICID(); // 인터럽트 플래그를 저장하여 Unlock 수행 시 복원 pstSpinLock->bInterruptFlag = bInterruptFlag; }
// Application Processor용 C 언어 커널 엔트리 포인트 // 대부분의 자료구조는 Bootstrap Processor가 생성해 놓았으므로 코어에 설정하는 작업만 함 void mainForApplicationProcessor(void) { // GDT 테이블을 설정 kLoadGDTR(GDTR_STARTADDRESS); // TSS 디스크립터를 설정, TSS 세그먼트와 디스크립터를 Application Processor의 // 수만큼 생성했으므로, APIC ID를 이용하여 TSS 디스크립터를 할당 kLoadTR(GDT_TSSSEGMENT+(kGetAPICID()*sizeof(GDTENTRY16))); // IDT 테이블을 설정 kLoadIDTR(IDTR_STARTADDRESS); // 스케줄러 초기화 kInitializeScheduler(); // 현재 코어의 로컬 APIC를 활성화 kEnableSoftwareLocalAPIC(); // 모든 인터럽트를 수신할 수 있도록 태스크 우선순위 레지스터를 0으로 설정 kSetTaskPriority(0); // 로컬 APIC의 로컬 벡터 테이블을 초기화 kInitializeLocalVectorTable(); // 인터럽트를 활성화 kEnableInterrupt(); kPrintf("Application Processor[APIC ID: %d] is Activated\n",kGetAPICID()); // 유후 태스크 실행 kIdleTask(); }
// 태스크 사이에서 사용하는 데이터를 위한 잠근 해제 함수 void kUnlock(MUTEX* pstMutex) { BOOL bInterruptFlag; // 인터럽트를 비활성화 bInterruptFlag = kSetInterruptFlag(FALSE); // 뮤텍스를 잠근 태스크가 아니면 실패 if((pstMutex->bLockFlag==FALSE)||(pstMutex->qwTaskID!=kGetRunningTask(kGetAPICID())->stLink.qwID)) { // 인터럽트를 복원 kSetInterruptFlag(bInterruptFlag); return ; } // 뮤텍스를 중복으로 잠갔으면 잠긴 횟수만 감소 if(pstMutex->dwLockCount>1) { pstMutex->dwLockCount--; } else { // 해제된 것으로 설정, 잠긴 플래그는 가장 나중에 해제해야함 pstMutex->qwTaskID = TASK_INVALIDID; pstMutex->dwLockCount = 0; pstMutex->bLockFlag=FALSE; } // 인터럽트를 복원 kSetInterruptFlag(bInterruptFlag); }
/** * Device Not Available 예외의 핸들러 */ void kDeviceNotAvailableHandler( int iVectorNumber ) { TCB* pstFPUTask, * pstCurrentTask; QWORD qwLastFPUTaskID; BYTE bCurrentAPICID; //========================================================================= // FPU 예외가 발생했음을 알리려고 메시지를 출력하는 부분 char vcBuffer[] = "[EXC: , ]"; static int g_iFPUInterruptCount = 0; // 예외 벡터를 화면 오른쪽 위에 2자리 정수로 출력 vcBuffer[ 5 ] = '0' + iVectorNumber / 10; vcBuffer[ 6 ] = '0' + iVectorNumber % 10; // 발생한 횟수 출력 vcBuffer[ 8 ] = '0' + g_iFPUInterruptCount; g_iFPUInterruptCount = ( g_iFPUInterruptCount + 1 ) % 10; kPrintStringXY( 0, 0, vcBuffer ); //========================================================================= // 현재 코어의 로컬 APIC ID를 확인 bCurrentAPICID = kGetAPICID(); // CR0 컨트롤 레지스터의 TS 비트를 0으로 설정 kClearTS(); // 이전에 FPU를 사용한 태스크가 있는지 확인하여, 있다면 FPU의 상태를 태스크에 저장 qwLastFPUTaskID = kGetLastFPUUsedTaskID( bCurrentAPICID ); pstCurrentTask = kGetRunningTask( bCurrentAPICID ); // 이전에 FPU를 사용한 것이 자신이면 아무것도 안 함 if( qwLastFPUTaskID == pstCurrentTask->stLink.qwID ) { return ; } // FPU를 사용한 태스크가 있으면 FPU 상태를 저장 else if( qwLastFPUTaskID != TASK_INVALIDID ) { pstFPUTask = kGetTCBInTCBPool( GETTCBOFFSET( qwLastFPUTaskID ) ); if( ( pstFPUTask != NULL ) && ( pstFPUTask->stLink.qwID == qwLastFPUTaskID ) ) { kSaveFPUContext( pstFPUTask->vqwFPUContext ); } } // 현재 태스크가 FPU를 사용한 적이 있는 지 확인하여 FPU를 사용한 적이 없다면 // 초기화하고, 사용한적이 있다면 저장된 FPU 콘텍스트를 복원 if( pstCurrentTask->bFPUUsed == FALSE ) { kInitializeFPU(); pstCurrentTask->bFPUUsed = TRUE; } else { kLoadFPUContext( pstCurrentTask->vqwFPUContext ); } // FPU를 사용한 태스크 ID를 현재 태스크로 변경 kSetLastFPUUsedTaskID( bCurrentAPICID, pstCurrentTask->stLink.qwID ); }
// 시스템 전역에서 사용하는 데이터를 위한 잠금 해제 함수 void kUnlockForSpinLock(SPINLOCK* pstSpinLock) { BOOL bInterruptFlag; // 인터럽트를 먼저 비활성화 bInterruptFlag = kSetInterruptFlag(FALSE); // 스핀락을 잠근 태스크가 아니면 실패 if((pstSpinLock->bLockFlag==FALSE)||(pstSpinLock->bAPICID!=kGetAPICID())) { kSetInterruptFlag(bInterruptFlag); return; } // 스핀락을 중복으로 잠갔으면 잠김 횟수만 감소 if(pstSpinLock->dwLockCount>1) { pstSpinLock->dwLockCount--; return; } // 스핀락을 해제된 것으로 설정하고 인터럽트 플래그를 복원 // 인터럽트 플래그는 미리 저장해두었다가 사용 bInterruptFlag = pstSpinLock->bInterruptFlag; pstSpinLock->bAPICID = 0xFF; pstSpinLock->dwLockCount = 0; pstSpinLock->bInterruptFlag = FALSE; pstSpinLock->bLockFlag = FALSE; kSetInterruptFlag(bInterruptFlag); }
/** * 인터럽트 부하 분산(Interrupt Load Balancing) 처리 */ void kProcessLoadBalancing( int iIRQ ) { QWORD qwMinCount = 0xFFFFFFFFFFFFFFFF; int iMinCountCoreIndex; int iCoreCount; int i; BOOL bResetCount = FALSE; BYTE bAPICID; bAPICID = kGetAPICID(); // 부하 분산 기능이 꺼져 있거나, 부하 분산을 처리할 시점이 아니면 할 필요가 없음 if( ( gs_stInterruptManager.vvqwCoreInterruptCount[ bAPICID ][ iIRQ ] == 0 ) || ( ( gs_stInterruptManager.vvqwCoreInterruptCount[ bAPICID ][ iIRQ ] % INTERRUPT_LOADBALANCINGDIVIDOR ) != 0 ) || ( gs_stInterruptManager.bUseLoadBalancing == FALSE ) ) { return ; } // 코어의 개수를 구해서 루프를 수행하며 인터럽트 처리 횟수가 가장 작은 코어를 // 선택 iMinCountCoreIndex = 0; iCoreCount = kGetProcessorCount(); for( i = 0 ; i < iCoreCount ; i++ ) { if( ( gs_stInterruptManager.vvqwCoreInterruptCount[ i ][ iIRQ ] < qwMinCount ) ) { qwMinCount = gs_stInterruptManager.vvqwCoreInterruptCount[ i ][ iIRQ ]; iMinCountCoreIndex = i; } // 전체 카운트가 거의 최대 값에 근접했다면 나중에 카운트를 모두 0으로 설정 else if( gs_stInterruptManager.vvqwCoreInterruptCount[ i ][ iIRQ ] >= 0xFFFFFFFFFFFFFFFE ) { bResetCount = TRUE; } } // I/O 리다이렉션 테이블을 변경하여 가장 인터럽트를 처리한 횟수가 작은 로컬 APIC로 전달 kRoutingIRQToAPICID( iIRQ, iMinCountCoreIndex ); // 처리한 코어의 카운트가 최댓값에 근접했다면 전체 카운트를 다시 0에서 시작하도록 // 변경 if( bResetCount == TRUE ) { for( i = 0 ; i < iCoreCount ; i++ ) { gs_stInterruptManager.vvqwCoreInterruptCount[ i ][ iIRQ ] = 0; } } }
/** * 공통으로 사용하는 예외 핸들러 */ void kCommonExceptionHandler( int iVectorNumber, QWORD qwErrorCode ) { char vcBuffer[ 100 ]; BYTE bAPICID; TCB* pstTask; // 현재 예외가 발생한 코어를 반환 bAPICID = kGetAPICID(); // 현재 코어에서 실행 중인 태스크를 반환 pstTask = kGetRunningTask( bAPICID ); // 메시지 출력 kPrintStringXY( 0, 0, "====================================================" ); kPrintStringXY( 0, 1, " Exception Occur~!!!! " ); // 예외 벡터와 코어 ID, 에러코드를 출력 kSPrintf( vcBuffer, " Vector:%d Core ID:0x%X ErrorCode:0x%X ", iVectorNumber, bAPICID, qwErrorCode ); kPrintStringXY( 0, 2, vcBuffer ); // 태스크 ID를 출력 kSPrintf( vcBuffer, " Task ID:0x%Q", pstTask->stLink.qwID ); kPrintStringXY( 0, 3, vcBuffer ); kPrintStringXY( 0, 4, "====================================================" ); // 유저 레벨 태스크의 경우는 무한 루프를 수행하지 않고 태스크를 종료시키고 다른 // 태스크로 전환 if( pstTask->qwFlags & TASK_FLAGS_USERLEVEL ) { // 태스크 종료 kEndTask( pstTask->stLink.qwID ); // 무한 루프 수행 // kEndTask() 함수에서 다른 태스크로 전환하므로 실제로 여기는 수행되지 않음 while( 1 ) { ; } } // 커널 레벨인 경우는 무한 루프 수행 else { // 무한 루프 수행 while( 1 ) { ; } } }
/** * 타이머 인터럽트의 핸들러 */ void kTimerHandler( int iVectorNumber ) { char vcBuffer[] = "[INT: , ]"; static int g_iTimerInterruptCount = 0; int iIRQ; BYTE bCurrentAPICID; //========================================================================= // 인터럽트가 발생했음을 알리려고 메시지를 출력하는 부분 // 인터럽트 벡터를 화면 오른쪽 위에 2자리 정수로 출력 vcBuffer[ 5 ] = '0' + iVectorNumber / 10; vcBuffer[ 6 ] = '0' + iVectorNumber % 10; // 발생한 횟수 출력 vcBuffer[ 8 ] = '0' + g_iTimerInterruptCount; g_iTimerInterruptCount = ( g_iTimerInterruptCount + 1 ) % 10; kPrintStringXY( 70, 0, vcBuffer ); //========================================================================= // 인터럽트 벡터에서 IRQ 번호 추출 iIRQ = iVectorNumber - PIC_IRQSTARTVECTOR; // EOI 전송 kSendEOI( iIRQ ); // 인터럽트 발생 횟수를 업데이트 kIncreaseInterruptCount( iIRQ ); // IRQ 0 인터럽트 처리는 Bootstrap Processor만 처리 bCurrentAPICID = kGetAPICID(); if( bCurrentAPICID == 0 ) { // 타이머 발생 횟수를 증가 g_qwTickCount++; } // 태스크가 사용한 프로세서의 시간을 줄임 kDecreaseProcessorTime( bCurrentAPICID ); // 프로세서가 사용할 수 있는 시간을 다 썼다면 태스크 전환 수행 if( kIsProcessorTimeExpired( bCurrentAPICID ) == TRUE ) { kScheduleInInterrupt(); } }
// 태스크 사이에서 사용하는 데이터를 위한 잠금 함수 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); }
/** * 태스크가 자신을 종료함 */ void kExitTask( void ) { kEndTask( gs_vstScheduler[ kGetAPICID() ].pstRunningTask->stLink.qwID ); }
/** * 태스크를 종료 */ 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; }
/** * 인터럽트가 발생했을 때, 다른 태스크를 찾아 전환 * 반드시 인터럽트나 예외가 발생했을 때 호출해야 함 */ BOOL kScheduleInInterrupt( void ) { TCB* pstRunningTask, * pstNextTask; char* pcContextAddress; BYTE bCurrentAPICID; QWORD qwISTStartAddress; // 현재 로컬 APIC ID 확인 bCurrentAPICID = kGetAPICID(); // 임계 영역 시작 kLockForSpinLock( &( gs_vstScheduler[ bCurrentAPICID ].stSpinLock ) ); // 전환할 태스크가 없으면 종료 pstNextTask = kGetNextTaskToRun( bCurrentAPICID ); if( pstNextTask == NULL ) { // 임계 영역 끝 kUnlockForSpinLock( &( gs_vstScheduler[ bCurrentAPICID ].stSpinLock ) ); return FALSE; } //========================================================================== // 태스크 전환 처리 // 인터럽트 핸들러에서 저장한 콘텍스트를 다른 콘텍스트로 덮어쓰는 방법으로 처리 //========================================================================== // IST의 끝부분부터 코어 0 -> 코어 15 순으로 64Kbyte씩 쓰고 있으므로, 로컬 APIC ID를 // 이용해서 IST 어드레스를 계산 qwISTStartAddress = IST_STARTADDRESS + IST_SIZE - ( IST_SIZE / MAXPROCESSORCOUNT * bCurrentAPICID ); pcContextAddress = ( char* ) qwISTStartAddress - sizeof( CONTEXT ); pstRunningTask = gs_vstScheduler[ bCurrentAPICID ].pstRunningTask; gs_vstScheduler[ bCurrentAPICID ].pstRunningTask = pstNextTask; // 유휴 태스크에서 전환되었다면 사용한 Tick Count를 증가시킴 if( ( pstRunningTask->qwFlags & TASK_FLAGS_IDLE ) == TASK_FLAGS_IDLE ) { gs_vstScheduler[ bCurrentAPICID ].qwSpendProcessorTimeInIdleTask += TASK_PROCESSORTIME; } // 태스크 종료 플래그가 설정된 경우, 콘텍스트를 저장하지 않고 대기 리스트에만 삽입 if( pstRunningTask->qwFlags & TASK_FLAGS_ENDTASK ) { kAddListToTail( &( gs_vstScheduler[ bCurrentAPICID ].stWaitList ), pstRunningTask ); } // 태스크가 종료되지 않으면 IST에 있는 콘텍스트를 복사하고, 현재 태스크를 준비 리스트로 // 옮김 else { kMemCpy( &( pstRunningTask->stContext ), pcContextAddress, sizeof( CONTEXT ) ); } // 다음에 수행할 태스크가 FPU를 쓴 태스크가 아니라면 TS bit 설정 if( gs_vstScheduler[ bCurrentAPICID ].qwLastFPUUsedTaskID != pstNextTask->stLink.qwID ) { kSetTS(); } else { kClearTS(); } // 임계 영역 끝 kUnlockForSpinLock( &( gs_vstScheduler[ bCurrentAPICID ].stSpinLock ) ); // 전환해서 실행할 태스크를 Running Task로 설정하고 콘텍스트를 IST에 복사해서 // 자동으로 태스크 전환이 일어나도록 함 kMemCpy( pcContextAddress, &( pstNextTask->stContext ), sizeof( CONTEXT ) ); // 종료하는 태스크가 아니면 스케줄러에 태스크 추가 if( ( pstRunningTask->qwFlags & TASK_FLAGS_ENDTASK ) != TASK_FLAGS_ENDTASK ) { // 스케줄러에 태스크를 추가, 부하 분산을 고려함 kAddTaskToSchedulerWithLoadBalancing( pstRunningTask ); } // 프로세서 사용 시간을 업데이트 gs_vstScheduler[ bCurrentAPICID ].iProcessorTime = TASK_PROCESSORTIME; return TRUE; }
/** * 다른 태스크를 찾아서 전환 * 인터럽트나 예외가 발생했을 때 호출하면 안됨 * 단, 인터럽트나 예외에서 태스크를 종료하는 경우는 사용 가능 */ BOOL kSchedule( void ) { TCB* pstRunningTask, * pstNextTask; BOOL bPreviousInterrupt; BYTE bCurrentAPICID; // 전환하는 도중 인터럽트가 발생하여 태스크 전환이 또 일어나면 곤란하므로 전환하는 // 동안 인터럽트가 발생하지 못하도록 설정 bPreviousInterrupt = kSetInterruptFlag( FALSE ); // 현재 로컬 APIC ID 확인 bCurrentAPICID = kGetAPICID(); // 전환할 태스크가 있어야 함 if( kGetReadyTaskCount( bCurrentAPICID ) < 1 ) { kSetInterruptFlag( bPreviousInterrupt ); return FALSE; } // 임계 영역 시작 kLockForSpinLock( &( gs_vstScheduler[ bCurrentAPICID ].stSpinLock ) ); // 실행할 다음 태스크를 얻음 pstNextTask = kGetNextTaskToRun( bCurrentAPICID ); if( pstNextTask == NULL ) { // 임계 영역 끝 kUnlockForSpinLock( &( gs_vstScheduler[ bCurrentAPICID ].stSpinLock ) ); kSetInterruptFlag( bPreviousInterrupt ); return FALSE; } // 현재 수행중인 태스크의 정보를 수정한 뒤 콘텍스트 전환 pstRunningTask = gs_vstScheduler[ bCurrentAPICID ].pstRunningTask; gs_vstScheduler[ bCurrentAPICID ].pstRunningTask = pstNextTask; // 유휴 태스크에서 전환되었다면 사용한 프로세서 시간을 증가시킴 if( ( pstRunningTask->qwFlags & TASK_FLAGS_IDLE ) == TASK_FLAGS_IDLE ) { gs_vstScheduler[ bCurrentAPICID ].qwSpendProcessorTimeInIdleTask += TASK_PROCESSORTIME - gs_vstScheduler[ bCurrentAPICID ].iProcessorTime; } // 다음에 수행할 태스크가 FPU를 쓴 태스크가 아니라면 TS 비트를 설정 if( gs_vstScheduler[ bCurrentAPICID ].qwLastFPUUsedTaskID != pstNextTask->stLink.qwID ) { kSetTS(); } else { kClearTS(); } // 태스크 종료 플래그가 설정된 경우 콘텍스트를 저장할 필요가 없으므로, 대기 리스트에 // 삽입하고 콘텍스트 전환 if( pstRunningTask->qwFlags & TASK_FLAGS_ENDTASK ) { kAddListToTail( &( gs_vstScheduler[ bCurrentAPICID ].stWaitList ), pstRunningTask ); // 임계 영역 끝 kUnlockForSpinLock( &( gs_vstScheduler[ bCurrentAPICID ].stSpinLock ) ); // 태스크 전환 kSwitchContext( NULL, &( pstNextTask->stContext ) ); } else { kAddTaskToReadyList( bCurrentAPICID, pstRunningTask ); // 임계 영역 끝 kUnlockForSpinLock( &( gs_vstScheduler[ bCurrentAPICID ].stSpinLock ) ); // 태스크 전환 kSwitchContext( &( pstRunningTask->stContext ), &( pstNextTask->stContext ) ); } // 프로세서 사용 시간을 업데이트 gs_vstScheduler[ bCurrentAPICID ].iProcessorTime = TASK_PROCESSORTIME; // 인터럽트 플래그 복원 kSetInterruptFlag( bPreviousInterrupt ); return FALSE; }
/** * 스케줄러를 초기화 * 스케줄러를 초기화하는데 필요한 TCB 풀과 init 태스크도 같이 초기화 */ void kInitializeScheduler( void ) { int i; int j; BYTE bCurrentAPICID; TCB* pstTask; // 현재 코어의 로컬 APIC ID 확인 bCurrentAPICID = kGetAPICID(); // Bootstrap Processor만 태스크 풀과 스케줄러 자료구조를 모두 초기화 if( bCurrentAPICID == 0 ) { // 태스크 풀 초기화 kInitializeTCBPool(); // 준비 리스트와 우선 순위별 실행 횟수를 초기화하고 대기 리스트와 스핀락을 초기화 for( j = 0 ; j < MAXPROCESSORCOUNT ; j++ ) { // 준비 리스트 초기화 for( i = 0 ; i < TASK_MAXREADYLISTCOUNT ; i++ ) { kInitializeList( &( gs_vstScheduler[ j ].vstReadyList[ i ] ) ); gs_vstScheduler[ j ].viExecuteCount[ i ] = 0; } // 대기 리스트 초기화 kInitializeList( &( gs_vstScheduler[ j ].stWaitList ) ); // 스핀락 초기화 kInitializeSpinLock( &( gs_vstScheduler[ j ].stSpinLock ) ); } } // TCB를 할당 받아 부팅을 수행한 태스크를 커널 최초의 프로세스로 설정 pstTask = kAllocateTCB(); gs_vstScheduler[ bCurrentAPICID ].pstRunningTask = pstTask; // BSP의 콘솔 쉘이나 AP의 유휴 태스크(Idle Task)는 모두 현재 코어에서만 실행하도록 // 로컬 APIC ID와 프로세서 친화도를 현재 코어의 로컬 APIC ID로 설정 pstTask->bAPICID = bCurrentAPICID; pstTask->bAffinity = bCurrentAPICID; // Bootstrap Processor는 콘솔 셸을 실행 if( bCurrentAPICID == 0 ) { pstTask->qwFlags = TASK_FLAGS_HIGHEST | TASK_FLAGS_PROCESS | TASK_FLAGS_SYSTEM; } // Application Processor는 특별히 긴급한 태스크가 없으므로 유휴(Idle) 태스크를 실행 else { pstTask->qwFlags = TASK_FLAGS_LOWEST | TASK_FLAGS_PROCESS | TASK_FLAGS_SYSTEM | TASK_FLAGS_IDLE; } pstTask->qwParentProcessID = pstTask->stLink.qwID; pstTask->pvMemoryAddress = ( void* ) 0x100000; pstTask->qwMemorySize = 0x500000; pstTask->pvStackAddress = ( void* ) 0x600000; pstTask->qwStackSize = 0x100000; // 프로세서 사용률을 계산하는데 사용하는 자료구조 초기화 gs_vstScheduler[ bCurrentAPICID ].qwSpendProcessorTimeInIdleTask = 0; gs_vstScheduler[ bCurrentAPICID ].qwProcessorLoad = 0; // FPU를 사용한 태스크 ID를 유효하지 않은 값으로 초기화 gs_vstScheduler[ bCurrentAPICID ].qwLastFPUUsedTaskID = TASK_INVALIDID; }
/** * 태스크를 생성 * 태스크 ID에 따라서 스택 풀에서 스택 자동 할당 * 프로세스 및 스레드 모두 생성 가능 * bAffinity에 태스크를 수행하고 싶은 코어의 ID를 설정 가능 */ TCB* kCreateTask( QWORD qwFlags, void* pvMemoryAddress, QWORD qwMemorySize, QWORD qwEntryPointAddress, BYTE bAffinity ) { TCB* pstTask, * pstProcess; void* pvStackAddress; BYTE bCurrentAPICID; // 현재 코어의 로컬 APIC ID를 확인 bCurrentAPICID = kGetAPICID(); // 태스크 자료구조 할당 pstTask = kAllocateTCB(); if( pstTask == NULL ) { return NULL; } // 동적 메모리 영역에서 스택 할당 pvStackAddress = kAllocateMemory( TASK_STACKSIZE ); if( pvStackAddress == NULL ) { kFreeTCB( pstTask->stLink.qwID ); return NULL; } // 임계 영역 시작 kLockForSpinLock( &( gs_vstScheduler[ bCurrentAPICID ].stSpinLock ) ); // 현재 프로세스 또는 스레드가 속한 프로세스를 검색 pstProcess = kGetProcessByThread( kGetRunningTask( bCurrentAPICID ) ); // 만약 프로세스가 없다면 아무런 작업도 하지 않음 if( pstProcess == NULL ) { kFreeTCB( pstTask->stLink.qwID ); kFreeMemory( pvStackAddress ); // 임계 영역 끝 kUnlockForSpinLock( &( gs_vstScheduler[ bCurrentAPICID ].stSpinLock ) ); return NULL; } // 스레드를 생성하는 경우라면 내가 속한 프로세스의 자식 스레드 리스트에 연결함 if( qwFlags & TASK_FLAGS_THREAD ) { // 현재 스레드의 프로세스를 찾아서 생성할 스레드에 프로세스 정보를 상속 pstTask->qwParentProcessID = pstProcess->stLink.qwID; pstTask->pvMemoryAddress = pstProcess->pvMemoryAddress; pstTask->qwMemorySize = pstProcess->qwMemorySize; // 부모 프로세스의 자식 스레드 리스트에 추가 kAddListToTail( &( pstProcess->stChildThreadList ), &( pstTask->stThreadLink ) ); } // 프로세스는 파라미터로 넘어온 값을 그대로 설정 else { pstTask->qwParentProcessID = pstProcess->stLink.qwID; pstTask->pvMemoryAddress = pvMemoryAddress; pstTask->qwMemorySize = qwMemorySize; } // 스레드의 ID를 태스크 ID와 동일하게 설정 pstTask->stThreadLink.qwID = pstTask->stLink.qwID; // 임계 영역 끝 kUnlockForSpinLock( &( gs_vstScheduler[ bCurrentAPICID ].stSpinLock ) ); // TCB를 설정한 후 준비 리스트에 삽입하여 스케줄링될 수 있도록 함 kSetUpTask( pstTask, qwFlags, qwEntryPointAddress, pvStackAddress, TASK_STACKSIZE ); // 자식 스레드 리스트를 초기화 kInitializeList( &( pstTask->stChildThreadList ) ); // FPU 사용 여부를 사용하지 않은 것으로 초기화 pstTask->bFPUUsed = FALSE; // 현재 코어의 로컬 APIC ID를 태스크에 설정 pstTask->bAPICID = bCurrentAPICID; // 프로세서 친화도(Affinity)를 설정 pstTask->bAffinity = bAffinity; // 부하 분산을 고려하여 스케줄러에 태스크를 추가 kAddTaskToSchedulerWithLoadBalancing( pstTask ); return pstTask; }
/** * 유휴 태스크 * 대기 큐에 삭제 대기중인 태스크를 정리 */ 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(); } }
// Bootstrap Processor용 C 언어 커널 엔트리 포인트 void main( void ) { int iCursorX, iCursorY; // 부트 로더에 있는 BSP 플래그를 읽어서 Application Processor이면 // 해당 코어용 초기화 함수로 이동 if(*((BYTE*)BOOTSTRAPPROCESSOR_FLAGADDESS)==0) { mainForApplicationProcessor(); } // Bootstrap Processor가 부팅을 완료했으므로, 0x7C09에 있는 Bootstrap Processor를 // 나타내는 플래그를 0으로 설정하여 Application Processor용으로 코드 실행 경로를 변경 *((BYTE*)BOOTSTRAPPROCESSOR_FLAGADDESS)=0; // 콘솔을 먼저 초기화한 후, 다음 작업을 수행 kInitializeConsole( 0, 10 ); kPrintf( "Switch To IA-32e Mode Success~!!\n" ); kPrintf( "IA-32e C Language Kernel Start..............[Pass]\n" ); kPrintf( "Initialize Console..........................[Pass]\n" ); // 부팅 상황을 화면에 출력 kGetCursor( &iCursorX, &iCursorY ); kPrintf( "GDT Initialize And Switch For IA-32e Mode...[ ]" ); kInitializeGDTTableAndTSS(); kLoadGDTR( GDTR_STARTADDRESS ); kSetCursor( 45, iCursorY++ ); kPrintf( "Pass\n" ); kPrintf( "TSS Segment Load............................[ ]" ); kLoadTR( GDT_TSSSEGMENT ); kSetCursor( 45, iCursorY++ ); kPrintf( "Pass\n" ); kPrintf( "IDT Initialize..............................[ ]" ); kInitializeIDTTables(); kLoadIDTR( IDTR_STARTADDRESS ); kSetCursor( 45, iCursorY++ ); kPrintf( "Pass\n" ); kPrintf( "Total RAM Size Check........................[ ]" ); kCheckTotalRAMSize(); kSetCursor( 45, iCursorY++ ); kPrintf( "Pass], Size = %d MB\n", kGetTotalRAMSize() ); kPrintf("TCB Poll And Scheduler Initialize...........[Pass]\n"); iCursorY++; kInitializeScheduler(); // 동적 메모리 초기화 kPrintf("Dynamic Memory Initialize...................[Pass]\n"); iCursorY++; kInitializeDynamicMemory(); // 1ms당 한 번씩 인터럽트가 발생하도록 설정 kInitializePIT(MSTOCOUNT(1),1); kPrintf( "Keyboard Activate And Queue Initialize......[ ]" ); // 키보드를 활성화 if( kInitializeKeyboard() == TRUE ) { kSetCursor( 45, iCursorY++ ); kPrintf( "Pass\n" ); kChangeKeyboardLED( FALSE, FALSE, FALSE ); } else { kSetCursor( 45, iCursorY++ ); kPrintf( "Fail\n" ); while( 1 ) ; } kPrintf( "PIC Controller And Interrupt Initialize.....[ ]" ); // PIC 컨트롤러 초기화 및 모든 인터럽트 활성화 kInitializePIC(); kMaskPICInterrupt( 0 ); kEnableInterrupt(); kSetCursor( 45, iCursorY++ ); kPrintf( "Pass\n" ); // 파일 시스템을 초기화 kPrintf("File System Initialize......................[ ]"); if(kInitializeFileSystem()==TRUE) { kSetCursor(45,iCursorY++); kPrintf("Pass\n"); } else { kSetCursor(45,iCursorY++); kPrintf("Fail\n"); } // 시리얼 포트 초기화 kPrintf("Serial Port Initialize......................[Pass]\n"); iCursorY++; kInitializeSerialPort(); // 유후 태스크를 시스템 스레드로 생성하고 셸을 시작 kCreateTask(TASK_FLAGS_LOWEST | TASK_FLAGS_THREAD | TASK_FLAGS_SYSTEM | TASK_FLAGS_IDLE,0,0,(QWORD)kIdleTask,kGetAPICID()); // 그래픽 모드가 아니면 콘솔 셸 실행 if(*(BYTE*)VBE_STARTGRAPHICMODEFLAGADDRESS==0) { kStartConsoleShell(); } // 그래픽 모드면 그래픽 모드 테스트 함수 실행 else { kStartGraphicModeTest(); } }
/** * 코어 별 인터럽트 처리 횟수를 증가 */ void kIncreaseInterruptCount( int iIRQ ) { // 코어의 인터럽트 카운트를 증가 gs_stInterruptManager.vvqwCoreInterruptCount[ kGetAPICID() ][ iIRQ ]++; }