본문 바로가기

MINT64 OS 개발

15장. 콘솔 셸

Ⅰ. 콘솔 입출력 처리

1. 콘솔 자료구조 생성과 printf( ) 함수 구현

  • 콘솔 자료구조와 매크로
// 비디오 메모리의 속성(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 );
}

2. 커서 제어

  • 커서 위치를 제어하는 코드
// 비디오 컨트롤러의 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;
}

3. getch() 함수 구현

  • 키 입력을 대기하여 키가 눌렸을 때, 키의 ASCII 코드를 반환하는 코드
BYTE kGetCh( void )
{
		KEYDATA stData;

		// 키가 눌러질 때까지 대기
		while( 1 )
		{
				// 키 큐에 데이터가 수신될 때까지 대기
				while( kGetKeyFromKeyQueue( &stData ) == FALSE )
				{
						;
				}
				
				// 키가 눌렸다는 데이터가 수신되면, ASCII 코드를 반환
				if( stData.bFlags & KEY_FLAGS_DOWN )
				{
						return stData.bASCIICode;
				}
		}
}

Ⅱ. 셸 구현

1. 프롬프트, 커맨드 버퍼, 사용자 입력 처리

  • 프롬프트(Prompt) : 셸이 사용자로부터 키를 입력받을 준비가 되어 있다는 것을 나타내는 표시
  • MINT64의 셸은 사용자의 입력을 크게 3가지 그룹으로 구분하여 처리한다.

① 알파벳이나 숫자 같은 셸 커맨드를 입력하는데 사용하는 그룹이다.
      (셸은 화면에 키를 표시하여, 정상적으로 처리됨을 알린다.)  

② 엔터 키와 백스페이스처럼 입력된 커맨드를 조작하는 그룹이다.
      (셸은 입력된 명령을 실행하거나 출력된 문자를 삭제한다.)

③ 셸에서 사용되지 않는 그룹이다.

     (셸은 이런 키를 무시하므로, 화면에 아무런 변화가 없다.)

입력된 키를 화면에 표시하는 셸 코드

  • Shift, Caps Lock, Num Lock, Scroll Lock 키를 제외한 나머지 키는 모두 입력 가능하게 처리하였다.
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 );
					}
			}
		}
}

 

2. 커맨드 비교와 커맨드 실행

  • 커맨드 문자열과 함수 포인터를 사용해서, 처리한다.
  • 함수 포인터를 통해, 함수를 호출하는 것과 함수를 직접 호출하는 것은 똑같이 처리된다.
    (이런 특징을 가진 함수 포인트를 사용하면, 함수 이름이 아닌 변수를 통해 함수 호출 가능)
  • 커맨드 사용 방법에 대한 문자열과 커맨드를 처리하는 함수의 포인터를 저장하는 자료구조
// 문자열 포인터를 파라미터로 받는 함수 포인터 타입 정의
typedef void ( * CommandFunction ) ( const char* pcParameter );

// 셸의 커맨드를 저장하는 자료구조
typedef struct kShellCommandEntryStruct
{
		// 커맨드 문자열
		char* pcCommand;
		// 커맨드의 도움말
		char* pcHelp;
		// 커맨드를 수행하는 함수의 포인터
		CommandFunction pfFunction;
} SHELLCOMMANDENTRY;
  • SHELLCOMMANDENTRY 자료구조로 처리한 커맨드 실행 코드
// 커맨드 테이블 정의
SHELLCOMMANDENTRY gs_vstCommandTable[]=
{
    { "help", "Show Help", kHelp },
    { "cls", "Clear Screen", kCls },
    { "totalram", "Show Total RAM Size", kShowTotalRAMSize },
    { "strtod", "String To Decial/Hex Convert", kStringToDecimalHexTest },
};

// 커맨드를 비교해서 실행하는 함수
void kExecuteCommand( const char* pcCommandBuffer )
{
    int i, iSpaceIndex;
    int iCommandBUfferLength, iCommandLength;
    int iCount;

    // 공백으로 구분된 커맨드를 추출
    iCommandBUfferLength = kStrLen( pcCommandBuffer );
    for( iSpaceIndex = 0; iSpaceIndex < iCommandBUfferLength; iSpaceIndex++ )
    {
        if( pcCommandBuffer[ iSpaceIndex ] == ' ')
        {
            break;
        }
    }

    // 커맨드 테이블을 검사해서, 같은 이름의 커맨드가 있는지 확인
    iCount = sizeof( gs_vstCommandTable ) / sizeof( SHELLCOMMANDENTRY );
    for( i = 0 ; i < iCount ; i++ )
    {
        iCommandLength = kStrLen( gs_vstCommandTable[ i ].pcCommand );
        // 커맨드의 길이와 내용이 완전히 일치하는지 검사
        if( ( iCommandLength == iSpaceIndex ) &&
            ( kMemCmp( gs_vstCommandTAble[ i ].pcCommand, pcCommandBuffer,
                       iSpaceIndex ) == 0 ) )
        {
            // 일치하는 함수를 실행
            gs_vstCommandTable[ i ].pfFunction( pcCommandBuffer + iSpaceIndex + 1 );
            break;
        }
    }

    // 리스트에서 찾을 수 없다면, 에러 출력
    if( i >= iCount )
    {
        kPrintf( "'%S' is not found\n", pcCommandBuffer );
    }
}

// 실제 커맨드를 처리하는 함수들
void kHelp( const char* pcCommandBuffer )
{
    ``` 생략 ```
}

void kCls( const char* pcParameterBuffer )
{
    ``` 생략 ```
}

void kShowTotalRAMSize( const char* pcParameterBuffer )
{
    ``` 생략 ```
}

void kStringToDecimalHexTest( const char* pcParameterBuffer )
{
    ``` 생략 ```
}

Ⅳ. 콘솔 라이브러리와 셸의 통합과 빌드