我的博客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;
}
}
如果是一個數字,那麼直接將字符轉換成數字就是域寬,如果是*號,那麼後一個參數就是域寬,如果後一個參數是負數就理解是左對齊的符號和域寬放在了一起
爲什麼的參數是 ,首先因爲 是 ,所以它也是,然後我們需要改變的值,那麼參數就得是的指針,所以是
.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);
}
現在的目錄結構是