Project/OStrial in AMAZON

#n+1 이제 키보드 입력을 통한 쉘을 만들어봅시다.

NONE_31D 2019. 11. 24. 18:47

우리가 평소에 우분투나 윈도우에서 사용하는 쉘도, 결국 키보드 입력을 받아 그에 맞는 명령어를 실행시키는 것과 같다. 그럼 이 작은 운영체제에도 비슷한 방법으로 쉘을 구현할 수 있지 않을까?

 

큰 틀은 비슷하다. 이미 이전 포스트에서 소문자 알파벳을 입력받아 실행하도록 구현했으니, 이번에는 엔터를 구현하고 엔터를 누르는 순간 지금까지 입력했는 문자열에 맞는 명령어를 실행시키도록 한다.


먼저 기능 구현 전, 메인에서 사용할 수 있도록 버퍼와 쉘에서 사용할 현재 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(charstr1charstr2)
{
    for (int i = 0; i < kstrlen(str1); i++)
        if (str1[i] != str2[i]) return FALSE;
    return TRUE;
}

int kstrlen(charstr1)
{
    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!"1010);
    
    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를 통해 컴파일을 하고 가상머신을 돌리면 정상적으로 실행하는 것을 확인할 수 있다.