Skelix OS Tutorial
Prev
Tutorial 03: Auxiliary Functions
Next

Our Goal

This tutorial is irrelative to operating system, but these functions are going to be introduced are important and are widely used in our later tutorials, in general, they are used as substitutions of functions C standard library. If you are not interested in implementations of these functions, just note kprintf works likes printf in C for output and print_c prints one character on screen with given background and foreground colors, and we might have to implements some standard library functions, then you can simply skip this tutorial.

Download source file

kprintf

The function printf in C library is very flexible, and to be quite honest, the stream operators in C++ is really hard to use. So for printing strings, numbers etc on screen, I'm going to implement a function works like printf for output instead of manipulating bytes in B8000.

Because I'm not going to implement a full working printf, for Skelix we just need it can print string, numbers in hexadecimal or binary, positive integers and characters and the most important one, it must can accept non-fixed arguments.

Here is the way to achieve this, we know when a function like func(int arg1, int arg2, int arg3) is invoked, it should like this in assembly:
pushl   arg3
pushl   arg2
pushl   arg1
call    func
Arguments are pushed one by one from right to left in arguments list, so if we want to get more arguments then just dig the stack deeper, then how can we know how many arguments we should parse? The answer is by the format string in the printf family. How many %X stuff in format string, then how many arguments we have to parse.

Because in 32-bit mode, all date types which byte length smaller than integer will still be pushed as an integer, that is even a one byte long character also occupies 4 bytes in stack, so we have to make sure we can parse the argument correctly.

We are going to let kprintf accept arguments in this format kprintf(color, format string, arguments...), the first argument defines what fg/bg color combination is going to be used for output. There are some macros we are going to use for parsing stack, if you are familiar with C, then you must be familiar with these macros.
03/kprintf.c#define args_list char *This macro used in function kprintf to convert the stack space to a character stream for further processing.
#define _arg_stack_size(type)    (((sizeof(type)-1)/sizeof(int)+1)*sizeof(int))The macro up rounds the argument size to calculate how many 4-byte it occupies in stack
#define args_start(ap, fmt) do {    \
ap = (char *)((unsigned int)&fmt + _arg_stack_size(&fmt));   \
} while (0)
The arguments are to be parsed are after arugment format string, that is on the top of fmt in stack, this macro gets the start address of those arguments.
#define args_end(ap)At this moment, it does nothing.
#define args_next(ap, type) (((type *)(ap+=_arg_stack_size(type)))[-1])Gets the address of next argument

We put all characters are going to be printed into a buffer and make a pointer point to the next available place.
03/kprintf.cstatic char buf[1024] = {-1};
static int ptr = -1;
The next two functions parse the given value to different radix.
static void
parse_num(unsigned int value, unsigned int base) {
    unsigned int n = value / base;
    int r = value % base;
    if (r < 0) {
        r += base;
        --n;
    }
    if (value >= base)
        parse_num(n, base);
    buf[ptr++] = (r+'0');
}

static void
parse_hex(unsigned int value) {
    int i = 8;
    while (i-- > 0) {
        buf[ptr++] = "0123456789abcdef"[(value>>(i*4))&0xf];
    }
}
Then let's look at the main function of kprintf
/* %s, %c, %x, %d, %% */
void
kprintf(enum KP_LEVEL kl, const char *fmt, ...) {
    int i = 0;
    char *s;
    /* must be the same size as enum KP_LEVEL */
    struct KPC_STRUCT {
        COLOUR fg;
        COLOUR bg;
    } KPL[] = {
        {BRIGHT_WHITE, BLACK},
        {YELLOW, RED},
    };
enum KP_LEVEL {KPL_DUMP, KPL_PANIC} is defined in include/kprintf.h, it inicates two color scheme for output, KPL_DUMP print strings with bright white foreground and black background, KPL_PANIC prints strings with yellow foreground and red background. Those color constants are defined in include/scr.h, they are going to be introduced later.     args_list args;
    args_start(args, fmt);

    ptr = 0;

    for (; fmt[i]; ++i) {
        if ((fmt[i]!='%') && (fmt[i]!='\\')) {
            buf[ptr++] = fmt[i];
            continue;
        } else if (fmt[i] == '\\') {
            /* \a \b \t \n \v \f \r \\ */
            switch (fmt[++i]) {
            case 'a': buf[ptr++] = '\a'; break;
            case 'b': buf[ptr++] = '\b'; break;
            case 't': buf[ptr++] = '\t'; break;
            case 'n': buf[ptr++] = '\n'; break;
            case 'r': buf[ptr++] = '\r'; break;
            case '\\':buf[ptr++] = '\\'; break;
            }
            continue;
        }
We accept those escape sequence like prinf        /* fmt[i] == '%' */
        switch (fmt[++i]) {
        case 's':
            s = (char *)args_next(args, char *);
            while (*s)
                buf[ptr++] = *s++;
            break;
        case 'c':
            buf[ptr++] = (char)args_next(args, int);
            break;
        case 'x':
            parse_hex((unsigned long)args_next(args, unsigned long));
            break;
        case 'd':
            parse_num((unsigned long)args_next(args, unsigned long), 10);
            break;
        case '%':
            buf[ptr++] = '%';
            break;
        default:
            buf[ptr++] = fmt[i];
            break;
        }
    }
    buf[ptr] = '\0';
Puts an trailing \0 in buffer    args_end(args);
    for (i=0; i<ptr; ++i)
        print_c(buf[i], KPL[kl].fg, KPL[kl].bg);
Prints all characters in buffer on screen with function print_c}
libcc

Before we go any further, there is a problem might occur depends on you complier version, even with -nostdlib option "The compiler may generate calls to memcmp, memset and memcpy for System V (and ISO C) environments or to bcopy and bzero for BSD environment." so that means it might give you some "can't find memcpy" error etc. I didn't have this problem when I use my old version GCC, but it happens now. So we have to implement these functions by ourselves.
03/libcc.c/* result is currect, even when both area overlap */
void
bcopy(const void *src, void *dest, unsigned int n) {
    const char *s = (const char *)src;
    char *d = (char *)dest;
    if (s <= d)
        for (; n>0; --n)
            d[n-1] = s[n-1];
    else
        for (; n>0; --n)
            *d++ = *s++;
}

void
bzero(void *dest, unsigned int n) {
    memset(dest, 0, n);
}

void *
memcpy(void *dest, const void *src, unsigned int n) {
    bcopy(src, dest, n);
    return dest;
}

void *
memset(void *dest, int c, unsigned int n) {
    char *d = (char *)dest;
    for (; n>0; --n)
        *d++ = (char)c;
    return dest;
}

int
memcmp(const void *s1, const void *s2, unsigned int n) {
    const char *s3 = (const char *)s1;
    const char *s4 = (const char *)s2;
    for (; n>0; --n) {
        if (*s3 > *s4)
            return 1;
        else if (*s3 < *s4)
            return -1;
        ++s3;
        ++s4;
    }
    return 0;
}

int
strcmp(const char *s1, const char *s2) {
    while (*s1 && *s2) {
        int r = *s1++ - *s2++;
        if (r)
            return r;
    }
    if (*s1 == *s2)
        return 0
    else
        return (*s1)?1:-1;
}

char *
strcpy(char *dest, const char *src) {
    char *p = dest;
    while ( (*dest++ = *src++))
        ;
    *dest = 0;
    return p;
}

unsigned int
strlen(const char *s) {
    unsigned int n = 0;
    while (*s++)
        ++n;
    return n;
}

print_c

Manipulating video memory directly is not so convenient, so we need a module to handle screen output, as usually we have some constants defined
03/include/scr.h#define MAX_LINES    25
#define MAX_COLUMNS  80
By default we have a 80x25 screen#define TAB_WIDTH    8            /* must be 2^n */

/* color text mode, the video ram starts from 0xb8000,
   we all have color text mode, right? :) */
#define VIDEO_RAM    0xb8000
We have mentioned about this address briefly, I presume we are all in color text mode, at this mode the adapter uses 0xB8000-0xBF000 as video RAM. In general it works in 80 rows, 25 columns and 16 colors. This memory space is divided into multiple video pages of 4KB each. We can use all pages at the same time, but only one page is visible. To display a single character, two bytes are being used which called the character byte and the attribute byte. The character byte contains the value of the character. The attribute byte is defined like this:
Bit 7 Blinking
Bits 6-4 Background color
Bit 3 Bright
Bit3 2-0 Foreground color
#define LINE_RAM    (MAX_COLUMNS*2)

#define PAGE_RAM    (MAX_LINE*LINE_RAM)

#define BLANK_CHAR    (' ')
#define BLANK_ATTR    (0x07)        /* white fg, black bg */ //#Bug 002

#define CHAR_OFF(x,y)    (LINE_RAM*(y)+2*(x))
Calculates the offset of a given ordinary x, y from 0xB8000typedef enum COLOUR_TAG {
    BLACK, BLUE, GREEN, CYAN, RED, MAGENTA, BROWN, WHITE,
    GRAY, LIGHT_BLUE, LIGHT_GREEN, LIGHT_CYAN,
    LIGHT_RED, LIGHT_MAGENTA, YELLOW, BRIGHT_WHITE
} COLOUR;
Bases on the above given table, we can define this colour tags.

03/scr.cstatic int csr_x = 0;
static int csr_y = 0;
Because we only use one video page, so we make csr_x and csr_y store global cursor position is enough. If you want to check out code about how to use multiply ttys then check out here.
static void
scroll(int lines) {
This function take how many lines we should scroll up as the argument to scroll whole screen up, actually it just does some memory overwriting.
    int x = MAX_COLUMNS-1, y = MAX_LINES*(lines-1)+MAX_LINES-1;
    short *p = (short *)(VIDEO_RAM+CHAR_OFF(x, y));
    int i = MAX_COLUMNS*lines;
    memcpy((void *)VIDEO_RAM, (void *)(VIDEO_RAM+LINE_RAM*lines),
           LINE_RAM*(MAX_LINES-lines));
    for (; i>0; --i)
        *p-- = (short)((BLANK_ATTR<<8)|BLANK_CHAR); //#Bug 002
Clear all characters in bottom lines to make it looks like whole screen scrolls up. (#Bug 002: Song Jiang pointed out another bug, BLANK_ATTR should be shift 8 bits instead of 4. By fixing this bug, I found I made a mistake on the value BLANK_ATTR, it should be 0x07 instead of 0x70. Another problem is this scroll function could just scroll one line originally, search #Bug 002 in this page to find the related code change).}

void
set_cursor(int x, int y) {
This function sets the position of cursor    csr_x = x;
    csr_y = y;
Setting the position of cursor could be a race condition, but print_c is going to be used just in kernel, so I did not close all interrupts, it might cause some trouble, which I did not discover.    outb(0x0e, 0x3d4);
We are going to set the higher 8-bit of cursor position    outb(((csr_x+csr_y*MAX_COLUMNS)>>8)&0xff, 0x3d5);
The higher 8-bit of cursor has been set    outb(0x0f, 0x3d4);
We are going to set the higher 8-bit of cursor position
    outb(((csr_x+csr_y*MAX_COLUMNS))&0xff, 0x3d5);
The higher 8-bit of cursor has been set
}

void
get_cursor(int *x, int *y) {
    *x = csr_x;
    *y = csr_y;
}

void
print_c(char c, COLOUR fg, COLOUR bg) {
We use this function to print one character on current cursor position on screen.
    char *p;
    char attr;
    p = (char *)VIDEO_RAM+CHAR_OFF(csr_x, csr_y);
Get's the current cursor position in memory.    attr = (char)(bg<<4|fg);
Get's the character's attribute byte    switch (c) {
    case '\r':
        csr_x = 0;
        break;
    case '\n':
        for (; csr_x<MAX_COLUMNS; ++csr_x) {
            *p++ = BLANK_CHAR;
            *p++ = attr;
        }
        break;
    case '\t':
        c = csr_x+TAB_WIDTH-(csr_x&(TAB_WIDTH-1));
        c = c<MAX_COLUMNS?c:MAX_COLUMNS;
        for (; csr_x<c; ++csr_x) {
            *p++ = BLANK_CHAR;
            *p++ = attr;
        }
        break;
    case '\b':
        if ((! csr_x) && (! csr_y))
            return;
        if (! csr_x) {
            csr_x = MAX_COLUMNS - 1;
            --csr_y;
        } else
            --csr_x;
        ((short *)p)[-1] = (short)((BLANK_ATTR<<8)|BLANK_CHAR); //#Bug 002
        break;
    default:
        *p++ = c;
        *p++ = attr;
        ++csr_x;
        break;
    }
    if (csr_x >= MAX_COLUMNS) {
        csr_x = 0;
        if (csr_y < MAX_LINES-1)
            ++csr_y;
        else
            scroll(1);
    }
    set_cursor(csr_x, csr_y);
Reset the cursor position}

Subject:

Your Name:

Your Email Address:

Comments:


Prev
Home Next
Up