printk函数的实现

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_colorconsole_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的运算,或者你链接的时候没有把这个库链接进去

猜你喜欢

转载自blog.csdn.net/weixin_45206746/article/details/113107156