정보공간_1

[3기 강남 정준호] Console & Shell 개발 본문

IT 놀이터/Elite Member Tech & Talk

[3기 강남 정준호] Console & Shell 개발

알 수 없는 사용자 2013. 5. 6. 13:35


주제:  Console & Shell 개발

요세 날씨가 너무 좋은거 같아요... 낮에는 따듯하고 꽃들도 화창하게 폇고

정말 좋은 날씨 네요^^ 그래도 저녘에는 좀 추은감이 있는(?) ㅎㅎ

오늘 블로그는 Console & Shell에 대하여 블로깅을 하겠습니다.

전 과정까지의 블로그를 통하여 OS가 갖추어 졌습니다. 하지만 사용자와의 커뮤니티가 없는데요.

키보드와 모니터 디바이스 드라이버를 완성 하였으니(전 블로그에서) 이제 사용자의 명령을 처리하고 수행하는

과정을 설명 드리겠습니다.


* SMP 와 MP Boot

* Scheduler

* Work Queue 동작 과정

* 키보드와 모니터 디바이스 드라이버

* Console & Shell

* Testing



1) Console Queue Buffer


OS_Console.c


OS_Console.h

------- Console 첨부파일 -------


필요성 : 폴링 방식은 프로세서가 키보드 컨트롤러를 주기적으로 확인하므로 프로세서의 소모가 심하고 확인하는 주기에 비례하여 데이터가 수신되는 시간이 길어지는 문제점이 있습니다

인터럽트 방식은 컨트롤러에 데이터가 있거나 데이터를 전송할 수 있을 때 인터럽트를 통해 이를 알려주므로 상대적으로 프로세서를 덜 사용하게 됩니. 이런 차이는 멀티태스킹 환경에서 더욱 확연히 드러날 것입니.

그래서 최종적으로 키보드 디바이스를 인터럽트 방식으로 변경하려면 버퍼는 필수적으로 필요하게 됩니. 왜냐하면 인터럽트는 발생 시점을 예측할 수가 없어 수신된 키 값을 온전히 전달하기가 어렵기 때문입니.

 


 

큐를 다양한 목적으로 활용하기 위해 범용으로 사용할 수 있도록 구현하였습니. 구현 시 주의사항은 데이터의 삽입, 제거 후 버퍼의 최대값을 초과하는 경우와 삽입, 제거 시 두 위치가 같아지는 경우이다. 전자는 환형 큐로 구현하여 간단히 해결하였고 후자는 삽입 후 위치가 같아졌는지 아니면 제거 후 위치가 같아졌는지 구분이 필요합니다.

 

typedef struct QueueManagerStruct

{

int iDataSize;

int iMaxDataCount;

 

void* pvQueueArray;

int iPutIndex;

int iGetIndex;

 

BOOL bLastOperationPut; // Full or Empty

} QUEUE; 


 다음은 큐를 초기화, 데이터 삽입 및 제거, 내부 정보 확인을 위한 함수입니.

void InitializeQueue( QUEUE* pstQueue, void* pvQueueBuffer, int iMaxDataCount,

int iDataSize )

{

pstQueue->iMaxDataCount = iMaxDataCount;

pstQueue->iDataSize = iDataSize;

pstQueue->pvQueueArray = pvQueueBuffer; // Buffer Address

 

pstQueue->iPutIndex = 0;

pstQueue->iGetIndex = 0;

pstQueue->bLastOperationPut = FALSE; // Empty State

}

BOOL IsQueueFull( const QUEUE* pstQueue )

{

if( ( pstQueue->iGetIndex == pstQueue->iPutIndex ) &&

( pstQueue->bLastOperationPut == TRUE ) ) // Last operation - Input

       {

return TRUE;

}

return FALSE;

} 

BOOL IsQueueEmpty( const QUEUE* pstQueue )

{

if( ( pstQueue->iGetIndex == pstQueue->iPutIndex ) &&

( pstQueue->bLastOperationPut == FALSE ) ) // Last operation - Remove

{

return TRUE;

}

return FALSE;

} 


BOOL PutQueue( QUEUE* pstQueue, const void* pvData )

{

if( IsQueueFull( pstQueue ) == TRUE )

{

return FALSE;

}

MemCpy( ( char* ) pstQueue->pvQueueArray + ( pstQueue->iDataSize *

pstQueue->iPutIndex ), pvData, pstQueue->iDataSize ); // Dst, Src, Size

pstQueue->iPutIndex = ( pstQueue->iPutIndex + 1 ) % pstQueue->iMaxDataCount;

pstQueue->bLastOperationPut = TRUE; // Input

return TRUE;

} 


BOOL GetQueue( QUEUE* pstQueue, void* pvData )

{

if( IsQueueEmpty( pstQueue ) == TRUE )

{

return FALSE;

} 

MemCpy( pvData, ( char* ) pstQueue->pvQueueArray + ( pstQueue->iDataSize *

pstQueue->iGetIndex ), pstQueue->iDataSize );

 

pstQueue->iGetIndex = ( pstQueue->iGetIndex + 1 ) % pstQueue->iMaxDataCount;

pstQueue->bLastOperationPut = FALSE; // Remove

return TRUE;

}


2) Console Environment


이번에 구현한 셀은 크게 세 부분으로 구성됩니다.

키보드에서 키를 입력받아 커맨드 버퍼를 관리하고, 커맨드에 해당하는 프로그램을 실행하는 부분입니.(커맨드를 구분하여 해당 프로그램 실행)

텍스트 화면을 관리하는 부분입니.(커서 위치 제어, 화면에 데이터 입출력)

셀에 의해 실행될 프로그램입니다.(각 프로그램은 셀과 독립적)

 

먼저 자주 반복되어 사용되고 또 앞으로도 계속 사용될 기본적인 기능들을 가지는 함수들을 C언어 표준 라이브러리를 참고하여 작성하였습니. 

int StrLen( const char* pcBuffer ); // String Length

long AToI( const char* pcBuffer, int iRadix ); // String -> Integer

int IToA( long lValue, char* pcBuffer, int iRadix ); // Integer -> String

int HexToString( QWORD qwValue, char* pcBuffer ); // Hex -> String

int DecimalToString( long lValue, char* pcBuffer ); // Dec -> String

QWORD HexStringToQword( const char* pcBuffer ); // Hex String -> QWORD

long DecimalStringToLong( const char* pcBuffer ); // Dec String -> Long

void ReverseString( char* pcBuffer ); // reverse order of string

int SPrintf( char* pcBuffer, const char* pcFormatString, ... ); // sprintf()

int VSPrintf( char* pcBuffer, const char* pcFormatString, va_list ap );// handle formatted char 


콘솔은 텍스트 방식이나 명령줄 방식으로 작업을 수행하는 입출력 장치입니다. 아마도 모니터가 출력장치, 키보드가 입력장치일 경우가 대부분일 것이다

현재 텍스트 모드에서 콘솔화면은 최대 80 컬럼에 25 라인까지 가능하여 한 화면에 총 2,000 문자를 출력할 수 있습니.

 

컬럼의 최대값이 80인 점을 이용하여 개행 문자인 ‘\n’은 현재 위치에서 다음 80의 배수 위치로 커서를 옮기는 식으로 처리가 가능할 것이다. 그 외 문자열이 라인의 최대값을 넘을 경우 화면의 스크롤 등의 처리도 고려를 해야 한다.

스크롤은 첫 라인을 제외한 나머지 라인을 하나씩 위로 복사하면 간단하게 구현할 수 있다. 그리고 마지막 라인은 공백으로 두면 마치 스크롤 효과가 일어난 것처럼 느끼게 되는 것입니다. 스크롤 처리는 메모리 복사 함수를 사용하여 구현하였습니. 제어 문자와 화면의 스크롤 효과를 처리하는 문자열 출력 함수는 다음과 같습니.

 

int ConsolePrintString( const char* pcBuffer )

{

CHARACTER* pstScreen = ( CHARACTER* ) CONSOLE_VIDEOMEMORYADDRESS;

int i, j;

int iLength;

int iPrintOffset;

 

iPrintOffset = gs_stConsoleManager.iCurrentPrintOffset;

 

iLength = StrLen( pcBuffer );

for( i = 0 ; i < iLength ; i++ )

{

if( pcBuffer[ i ] == '\n' ) // Line feed

{

iPrintOffset += ( CONSOLE_WIDTH - ( iPrintOffset % CONSOLE_WIDTH ) ); // multiple 80

}

else if( pcBuffer[ i ] == '\t' ) // Tab

{

iPrintOffset += ( 8 - ( iPrintOffset % 8 ) ); // multiple 8

}

else

{

pstScreen[ iPrintOffset ].bCharactor = pcBuffer[ i ];

pstScreen[ iPrintOffset ].bAttribute = CONSOLE_DEFAULTTEXTCOLOR;

iPrintOffset++;

}

if( iPrintOffset >= ( CONSOLE_HEIGHT * CONSOLE_WIDTH ) )

 // if offset > (80*25)? then scroll

{

MemCpy( CONSOLE_VIDEOMEMORYADDRESS,

CONSOLE_VIDEOMEMORYADDRESS + CONSOLE_WIDTH * sizeof( CHARACTER ),

( CONSOLE_HEIGHT - 1 ) * CONSOLE_WIDTH * sizeof( CHARACTER ) );

 

for( j = ( CONSOLE_HEIGHT - 1 ) * ( CONSOLE_WIDTH ) ;

j < ( CONSOLE_HEIGHT * CONSOLE_WIDTH ) ; j++ ) // last null line

{

pstScreen[ j ].bCharactor = ' ';

pstScreen[ j ].bAttribute = CONSOLE_DEFAULTTEXTCOLOR;

}

iPrintOffset = ( CONSOLE_HEIGHT - 1 ) * CONSOLE_WIDTH; // set offet to last line(first col)

}

}

return iPrintOffset;

} 



3) Shell


OS_Shell.c


OS_Shell.h

--------- Shell 첨부 파일 -----------


쉘은 유저의 입력을 크게 세 가지로 구분하여 처리하도록 구분하였습니다.

알파벳이나 숫자 같은 쉘 커맨드를 입력하는 경우입니다.

엔터나 백스페이스 키처럼 입력된 커맨드를 조작하는 경우입니다.

쉘에서 사용되지 않는 키로 무시되는 경우입니다. 

이를 구분하여 처리하는 쉘의 반복 루틴은 다음과 같이 구현하였습니.

void StartConsoleShell( void )

{

char vcCommandBuffer[ CONSOLESHELL_MAXCOMMANDBUFFERCOUNT ]; // Buffer MAX 300

int iCommandBufferIndex = 0;

BYTE bKey;

int iCursorX, iCursorY;

Printf( CONSOLESHELL_PROMPTMESSAGE ); // Command Prompt

while( 1 )

{

bKey = GetCh();

 

if( bKey == KEY_BACKSPACE )

{

if( iCommandBufferIndex > 0 )

{

GetCursor( &iCursorX, &iCursorY );

PrintStringXY( iCursorX - 1, iCursorY, " " );

SetCursor( iCursorX - 1, iCursorY );

iCommandBufferIndex--;

}

}

else if( bKey == KEY_ENTER )

{

Printf( "\n" );

if( iCommandBufferIndex > 0 )

{

vcCommandBuffer[ iCommandBufferIndex ] = '\0';

ExecuteCommand( vcCommandBuffer ); // command exec

}

Printf( "%s", CONSOLESHELL_PROMPTMESSAGE );

MemSet( vcCommandBuffer, '\0', CONSOLESHELL_MAXCOMMANDBUFFERCOUNT );

iCommandBufferIndex = 0; // Initialize buffer

}

else if( ( bKey == KEY_LSHIFT ) || ( bKey == KEY_RSHIFT ) ||

( bKey == KEY_CAPSLOCK ) || ( bKey == KEY_NUMLOCK ) ||

( bKey == KEY_SCROLLLOCK ) )

{

; // skip

}

else

{

if( bKey == KEY_TAB )

{

bKey = ' ';

}

if( iCommandBufferIndex < CONSOLESHELL_MAXCOMMANDBUFFERCOUNT )

{

vcCommandBuffer[ iCommandBufferIndex++ ] = bKey;

Printf( "%c", bKey );

}

}

}

} 

 

일반적으로 커맨드 버퍼에 있는 커맨드를 읽어 들여 실행하는 가장 간단한 방법은 if를 이용하여 분기 구문을 사용하는 것이다. 그러나 추후 많은 수의 커맨드가 추가될 지도 모르기 때문에 나중을 위해 함수 포인터를 사용하여 구현하였습니다.

typedef struct ShellCommandEntryStruct

{

char* pcCommand; // command string

char* pcHelp; // command info

CommandFunction pfFunction; // func ptr

} SHELLCOMMANDENTRY;

 

void ExecuteCommand( const char* pcCommandBuffer )

{

int i, iSpaceIndex;

int iCommandBufferLength, iCommandLength;

int iCount;

 

iCommandBufferLength = StrLen( pcCommandBuffer );

for( iSpaceIndex = 0 ; iSpaceIndex < iCommandBufferLength ; iSpaceIndex++ )

{

if( pcCommandBuffer[ iSpaceIndex ] == ' ' )

{

break;

}

}

iCount = sizeof( gs_vstCommandTable ) / sizeof( SHELLCOMMANDENTRY );

for( i = 0 ; i < iCount ; i++ ) // look for command in table

{

iCommandLength = StrLen( gs_vstCommandTable[ i ].pcCommand );

if( ( iCommandLength == iSpaceIndex ) &&

( MemCmp( gs_vstCommandTable[ i ].pcCommand, pcCommandBuffer,

iSpaceIndex ) == 0 ) )

{

gs_vstCommandTable[ i ].pfFunction( pcCommandBuffer + iSpaceIndex + 1 );

break;

}

}

 

if( i >= iCount )

{

Printf( "'%s' is not found.\n", pcCommandBuffer );

}

}


 기본적으로 문자열 출력이 주 바탕이 되는 커맨드 5개를 간단하게 구현해보았습니다. 

SHELLCOMMANDENTRY gs_vstCommandTable[] =

{

{ "help", "View available commands and their description.", Help },

{ "ver", "Show the version information of the OS and IU shell.", Ver },

{ "cls", "Clear Screen.", Cls },

{ "strto", "String To Decimal/Hex Convert.", StringToDecimalHexTest },

{ "shutdown", "Shutdown and Reboot OS.", Shutdown },

};

 

void Help( const char* pcParameterBuffer ); // HELP Command

void Ver( const char* pcParamegerBuffer ); // Version Info

void Cls( const char* pcParameterBuffer ); // Clear Screen

void StringToDecimalHexTest( const char* pcParameterBuffer ); // Convert str

void Shutdown( const char* pcParamegerBuffer ); // Reboot OS 


이렇게 하여 사용자와의 커뮤니케이션을 할 수 있게 됩니다.

첨부 파일에 구현 파일을 첨부하였습니다. 혹 이해가 잘 되지않으시면 코드를 참고 하셔서 이해에 도움이 되길 바랍니다.

다음 장은 이제 OS의 Testing과 성능 분석에 대하여 포스팅을 하도록 하겠습니다.