우리가 평소에 우분투나 윈도우에서 사용하는 쉘도, 결국 키보드 입력을 받아 그에 맞는 명령어를 실행시키는 것과 같다. 그럼 이 작은 운영체제에도 비슷한 방법으로 쉘을 구현할 수 있지 않을까?
큰 틀은 비슷하다. 이미 이전 포스트에서 소문자 알파벳을 입력받아 실행하도록 구현했으니, 이번에는 엔터를 구현하고 엔터를 누르는 순간 지금까지 입력했는 문자열에 맞는 명령어를 실행시키도록 한다.
먼저 기능 구현 전, 메인에서 사용할 수 있도록 버퍼와 쉘에서 사용할 현재 line과 col을 data.h에 구현해준다.
unsigned char keyboard [videomaxcol];
unsigned short kindex;
unsigned short curline;
unsigned short curcol;
keyboard[] : 문자를 입력받을 때 사용할 버퍼
kindex : 문자열과 관련하여 사용할 인덱스
curline : 쉘에서 현재 라인을 저장할 변수
curcol : 쉘에서 현재 열을 저장할 (몇번째 칸인지) 변수
이 다음, 쉘의 기능을 수행할 shell.h와 shell.c를 작성한다
shell.h
#pragma once
void sh_clear ();
void sh_version ();
shell.c
#include "data.h"
#include "shell.h"
#include "function.h"
void sh_clear ()
{
kprintf_clear_all ();
curline = -1 ;
}
void sh_version ()
{
kprintf ("OStrial_in_AMAZON-v0.0.1" , ++curline, 0 );
}
sh_clear() : 화면에 있는 모든 문자열을 지우고, 현재 커서의 행을 -1로 지정한다.
sh_version() : 쉘 정보를 출력한다.
쉘 기능을 추가한 만큼, interrupt의 내용도 맞춰서 적당히 수정해준다.
1. init_intdesc : 처음에 data.h에 선언해준 keyboard[]와 kindex 수정
{
isr = (unsigned short *)(0x0 + 8 * 0x21 );
*isr = inttable [2 ].offsetl ;
*(isr + 1 ) = inttable [2 ].selector ;
*(isr + 2 ) = inttable [2 ].type ;
*(isr + 3 ) = inttable [2 ].offseth ;
kindex = 0 ;
for (int i = 0 ; i < videomaxcol; i++)
keyboard [i] = 0 ;
}
2. idt_timer() : 출력 위치 수정
void idt_timer ()
{
__asm__ __volatile__
(
"push gs;"
"push fs;"
"push es;"
"push ds;"
"pushad;"
"pushfd;"
"mov al, 0x20;"
"out 0x20, al;"
);
kprintf (keyt, videomaxline-1 , videomaxcol-1 );
keyt [0 ]++;
__asm__ __volatile__
(
"popfd;"
"popad;"
"pop ds;"
"pop es;"
"pop fs;"
"pop gs;"
"leave;"
"nop;"
"iretd;"
);
}
3. idt_keyboard() : BACKSPACE = 0x08, define 문을 이용해 코드 정리
void idt_keyboard ()
{
__asm__ __volatile__
(
"push gs;"
"push fs;"
"push es;"
"push ds;"
"pushad;"
"pushfd;"
"xor al,al;"
"in al, 0x60;"
);
__asm__ __volatile__ ("mov % 0, al;" :"=r" (keybuf) );
keybuf = transScan (keybuf);
if (keybuf == BACKSPACE && kindex != 0 ) // 백스페이스 입력
keyboard [--kindex] = 0 ;
else if (keybuf != 0xFF && keybuf != BACKSPACE)
keyboard [kindex++] = keybuf;
__asm__ __volatile__
(
"mov al, 0x20;"
"out 0x20, al;"
);
__asm__ __volatile__
(
"popfd;"
"popad;"
"pop ds;"
"pop es;"
"pop fs;"
"pop gs;"
"leave;"
"nop;"
"iretd;"
);
}
4. transScan() : 엔터를 받을 수 있도록 수정
case 0x1C : result = ENTER; break ;
케이스에 한줄 추가하면 된다.
다음에 function.h와 function.c에 다음 기능을 선언 및 구현해준다.
1. function.h
void kprintf_clear_all ();
int kstrcmp (char* , char* );
int kstrlen (char* );
2. function.c
void kprintf_clear_all ()
{
for (int i = 0 ; i < videomaxline; i++)
kprintf_line_clear (i,0 );
}
int kstrcmp (char * str1 , char * str2 )
{
for (int i = 0 ; i < kstrlen (str1); i++)
if (str1 [i] != str2 [i]) return FALSE ;
return TRUE ;
}
int kstrlen (char * str1 )
{
int i = 0 ;
while (str1 [i] != 0 ) i++;
return i;
}
kprintf_clear_all() : 비디오 디스크립터가 갖는 최대 라인까지 모든 줄을 다 지움.
kstrcmp() : 주어진 두 문자열을 비교하여 같으면 TRUE, 다르면 FALSE 리턴
kstrlen() : 문자열의 길이 반환
마지막으로, 메인의 소스코드를 수정해준다.
#include "function.h"
#include "interrupt.h"
#include "data.h"
#include "shell.h"
void shell ();
void translate_shell ();
void main ()
{
kprintf ("We are now in C!" , 10 , 10 );
init_intdesc ();
kprintf_clear_all ();
shell ();
}
void shell ()
{
char path[] = "OStrial_in_AMAZON>>" ;
curline = 0 ;
curcol = kstrlen (path);
while (1 )
{
__asm__ __volatile__ ("cli" );
if ( kindex != 0 && keyboard [kindex - 1 ] == ENTER)
{
kprintf_line_clear (curline, curcol + kindex - 1 );
keyboard [kindex - 1 ] = 0 ;
translate_shell ();
for (int i = 0 ; i < videomaxcol; i++)
keyboard [i] = 0 ;
curline++;
kindex = 0 ;
}
kprintf (path,curline,0 );
kprintf_line_clear (curline, curcol+kindex);
kprintf (keyboard, curline, curcol);
__asm__ __volatile__ ("sti" );
}
}
void translate_shell ()
{
if (keyboard [0 ] == 0 ) { return ; }
if (kstrcmp (keyboard, "clear" )) { sh_clear (); return ; }
if (kstrcmp (keyboard, "version" )) { sh_version (); return ; }
kprintf ("There is no such command." ,++curline, 0 );
}
main() 내부에서 C코드로 진입했다는 것을 알리고, init_intdesc()를 이용하여 사용할 값을 초기화시켜준다.
이후 화면 전체를 지우고 쉘로 진입한다.
shell() 내부에서는 명령어 입력받는 것을 반복하는데, 우선 맨 앞에 쉘인 것처럼 보이게 유저>> 와 같은 형식으로 출력한다. 그 이후 키보드 입력을 받는데, 입력 값이 존재하고 엔터를 눌렀다면 if 분기점 내부로 들어와 translate_shell 함수를 호출한다,
아니라면 입력 값을 차근차근 받게 된다.
translate_shell() 함수에서는 어떤 명령어를 입력했느냐에 따라 실행 내용이 달라진다.
clear라고 칠 경우, 전체 화면을 지우고, version이라고 칠 경우 쉘의 버전을 출력한다.
만일 아무 값을 입력하지 않았을 경우, 아무 역할도 하지 않고 넘어간다.
이 외의 값을 입력한 경우에는 해당 명령어가 존재하지 않는다는 안내문을 내보내고, 커서의 행에 값을 가한다.
이렇게 make를 통해 컴파일을 하고 가상머신을 돌리면 정상적으로 실행하는 것을 확인할 수 있다.