從零實現一個操作系統-day8

我的博客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;
	}
}

如果是一個數字,那麼直接將字符轉換成數字就是域寬,如果是*號,那麼後一個參數就是域寬,如果後一個參數是負數就理解是左對齊的符號和域寬放在了一起
爲什麼skipatoiskip_atoi的參數是 constcharconst char** ,首先因爲formatformatconstconst,所以它也是constconst,然後我們需要改變formatformat的值,那麼參數就得是formatformat的指針,所以是charchar**

.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);
}

現在的目錄結構是

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章