我们的目标

这节内容和操作系统无关,但是这些函数会在后面的教程中广泛使用,它们被用来替代C标准库中的函数。如果你不关心它们是怎么实现的,只需要记住kprintf就像C中的printf一样,print_c在屏幕上输出黑底白字的一个字符。你可以安全地跳过此节。

下载代码


kprintf

printf是程序员的好朋友,因此为了输出字符串、数字之类的东西,我要实现一个类似printf的函数而不是再在B8000处瞎搞。

我不打算实现完全版的printf,在Skelix中我们只需要输出字符串、十六进制和二进制数字、正整数和字符而已。最重要的是它必须接受变长参数。

默认情况下,当调用函数func(int arg1, int arg2, int arg3)时,它的汇编代码如下,

pushl arg3

pushl arg2

pushl arg1

call func

参数被从右到左一个个地压入堆栈,所以更多的参数不过是更深地压栈而已。我们怎么才能知道有多少个参数呢?答案就是格式串中有多少个%X就有多少个参数。

在32位模式下,所有长度小于整形的数据都会被当做整形压栈。也就是说哪怕一个字符也会在栈中占据4字节空间,我们必须正确地处理栈中的参数。

我们准备让kprintf这样接受参数kprintf(color, format string, arguments...),第一个参数定义了输出的前景和背景色。还有一些为处理栈而定义的宏,如果你熟悉C的话,那么你一定也熟悉这些宏。

03/kprintf.c

#define args_list char *

kprintf用这个宏把栈空间转换成字符流以方便处理。

#define _arg_stack_size(type)   \

(((sizeof(type)-1)/sizeof(int)+1)*sizeof(int))

这个宏将参数对4圆整,以计算它在栈中需占用多少个4字节空间。

#define args_start(ap, fmt) do {        \

ap = (char *)((unsigned int)&fmt + _arg_stack_size(&fmt));      \

while (0)

参数在格式串之后,或者我应该说是在栈中fmt的顶上,这个宏获取参数的开始地址。

#define args_end(ap)

什么也不做。

#define args_next(ap, type) (((type *)(ap+=_arg_stack_size(type)))[-1])

获得下一个参数的地址。

static char buf[1024] = {-1};

static int ptr = -1;

将所有要输出的字符放入缓冲中区,并用指针指向下一个可用空间。

/* valid base: 2, 8, 10 */

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++] = "0123456789"[r];

}

 

static void

parse_hex(unsigned int value) {

        int i = 8;

        while (i-- > 0) {

                buf[ptr++] = "0123456789abcdef"[(value>>(i*4))&0xf];

        }

}

这两个函数转换数值到不同进制。

/* %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}的定义在03/include/kprintf.h中,它定义了输出的两种颜色方案,KPL_DUMP输出黑底白字,KPL_PANIC输出红底黄字。细节在03/include/scr.h中,后面介绍它们。

        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;

                }

printf一样接受转义字符串。

                /* fmt[i] == '%' */

                switch (fmt[++i]) {

                case 's':

                        s = (char *)args_next(args, 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 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';

在缓冲中放入结尾的\0。

        args_end(args);

        for (i=0; i<ptr; ++i)

                print_c(buf[i], KPL[kl].fg, KPL[kl].bg);

}

print_c在屏幕上打印缓冲中的所有字符。


libcc

在继续之前,你的编译器版本可能造成一些问题。即使使用了-nostdlib选项,根据GCC手册:“编译器可能在System V(和 ISO C)环境下生成对memcmpmemsetmemcpy的调用;可能在BSD环境下生成对bcopybzero的调用。”。GCC可能会给你“找不到memcpy”之类的错误。我用之前旧版本的GCC是没有问题的,但现在有了。我们必须自己实现这些函数了。

/* 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;

        }

        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;

}




你可以自由使用我的代码,如有疑问请联系我