Ⅰ. 콘솔 입출력 처리
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 )
{
``` 생략 ```
}
Ⅳ. 콘솔 라이브러리와 셸의 통합과 빌드
'MINT64 OS 개발' 카테고리의 다른 글
MINT64 OS 개발 - 내용 정리 (0) | 2024.08.05 |
---|---|
14장. 키보드 디바이스 드라이버 업그레이드 (0) | 2023.07.21 |
13장. PIC 컨트롤러와 인터럽트 핸들러 이용해, 인터럽트 처리 (0) | 2023.07.21 |
12장. GDT, IDT 테이블, TSS 세그먼트를 추가해 인터럽트에 대비하자 (0) | 2023.07.21 |
11장. 키보드 디바이스 드라이버 추가 (0) | 2023.07.20 |