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.
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++;
}
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 }