키보드 디바이스 드라이버를 이용하여, 커맨드를 입력받아 작업을 수행하는 셸을 만든다.
① 키보드에서 키를 입력받아 커맨드 버퍼를 관리하고, 커맨드에 해당하는 프로그램을 실행하는 부분
② 텍스트 화면을 관리하는 부분
③ 셸에 의해, 실행될 프로그램
콘솔 자료구조와 매크로
// 비디오 메모리의 속성(Attribute) 값 설정
#define CONSOLE_BACKGROUND_BLACK 0x00
#define CONSOLE_BACKGROUND_BLUE 0x10
#define CONSOLE_BACKGROUND_GREEN 0x20
#define CONSOLE_BACKGROUND_CYAN 0x30
#define CONSOLE_BACKGROUND_RED 0x40
#define CONSOLE_BACKGROUND_MAGENTA 0x50
#define CONSOLE_BACKGROUND_BROWN 0x60
#define CONSOLE_BACKGROUND_WHITE 0x70
#define CONSOLE_BACKGROUND_BLINK 0x80
#define CONSOLE_FOREGROUND_DARKBLACK 0x00
#define CONSOLE_FOREGROUND_DARKBLUE 0x01
#define CONSOLE_FOREGROUND_DARKGREEN 0x02
#define CONSOLE_FOREGROUND_DARKCYAN 0x03
#define CONSOLE_FOREGROUND_DARKRED 0x04
#define CONSOLE_FOREGROUND_DARKMAGENTA 0x05
#define CONSOLE_FOREGROUND_DARKBROWN 0x06
#define CONSOLE_FOREGROUND_DARKWHITE 0x07
#define CONSOLE_FOREGROUND_BRIGHTBLACK 0x08
#define CONSOLE_FOREGROUND_BRIGHTBLUE 0x09
#define CONSOLE_FOREGROUND_BRIGHTGREEN 0x0A
#define CONSOLE_FOREGROUND_BRIGHTCYAN 0x0B
#define CONSOLE_FOREGROUND_BRIGHTRED 0x0C
#define CONSOLE_FOREGROUND_BRIGHTMAGENTA 0x0D
#define CONSOLE_FOREGROUND_BRIGHTYELLOW 0x0E
#define CONSOLE_FOREGROUND_BRIGHTWHITE 0x0F
// 기본 문자 색상
#define CONSOLE_DEFAULTTEXTCOLOR ( CONSOLE_BACKGROUND_BLACK | \\
CONSOLE_FOREGROUND_BRIGHTGREEN )
// 콘솔의 너비(Width)와 높이(Height),그리고 비디오 메모리의 시작 어드레스 설정
#define CONSOLE_WIDTH 80
#define CONSOLE_HEIGHT 25
#define CONSOLE_VIDEOMEMORYADDRESS 0xB8000
// 비디오 컨트롤러의 I/O 포트 어드레스와 레지스터
#define VGA_PORT_INDEX 0x3D4
#define VGA_PORT_DATA 0x3D5
#define VGA_INDEX_UPPERCURSOR 0x0E
#define VGA_INDEX_LOWERCURSOR 0x0F
// 1바이트로 정렬
#pragma pack( push, 1 )
// 콘솔에 대한 정보를 저장하는 자료구조
typedef struct kConsoleManagerStruct
{
// 문자와 커서를 출력할 위치
int iCurrentPrintOffset;
} CONSOLEMANAGER;
#pragma pack( pop )
제어 문자와 스크롤을 처리하는 문자열 출력 함수의 코드
int kConsolePrintString( const char* pcBuffer )
{
CHARACTER* pstScreen = ( CHARACTER* ) CONSOLE_VIDEOMEMORYADDRESS;
int i, j;
int iLength;
int iPrintOffset;
// 문자열을 출력할 위치를 저장
iPrintOffset = gs_stConsoleManager.iCurrentPrintOffset;
// 문자열의 길이만큼 화면에 출력
iLength = kStrLen( pcBuffer );
for(i = 0 ; i < iLength ; i++)
{
// 줄바꿈 처리
if( pcBuffer[ i ] == '\\n')
{
// 출력할 위치를 80의 배수 컬럼으로 옮김
// 현재 라인의 남은 문자열의 수만큼 더해서 다음 라인으로 위치
iPrintOffset += ( CONSOLE_WIDTH - ( iPrintOffset % CONSOLE_WIDTH ) );
}
// 탭 처리
else if( pcBuffer[ i ] == '\\t' )
{
// 출력할 위치를 8의 배수 컬럼으로 옮김
iPrintOffset += ( 8 - ( iPrintOffset % 8 ) );
}
// 일반 문자열 출력
else
{
// 비디오 메모리에 문자와 속성을 설정하여 문자를 출력하고
// 출력할 위치를 다음으로 이동
pstScreen[ iPrintOffset ].bCharactor = pcBuffer[ i ];
pstScreen[ iPrintOffset ].bAttribute = CONSOLE_DEFUALTTEXTCOLOR;
iPrintOffset++;
}
// 출력할 위치가 화면의 최댓값(80 * 25)을 벗어나면 스크롤 처리
if( iPrintOffset >= ( CONSOLE_HEIGHT * CONSOLE_WIDTH ) )
{
// 가장 윗줄을 제외한 나머지를 한 줄 위로 복사
kMemCpy( 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++ )
{
// 공백 출력
pstScreen[ j ].bCharactor = ' ';
pstScreen[ j ].bAttribute = CONSOLE_DEFAULTTEXTCOLOR;
}
// 출력할 위치를 가장 아래쪽 라인의 처음으로 설정
iPrintOffset = ( CONSOLE_HEIGHT - 1 ) * CONSOLE_WIDTH;
}
}
return iPrintOffset;
}
printf( ) 함수의 구현
void kPrintf( const char* pcFormatString, ... )
{
va_list ap;
char vcBuffer[ 100 ];
int iNextPrintOffset;
// 가변 인자 리스트를 사용해서 vsprintf()로 처리
va_start( ap, pcFormatString );
kVSPrintf( vcBuffer, pcFormatString, ap );
va_end( ap );
// 포맷 문자열을 화면에 출력
iNextPRintOffset = kConsolePrintString( vcBuffer );
// 커서의 위치를 업데이트
kSetCursor( iNextPrintOffset % CONSOLE_WIDTH, iNextPrintOffset / CONSOLE_WIDTH );
}
커서 위치를 제어하는 코드
// 비디오 컨트롤러의 I/O 포트 어드레스와 레지스터
#define VGA_PORT_INDEX 0x3D4
#define VGA_PORT_DATA 0x3D5
#define VGA_INDEX_UPPERCURSOR 0x0E
#define VGA_INDEX_LOWERCURSOR 0x0F
// 콘솔의 너비
#define CONSOLE_WIDTH 80
// 커서의 위치를 설정
// 문자를 출력할 위치도 같이 설정
void kSetCursor( int iX, int iY )
{
int iLinearValue;
// 커서의 위치를 계산
iLinearValue = iY * CONSOLE_WIDTH + iX;
// CRTC 컨트롤 어드레스 레지스터(포트 0x3D4)에 0x0E를 전송하여 상위 커서 위치 레지스터를 선택
kOutPortByte( VGA_PORT_INDEX, VGA_INDEX_UPPERCURSOR );
// CRTC 컨트롤 데이터 레지스터(포트 0x3D5)에 커서의 상위 바이트를 출력
kOutPortByte( VGA_PORT_DATA, iLinearValue >> 8 );
// CRTC 컨트롤 어드레스 레지스터(포트 0x3D4)에 0x0F를 전송하여
// 하위 커서 위치 레지스터를 선택
kOutPortByte( VGA_PORT_INDEX, VGA_INDEX_LOWERCURSOR );
kOutPortByte( VGA_PORT_DATA, iLinearValue & 0xFF );
// 문자를 출력할 위치 업데이트
gs_stConsoleManager.iCurrentPrintOffset = iLinearValue;
}
// 현재 커서의 위치를 반환
void kGetCursor( int* piX, int* piY )
{
// 저장된 위치를 콘솔 화면의 너비로 나눈 나머지로 X좌표를 구할 수 있으며,
// 화면 너비로 나누면 Y좌표를 구할 수 있음
*piX = gs_stConsoleManager.iCurrentPrintOffset % CONSOLE_WIDTH;
*piY = gs_stConsoleManager.iCurrentPrintOffset / CONSOLE_WIDTH;
}
키 입력을 대기하여 키가 눌렸을 때, 키의 ASCII 코드를 반환하는 코드
BYTE kGetCh( void )
{
KEYDATA stData;
// 키가 눌러질 때까지 대기
while( 1 )
{
// 키 큐에 데이터가 수신될 때까지 대기
while( kGetKeyFromKeyQueue( &stData ) == FALSE )
{
;
}
// 키가 눌렸다는 데이터가 수신되면, ASCII 코드를 반환
if( stData.bFlags & KEY_FLAGS_DOWN )
{
return stData.bASCIICode;
}
}
}
프롬프트(Prompt) : 셸이 사용자로부터 키를 입력받을 준비가 되어 있다는 것을 나타내는 표시
MINT64의 셸은 사용자의 입력을 크게 3가지 그룹으로 구분하여 처리한다.
① 알파벳이나 숫자 같은 셸 커맨드를 입력하는데 사용하는 그룹이다.
(셸은 화면에 키를 표시하여, 정상적으로 처리됨을 알린다.)
② 엔터 키와 백스페이스처럼 입력된 커맨드를 조작하는 그룹이다. (셸은 입력된 명령을 실행하거나 출력된 문자를 삭제한다.)
③ 셸에서 사용되지 않는 그룹이다.
(셸은 이런 키를 무시하므로, 화면에 아무런 변화가 없다.)
입력된 키를 화면에 표시하는 셸 코드
void kStartConsoleShell( void )
{
BYTE bKey;
int iCursorX, iCursorY;
// 프롬프트 출력
kPrintf( "MINT64>" );
while( 1 )
{
// 키가 수신될 때까지 대기
bKey = kGetCh();
// Backspace 키 처리
if( bKey == KEY_BACKSPACE )
{
// 현재 커서 위치를 얻어서 한 문자 앞으로 이동한 다음 공백을 출력
kGetCursor( &iCursorX, &iCursorY );
kPrintString( iCursorX - 1, iCursorY, " " );
kSetCursor( iCurSorX - 1, iCursorY );
}
// 엔터 키 처리
else if( bKey == KEY_ENTER )
{
kPrintf( "\\n" );
// 프롬프트 출력
kPrintf( "%S", "MINT64>" );
}
// shift, Caps Lcok, Num Lock, Scroll Lock은 무시
else if( ( bKey == KEY_LSHIFT ) || ( bKey == KEY_RSHIFT ) ||
( bKey == KEY_CAPSLOCK ) || ( bKey == KEY_NUMLOCK ) ||
( bKey == KEY_SCROLLLOCK ) )
{
;
}
else
{
// TAB은 공백으로 전환
if( bKey == KEY_TAB )
{
bKey = ' ';
}
kPrintf( "%c", bKey );
}
}
}
커맨드 버퍼를 관리하는 기능과 커맨드를 실행하는 기능이 추가된 셸 코드
void kStartConsoleShell( void )
{
char vcCommandBuffer[ 300 ];
int iCommandBufferIndex = 0;
BYTE bKey;
int iCursorX, iCursorY;
// 프롬프트 출력
kPrintf( "MINT64>" );
while( 1 )
{
// 키가 수신될 때까지 대기
bKey = kGetCh();
// Backspace 키 처리
if( bKey == KEY_BACKSPACE )
{
if( iCommandBufferIndex > 0 )
{
// 현재 커서 위치를 얻어서 한 문자 앞으로 이동한 다음 공백을 출력
kGetCursor( &iCursorX, &iCursorY );
kPrintStringXY( iCursorX - 1, iCursorY, " " );
kSetCursor( iCurSorX - 1, iCursorY );
iCommandBufferIndex--;
}
}
// 엔터 키 처리
else if( bKey == KEY_ENTER )
{
kPrintf( "\\n" );
if( iCommandBufferIndex > 0 )
{
// 커맨드 버퍼에 있는 명령을 실행
vcCommandBuffer[ iCommandBUfferIndex ] = '\\0';
kExecuteCommand( vcCommandBuffer );
}
// 프롬프트 출력 및 커맨드 버퍼 초기화
kPrintf( "%S", "MINT64>" );
kMemSet( vcCommandBuffer, '\\0', 300 );
iCommandBufferIndex = 0;
}
// shift, Caps Lcok, Num Lock, Scroll Lock은 무시
else if( ( bKey == KEY_LSHIFT ) || ( bKey == KEY_RSHIFT ) ||
( bKey == KEY_CAPSLOCK ) || ( bKey == KEY_NUMLOCK ) ||
( bKey == KEY_SCROLLLOCK ) )
{
;
}
else
{
// TAB은 공백으로 전환
if( bKey == KEY_TAB )
{
bKey = ' ';
}
// 버퍼에 공간이 남아, 있을 때만 입력 가능
if( iCommandBUfferIndex < 300 )
{
vcCommandBuffer[ iCommandBufferIndex++ ] = bKey;
kPrintf( "%c", bKey );
}
}
}
}