This article is part of a series: Jump to series overview

Bevor wir mit unserem nächsten Tutorial zum Thema Tastatureingaben weitermachen, könnten wir noch ein paar Funktionen gebrauchen, die Zeichen und Texte auf dem Bildschirm anzeigen können.

Im Grunde reichen uns hierfür ein paar wirkliche Basisfunktionen, zur Ausgabe von einzelnen Zeichen, Zahlen und ganzen Strings:

void kprint(char *str);
void kprintc(char c);
void kprinti(int value);

Sinnvollerweise definieren wir uns gleich ein paar Konstanten, die den Bildschirm und die Position des Bildschirms im Speicher beschreiben und haben dann die gesamte Header-Datei io.h auch schon fertig:

#ifndef IO_H
#define IO_H

#include <stdint.h>

#define VIDMEM 0xb8000
#define VIDEO_SCREEN_WIDTH 80
#define VIDEO_SCREEN_HEIGHT 25

void kprint(char *str);
void kprintc(char c);
void kprinti(int value);

#endif

Mit dem Wissen aus vorherigen Tutorials, wie man auf den Bildschirm-Speicher schreibt, können wir die Funktion printc() schnell schreiben. Wir schreiben einfach das Zeichen an die aktuelle Textposition.

Wichtig ist hier der Begriff aktuelle Textposition: Wir müssen uns irgendwie merken, wo der nächste Buchstabe auf dem Bildschirm stehen sollte. Also definieren wir uns eine Variable, die genau diese Information enthält:

static uint16_t curpos = 0;

void kprintc(char c) { 
    char *vidmem = (char*)VIDMEM;
    vidmem[2*curpos] = c;
    vidmem[2*curpos + 1] = 0x9;

    curpos++;
}

Was passiert nun aber, wenn wir den ganzen Monitor von 80x25 Zeichen vollgeschrieben haben? Dann läuft unser Cursor über den Bildschirmspeicher hinaus und schreibt in fremden Speicher. Das darf natürlich nicht passieren! Außerdem wäre es sinnvolles Verhalten, wenn in so einem Fall der Bildschirm entweder geleert wird und der Text wieder oben beginnt oder die letzte Zeile geleert wird und alle anderen Zeilen nach oben geschoben werden.

Ich habe mich für zweitere Lösung entschieden, d.h. es wird immer nur eine Zeile vom Bildschirm geworfen. Dazu programmieren wir am besten eine Funktion increment_cursor() und diese ruft wiederum eine Funktion shift_screen_up() auf (oder falls ihr die andere Variante implementieren wollt, eine Funktion clear_screen_to_begin()).

static void shift_screen_up() {
    char *vidmem = (char*)0xb8000;

    // Move existing lines one row up
    for (int line = 1; line < VIDEO_SCREEN_HEIGHT; line++) {
        for (int line_byte = 0; line_byte < 2 * VIDEO_SCREEN_WIDTH; line_byte++) {
            int offset = 2 * VIDEO_SCREEN_WIDTH * line + line_byte;

            vidmem[offset - 2 * VIDEO_SCREEN_WIDTH] = vidmem[offset];
        }
    }
    // Reset text on "new" line
    for (int col = 0; col < VIDEO_SCREEN_WIDTH; col++) {
        int offset = 2 * VIDEO_SCREEN_WIDTH * (VIDEO_SCREEN_HEIGHT - 1) + 2 * col;

        vidmem[offset] = ' ';
    }
    // Reset colors on "new" line
    for (int col = 0; col < VIDEO_SCREEN_WIDTH; col++) {
        int offset = 2 * VIDEO_SCREEN_WIDTH * (VIDEO_SCREEN_HEIGHT - 1) + 2 * col + 1;

        vidmem[offset] = 0xf;
    }

    curpos -= VIDEO_SCREEN_WIDTH;
}

static void increment_cursor() {
    curpos++;

    if (curpos >= VIDEO_SCREEN_WIDTH * VIDEO_SCREEN_HEIGHT) {
        shift_screen_up();
    }
}

Damit können wir nun auch die Funktion kprintc() ausbessern:

void kprintc(char c) {
    char *vidmem = (char*)VIDMEM;
    vidmem[2*curpos] = c;
    vidmem[2*curpos + 1] = 0x9;

    increment_cursor();
}

Sobald wir diese Funktion einmal haben, ist auch die Funktion zum Ausgeben von Strings nicht mehr schwer:

void kprint(char *str) {
    for (int i = 0; str[i] != 0; i++) {
        kprintc(str[i]);
    }
}

Die Funktion zum Ausgeben von Zahlen wird hingegen ein bisschen trickreich. Wir haben nämlich keine Mathematik-Bibliothek in einer Freestanding-GCC-Umgebung. Und bisher haben wir auch keine für unseren Kernel programmiert. Wir müssen die Zahl jedoch von links nach rechts schreiben, d.h. mit der höchsten Potenz zuerst. Dazu müssen wir aber wissen, durch welche Potenz von 10 wir maximal teilen müssen (oder alternativ alle durchprobieren, aber da wäre wieder das Problem, dass int in der Länge nicht plattformübergreifend standardisiert ist).

Deshalb brauchen wir erstmal eine Funktion, die uns die höchste Zehnerpotenz gibt oder anders gesagt die Anzahl der Stellen, wenn man die Zahl in Basis 10 schreibt. Eine Multiplikation mit 10 kann man auch durch eine Kombination von Bitshifts schreiben, nämlich indem man den Wert um drei Stellen nach links shiftet (Multiplikation mit 8), um eine Stelle nach links shiftet (Multiplikation mit 2) und die beiden Werte addiert:

static short no_of_digits(int value) {
    short len = 0;
    int tmp = 1;

    while (tmp < value) {
        len++;
        tmp = (tmp << 3) + (tmp << 1);
    }

    return len;
}

Außerdem werden wir für das weitere vorgehen eine Funktion für die Potenz benötigen. Ich selbst habe diese direkt in eine Datei math.c geschrieben, aber ihr könnt sie natürlich auch vorerst in der io.c lassen:

long power(long number, int power) {
    long tmp = number;
    for (int i = 1; i < power; i++) {
        tmp *= number;
    }

    return tmp;
}

Wer die Funktion direkt in die io.c schreibt, sollte sie zusätzlich als static deklarieren, damit sie nicht im Namensraum des Linkers auftaucht.

Mit dieser Funktionen können wir nun unsere Eingabezahl nach und nach durch immer kleinere Zehnerpotenzen teilen und so jede einzelne Stelle ermitteln. Nehmen wir als Beispiel die Zahl 12345. Wir würden beginnen:

  1. 12345 / 10000 = 1 (/ steht für Integerdivision)
  2. 12345 % 10000 = 2345; 2345 / 1000 = 2
  3. 2345 % 1000 = 234; 345 % 100 = 3
  4. 345 % 100 = 45; 45 % 10 = 4
  5. 45 % 10 = 5; 5 % 1 = 5

Dies können wir mit folgender Funktion automatisiert erledigen:

void kprinti(int value) {
    short digits = no_of_digits(value);

    int divisor = (int)power(10, digits - 1);
    for (int i = divisor; i > 0; i /= 10) {
        short digit = value / i;
        value %= i;
        kprintc((char)(digit + 48));
    }
}
I do not maintain a comments section. If you have any questions or comments regarding my posts, please do not hesitate to send me an e-mail to blog@stefan-koch.name.