Welcome to My World (www.dgmayor.com)

소프트웨어/C & 자료구조 & 커널 & DB

16. 기초적인 쉘

dgmayor 2022. 6. 14. 11:23
728x90
 
 
 
16강 소스코드입니다.
 

--------------------------------------------------------------------------------
 

오늘은 Shell 을 만들어봅시다!
 
Shell을 만드는 궁극적인 목적은 사용자로부터 명령어를 입력받아 처리하기 위함입니다.
 
키보드 드라이버를 저번 강의 때 구현했기 때문에, Shell은 이를 이용하기만 하면 됩니다.
 
따라서 어떤 방식으로 구현하던지 상관이 없겠죠? 아래의 코드를 참고로 자신만의 Shell을 만들어봅시다.
 
코드를 수정하기에 앞서 다음과 같이 shell.h와 shell.c 를 추가합시다. ( 새로 만들었으니 당연히 makefile 에 추가하는 것도 잊지 말아야겠죠? ) 그리고 data.h 도 추가합시다.
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// data.h
#pragma once
 
#define videomaxline 25
#define videomaxcol 80
 
#define BACKSPACE 0x08
#define ENTER 0x13
 
#define TRUE 1
#define FALSE 0
 
#define DEBUG 24
 
unsigned char keyboard[videomaxcol]; // 지난 강의의 두 변수를 data.h로 옮긴 겁니다.
unsigned short kindex;  // 왜냐하면 main.c 에서 이 변수들을 접근할 것이기 때문입니다.
 
unsigned short curline; // shell 상의 현재 라인
unsigned short curcol; // shell 상의 현재 column
 
cs
 
 
1
2
3
4
5
6
// shell.h
#pragma once
 
void sh_clear();
void sh_version();
 
cs
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// shell.c
#include "data.h"
#include "shell.h"
#include "function.h"
 
void sh_clear()
{
    kprintf_clear_all();
    curline = -1;
}
 
void sh_version()
{
    kprintf("HoGoS [version 0.0.1]"++curline, 0);
    kprintf("Copyright <c> 2018 HoGuSoft. All rights reserved"++curline, 0);
}
 
cs
 

data.h를 만들어서 어디에서나 include 하면 변수들을 접근할 수 있도록 전역변수 성격의 변수들을 저장합니다. 그리고 shell.h 와 .c 를 만들어서 shell이 제공하는 명령어들을 이곳에서 직접 구현합시다.
 
그 다음에 interrupt.c를 다음과 같이 수정합시다.
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#include "interrupt.h"
#include "function.h"
#include "data.h"
 
struct IDT inttable[3];
struct IDTR idtr = { 256 * 8 - 1,0 };
 
unsigned char keyt[2= { 'A'0 };
unsigned char keybuf;
 
void init_intdesc()
{
 
    {
        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;
    }
 
}
 
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;"
    );
 
 
}
 
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;"
    );
 
}
 
unsigned char transScan(unsigned char target)
{
    unsigned char result;
 
    switch (target) // scan code set 1 기준
    {
    case 0x1E: result = 'a'break;
    case 0x30: result = 'b'break;
    case 0x2E: result = 'c'break;
    case 0x20: result = 'd'break;
    case 0x12: result = 'e'break;
    case 0x21: result = 'f'break;
    case 0x22: result = 'g'break;
    case 0x23: result = 'h'break;
    case 0x17: result = 'i'break;
    case 0x24: result = 'j'break;
    case 0x25: result = 'k'break;
    case 0x26: result = 'l'break;
    case 0x32: result = 'm'break;
    case 0x31: result = 'n'break;
    case 0x18: result = 'o'break;
    case 0x19: result = 'p'break;
    case 0x10: result = 'q'break;
    case 0x13: result = 'r'break;
    case 0x1F: result = 's'break;
    case 0x14: result = 't'break;
    case 0x16: result = 'u'break;
    case 0x2F: result = 'v'break;
    case 0x11: result = 'w'break;
    case 0x2D: result = 'x'break;
    case 0x15: result = 'y'break;
    case 0x2C: result = 'z'break;
    case 0x39: result = ' 'break// 스페이스
    case 0x0E: result = BACKSPACE; break// 백스페이스 아스키코드 = 8
    case 0x1C: result = ENTER; break// Enter key
    default: result = 0xFFbreak
        // 구현안된 것은 무시한다. 구분자는 0xFF
 
    }
 
    return result;
 
}
 
cs
 

전체 코드는 빼고, 수정된 부분만 넣었습니다.
 
init_intdesc() 에서는 data.h에 있는 kindex와 keyboard 를 초기화하는 코드를 넣었습니다.
idt_timer() 에서 출력되는 요란한(?) 타이머는 오른쪽 밑 맨 구석에다가 넣어놨습니다.
idt_keyboard() 에서는 data.h 에 정의된 #define 등을 이용해 코드를 좀 이쁘게 다듬었습니다.
마지막으로 transScan 에서는 ScanCode 0x1C 즉 ENTER 값을 받았을 경우를 추가했습니다.
 
그리고 function.h와 .c 를 다음과 같이 수정합시다.
 
1
2
3
4
5
6
7
8
#pragma once
 
void kprintf(char*intint); // str : 출력할 스트링 주소 , int : 몇 번째 줄에 출력할 것인지, int : 몇 번째 행에 출력할 것인지
void kprintf_line_clear(intint);
void kprintf_clear_all();
 
int kstrcmp(char*char*);
int kstrlen(char*);
cs
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include "function.h"
#include "data.h"
 
void kprintf(char* str, int line, int col) // str 글자를 line 번째 줄에 출력하는 함수
{
    char *video = (char*)(0xB8000 + 160 * line + 2*col);
 
    for (int i = 0; str[i] != 0; i++)
    {
        *video++ = str[i];
        *video++ = 0x03// 첫 째는 배경색, 둘 때는 글자 자체 색
    }
 
    return;
}
 
void kprintf_line_clear(int line, int col)
{
    char *video = (char*)(0xB8000 + 160 * line + 2*col);
    for (int i = 0; i < 160-2*col ; i++)
    {
        *video++ = 0;
        *video++ = 0x03;
    }
}
 
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;
}
 
cs
 
 

--------------------------------------------------------------------------------
 
 
 
좋습니다! 준비는 이제 다 끝났습니다. main.c를 다음과 같이 수정해봅시다.
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// main.c
 
#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[] = "HoGoS>>";
 
    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; } // 명령어 없이 그냥 ENTER 침
    if (kstrcmp(keyboard, "clear")) { sh_clear(); return; }
    if (kstrcmp(keyboard, "version")) { sh_version(); return; }
    
    kprintf("There is no such command.",++curline, 0);
}
 
cs
 
 

논리가 어려울 수 있습니다. 하지만 논리를 요약하면 다음과 같습니다.
 
" HoGoS>> " 이후에 명령어를 입력한다. while(1) 이 돌아가면서 주기적으로 >> 이후에 keyboard에 적혀있는 버퍼값을 써준다 ( 이는 사용자가 키보드를 두들긴 값들이 차곡차곡 적혀있다. )
 
그러다가 ENTER 값이 들어왔을 경우를 IF로 분기하여, ENTER를 눌렀다면 곧바로 해당 명령어를 해석하는 translate_shell()를 호출한다. 이 함수에서는 if 문을 통해 어떤 명령어가 입력되었는지 확인한다. 그리고 알맞게 해당 명령어 수행하는 함수를 호출한다.
 
지금 Shell 은 매우매우 기초적인 단계이기 때문에 지원하는 명령어가 clear와 version이 전부입니다. 차차 개발해나가면서 많은 명령어들이 추가될 것입니다.
 
이제 제대로 동작하는지 돌려봅시다. 언제나 그랬듯이 컴파일 후 가상머신에 돌립시다.
 
 
 
 
 
 

아주 잘 돌아가는군요. version 과 clear 명령어 모두 잘 동작합니다. 그 외 입력이 오면 모두 오류처리를 하구요. 깨알같이 우측하단에는 타이머 인터럽트에 의해 계속 프린트가 찍히는 것을 볼 수 있습니다.
 
이렇게 해서 기본적인 Shell 까지 구현해보았습니다.
 
다음 강의에서는 대망의 파일시스템을 구현해보고, 관련된 shell 명령어를 추가해보겠습니다. 제가 생각하는 마의 고지(?) 중 하나인데 성공한다면 Unix 기반 운영체제처럼 디렉토리 및 파일을 읽고 쓸 수 있게 될 겁니다!
 
감사합니다.
728x90