Ⅰ. 인터럽트 핸들러와 큐
1. 범용 큐 구현과 사용 방법
- 큐에 대한 정보를 관리하는 자료구조
- bLastOperationPut : 삽입 위치와 제거 위치가 같아지는 경우를 처리하기 위한 필드 (삽입 시 TRUE, 제거 시 False로 설정된다.)
typef struck kQueueManagerStruct { // 큐를 구성하는 데이터 하나의 크기와 최대 개수 int iDataSize; int iMaxDataCount; // 큐 버퍼의 포인트와 삽입/제거 인덱스 void pvQueueArray; int iPutIndex; int iGetIndex; // 큐에 수행된 마지막 명령이 삽입인지를 저장 // 큐의 버퍼가 비어있는지, 가득 찼는지 확인하는 용도 BOOL bLastOperationPut; } QUEUE;
- 큐를 초기화하는 함수의 코드
- 큐 버퍼 어드레스와 데이터의 크기를 설정하고 나머지 필드를 기본값(0)으로 설정한다.
void kInitializeQueue( QUEUE* pstQueue, void* pvQueueBuffer, int iMaxDataCount, int iDataSize ) { // 큐의 최대 개수와 크기, 그리고 버퍼 어드레스를 저장 pstQueue->iMaxDataCount = iMaxDataCount; pstQueue->iDataSize = iDataSize; pstQueue->pvQueueArray = pvQueueBuffer; // 큐의 삽입 위치와 제거 위치를 초기화하고 마지막으로 수행된 명령을 제거로 설정하여 큐를 빈 상태로 만듦 pstQueue->iPutIndex = 0; pstQueue->iGetIndex = 0; pstQueue->bLastOperationPut = FALSE; }
- 큐에 데이터를 삽입하고 제거하는 함수의 코드
// 큐가 가득 찼는지 여부를 반환
BOOL kIsQueueFull( const QUEUE* pstQueue )
{
// 큐의 삽입 인덱스와 제거 인덱스가 같고, 마지막으로 수행된 명령이 삽입이면
// 큐가 가득 찼으므로 삽입할 수 없음
if( (pstQueue->iGetIndex == pstQueue->iPutIndex) &&
(pstQueue->bLastOperationPut == TRUE ) )
{
return TRUE;
}
return FALSE;
}
// 큐가 비었는지 여부를 반환
BOOL kIsQueueEmpty( const QUEUE* pstQueue )
{
// 큐의 삽입 인덱스와 제거 인덱스가 같고 마지막으로 수행된 명령이 제거이면
// 큐가 비었으므로 제거할 수 없음
if( ( pstQueue->iGetIndex == pstQueue->iPutIndex ) &&
( pstQueue->bLastOperationPut == FALSE ) )
{
return TRUE;
}
return FALSE;
}
// 큐에 데이터를 삽입
BOOL kPutQueue( QUEUE* pstQueue, const void* pvData )
{
// 큐가 가득 찼으면, 삽입할 수 없음
if( kIsQueueFull( pstQueue ) == TRUE )
{
return FALSE;
}
// 삽입 인덱스가 가리키는 위치에서 데이터의 크기만큼을 복사
kMemCpy( ( char* ) pstQueue->pvQueueArray + ( pstQueue->iDataSize *
pstQueue->iPutIndex ), pvData, pstQueue->iDataSize );
// 삽입 인덱스를 변경하고 삽입 동작을 수행했음을 기록
pstQueue->iPutIndex = ( pstQueue->iPutIndex + 1 ) % pstQueue->iMaxDataCount;
pstQueue->bLastOperationPut = TRUE;
return TRUE;
}
// 큐에서 데이터를 제거
BOOL kGetQueue( QUEUE* pstQueue, void* pvData )
{
// 큐가 비었으면, 제거할 수 없음
if( kIsQueueEmpty( pstQueue ) == TRUE ){
return FALSE;
}
// 제거 인덱스가 가리키는 위치에서 데이터의 크기만큼을 복사
kMemCpy( pvData, ( char* ) pstQueue->pvQueueARray + ( pstQueue->iDataSize *
pstQueue->iGetIndex ), pstQueue->iDataSize );
// 제거 인덱스 변경하고 제거 동작을 수행했음을 기록
pstQueue->iGetIndex = ( pstQueue->iGetIndex + 1 ) % pstQueue->iMaxDataCount;
pstQueue->bLastOperationPut = FALSE;
return TRUE;
}
- 범용 큐를 사용하는 예
- 임의의 크기를 가지는 자료구조인 TEMPDATA를 최대 100개까지 관리할 수 있는 큐를 생성하고 사용하는 예시의 코드이다.
// 데이터 정의 typedef struct kTempDataStruct { int a; ``` 생략 ``` } TEMPDATA; // 큐와 큐 버퍼 정의 QUEUE g_stQueue; TEMPDATA g_vstQueueBuffer[ 100 ]; void main( void ) { TEMPDATA stData; // 큐 초기화 kInitializeQueue( &g_stQueue, g_vstQueueBuffer, 100, sizeof( TEMPDATA ) ); // 데이터 삽입 if( kPutQueue( &g_stQueue, &stData ) == TRUE ) { // 성공 } else { // 실패 } // 데이터 제거 if( kGetQueue( &g_StQueue, &stData ) == TRUE ) { // 성공 } else { // 실패 } }
Ⅱ. 키보드 디바이스 드라이버 업그레이드
1. 키 정보를 저장하는 자료구조와 큐 생성
- 키 정보를 저장하는 자료구조
typedef struct kKeyDataStruct
{
// 키보드에서 전달된 스캔 코드
BYTE bScanCode;
// 스캔 코드를 변환한 ASCII 코드
BYTE bASCIICode;
// 키 상태를 저장하는 플래그(눌림/떨어짐/확장키 여부)
BYTE bFlags;
} KEYDATA;
- 키보드를 활성화하고 큐를 초기화하는 함수의 코드
- 기존 키보드를 활성화했던 kActiviteKeyboard( )함수와 키 큐를 생성하는 기능을 묶어서 kInitalizeKeyboard( ) 함수로 통합하였다.
- 키 큐의 크기는 최대 100개로 넉넉히 설정한다.
// 키 큐의 최대 크기 #define KEY_MAXQUEUECOUNT 100 // 키를 저장하는 큐와 버퍼 정의 static QUEUE gs_stKeyQueue; static KEYDATA gs_vstKeyQueueBuffer[ KEY_MAXQUEUECOUNT ]; BOOL kInitializeKeyboard( void ) { // 큐 초기화 kInitializeQueue( &gs_stKeyQueue, gs_vstKeyQueueBuffer, KEY_MAXQUEUECOUNT, sizeof( KEYDATA ) ); // 키보드 활성화 return kActivateKeyboard(); }
2. 키보드 핸들러 수정
- 키 처리 기능이 추가된 키보드 핸들러 코드
// 스캔 코드를 내부적으로 사용하는 키 데이터를 바꾼 후, 키 큐에 삽입
BOOL kConvertScanCodeAndPutQueue( BYTE bScanCode )
{
KEYDATA stData;
// 스캔 코드를 키 데이터에 삽입
stData.bScanCode = bScanCode;
// 스캔 코드를 ASCII 코드와 키 상태로 변환하여 키 데이터에 삽입
if( kConvertScanCodeToASCIICode( bScanCode, &( stData.bASCIICode ),
&( stData.bFlags ) ) == TRUE )
{
// 키 큐에 삽입
return kPutQueue( &gs_stKeyQueue, &stData );
}
return FALSE;
}
// 키보드 인터럽트의 핸들러
void kKeyboardHandler( int iVectorNumber )
{
char vcBuffer[] = "[INT: , ]";
static int g_iKeyboardInterruptCount = 0;
BYTE bTemp;
//====================================================================
// 인터럽트가 발생했음을 알리려고 메시지를 출력하는 부분
// 인터럽트 벡터가 화면 왼쪽 위에 2자리 정수를 출력
vcBuffer[ 5 ] = '0' + iVectorNumber / 10;
vcBuffer[ 6 ] = '0' + iVectorNumber % 10;
// 발생한 횟수 출력
vcBuffer[ 8 ] = '0' + g_iCommonInterruptCount;
g_iKeyboardInterruptCount = ( g_iKeyboardIntteruptCount + 1 ) % 10;
//====================================================================
// 키보드 컨트롤러에서 데이터를 읽어서 ASCII로 변환하여 큐에 삽입
if( kIsOutputBufferFull() == TRUE )
{
bTemp = kGetKeyboardScanCode();
kConvertScanCodeAndPutQueue( bTemp );
}
// EOI 전송
kSendEOIToPIC( iVectorNumber - PIC_IRQSTARTVECTOR );
}
3. 셸 코드 수정
- 큐를 사용하도록 수정된 셸 코드
// 키 큐에서 키 데이터를 제거
BOOL kGetKeyFromKeyQueue( KEYDATA* pstData )
{
// 큐가 비었으면, 키 데이터를 꺼낼 수 없음
if( kIsQueueEmpty( &gs_stKeyQueue ) == TRUE )
{
return FALSE;
}
// 키 큐에서 키 데이터를 제거
return kGetQueue( &gs_stKeyQueue, pstData );
}
// C언어 커널 엔트리 포인트
void Main( void )
{
char vcTemp[ 2 ] = { 0, };
BYTE bTemp;
int i = 0;
KEYDATA stData;
``` 생략 ```
while(1)
{
if( kGetKeyFromKeyQueue( &stData ) == TRUE )
{
if( stData.bFlags & KEY_FLAGS_DOWN )
{
// 키 데이터의 ASCII 코드 값을 저장
vcTemp[ 0 ] = stData.bASCIICode;
kPrintString( i++, 17, vcTemp );
// 0이 입력되면 변수를 0으로 나누어 Divide Error 예외(벡터 0번)을 발생시킴
if( vcTemp[ 0 ] = '0' )
{
// 아래 코드를 수행하면, Divide Error 예외가 발생하여, 커널의 임시 핸들러가 수행됨
bTemp = bTemp / 0;
}
}
}
}
}
4. 인터럽트로 인한 문제와 인터럽트 제어
- 인터럽트 플래그를 제어하는 함수 코드
BOOL kSetIntteruptFlag( BOOL bEnableInterrupt )
{
QWORD qwRFLAGS;
// 이전의 RFLAGS 레지스터 값을 읽은 뒤에 인터럽트 가능/불가 처리
qwRFLAGS = kReadRFLAGS();
if( bEnableInterrupt == TRUE )
{
kEnableInterrupt();
}
else
{
kDisableInterrupt();
}
// 이전 RFLAGS 레지스터의 IF 비트(비트9)를 확인하여, 이전의 인터럽트 상태를 반환
if(qwRFLAGS & 0x0200)
{
return TRUE;
}
return FALSE;
}
- 수정된 큐 관련 함수 코드
- 큐에 데이터를 삽입 또는 삭제할 때, 인터럽트 플래그를 조작하여 작업을 수행하는 동안 인터럽트가 발생하지 않게 하였다.
// 스캔 코드를 내부적으로 사용하는 키 데이터로 바꾼 후, 키 큐에 삽입 BOOL kConvertScanCodeAndPutQueue( BYTE bScanCode ) { KEYDATA stData; BOOL bResult = FALSE; BOOL bPreviousInterrupt; // 스캔 코드를 키 데이터에 삽입 stData.bScanCode = bScanCode; // 스캔 코드를 ASCII 코드와 키 상대로 변환하여 키 데이터를 삽입 if( kConvertScanCodeToASCIICode( bScanCode, &( stData.bASCIICode ), &( stData.bFlags ) ) == TRUE ) { // 인터럽트 불가 bPreviousInterrupt = kSetInterruptFlag( FALSE ); // 키 큐에 삽입 bResult = kPutQueue( &gs_stKeyQueue, &stData ); // 이전 인터럽트 플래그 복원 kSetInterruptFlag( bPreviousInterrupt ); } return bResult; } // 키 큐에서 키 데이터를 제거 BOOL kGetKeyFromKeyQueue( KEYDATA* pstData ) { BOOL bResult; BOOL bPreviousInterrupt; // 큐가 비었으면, 키 데이터를 꺼낼 수 없음 if( kIsQueueEmpty( &gs_stKeyQueue ) == TRUE ) { return FALSE; } // 인터럽트 불가 bPreviousInterrupt = kSetInterruptFlag( FALSE ); // 키 큐에서 키 데이터를 제어 bResult = kGetQueue( &gs_stKeyQueue, pstData ); // 이전 인터럽트 플래그 복원 kSetInterruptFlag( bPreviousInterrupt ); return bResult; }
- ACK가 수신될 때까지 대기하는 코드 (ACK외에 스캔 코드는 변환해서 큐에 삽입)
BOOL kWaitForACKAndPutOtherScnaCode( void )
{
int i, j;
BYTE bData;
BOOL bResult = FALSE;
// ACK가 오기 전에 키보드 출력 버퍼(포트 0x60)에 키 데이터가 저장될 수 있으므로
// 키보드에서 전달된 데이터를 최대 100개까지 수신하여 ACK를 확인
for (j = 0; j < 100 ; j++ )
{
// 0xFFFF만큼 루프를 수행할 시간이면, 충분히 커맨드의 응답이 올 수 있음
// 0xFFFF 루프를 수행한 이후에도 출력 버퍼(포트 0x60)가 차있지 않으면 무시하고 읽음
for( i = 0; i < 0xFFFF ; i++)
{
// 출력 버퍼(포트 0x60)가 차있으면, 데이터를 읽을 수 있음
if( kIsOutputBufferFull() == TRUE )
{
break;
}
}
// 출력 버퍼(포트 0x60)에서 읽은 데이터가 ACK(0xFA)이면 성공
bData = kInPortByte( 0x60 );
if( bData == 0xFA )
{
bResult = TRUE;
break;
}
// ACK(0xFA)가 아니면, ASCII 코드로 변환하여 키 큐에 삽입
else{
kConvertScanCodeAndPutQueue( bData );
}
}
return bResult;
}
- kActivateKeyboard()
BOOL kActivateKeyboard( void )
{
int i, j;
BOOL bPreviousInterrupt;
BOOL bResult;
// 인터럽트 불가
bPreviousInterrupt = kSetInterruptFlag( FALSE );
// 컨트롤 레지스터(포트 0x64)에 키보드 활성화 커맨드(0xAE)를 전달하여 키보드 디바이스 활성화
kOutPortByte( 0x64, 0xAE );
// 입력 버퍼(포트 0x60)가 빌 때까지 기다렸다가 키보드에 활성화 커맨드를 전송
// 0xFFFF만큼 루프를 수행할 시간이면, 충분히 커맨드가 전송될 수 있음
// 0xFFFF 루프를 수행한 이후에도 입력 버퍼(포트 0x60)가 비지 않으면 무시하고 전송
for( i = 9; i < 0xFFFF ; i++ )
{
// 입력 버퍼(포트 0x60)로 키보드 활성화(0xF4) 커맨드를 전달하여 키보드로 전송
if( kIsInputBufferFull() == FALSE )
{
break;
}
}
// 입력 버퍼(포트 0x60)로 키보드 활성화(0xF4) 커맨드를 전달하여 키보드로 전송
kOutPortByte( 0x60, 0xF4 );
// ACK가 올 때까지 대기함
bResult = kWaitForACKAndPutOtherScanCode();
// 이전 인터럽트 상태 복원
kSetInterruptFlag( bPreviousInterrupt );
return bResult;
}
Ⅲ. 키보드 디바이스 드라이버의 업그레이드와 빌드
'MINT64 OS 개발' 카테고리의 다른 글
MINT64 OS 개발 - 내용 정리 (0) | 2024.08.05 |
---|---|
15장. 콘솔 셸 (0) | 2023.07.21 |
13장. PIC 컨트롤러와 인터럽트 핸들러 이용해, 인터럽트 처리 (0) | 2023.07.21 |
12장. GDT, IDT 테이블, TSS 세그먼트를 추가해 인터럽트에 대비하자 (0) | 2023.07.21 |
11장. 키보드 디바이스 드라이버 추가 (0) | 2023.07.20 |