Now that we know how to compile our C++ kernel and boot the binary using GRUB, we can start to do some cool things in C/C++.
We are going to use VGA default mode (03h) to display some text to the user. The screen can be directly access using the video memory at 0xB8000. The screen resolution is 80x25 and each character on the screen is defined by 2 bytes: one for the character code, and one for the style flag. This means that the total size of the video memory is 4000B (80B25B2B).
In the IO class (io.cc),:
- x,y: define the cursor position on the screen
- real_screen: define the video memory pointer
- putc(char c): print a unique character on the screen and manage cursor position
- printf(char s, ...)*: print a string
We add a method putc to the IO Class to put a character on the screen and update the (x,y) position.
/* put a byte on screen */
void Io::putc(char c){
kattr = 0x07;
unsigned char *video;
video = (unsigned char *) (real_screen+ 2 * x + 160 * y);
if (c == 10) {
x = 0;
y++;
} else if (c == 8) {
if (x) {
*(video + 1) = 0x0;
x--;
}
} else if (c == 9) {
x = x + 8 - (x % 8);
} else if (c == 13) {
x = 0;
} else {
*video = c;
*(video + 1) = kattr;
x++;
if (x > 79) {
x = 0;
y++;
}
}
if (y > 24)
scrollup(y - 24);
}
We also add a useful and very known method: printf
/* put a string in screen */
void Io::print(const char *s, ...){
va_list ap;
char buf[16];
int i, j, size, buflen, neg;
unsigned char c;
int ival;
unsigned int uival;
va_start(ap, s);
while ((c = *s++)) {
size = 0;
neg = 0;
if (c == 0)
break;
else if (c == '%') {
c = *s++;
if (c >= '0' && c <= '9') {
size = c - '0';
c = *s++;
}
if (c == 'd') {
ival = va_arg(ap, int);
if (ival < 0) {
uival = 0 - ival;
neg++;
} else
uival = ival;
itoa(buf, uival, 10);
buflen = strlen(buf);
if (buflen < size)
for (i = size, j = buflen; i >= 0;
i--, j--)
buf[i] =
(j >=
0) ? buf[j] : '0';
if (neg)
print("-%s", buf);
else
print(buf);
}
else if (c == 'u') {
uival = va_arg(ap, int);
itoa(buf, uival, 10);
buflen = strlen(buf);
if (buflen < size)
for (i = size, j = buflen; i >= 0;
i--, j--)
buf[i] =
(j >=
0) ? buf[j] : '0';
print(buf);
} else if (c == 'x' || c == 'X') {
uival = va_arg(ap, int);
itoa(buf, uival, 16);
buflen = strlen(buf);
if (buflen < size)
for (i = size, j = buflen; i >= 0;
i--, j--)
buf[i] =
(j >=
0) ? buf[j] : '0';
print("0x%s", buf);
} else if (c == 'p') {
uival = va_arg(ap, int);
itoa(buf, uival, 16);
size = 8;
buflen = strlen(buf);
if (buflen < size)
for (i = size, j = buflen; i >= 0;
i--, j--)
buf[i] =
(j >=
0) ? buf[j] : '0';
print("0x%s", buf);
} else if (c == 's') {
print((char *) va_arg(ap, int));
}
} else
putc(c);
}
return;
}
A large number of instructions are available in Assembly but there is not equivalent in C (like cli, sti, in and out), so we need an interface to these instructions.
In C, we can include Assembly using the directive "asm()", gcc use gas to compile the assembly.
Caution: gas use the AT&T syntax.
/* output byte */
void Io::outb(u32 ad, u8 v){
asmv("outb %%al, %%dx" :: "d" (ad), "a" (v));;
}
/* output word */
void Io::outw(u32 ad, u16 v){
asmv("outw %%ax, %%dx" :: "d" (ad), "a" (v));
}
/* output word */
void Io::outl(u32 ad, u32 v){
asmv("outl %%eax, %%dx" : : "d" (ad), "a" (v));
}
/* input byte */
u8 Io::inb(u32 ad){
u8 _v; \
asmv("inb %%dx, %%al" : "=a" (_v) : "d" (ad)); \
return _v;
}
/* input word */
u16 Io::inw(u32 ad){
u16 _v; \
asmv("inw %%dx, %%ax" : "=a" (_v) : "d" (ad)); \
return _v;
}
/* input word */
u32 Io::inl(u32 ad){
u32 _v; \
asmv("inl %%dx, %%eax" : "=a" (_v) : "d" (ad)); \
return _v;
}