This tutorial is irrelative to operating system, but these functions are widely used in our later tutorials. In general, they are used as substitutions of C standard library functions.
If you are not interested in implementations of these functions, just note kprintf works likes printf in C and print_c prints one character on screen with given background and foreground colors.
It is safe for you to skip this tutorial.
printf is a programmer's good friend, so for printing strings, numbers etc on screen, I am going to implement a function works like printf for output instead of messing with B8000.
I'm not going to implement a fully functioning printf, for Skelix we just need it to print strings, numbers in hexadecimal or binary format, positive integers and characters.
The most important feature is it must be able to accept variable length arguments.
By default, when a function like func( is called, its
assembly likes this,int arg1, int arg2, int arg3)
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. How can we know how many arguments are there?
The answer is how many %X stuff in format string, how many arguments we have to parse.
In 32-bit mode, all date types which byte length smaller than a integer will be pushed as an integer. That implies even a one byte character occupies 4 bytes in stack We have to be sure we 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 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 kprintf to convert the stack space to a character stream for further processing.
#define _arg_stack_size(type) \
((((type)-1)/sizeof(sizeof)+1)*int(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)
Arguments are after format string, or I should say on the top of fmt in stack, this macro gets the starting address of those arguments.
#define args_end(ap)
Doesn't do anything at this moment
#define args_next(ap, type) (((type *)(ap+=_arg_stack_size(type)))[-1])
Gets the address of next argument.
static char buf[1024] = {-1};
static int ptr = -1;
We put all characters are going to be printed into a buffer and make a pointer point to the next available place.
/* valid base: 2, 8, 10 */
static void
parse_num(unsigned value, intunsigned base) {int
unsigned int n = value / base;
int r = value % base;
if (r < 0) {
r += base;
--n;
}
if (value >= base)
parse_num(n, base);
buf[ptr++] = "0123456789"[r];
}
static void
parse_hex(unsigned int value) {
int i = 8;
while (i-- > 0) {
buf[ptr++] = "0123456789abcdef"[(value>>(i*4))&0xf];
}
}
These two functions parse the given value into different radix.
/* %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},
};
is defined in enum KP_LEVEL {KPL_DUMP, KPL_PANIC}03/include/kprintf.h, it inicates two color scheme for output,
KPL_DUMP prints strings with bright white foreground and black background, KPL_PANIC prints strings with yellow foreground and red background.
Those color constants are defined in 03/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 printf.
/* fmt[i] == '%' */
switch (fmt[++i]) {
case 's':
s = ( *)args_next(args, char *);char
while (*s)
buf[ptr++] = *s++;
break;
case 'c':
/* why is int?? */
buf[ptr++] = (char)args_next(args, int);
break;
case 'x':
parse_hex((unsigned)args_next(args, longunsigned));long
break;
case 'd':
parse_num((unsigned)args_next(args, longunsigned), 10);long
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.
Before we go any further, there is a problem might occur depends on you complier version. Even with -nostdlib option, based on GCC manual,
"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."
GCC might give you some "can't find memcpy" error etc. I didn't have this problem before when I was using my old version GCC, but it happens now.
Wee have to implement these functions by ourselves.
/* result is currect, even when both area overlap */
void
bcopy(const *src, void *dest, voidunsigned int n) {
const *s = (charconst *)src;char
*d = (char *)dest;char
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( *dest, voidconst *src, voidunsigned int n) {
bcopy(src, dest, n);
return dest;
}
void *
memset(void *dest, c, intunsigned n) {int
*d = (char *)dest;char
for (; n>0; --n)
*d++ = (char)c;
return dest;
}
int
memcmp(const *s1, voidconst *s2, voidunsigned int n) {
const *s3 = (charconst *)s1;char
const *s4 = (charconst *)s2;char
for (; n>0; --n) {
if (*s3 > *s4)
return 1;
else if (*s3 < *s4)
return -1;
++s3;
++s4;
}
return 0;
}
int
strcmp(const *s1, charconst *s2) {char
while (*s1 && *s2) {
int r = *s1++ - *s2++;
if (r)
return r;
}
return (*s1)?1:-1;
}
char *
strcpy( *dest, charconst *src) {char
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;
}
Manipulating video memory directly is messy, so we need a module to handle screen output. We need to define some contants as usual.
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 power of 2 */
/* color text mode, the video ram starts from 0xb8000,
we all have color text mode, right? :) */
#define VIDEO_RAM 0xb8000
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 (here is a program I wrote before uses multiple display pages), but only one page is visible. To display a single character, two bytes are used, that is 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 |
This table will be used shortly.
#define LINE_RAM (MAX_COLUMNS*2)
#define PAGE_RAM (MAX_LINE*MAX_COLUMNS)
#define BLANK_CHAR (' ')
#define BLANK_ATTR (0x70) /* white fg, black bg */ //#Bug 002
#define CHAR_OFF(x,y) (LINE_RAM*(y)+2*(x))
Calculates the offset of a given x, y.
typedef 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 table given above, defines these colour tags.
03/scr.c
static int csr_x = 0;
static int csr_y = 0;
Because we are going to use just one video page, so cursor postion is stored in csr_x and csr_y globally.
static void
scroll(int lines) {
This function takes the number of lines we want to scroll down as the argument, it just overwrite some memory to make it looks like scroll down.
*p = (short *)(VIDEO_RAM+CHAR_OFF(MAX_COLUMNS-1, MAX_LINES-1));short
int i = MAX_COLUMNS-1;
memcpy(( *)VIDEO_RAM, (void *)(VIDEO_RAM+LINE_RAM*lines),void
LINE_RAM*(MAX_LINES-lines));
for (; i>=0; --i)
*p-- = (short)((BLANK_ATTR<<4)|BLANK_CHAR); //#Bug 002
}
Clears 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
shifted 8 bits instead of 4. By fixing this bug, I found I made a mistake on 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( x, int y) {int
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 used in kernel only, so I did not close all interrupts.
It might cause some problem which I have not discovered.
outb(0x0e, 0x3d4);
outb(((csr_x+csr_y*MAX_COLUMNS)>>8)&0xff, 0x3d5);
outb(0x0f, 0x3d4);
outb(((csr_x+csr_y*MAX_COLUMNS))&0xff, 0x3d5);
Sets the cursor position via port 0x3D4, 0x3D5.
}
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);
Gets the current cursor postion in memory.
attr = (char)(bg<<4|fg);
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;
(( *)p)[-1] = (short)((BLANK_ATTR<<4)|BLANK_CHAR);short
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 postion after printing.
}
Feel free to use my code. Please contact me if you have any questions.