printf函数的模拟实现:printk
在照着教程写内核的时候,需要写自己的printf函数用来打印调试信息,暂且叫它printk函数吧,print kernel的意思
实现这个函数的主要难点有两个,一个是变长参数,另一个是格式字符串的判断
变长参数,参考这篇文章
https://www.cnblogs.com/mysky007/p/11204296.html
我把我写的vargs.h亮出来
#ifndef INCLUDE_VARGS_
#define INCLUDE_VARGS_
#define va_list char*
#define va_start(ap, first) (ap = (va_list)&first + sizeof(first))
#define va_arg(ap, type) (*(type*)((ap+=sizeof(type))-sizeof(type)))
#define va_end(ap) (ap = (va_list)NULL)
#endif
是这么个意思:
va_list是一个指针,指向保存参数栈结构的结构体
va_start就是初始化以下这个指针,它的位置在第一个确定的参数后面
因为参数是从右往左塞进栈的,又因为栈顶的地址是最低的,所以是加号
va_arg就是传递下一个参数,并且把结构体指针往后面移位
va_end就是关闭这个指针,避免它变成野指针
这个东西的用法太多了,秉承别人写了我就懒得写的原则,跳过这一段
第二个难点就是如何分析格式化字符串
我参考了这一篇博文
https://blog.csdn.net/c1204611687/article/details/86133774
主要是看printf的源码
可以看到printf的调用真的是一波三则,先调用vprintf,再调用vfprintf
我们仿照它这个,也这么写:
static void real_print_color(real_color_t back, real_color_t fore, const char *format, va_list argp);
void pirntk(const char *format, ...)
{
va_list argp;
va_start(argp, format);
real_print_color(rc_black, rc_white, format, argp);
va_end(argp);
}
void print_color(real_color_t back, real_color_t fore, const char *format, ...)
{
va_list argp;
va_start(argp, format);
real_print_color(back, fore, format, argp);
va_end(argp);
}
其中,real_color_t
是我在其他文件里面定义的
接下来,需要写real_print_color函数
考虑到我们的需求仅仅是打印调试信息,所以其实不用写得很庞大,够用就行。因为毕竟我写的是一个toy kernel,体验一下原理的
所以我的printf函数能识别的格式字符只有:
格式字符 | 含义 |
---|---|
%d | 整型 |
%ld | 长整型 |
%u | 无符号整型 |
%lu | 无符号长整型 |
%x,%X | 16进制,字母全部大小(反正是给自己看,能看懂就行) |
%o,%O | 8进制 |
%% | % |
我的思路大体是这样子的:
先构建一个大小为64的缓存(绝对够用)
然后,如果不是格式字符,就直接输出
如果是格式字符,先判断前缀。因为我只设计了一个长型的前缀,所以简单判断一下就可以了
然后,判断主体部分
首先,判断输入的值有没有符号,有符号的话就用一个flag记录,然后取相反数
接下来,反向输入到缓存里面
最后,再反向输出到控制台
什么意思呢,例如我要输入的数字是-12345
,如果直接输出到控制台,我势必要先输出符号位,然后循环一遍,获取它的长度信息,然后再循环一遍,输出到控制台
如果我反着来,先记录符号位,然后存到缓存,最后的结果是54321-
,再反向输出到控制台
这么做的坏处是多用了64大小的缓存
好处是我没有必要多浪费脑细胞去想12345要怎么变才能按顺序输出12345,我得先用一个循环发现长度是5(十进制的话),然后取最高位,num / 10000,接着输出,还得去掉最高位,num %= 10000,然后长度减一变成4,我得做多一倍的除法和取模运算。
我使用了两个宏来达到上面的目的
#define castarg(NUM, TYPE, SIG) \
TYPE cast_tmp = (TYPE)(NUM); \
SIG = cast_tmp < 0; \
NUM = SIG ? -cast_tmp : cast_tmp
#define num_stack_in(NUM, SIG, BASE, PTR) \
do{\
int tmp = NUM % BASE; \
NUM /= BASE; \
*PTR++ = charset[tmp];\
}while(NUM); \
if (SIG) \
*PTR++ = '-'
完整代码如下:
#include "debug.h"
static const char charset[16] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F'
};
static char buf[64] = {
'\0'};
static void real_print_color(real_color_t back, real_color_t fore, const char *format, va_list argp);
void printk(const char *format, ...)
{
va_list argp;
va_start(argp, format);
real_print_color(rc_black, rc_white, format, argp);
va_end(argp);
}
void print_color(real_color_t back, real_color_t fore, const char *format, ...)
{
va_list argp;
va_start(argp, format);
real_print_color(back, fore, format, argp);
va_end(argp);
}
#define castarg(NUM, TYPE, SIG) \
TYPE cast_tmp = (TYPE)(NUM); \
SIG = cast_tmp < 0; \
NUM = SIG ? -cast_tmp : cast_tmp
#define num_stack_in(NUM, SIG, BASE, PTR) \
do{\
int tmp = NUM % BASE; \
NUM /= BASE; \
*PTR++ = charset[tmp];\
}while(NUM); \
if (SIG) \
*PTR++ = '-'
static void
real_print_color(real_color_t back, real_color_t fore, const char *format, va_list argp)
{
if (format == NULL)
return;
char *p = (char*)format;
char *buf_p = buf; // 开一个大小为64的缓存
while (*p)
{
buf_p = buf;
if (*p != '%') // 不是格式符
console_putc_color(*p, back, fore);
else
{
int is_long = 0;
if(*(++p) == 'l')
{
is_long = 1;
p ++;
}
switch(*p)
{
case 'd': // Decimal
{
long arg = va_arg(argp, long);
int is_neg = 0;
if (is_long)
{
castarg(arg, long, is_neg);
}
else
{
castarg(arg, int, is_neg);
} // 这一步操作后,is_neg记录符号,arg记录去除符号后的值
num_stack_in(arg, is_neg, 10, buf_p);
break;
}
case 'u':
{
unsigned long arg = va_arg(argp, unsigned long);
if (is_long)
arg = (unsigned long)arg;
else
arg = (unsigned int)arg;
num_stack_in(arg, 0, 10, buf_p);
break;
}
case 'O':
case 'o':
{
unsigned long arg = va_arg(argp, unsigned long);
num_stack_in(arg, 0, 8, buf_p);
*buf_p++ = '0';
break;
}
case 'X':
case 'x':
{
unsigned long arg = va_arg(argp, unsigned long);
num_stack_in(arg, 0, 16, buf_p);
*buf_p++ = 'x';
*buf_p++ = '0';
break;
}
case '%':
{
*buf_p++ = '%';
break;
}
case 's':
{
char *arg = va_arg(argp, char *);
console_write_color(arg, back, fore);
break;
}
}
while (buf_p != buf) // 输出
console_putc_color(*--buf_p, back, fore);
}
p ++;
}
}
其中,console_putc_color
和console_write_color
是我写的输出带颜色的字符到控制台的指令
进行测试,打印一个九九乘法表看看
#include "debug.h"
int kern_entry()
{
console_clear();
print_color(rc_light_grey, rc_black, "A MULTIPLICATION TABLE\n");
for (int i = 1; i <= 9; i ++)
{
for (int j = 1; j <= i; j ++)
{
printk("%dx%d=%d\t", j,i,i*j);
}
printk("\n");
}
return 0;
}
花絮
我本来打算加入long long的支持的,但是,C语言并不原生提供对longlong运算的支持——它的实现与具体的内核架构有关,而我又嫌麻烦,就干脆直接取消了long long的支持
C语言实现long long的运算是通过调用函数实现的,所以在连接的时候出现类似于__moddi3
之类的未寻找到的错误,不是编译器坏了,而是你的内核架构不包含longlong的运算,或者你链接的时候没有把这个库链接进去