uC/OS-ii 기반의 멀티프로세서를 지원하는 Kernel 개발 ③ 이미 PC의 프로세서들은 듀얼 코어 이상의 멀티코어가 대중화 된지 오래이다. 심지어 스마트폰을 비롯한 임베디드 시장에서도 점차 듀얼 코어 이상의 멀티 프로세서 기술들이 도입되고 있다. 이를 통해 머지 않아(혹은 이미) 멀티 코어 시스템 플랫롬이 시장의 주류가 될 것이라는 것은 쉽게 예상 할 수가 있다. 따라서, 멀티프로세서 환경에 대한 이해와 발생하는 이슈에 대해 알아보고, 이러한 시스템에서 동작하기 위해서 필요한 태스크 스케쥴링, 동기화 등을 제공하는 OS를 개발하기 위하여 선정하였다.
글 M.P.(정준호/한양대, 김운철/과기대, 이동욱/광운대)자료제공 임베디드소프트웨어산업협의회


 IPI가 무사히 전달되어 AP가 Wake Up에 성공하면FLAG값이 1이 되며 loop문을 탈출하게 되고, CPU Counter가 1 상승하여, AP를 기다리는 프로세서가 AP가 무사히 일어났다는 메시지를 출력하게 된다. Task Scheduler는 Round Robin 방식으로 구현되었으며 제한 사항 외에 다음과 같은 특징을 가지도록 구현되었다. 
 1. Task 개수가 Processor의 개수보다 작거나 같은 경우, Context Switch를 하지 않는다.: 이와 같은 경우 굳이 Context Switch를 수행하여  Performance를 감소시킬 필요가 없다고 느꼈다.2. 기존 Priority Scheduler의 Priority Queue Code를 그대로 Round Robin Scheduler Queue로 이용한다.: 64개의 Priority를 가진 Queue는 그대로 64개 slot을 가진 Round Robin Queue로 사용하며, Priority는 각 slot의 index로 사용했다.3. 스케쥴러 개발 각 프로세서들은 다음과 같은 흐름으로 Scheduler를 실행, Task를 수행한다.  1. Processor On2. MPOSStart( )를 통해, 사전에 생성된 Ready 상태인 Task 중 하나를 선택3. OSStartHighRdy( )를 통해, 선택한 Task의 TCB를 이용해 해당 Task를 Load한다.4. Task 수행5. Time Interrupt 발생6. OneTimeInter( ) Time ISR이 실행7. OSIntEnter_2( )를 통해, Time Interrupt를 Mask8. OSIntExit_2( )를 통해, context switch를 하기 위한 다른 Ready 상태의 Task가 있는지 검색하고 있는 경우 현재 실행중인 Task를 Ready 상태로 바꾸고 선택한 Task를 Running 상태로 바꾼다.9. OSIntCtxSwRR( )를 통해, 앞서 현재 실행중인 Task와 앞에서 선택한 Task의 context switch를 수행한다.10. 새로 선택한 Task를 수행11. 5번-10번 반복
 (1) OS Start 전의 준비 endPoint는 Queue에서 마지막으로 생성된 Task ID를 가리킨다.ProcTaskNo[ ]는 각 프로세서가 현재 실행 중인 Task의 ID를 저장하는 Array이다. Array Index는 각 프로세서의 Logical ID에 대응한다. 그림 6의 순서도를 참고해 보면, OSTaskCreate2( ) 함수는 실제 작업할 Task가 추가되며, OSTaskCreateNTD( ) 함수는 IDLE Task를 생성하는 함수이다. 각 Processor는 서로 충돌이 발생하지 않기 위해, 서로 다른 Stack Frame을 가리키는 ESP Register 값을 가져야 한다. 따라서, 작업할 Task가 하나도 없는 최악의 경우를 대비하여 4개의 Idle Task stack이 필요하다. 만약, 작업할 Task가 없다면 이 Idle task stack의 ESP register 값을 가짐으로 각 Processor의 수행에 충돌이 발생하지 않게 된다. MP_start( ) 함수는 앞서 구현한 Multiprocessor 환경을 위한 초기화 및 AP의 Wakeup을 수행하는 함수 이다. 위의 준비가 모두 완료되면, MPOSStart( ) 함수를 통해 최초의 Task 수행을 시작하고 Timer Interrupt를 시작하게 된다. 
 (2) OS Start

OS를 시작하면 앞서 생성된 작업 Task TCB 중 하나를 선택하거나, 작업 Task가 없는 경우 Idle Task TCB를 선택하고 해당 Task를 수행하기 위한 함수 OSStartHighRdy( )를 수행한다.Scheduler에 접근하는 프로세서는 하나여야만 한다.   OS_Sched.c
void MPOSStart (void){         int cpuid, procno;         SCHED_LOCK();
         __asm         {                 mov eax, 1                  cpuid                  shr ebx, 24                  mov [cpuid], ebx         }                                  /* Get Processor ID*/         while(ProcTaskNo[cpuid] <= endPoint){/* Find excutable task in queue*/                  procno = ProcTaskNo[cpuid];                  if(OSTCBPrioTbl[procno]->OSTCBStat == OS_STAT_RDY){                           OSTCBHighRdy = OSTCBPrioTbl[procno];                           OSTCBPrioTbl[procno]->OSTCBStat = OS_STAT_RUN;                           OSStartHighRdy(); /* Execute target specific code to start task*/                  }                  ProcTaskNo[cpuid]++;                                 }         OSTCBHighRdy= OSTCBNTDTbl[cpuid]; /* If task don't exist in queue, do nothing*/
         OSStartHighRdy();                  /* Execute target specific code to start task*/}


 그림 6. OS Task순서도   복수의 프로세서가 접근하면 충돌이 일어나 Consistency를 잃고 오작동을 하게 되기 때문이다. 따라서, SCHED_LOCK( ) 함수를 통해 Lock을 걸어준다. 이 함수는 앞서 구현한 KERNEL_LOCK( )과 같은 Spin Lock으로 구현되어 있으며 변수만 다른 것을 사용한다. MP 환경에서 후에 Scheduler 실행 시, 각 프로세서는 현재 실행 중인 Task ID 정보를 보존해야 하기 때문에, 프로세서 ID를 구할 필요가 있다. 앞서 만든 Get_CPUID( )함수를 사용해도 프로세서 ID를 구할 수는 있지만, 이러한 함수 호출을 이용하면 시간이 오래걸리기 때문에 Assembly를 통해 구하도록 하였다. 다음은 이에 사용된 cupid instruction에 대한 내용이다.   cupid instruction Intel? 64 and IA-32 Architectures Software Developer's Manual Volume 2A: Instruction Set Reference, A-MCPUID returns processor identification and feature information in the EAX, EBX, ECX, and EDX registers.1 The instruction's output is dependent on the contents of the EAX register upon execution (in some cases, ECX as well). Initial EAX Value : 01HEAX : Version Information: Type, Family, Model, and Stepping IDEBX : Bits 07-00: Brand IndexBits 15-08: CLFLUSH line size (Value ? 8 = cache line size in bytes)Bits 23-16: Maximum number of addressable IDs for logical processors in this physical package*.Bits 31-24: Initial APIC IDECX : Feature Information EDX : Feature Information    NOTES:*The nearest power-of-2 integer that is not smaller than EBX[23:16] is the number of unique initial APIC IDs reserved for addressing different logical processors in a physical package. 프로세서 ID를 구하고 나면, 현재 Queue에서 Ready 상태인 Task를 찾는다. 만약 Ready 상태의 Task가 존재한다면, 해당 Task의 TCB를 OSTCBHighRdy 변수에 할당하고 상태를 Run으로 바꿔준다. 만약 존재하지 않는다면, OSTCBNTDTbl[]에서 자기 프로세서 ID에 해당하는 IDLE Task TCB를 선택하여 OSTCBHighRdy 변수에 할당한다. 각 프로세서가 실행한Task TCB 선택이 완료되면, OSStartHighRdy() 함수를 실행한다.
 (3) Initial Task Start 선택한 TCB 정보를 이용하여 해당 Task를 실행할 준비를 하고, 준비가 완료되면 Lock을 해제한다. 이후, context switch를 하기 위해 Local APIC를 Enable하게 해주는 EnlocAPIC()와 Time Interrupt를 실행하는 SetTimer()를 시작시키고, Task를 실행한다.  os_cpu_a.asm ;--------------------------------------; OSStartHighRdy;-------------------------------------- PUBLIC   _OSStartHighRdyEXTRN    _OSRunning:BYTEEXTRN    _OSTCBHighRdy:DWORDEXTRN    _SetTimer:NEAREXTRN    _EnlocAPIC:NEAREXTRN    _SCHED_LOCK_VALUE:DWORD_OSStartHighRdy PROC NEAR         ; Increment OSRunning by 1 (the documentation says to set it to 1,         ; but the ix86l example increments it).                  inc               [_OSRunning]         ; Load the processor stack pointer with OSTCBHighRdy->OSTCBStkPtr                  mov      eax,[_OSTCBHighRdy]; Point to TCB of highest priority task ready to run                           mov      esp,[eax]         ; ESP = OSTCBHighRdy->OSTCBStkPtr        ; Pop all the processor registers from the stack                 popad                  lock btr [_SCHED_LOCK_VALUE], 0;Reset Lock Value                  call _EnlocAPIC;APIC enble                  call _SetTimer;Timer setting         ; Execute a Return from interrupt intruction;                  iretd_OSStartHighRdy ENDP    (4) Timer Interrupt ISR Timer Interrupt가 발생했을 시 수행되는 함수는 OneTimeInter( ) 이다. 이 함수는 Interrupt가 발생되면 OSIntEnter_2( )를 통해 Timer Interrupt를 Mask하고, OSIntExit_2( )를 통해 Task Scheduling을 수행한다.  os_cpu_a.asm ;--------------------------------------; OneTimeInter;-------------------------------------- PUBLIC   _OneTimeInterEXTRN    _SendEOI:NEAREXTRN    _OSIntEnter_2:NEAREXTRN    _OSIntExit_2:NEAR_OneTimeInter     PROC NEAR         ; PUSH processor registers onto the current task's stack                  pushad         ; Send an end-of-interrupt                  call _SendEOI         ; Standard uCOS processing.                 call     _OSIntEnter_2                 call     _OSIntExit_2          ; Pop all the processor registers from the stack                  popad          ; Execute a Return from interrupt intruction;                 iretd_OneTimeInter     ENDP    (5) Mask Timer InterruptTask Scheduling을 방해하지 않도록 Timer Interrupt는 Mask한다.    OS_Sched.c void OSIntEnter_2 (void){         unsigned int *reg;         reg = (unsigned int*)(LocalAPICAddr+LOCAL_LVT_TIMER);         *reg |= TIMER_MASK;        /* Mask timer interrupt           */OS_ENTER_CRITICAL();OSIntNesting++;/* Increment ISR nesting level*/OS_EXIT_CRITICAL();}   Timer register는 다음과 같다.
 (6) Scheduling OS_Sched.c void OSIntExit_2 (void){         int      cpuid;         INT8U    tcbno;         INT8U    sel_tcb;SCHED_LOCK();OS_ENTER_CRITICAL();/* Reschedule only if all ISRs completed & not locked */if((--OSIntNesting | OSLockNesting) == 0) {              __asm{                           mov eax, 1                           cpuid                           shr ebx, 24                           mov [cpuid], ebx     }        /* get cpuid */     tcbno = ProcTaskNo[cpuid]; /* get current TCB no. *//* If current processor don't have some task */if(OSTCBPrioTbl[tcbno] == (OS_TCB *)0){                       sel_tcb = 0;       while( sel_tcb <= endPoint ){/* Search for excutable task */if(OSTCBPrioTbl[sel_tcb]->OSTCBStat == OS_STAT_RDY){       OSTCBCur = OSTCBNTDTbl[cpuid];       OSTCBHighRdy= OSTCBPrioTbl[sel_tcb];      OSTCBHighRdy->OSTCBStat = OS_STAT_RUN;                                            OSCtxSwCtr++;                                             OSIntCtxSwRR();                                                                                           break;                                   }                                   sel_tcb++;                           }                  }      else{     /* If current processor have some task */                           sel_tcb = tcbno;       do{               /* Search for excutable task */                                   sel_tcb++; /* if excutable task exist in queue, switch context*/                                   if(sel_tcb > endPoint)                                      sel_tcb = 0;        if(OSTCBPrioTbl[sel_tcb]->OSTCBStat == OS_STAT_RDY){                      /* change state to ready */OSTCBCur = OSTCBPrioTbl[tcbno];               OSTCBCur->OSTCBStat = OS_STAT_RDY;               OSTCBHighRdy= OSTCBPrioTbl[sel_tcb];             OSTCBHighRdy->OSTCBStat = OS_STAT_RUN;                              ProcTaskNo[cpuid] = sel_tcb;                                           OSCtxSwCtr++;                                             OSIntCtxSwRR();                                            break;                                   }                                                     }while( sel_tcb != tcbno);                  }}OS_EXIT_CRITICAL();SCHED_UNLOCK();ReTimeISR();}  Scheduling은 Consistency를 위해 하나의 프로세서만 접근해야 하므로 Lock을 건다. 현재 Scheduling을 수행할 프로세서가 무엇인지 알기 위해 cpuid instruction을 이용하여 CPU ID를 구한다. 구한 CPU ID를 통해 현재 Scheduling을 수행하는 프로세서가 작업 중인 Task ID를 구하여 tcbno에 할당한다. 만약 현재 CPU가 IDLE Task를 수행 중이라면, (idle task를 수행 중인 CPU의 ProcTaskNo[]에 저장된 TCB ID는 항상 endPoint+1 의 값을 가진다.) Scheduler Queue의 처음부터 Ready상태인 Task가 있는지 검색하고 있다면 해당 Task를 선택한다. 만약 현재 CPU가 Idle Task가 아닌 다른 Task를 작업 중에 있다면, Scheduler Queue에서 다른 Ready 상태인 Task가 있는지 검색하고 있다면 해당 Task를 선택한다. 새로이 작업할 Task를 찾으면, 현재 작업 중인 Task의 TCB 포인터를 OSTCur 변수에 할당하고 상태를 Reday로, 새로 선택한 Task TCB 포인터를 OSTCBHighRdy 변수에 할당하고 상태를 Running으로 만든다. 새로 선택한 Task ID를 ProcTaskNo[ ]에 저장 후 Context Switch를 위해 OSIntCtxSwRR()를 호출한다. 만약 Queue에 Ready 상태인 Task가 없다면 CPU는 현재 작업 중인 Task를 계속해 수행할 것이며, Lock을 해제하고 Timer Interrupt를 재 시작하며 Scheduling을 종료한다.



그림 7. LVT Timer Register


  (7) Context Switch  os_cpu_a.asm- ;---------------------------------------; OSIntCtxSwRR;--------------------------------------- PUBLIC   _OSIntCtxSwRREXTRN    _ReTimeISR:NEAREXTRN    _OSTCBCur:DWORDEXTRN    _OSTCBHighRdy:DWORDEXTRN    _SCHED_LOCK_VALUE:DWORD_OSIntCtxSwRR PROC NEAR; Adjust the stack pointer to remove call to OsIntExit(), locals in          ; OsIntExit() and the call to OSIntCtxSw();                  add      esp,32                     ; Ignore calls to OSIntExit and OSIntCtxSw  ; Save the stack pointer into OSTCBCur->OSTCBStkPtr                  mov      eax,[_OSTCBCur]                  mov      [eax],esp                          ; Stack pointer is ESP         call     _ReTimeISR       ; unmask time interrupt         ; Load the processor stack pointer with OSTCBHighRdy->OSTCBStkPtr         ; Note that EAX is still OSTCBHighRdy.         mov        eax,[_OSTCBHighRdy]            ; EAX is OSTCBHighRdy         mov        esp,[eax]                          ; ESP = OSTCBHighRdy->OSTCBStkPtr         ; Scheduler Unlock               lock btr [_SCHED_LOCK_VALUE], 0             ; Pop all the processor registers from the stack          popad         ; Execute a Return from interrupt intruction;                iretd_OSIntCtxSwRR ENDP    현재 ESP Register 값을 Restore를 위하여 현재 작업 중인 TCB에 저장하고, Timer Interrupt 를 재 시작한다. 새로 작업할 Task TCB의 정보를 Load하여 새로운 Task 작업 준비가 끝나면 Lock을 해제하고 Task를 실행한다. (8)  ETC FunctionIDLE Task를 생성하는 함수는 다음과 같다.   OS_Sched.cINT8U OSTaskCreateNTD (void (*task)(void*pd), void *pdata, OS_STK *ptos, int id){void*psp;INT8Uerr;KERNEL_LOCK();OS_ENTER_CRITICAL();OSTCBNTDTbl[id] = (OS_TCB *)1;/* Reserve the slot to prevent others from doing ...*//* ... the same thing until task is created.*/OS_EXIT_CRITICAL();KERNEL_UNLOCK();psp = (void*)OSTaskStkInit(task, pdata, ptos, 0); /* Initialize the task's stack*/err = OSTCBInitNTD(id, psp, (void *)0, 0, 0, (void*)0, 0);if (err == OS_NO_ERR) {         KERNEL_LOCK();         OS_ENTER_CRITICAL();         OSTaskCtr++;                /* Increment the #tasks counter*/OS_EXIT_CRITICAL();KERNEL_UNLOCK();} else {         KERNEL_LOCK();         OS_ENTER_CRITICAL();OSTCBNTDTbl[id] = (OS_TCB *)0;      /* Make this slot available to others*/OS_EXIT_CRITICAL();         KERNEL_UNLOCK();}return(err); }   IDLE Task TCB를 초기화 하는 함수는 다음과 같다.  OS_Sched.c T8U OSTCBInitNTD (INT8U proc_id, OS_STK *ptos, OS_STK *pbos, INT16U id, INT16U stk_size, void *pext, INT16U opt){OS_TCB *ptcb; KERNEL_LOCK();OS_ENTER_CRITICAL();ptcb = OSTCBFreeList; /* Get a free TCB from the free TCB list*/if (ptcb != (OS_TCB *)0) {OSTCBFreeList= ptcb->OSTCBNext;/* Update pointer to free TCB list*/OS_EXIT_CRITICAL();         KERNEL_UNLOCK();ptcb->OSTCBStkPtr= ptos;/* Load Stack pointer in TCB*/ptcb->OSTCBPrio= (INT8U)proc_id;/* Load task priority into TCB*/ptcb->OSTCBStat= OS_STAT_RDY;/* Task is ready to run*/ptcb->OSTCBDly= 0;/* Task is not delayed*/ #if OS_TASK_CREATE_EXT_ENptcb->OSTCBExtPtr= pext;/* Store pointer to TCB extension*/ptcb->OSTCBStkSize= stk_size;/* Store stack size*/ptcb->OSTCBStkBottom = pbos;/* Store pointer to bottom of stack*/ptcb->OSTCBOpt= opt;/* Store task options*/ptcb->OSTCBId= id;/* Store task ID*/#elsepext= pext;/* Prevent compiler warning if not used */stk_size= stk_size;pbos= pbos;opt= opt;id= id;#endif #if OS_TASK_DEL_ENptcb->OSTCBDelReq= OS_NO_ERR;#endif #ifOS_MBOX_EN || (OS_Q_EN && (OS_MAX_QS >= 2)) || OS_SEM_ENptcb->OSTCBEventPtr= (OS_EVENT *)0;/* Task is not pending on an event*/#endif #ifOS_MBOX_EN || (OS_Q_EN && (OS_MAX_QS >= 2))ptcb->OSTCBMsg= (void*)0;/* No message received*/#endif          KERNEL_LOCK();OS_ENTER_CRITICAL();OSTCBNTDTbl[proc_id]= ptcb;ptcb->OSTCBNext= OSTCBList;/* Link into TCB chain*/ptcb->OSTCBPrev= (OS_TCB *)0;if(OSTCBList != (OS_TCB *)0) {OSTCBList->OSTCBPrev = ptcb;}OSTCBList= ptcb;OS_EXIT_CRITICAL();                  KERNEL_UNLOCK();return(OS_NO_ERR);} else {OS_EXIT_CRITICAL();                  KERNEL_UNLOCK();return(OS_NO_MORE_TCB);}}     Task를 생성하는 함수는 다음과 같다.   OS_Sched.c INT8U OSTaskCreate2 (void (*task)(void*pd), void *pdata, OS_STK *ptos){void*psp;INT8Uerr; if(endPoint > OS_LOWEST_PRIO) {/* Check Task Numbers*/return(OS_PRIO_INVALID);}OS_ENTER_CRITICAL();if(OSTCBPrioTbl[endPoint] != (OS_TCB *)0) /* Check whether First-Task or not */        endPoint++;           OSTCBPrioTbl[endPoint] = (OS_TCB *)1;/* Reserve the slot to prevent others from doing ...*//* ... the same thing until task is created.*/OS_EXIT_CRITICAL();psp = (void*)OSTaskStkInit(task, pdata, ptos, 0); /* Initialize the task's stack*/err = OSTCBInit(endPoint, psp, (void *)0, 0, 0, (void*)0, 0);         if (err == OS_NO_ERR) {           OS_ENTER_CRITICAL();             OSTaskCtr++; /* Increment the #tasks counter*/                  OS_EXIT_CRITICAL();         } else {                  OS_ENTER_CRITICAL();OSTCBPrioTbl[endPoint] = (OS_TCB *)0;/* Make this slot available to others*/                  OS_EXIT_CRITICAL();         }         return (err);}    Scheduler Lock과 Unlock 함수는 다음과 같다.  OS_Sched.c void SCHED_LOCK(){        __asm        {               retry:                      lock BTS [SCHED_LOCK_VALUE], 0                      jnc lock_out               lock_jp:                       cmp [SCHED_LOCK_VALUE], 0                       jne lock_jp                       jmp retry               lock_out:        }}void SCHED_UNLOCK(){        __asm        {               lock btr [SCHED_LOCK_VALUE], 0        }}   Timer Interrupt를 Unmask하여 재 시작하는 함수는 다음과 같다.  OS_Sched.c void ReTimeISR(void){         unsigned int *reg;          reg = (unsigned int*)(LocalAPICAddr+LOCAL_LVT_TIMER);         *reg &= ~TIMER_MASK;}  
4. 키보드 디바이스 드라이버 개발 키보드 컨트롤러는 PC 내부버스와 PortI/O 방식으로 연결되어 있으며, Portaddress는 0x60와 0x64를 사용한다. 실제 할당된 port는 두 개나 데이터를 읽고 쓸 때, 접근 레지스터가 다르므로 실제로는 네 개의 레지스터와 연결된 것과 같다. 크기는 모두 1Byte이며 그 중 상태레지스터가 가장 중요 하다. 키보드 컨트롤러의 상태를 표시하기 때문에 킷값을 읽거나 쓰려면 반드시 체크해야 하는 비트를 포함하기 때문이다.
 Keyboard Register는Control Register, Status Register, Input Buffer, Output Buffer 네 개가 있으며 크기는 모두 1Byte이다. 이중에서 Status Register가 가장 중요한 기능을 담당하는데 Keyboard Controller의 상태를 표시하는 레지스터이기 때문에 키 값을 읽거나 쓰려면 반드시 체크해야 하는 Bit를 포함한다.
 그 외 참고할만한 Keyboard Controller Command는 다음과 같다.    일반적으로 Bootloader가 실행되기 전에 Keyboard는 이미 BIOS에 의해 활성화된 상태이다. 따라서 Keyboard를 활성화하는 단계를 굳이 수행하지 않아도 Keyboard에서 키 값을 읽는데 아무런 문제가 없지만, 만약을 대비하여 Keyboard를 직접 활성화할 필요성이 있다. Keyboard Controller에서 Keyboard Device를 사용 가능하게 하려면, 커맨드 Port로 KeyBoard Device 활성화 커맨드인 0xAE를 보내면 된다. 하지만 이것은 엄밀히 말하면 Keyboard Controller에서 활성화된 것이지 실제 Keyboard가 활성화된 것은 아니다. Keyboard Controller와 Keyboard는 PS/2 방식의 케이블로 연결되어 있으며 PC의 외부에 존재한다. 그래서 Keyboard에도 활성화 커맨드를 보내줄 필요가 있다. Keyboard에 직접 데이터를 보내는 방법은 커맨드를 전송하지 않고, Input Buffer에 Keyboard로 보낼 커맨드를 직접 쓰면 된다. Keyboard는 keyboard Controller와 달리 커맨드나 데이터에 대한 응답이 전송되며, 정상적으로 처리한 경우 ACK(0xFA)를 전송한다. 만일 ACK가 수신되지 않으면 수행 도중 에러가 발생한 것이므로, 재시도하거나 작업을 포기해야 한다.


 앞서 언급했듯이 Keyboard Controller의 Keyboard 활성화는 커맨드 Port에0xAE를 보내는 것으로 가능하다. Keyboard Controller에 커맨드를 보냈다면 남은 것은 Keyboard에 직접 커맨드를 보내는 일이다. Keyboard로 커맨드를 보내려면 Input Buffer의 상태 처리와 Keyboard의 응답 처리를 해야 한다. Keyboard와 Keyboard Controller는 프로세서와 비교하면 아주 느리게 동작하므로 프로세서가 커맨드를 전송하고 한참을 기다려야 수행이 완료된다. 여기서 다루어야 할 문제는 커맨드가 완료될 때까지 얼마나 기다려야 하는 것인가이다. 커맨드를 처리하는 시간은 Keyboard와Keyboard Controller의 상태에 따라 가변적인 부분이기에 Keyboard Controller의 상태를 확인할 수 있는 무언가가 필요하게 된다. 이때 사용하는 것이 Keyboard Controller의 Status Register(Port 0x64)이다. 앞선 표에서 알 수 있듯이, Status Register는 Input Buffer 상태를 표시하는 Bit(Bit 1)와 Output Buffer의 상태를 표시하는 Bit(Bit 0)가 있다. 입력 버터 상태 Bit(Bit 1)를 통해 Input Buffer가 비어있는지 확인 후 Keyboard 커맨드를 송신하고, Output Buffer 상태 Bit(Bit 0)을 통해 Output Buffer에 데이터가 있는지 확인한 후 실행 결과를 읽어들여 보다 효율적으로 처리할 수 있다. 이 작업은 OS_Keyboard.C 파일의 ActivateKeyboard() 함수에서 수행한다.   
 표 4. I/O Port와 Keyboard Controller Register의 관계 


표 5. Status Register의 Bit 구성과 의미


 표 6. Keyboard와 기타 시스템 제어에 관련된 Keyboard Controller Command


  
표 7. LED와 Keyboard 활성화에 관련된 Keyboard Command

  *4월호에 계속 됩니다.
회원가입 후 이용바랍니다.
개의 댓글
0 / 400
댓글 정렬
BEST댓글
BEST 댓글 답글과 추천수를 합산하여 자동으로 노출됩니다.
댓글삭제
삭제한 댓글은 다시 복구할 수 없습니다.
그래도 삭제하시겠습니까?
댓글수정
댓글 수정은 작성 후 1분내에만 가능합니다.
/ 400
내 댓글 모음
저작권자 © 테크월드뉴스 무단전재 및 재배포 금지