This article should be huge.
Shell.
Finally, My 0SOS has an initial version CLI.
Now It looks like an OS.
Also, the Source tree grow bigger and bigger.
Above image was source tree of 0SOS. Many things already implemented. Also, many things need implementation
Anyway, This time It was made here and there. I’ve implemented Printf and SPrint functions in the All-time Cafe where I went before, talking with friends.
In fact, I’ve spent talking time more than development time, so It made slowly.
And Next day Also I’ve spent development time and the next day too.
Writing Article on the blog was enjoyment after rushing development time.
I think this article would be written separately.
Ah anyway, 0SOS has a console shell.
Scrolling looks nice.
If you see it an only image, you wouldn’t feel like me, so I prepare a youtube video.
How many time I spent preparing for running a command shell
It needs starting at 16-bit bootloader assembly codes, Preparing to jump 16-bit mode to 32-bit kernel and preparing to jump 32 bit to 64-bit mode, Keyboard driver and Interrupt handling.
In this time, Developing Printf function(also SPrintf for later) and GetCh function is made for input commands. And It would be used on Shell codes.
This time also I don’t refer the book. Well if it works same, it’s fine (And I don’t carry my book cause it’s too heavy)
Variable argument function can be found on Google, So It’s no problem.
But the problem is… I’m not sure it can be used OS development. So I try to search.
After that, I knew it is possible.
So We need to implement output format and escape sequence.
We should print %d and %x that should works number->charactor on screen or memory.
I think standard function already have an efficient algorithm, so I try to google it and I found magnificent codes.
BOOL _itoa(long _value, char* _result, int _base)
{
if (_base < 2 || _base > 36) { *_result = '\0'; return FALSE; }
char* ptr = _result, *ptr1 = _result, tmp_char;
int tmp_value;
//It works negative and positive.
do {
tmp_value = _value;
_value /= _base;
*ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz"[35 + (tmp_value - _value * _base)];
} while (_value);
//Sign
if (tmp_value < 0) *ptr++ = '-';
*ptr-- = '\0';
//Reverse
while (ptr1 < ptr) {
tmp_char = *ptr;
*ptr-- = *ptr1;
*ptr1++ = tmp_char;
}
return TRUE;
}
I try to change a little, but it is same structure.
It uses on GCC they said.
Constructing-character table and reserve table for a negative and positive number.
And we use that to make characters from the number.
Now We have an itoa function, let’s implement atoi function to change the character to number.
BOOL _atoi(const char* _number, long* _value, int _base)
{
if (!_number)
return FALSE;
if (_base < 2 || _base > 36)
return FALSE;
int sign = 1;
int i = 0;
if (_number[0] == '-')
{
sign = -1;
i = 1;
}
long result = 0;
char c = 0;
while ((c = _number[i++]) != '\0')
{
int num = _ctoi(c);
if (num > _base)
return FALSE;
result = result * _base + num;
}
*_value = sign*result;
return TRUE;
}
On the above code, _ctoi function returns a number corresponding to the character. ex) ‘a’ is 10
(You can read the full source code.)
Returning value takses r = r * base + v per digit.
For example, When ‘202’ value number input:
1) r = 0 * 10 + 2
r = 2
2) r = 2 * 10 + 0
r = 20
3) r = 20 * 10 + 2
r = 202
end.
So It makes the number 202.
No difference if it hexadecimal value or octet.
Anyway, Those functions were implemented in Utility/string.c.
Now we have functions for change the character to number and reverse, so let’s makes Printf and Sprtinf functions.
I defined information of attribute for output and cursor position in a header file.
//Console.h
#ifndef __CONSOLE_H__
#define __CONSOLE_H__
#include <Types.h>
#include <stdarg.h>
#define FORMAT_BUFFER_SIZE 200
#define PRINT_MEMORY 0x01
#define PRINT_OUTPUT 0x02
#define CONSOLE_WIDTH 80
#define CONSOLE_HEIGHT 25
#define CONSOLE_VIDEO_MEMORY 0xB8000
#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_BLACK 0x08
#define CONSOLE_FOREGROUND_BLUE 0x09
#define CONSOLE_FOREGROUND_GREEN 0x0A
#define CONSOLE_FOREGROUND_CYAN 0x0B
#define CONSOLE_FOREGROUND_RED 0x0C
#define CONSOLE_FOREGROUND_MAGENTA 0x0D
#define CONSOLE_FOREGROUND_BROWN 0x0E
#define CONSOLE_FOREGROUND_WHITE 0x0F
#define COLOR_DEFAULT (CONSOLE_BACKGROUND_BLACK|CONSOLE_FOREGROUND_WHITE)
#pragma pack(push, 1)
typedef struct _Struct_ConsoleCursor
{
int cursor_offset;
DWORD current_attribute;
} CONSOLESYSTEM;
typedef struct _Charactor_Struct
{
BYTE bCharactor;
BYTE bAttribute;
} CHARACTER_MEMORY;
#pragma pack(pop)
static CONSOLESYSTEM g_console_system = {0,COLOR_DEFAULT};
void _PrintStringXY(int _x, int _y, BYTE _Attribute ,const char* _str);
void _PrintChar_offset(int _offset, BYTE _attribute, char _ch);
void _Printf(char* _str, ...);
void _SPrintf(void* _dst, char* _str, ...);
char _GetCh();
void __VSPrintf(BYTE _type, const void* _dst, char* str, va_list _arg);
void __NextLine();
void __NextScroll();
void __SetConsole_System(CONSOLESYSTEM value);
CONSOLESYSTEM __GetConsole_System();
#endif /*__CONSOLE_H__*/
Uh, too many define.
But as you can see, it’s all attribute value for Framebuffer.
CONSOLESYSTEM g_console_system variable takes cursor position, output color and etc.
//Console.c
void __UpdateWithCheckConsoleCursor()
{
if(g_console_system.cursor_offset + 1 >=
CONSOLE_HEIGHT * CONSOLE_WIDTH)
{
__NextScroll();
g_console_system.cursor_offset = CONSOLE_WIDTH*(CONSOLE_HEIGHT - 1);
}
else
{
g_console_system.cursor_offset++;
}
}
void __putch(char _ch, BYTE _attribute)
{
_PrintChar_offset(g_console_system.cursor_offset, _attribute, _ch);
__UpdateWithCheckConsoleCursor();
}
void __PrintOutInteger(long _value, char* _buffer, int _base)
{
_itoa(_value, _buffer, _base);
for (int i = 0; _buffer[i] != '\0'; i++)
{
__putch(_buffer[i], g_console_system.current_attribute);
}
}
char* __PrintMemInteger(long _value, char* _dst, int _base)
{
char Buffer[FORMAT_BUFFER_SIZE];
_itoa(_value, Buffer, _base);
int length = __StringLength(Buffer);
_MemCpy(_dst, Buffer, length);
return _dst + length;
}
void __VSPrintf(BYTE _type, const void* _dst, char* str, va_list _arg)
{
char* ptr = str;
char Buffer[FORMAT_BUFFER_SIZE];
char* dst = (char*)_dst;
while (*ptr != '\0')
{
char output = *ptr;
int value = 0;
char ch = 0;
char* str = 0;
QWORD qvalue = 0;
if (*ptr == '%')
{
ptr++;
switch (*ptr)
{
case 'd':
value = (int)(va_arg(_arg, int));
if (_type == PRINT_OUTPUT)
__PrintOutInteger(value, Buffer, 10);
else if (_type == PRINT_MEMORY)
dst = __PrintMemInteger(value, dst, 10);
break;
case 'o':
value = (int)(va_arg(_arg, int));
if (_type == PRINT_OUTPUT)
__PrintOutInteger(value, Buffer, 8);
else if (_type == PRINT_MEMORY)
dst = __PrintMemInteger(value, dst, 8);
break;
case 'x':
value = (int)(va_arg(_arg, int));
if (_type == PRINT_OUTPUT)
__PrintOutInteger(value, Buffer, 16);
else if (_type == PRINT_MEMORY)
dst = __PrintMemInteger(value, dst, 16);
break;
//case 'f':
//case 'g':
//TODO: Floating Point
case 'p':
qvalue = (QWORD)(va_arg(_arg, void*));
if (_type == PRINT_OUTPUT)
{
__putch('0', g_console_system.current_attribute);
__putch('x', g_console_system.current_attribute);
__PrintOutInteger(qvalue, Buffer, 16);
}
else if (_type == PRINT_MEMORY)
{
dst[0] = '0';
dst[1] = 'x';
dst = __PrintMemInteger(qvalue, dst+ 2, 16);
}
break;
case 'c':
ch = (char)(va_arg(_arg,int));
if (_type == PRINT_OUTPUT)
__putch(ch, g_console_system.current_attribute);
else if (_type == PRINT_MEMORY)
{
dst[0] = ch;
dst++;
}
break;
case 's':
str = (char*)(va_arg(_arg, char*));
if (_type == PRINT_OUTPUT)
_Printf(str);
else if (_type == PRINT_MEMORY)
{
_SPrintf(dst, str);
int len = __StringLength(str);
dst += len;
}
break;
default:
break;
}
}
This is the heart of Printf and Sprintf function.
Also, the above codes are helper functions for the implementation of VSPrintf.
Implementations are so different from the book.
Book implemented this using buffer array, but I don’t wanna that.
So I made this to write directly without a buffer.
and __putch.
It processes output characters when it meets % character.
And I implement this separately from Sprintf and Printf function.
When it meets Character ‘%s’, _Sprint and _Printf function are called. Because if it has an escape sequence, it needs to process that.
You can notice about internal of the Printf and Sprintf will call a _VSPrintf function by type.
//Console.c
void _Printf(char* _str, ...)
{
va_list _arg;
va_start(_arg, _str);
__VSPrintf(PRINT_OUTPUT, 0, _str, _arg);
va_end(_arg);
_SetCursor(g_console_system.cursor_offset % CONSOLE_WIDTH,
g_console_system.cursor_offset / CONSOLE_WIDTH);
}
void _SPrintf(void* _dst, char* _str, ...)
{
va_list _arg;
va_start(_arg, _str);
__VSPrintf(PRINT_MEMORY, _dst, _str, _arg);
va_end(_arg);
}
void _PrintStringXY(int _x, int _y, BYTE _Attribute ,const char* _str)
{
CHARACTER_MEMORY* Address = ( CHARACTER_MEMORY* ) CONSOLE_VIDEO_MEMORY;
int i = 0;
Address+= ( _y * 80 ) + _x;
for ( i = 0; _str[i] != 0; i++)
{
Address[i].bCharactor = _str[i];
Address[i].bAttribute = _Attribute;
}
}
void _PrintChar_offset(int _offset, BYTE _attribute, char _ch)
{
CHARACTER_MEMORY* Address = ( CHARACTER_MEMORY* ) CONSOLE_VIDEO_MEMORY;
int i = 0;
Address+= _offset;
Address[0].bCharactor = _ch;
Address[0].bAttribute = _attribute;
}
void __NextLine()
{
int addoffset =CONSOLE_WIDTH - (g_console_system.cursor_offset%CONSOLE_WIDTH);
if( g_console_system.cursor_offset + addoffset >= CONSOLE_HEIGHT * CONSOLE_WIDTH)
{
__NextScroll();
g_console_system.cursor_offset = CONSOLE_WIDTH*(CONSOLE_HEIGHT - 1);
}
else
g_console_system.cursor_offset += addoffset;
}
void __NextScroll()
{
_MemCpy(CONSOLE_VIDEO_MEMORY,
CONSOLE_VIDEO_MEMORY + CONSOLE_WIDTH* sizeof(CHARACTER_MEMORY),
(CONSOLE_HEIGHT-1) * CONSOLE_WIDTH * sizeof(CHARACTER_MEMORY) );
CHARACTER_MEMORY* target = (CHARACTER_MEMORY*)(CONSOLE_VIDEO_MEMORY) + (CONSOLE_HEIGHT-1) * CONSOLE_WIDTH ;
for(int i = 0; i <CONSOLE_WIDTH; i++)
target[i].bCharactor = ' ';
}
New line and scrolling look a little bit complicated but the principle is simple.
Scrolling is just shifting-down in video memory. (Clear the last line)
And Lastly, I made _GetCh function.
It’s simply worked get character.
//Console.c
char _GetCh()
{
KEYDATA keydata;
while(1)
{
if(GetKeyData(&keydata) == TRUE)
{
if(keydata.Flags & KEY_DOWN)
{
return keydata.ASCIICode;
}
}
}
}
So Now we are ready for developing Shell.
Let’s see Shell.h.
//Shell.c
#ifndef __SHELL_H__
#define __SHELL_H__
#include <Types.h>
#define SHELL_INPUT_BUFFER_SIZE 500
#define SHELL_PROMPT_MESSAGE "0SOS>"
typedef void (*CommandCallBack) (const char* parameter);
#pragma pack(push, 1)
typedef struct __Struct_ShellCommandEntry
{
char* Command;
char* Comment;
CommandCallBack command_callback;
} SHELLCOMMAND;
typedef struct __Struct_Shell_Parameters
{
const char* Buffer;
int Length;
int CurrentPosition;
} PARAMETERLIST;
#pragma pack(pop)
//--------------------------------------------------- *
void Command_Help(const char* _Parameter);
void Command_Clear(const char* _Parameter);
void Command_ShutDown(const char* _Parameter);
void Command_TotalRamSize(const char* _Parameter);
void Command_StringToNumber(const char* _Parameter);
//---------------------------------------------------*
static SHELLCOMMAND g_ShellCommandTable[] = {
{"clear", "clear the consol\n-f {white, green, cyan,black} front color\n-b {black, white, blue} back ground\n"
, Command_Clear},
{"help", "help for 0SOS", Command_Help},
{"shutdown", "Shutdown PC", Command_ShutDown},
{"strtod", "String To Hex or Decimal", Command_StringToNumber},
{"totalram", "Show Ram Size", Command_TotalRamSize}
};
void Clear();
void SetAttribute(BYTE _attribute);
void StartShell();
void ExecuteCommand(const char * CommandBuffer);
void InitalizeParameter(PARAMETERLIST* _List, const char* _Parameter);
int GetNextParameter(PARAMETERLIST* _List, char* _Parameter_Out);
#endif /*__SHELL_H__*/
Shell header file.
The structure is just called StartShell function from Kernel Entry. And process command line with an infinite loop.
You could see something special line.
typedef void (*CommandCallBack) (const char* parameter);
Ta-da, function pointer.
That is contained in SHELLCOMMAND and connect commands through the g_ShellCommandTable.
Also, InitializeParameter and GetNextParameter function works registering the parameter characters and get parameter from those characters.
For that, We had PARAMETERLIST structure.
All of the prefix Command_ functions are callback function for the process command line.
//Shell.c
void StartShell()
{
__InitializeMemoryCheck();
char CommandBuffer[SHELL_INPUT_BUFFER_SIZE];
_SetCursor(0, 17);
int CommandBufferIndex = 0;
_Printf(SHELL_PROMPT_MESSAGE);
const int Prompt_length = sizeof(SHELL_PROMPT_MESSAGE);
while(1)
{
BYTE c = _GetCh();
if(c == KEY_BACKSPACE)
{
if(CommandBufferIndex > 0)
{
int x, y;
_GetCursor(&x, &y);
int dx = x - Prompt_length;
for(int i = dx; i < CommandBufferIndex; i++)
{
CommandBuffer[i] = CommandBuffer[i + 1];
}
for(int i = 0; i< CONSOLE_WIDTH; i++)
{
_PrintStringXY(i,y, __GetConsole_System().current_attribute, " ");
}
_SetCursor(0, y);
_Printf(SHELL_PROMPT_MESSAGE);
_Printf("%s", CommandBuffer);
if((x-1) >= Prompt_length)
_SetCursor(x-1, y);
else
_SetCursor(x, y);
CommandBufferIndex --;
if(CommandBufferIndex == 0)
{
_SetCursor(Prompt_length-1, y);
}
}
}
else if(c == KEY_ENTER)
{
_Printf("\n");
if(CommandBufferIndex > 0)
{
CommandBuffer[CommandBufferIndex] = '\0';
ExecuteCommand(CommandBuffer);
}
_Printf(SHELL_PROMPT_MESSAGE);
CommandBufferIndex = 0;
_MemSet(CommandBuffer,0 , SHELL_INPUT_BUFFER_SIZE);
}
else if( (c == KEY_LSHIFT) || (c == KEY_RSHIFT) || (c == KEY_CAPSLOCK)
|| (c == KEY_NUMLOCK) || (c == KEY_SCROLLLOCK))
{
;
}
else if (c == KEY_ARROW_LEFT)
{
int x,y;
_GetCursor(&x, &y);
if((x - 1) >= Prompt_length)
{
_SetCursor(x - 1, y);
}
}
else if(c == KEY_ARROW_RIGHT)
{
int x,y;
_GetCursor(&x, &y);
if((x + 1) <= CommandBufferIndex)
{
_SetCursor(x + 1, y);
}
}
else
{
if(c == KEY_TAB)
c = ' ';
if(CommandBufferIndex < SHELL_INPUT_BUFFER_SIZE)
{
CommandBuffer[CommandBufferIndex ++ ] = c;
_Printf("%c", c);
}
}
}
}
Start Shell function.
__InitalieMemoryCheck is implemented in Utility/Memory and set global variable after checking memory size and get that value using __GetTotalRamSize() function.
This will ignore arrows, shift, and capslock.
Until now, When we press kind of shift key, it makes some weird characters.
Now, that problem solved by those codes. (But another alt key makes that, it need to implement)
And Enter the key most important part is processed by the ExecuteCommand function.
void ExecuteCommand(const char * CommandBuffer)
{
int command_index = 0;
const int length = __StringLength(CommandBuffer);
for(command_index = 0; command_index < length; command_index++)
{
if(CommandBuffer[command_index] == ' ')
break;
}
const int CommandTableSize = sizeof(g_ShellCommandTable)/sizeof(SHELLCOMMAND);
for(int i = 0; i <CommandTableSize; i++)
{
int command_length = __StringLength(g_ShellCommandTable[i].Command);
if((command_length == command_index) && (_MemCmp(g_ShellCommandTable[i].Command, CommandBuffer, command_index) == 0))
{
g_ShellCommandTable[i].command_callback(CommandBuffer + command_index + 1);
return;
}
}
_Printf("\'%s\' command not found\n",CommandBuffer);
}
This function simple work separating parameter and command and calling the function from the command table.
It has so many commands so I can’t descript all about that.
So Except help command, You can see here!
//Shell.c
void Command_Help(const char* _Parameter)
{
_Printf("\n");
_Printf("============================= Help ===========================\n");
const int command_table_size = sizeof(g_ShellCommandTable) / sizeof(SHELLCOMMAND);
int max_command_length = 0;
for(int i = 0; i <command_table_size; i ++)
{
int length = __StringLength(g_ShellCommandTable[i].Command);
if(length > max_command_length)
max_command_length = length;
}
int x,y;
for(int i = 0; i < command_table_size; i++)
{
_Printf("%s", g_ShellCommandTable[i].Command);
_GetCursor(&x, &y);
_SetCursor(max_command_length, y);
_Printf(" : %s\n", g_ShellCommandTable[i].Comment);
}
_Printf("============================= Help ===========================\n");
_Printf("\n");
}
When We type ‘help’ characters and press enter key, that function will be called.
I can see a little bit about how massive modern-OS.
If you try this, you need many things for boot.
Anyway, I can see more and more about low-level programming.
But, It takes a lot of time.
and next…. timer device driver and next is finally task…
Let’s do this!