我的博客startcraft
实现简易版的printf函数
屏幕的输入输出函数主要的功能还是dubug,我们模仿标准库来实现,标准库的printf基于vsprintf
int vsprintf(const char *format, va_list arg)
先看看printk的内容
void printk(const char *format, ...)
{
// 避免频繁创建临时变量,内核的栈很宝贵
static char buff[1024];
va_list args;
va_start(args, format);
int i = vsprintf(buff, format, args);
va_end(args);
buff[i] = '\0';
console_write(buff);
}
void printk_color(real_color_t back, real_color_t fore, const char *format, ...)
{
// 避免频繁创建临时变量,内核的栈很宝贵
static char buff[1024];
va_list args;
va_start(args, format);
int i = vsprintf(buff, format, args);
va_end(args);
buff[i] = '\0';
console_write_color(buff, back, fore);
}
可以看出vsprint返回值是输出的字符个数,它的工作过程是格式化要输出的字符串,buff最后就是可以输出的字符串
vprintf
参考:https://www.cnblogs.com/zhoug2020/p/7676586.html
格式字符串的格式如下
%[flags][width][.prec][length]type
flags
flags的格式如上图,我们可以在程序中用二进制位来表示,先来定义这些标志的二进制位
#define ZEROPAD 1 //将输出的前面补上0
#define SIGN 2 // unsigned/signed long
#define PLUS 4 // 显示加号
#define SPACE 8 // 正数前面输出空格,负数输出负号
#define LEFT 16 // 左对齐
#define SPECIAL 32 // '#'标志位
#define SMALL 64 // 16进制使用'abcdef'而不是'ABCDEF'
开始实现vsprintf的flags部分的判断
for (;*format;++format)
{
if(*format!='%')
{
*str++=*format;
continue;
}
flags=0;
repate:
format++;
switch(*format)
{
case '-':
flags|=LEFT;
goto repate;
case '+':
flags|=PLUS;
goto repate;
case ' ':
flags|=SPACE;
goto repate;
case '#':
flags|=SPECIAL;
goto repate;
case '0':
flags|=ZEROPAD;
goto repate;
}
}
这一段就是将标志位对应的那一段置1,当然如果没有’%'就直接输出就完事了
width
用十进制整数来表示输出的最少位数。若实际位数多于指定的宽度,则按实际位数输出,若实际位数少于定义的宽度则补以空格或0。width的可能取值如下:
#define is_digit(c) ((c)>='0'&&(c)<='9')
static int skip_aoti(const char **s)
{
int i=0;
while(is_digit(**s))
i=i*10+*((*s)++)-'0';
return i;
}
//获取域宽
field_width=-1;
if (is_digit(*format))
field_width=skip_aoti(&format);
else if(*format=='*')
{
field_width=va_arg(args,int);
if(field_width<0)
{
field_width=-field_width;
flags|=LEFT;
}
}
如果是一个数字,那么直接将字符转换成数字就是域宽,如果是*号,那么后一个参数就是域宽,如果后一个参数是负数就理解是左对齐的符号和域宽放在了一起
为什么
s
k
i
p
a
t
o
i
skip_atoi
skipatoi的参数是
c
o
n
s
t
c
h
a
r
∗
∗
const char**
constchar∗∗ ,首先因为
f
o
r
m
a
t
format
format 是
c
o
n
s
t
const
const,所以它也是
c
o
n
s
t
const
const,然后我们需要改变
f
o
r
m
a
t
format
format的值,那么参数就得是
f
o
r
m
a
t
format
format的指针,所以是
c
h
a
r
∗
∗
char**
char∗∗
.prec
精度格式符以“.”开头,后跟十进制整数。可取值如下:
//获取精度
precision=-1;
if(*format=='.')
{
++format;
if(is_digit(*format))
precision=skip_aoti(&format);
else if(*format=='*')
{
precision=va_arg(args,int);
}
if(precision<0)
precision=0;
}
处理跟获取域宽差不多,但是负值就忽略了
type
length我们就直接忽略了,毕竟是简易的printf,type有很多,我们一个个来
c(字符)
//输出字符
case 'c':
if(!(flags&LEFT))//右对齐
{
while(--field_width>0)
*str++ =' ';
}
*str++ =(unsigned char) va_arg(args,int);//获取要输出的字符
while(--field_width>0)
*str++ =' ';
break;
先判断左对齐的标志位是否为1,不然就是右对齐要在左边填空格直到长度为域宽-1
s(字符串)
//输出字符串
case 's':
s=va_arg(args,char*);
len=strlen(s);
if(precision<0)//代表未指定
precision=len;
else if(len>precision)
len=precision;
if(!(flags&LEFT))
{
while(--field_width)
*str++ =' ';
}
for(int i=0;i<len;++i)
*str++ = *s++;
while(len<field_width--)
*str++ =' ';
break;
%ms:输出的字符串占m列,如字符串本身长度大于m,则突破获m的限制,将字符串全部输出。若串长小于m,则左补空格。
%-ms:如果串长小于m,则在m列范围内,字符串向左靠,右补空格。
%m.ns:输出占m列,但只取字符串中左端n个字符。这n个字符输出在m列的右侧,左补空格,注意:如果n未指定,默认为0.
%-m.ns:其中m、n含义同上,n个字符输出在m列范围的左侧,右补空格。如果n>m,则自动取n值,即保证n个字符正常输出,注意:如果n未指定,默认为0.
o (无符号8进制(octal)整数(不输出前缀0))
对于整数的输出比较复杂,所以写了一个number函数来构造输出的字符串
static char *number(char *str, int num, int base, int size, int precision, int type)
base是进制,type就是flags规定输出的格式
#define do_div(n,base) ({ int __res;\
__asm__("divl %4":"=a" (n),"=d" (__res):"0" (n),"1" (0),"r" (base));\
__res; })
static char *number(char *str, int num, int base, int size, int precision, int type)
{
char c,sign,tmp[36];
const char *digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";//0-36进制数的字符集
int i;
if(type&SMALL)//若规定输出小写字母字符集就是这个
digits ="0123456789abcdefghijklmnopqrstuvwxyz";
if(type&LEFT)
type&= ~ZEROPAD;//即‘0’和‘-’不能同时使用
if(base<2||base>36)
return 0;
c=(type&ZEROPAD)? '0':' ';//判断填充满域宽的应该是什么字符
if((type&SIGN)&&num<0)//若输出的是有符号数且为负数,则符号设为负号
{
sign='-';
num=-num;
}else
sign=(type&PLUS)? '+': (type&SPACE)? ' ' : 0;//决定正数左端的符号
if(sign)
size--;//如果有符号
if(type&SPECIAL){//type为井号
if(base==16)//16进制前面输出0x或0X
size-=2;
else if(base==8)//8进制前面输出0
size--;
}
i=0;
if(num==0)
tmp[i++]='0';
else
{
//进制转换
while(num!=0)
tmp[i++]=digits[do_div(num,base)];
}
if(i>precision)
precision=i;
size-=precision;
if(!(type&(ZEROPAD+LEFT)))//既不填0也不左对齐,那么填充的是空格
{
while(size-- >0)
*str++ =' ';
}
if(sign)
*str++ =sign;//加上符号
if(type&SPECIAL)
{
if(base==8)
*str++ ='0';//八进制前面加0
else if(base==16)
{
//16进制前加0x或0X
*str++ ='0';
*str++ =digits[33];
}
}
if(!(type&LEFT))//如果部署左对齐,左端要填上对应的填充符号
{
while(size-- >0)
*str++=c;
}
while(i<precision--)//精度不够的话,因为是整数所以要在左端加0
*str++ ='0';
while(i-- >0)
{
*str++=tmp[i];
}
while(size-- >0)
*str++=' ';
return str;
}
然后输出无符号八进制整数就很简单了
case 'o':
str=number(str,va_arg(args,unsigned long),8,field_width,precision,flags);
break;
p (以16进制形式输出指针)
case 'p':
if(field_width ==-1)
{
field_width=8;//默认32位
flags|=ZEROPAD;
}
str=number(str,(unsigned long)va_arg(args,void*),16,field_width,precision,flags);
break;
x和X (16进制大小写)
case 'x':
flags|=SMALL;
case 'X':
str=number(str,va_arg(args,unsigned long),16,field_width,precision,flags);
break;
i和d和u (有符号32位整数和无符号32位整数)
case 'i':
case 'd':
flags|=SIGN;
case 'u':
str=number(str,va_arg(args,unsigned long),10,field_width,precision,flags);
break;
b (二进制)
case 'b':
str=number(str,va_arg(args,unsigned long),2,field_width,precision,flags);
break;
n (什么也不输出。%n对应的参数是一个指向signed int的指针,在此之前输出的字符数将存储到指针所指的位置)
case 'n':
ip=va_arg(args,int *);
*ip=(str-buff);
break;
最后完整的printk代码
kernel/debug/printk.c
#include "console.h"
#include "string.h"
#include "vargs.h"
static int vsprintf (char *buff, const char *format, va_list args);
void printk (char *format, ...)
{
// 避免频繁创建临时变量,内核的栈很宝贵
static char buff[1024];
va_list args;
va_start(args,format);
int i=vsprintf(buff,format,args);
va_end(args);
buff[i]='\0';
console_write(buff);
}
void printk_color (real_color_t back, real_color_t fore,const char*format, ...)
{
// 避免频繁创建临时变量,内核的栈很宝贵
static char buff[1024];
va_list args;
va_start(args,format);
int i=vsprintf(buff,format,args);
va_end(args);
buff[i]='\0';
console_write_color(buff,back,fore);
}
#define is_digit(c) ((c)>='0'&&(c)<='9')
#define ZEROPAD 1 //将输出的前面补上0
#define SIGN 2 // unsigned/signed long
#define PLUS 4 // 显示加号
#define SPACE 8 // 正数前面输出空格,负数输出负号
#define LEFT 16 // 左对齐
#define SPECIAL 32 // '#'标志位
#define SMALL 64 // 16进制使用'abcdef'而不是'ABCDEF'
#define do_div(n,base) ({ int __res; __asm__("divl %4":"=a" (n),"=d" (__res):"0" (n),"1" (0),"r" (base)); __res; })
static int skip_aoti(const char **s)
{
int i=0;
while(is_digit(**s))
i=i*10+*((*s)++)-'0';
return i;
}
static char *number(char *str, int num, int base, int size, int precision, int type)
{
char c,sign,tmp[36];
const char *digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";//0-36进制数的字符集
int i;
if(type&SMALL)//若规定输出小写字母字符集就是这个
digits ="0123456789abcdefghijklmnopqrstuvwxyz";
if(type&LEFT)
type&= ~ZEROPAD;//即‘0’和‘-’不能同时使用
if(base<2||base>36)
return 0;
c=(type&ZEROPAD)? '0':' ';//判断填充满域宽的应该是什么字符
if((type&SIGN)&&num<0)//若输出的是有符号数且为负数,则符号设为负号
{
sign='-';
num=-num;
}else
sign=(type&PLUS)? '+': (type&SPACE)? ' ' : 0;//决定正数左端的符号
if(sign)
size--;//如果有符号
if(type&SPECIAL){//type为井号
if(base==16)//16进制前面输出0x或0X
size-=2;
else if(base==8)//8进制前面输出0
size--;
}
i=0;
if(num==0)
tmp[i++]='0';
else
{
//进制转换
while(num!=0)
tmp[i++]=digits[do_div(num,base)];
}
if(i>precision)
precision=i;
size-=precision;
if(!(type&(ZEROPAD+LEFT)))//既不填0也不左对齐,那么填充的是空格
{
while(size-- >0)
*str++ =' ';
}
if(sign)
*str++ =sign;//加上符号
if(type&SPECIAL)
{
if(base==8)
*str++ ='0';//八进制前面加0
else if(base==16)
{
//16进制前加0x或0X
*str++ ='0';
*str++ =digits[33];
}
}
if(!(type&LEFT))//如果部署左对齐,左端要填上对应的填充符号
{
while(size-- >0)
*str++=c;
}
while(i<precision--)//精度不够的话,因为是整数所以要在左端加0
*str++ ='0';
while(i-- >0)
{
*str++=tmp[i];
}
while(size-- >0)
*str++=' ';
return str;
}
static int vsprintf (char *buff, const char *format, va_list args)
{
int *ip;
char *str=buff;
int len;
int flags;//标志位
char *s;
int field_width;
int precision;
for (;*format;++format)
{
if(*format!='%')
{
*str++=*format;
continue;
}
flags=0;
repate:
format++;
switch(*format)
{
case '-':
flags|=LEFT;
goto repate;
case '+':
flags|=PLUS;
goto repate;
case ' ':
flags|=SPACE;
goto repate;
case '#':
flags|=SPECIAL;
goto repate;
case '0':
flags|=ZEROPAD;
goto repate;
}
//获取域宽
field_width=-1;
if (is_digit(*format))
field_width=skip_aoti(&format);
else if(*format=='*')
{
field_width=va_arg(args,int);
if(field_width<0)
{
field_width=-field_width;
flags|=LEFT;
}
}
//获取精度
precision=-1;
if(*format=='.')
{
++format;
if(is_digit(*format))
precision=skip_aoti(&format);
else if(*format=='*')
{
precision=va_arg(args,int);
}
if(precision<0)
precision=0;
}
if (*format=='h' || *format=='l'||*format=='L')
++format;
switch (*format)
{
//输出字符
case 'c':
if(!(flags&LEFT))//右对齐
{
while(--field_width>0)
*str++ =' ';
}
*str++ =(unsigned char) va_arg(args,int);//获取要输出的字符
while(--field_width>0)
*str++ =' ';
break;
//输出字符串
case 's':
s=va_arg(args,char*);
len=strlen(s);
if(precision<0)//代表未指定
precision=len;
else if(len>precision)
len=precision;
if(!(flags&LEFT))
{
while(--field_width)
*str++ =' ';
}
for(int i=0;i<len;++i)
*str++ = *s++;
while(len<field_width--)
*str++ =' ';
break;
case 'o':
str=number(str,va_arg(args,unsigned long),8,field_width,precision,flags);
break;
case 'p':
if(field_width ==-1)
{
field_width=8;//默认32位
flags|=ZEROPAD;
}
str=number(str,(unsigned long)va_arg(args,void*),16,field_width,precision,flags);
break;
case 'x':
flags|=SMALL;
case 'X':
str=number(str,va_arg(args,unsigned long),16,field_width,precision,flags);
break;
case 'i':
case 'd':
flags|=SIGN;
case 'u':
str=number(str,va_arg(args,unsigned long),10,field_width,precision,flags);
break;
case 'b':
str=number(str,va_arg(args,unsigned long),2,field_width,precision,flags);
break;
case 'n':
ip=va_arg(args,int *);
*ip=(str-buff);
break;
default:
if (*format != '%')
*str++ = '%';
if (*format) {
*str++ = *format;
} else {
--format;
}
break;
}
}
*str++='\0';
return (str-buff);
}
现在的目录结构是