从零实现一个操作系统-day8

实现简易版的printf函数

屏幕的输入输出函数主要的功能还是dubug,我们模仿标准库来实现,标准库的printf基于vsprintf

1
int vsprintf(const char *format, va_list arg)

先看看printk的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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
格式字符串的格式如下

1
%[flags][width][.prec][length]type

flags

flags的格式如上图,我们可以在程序中用二进制位来表示,先来定义这些标志的二进制位

1
2
3
4
5
6
7
#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部分的判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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的可能取值如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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;
}
}

如果是一个数字,那么直接将字符转换成数字就是域宽,如果是*号,那么后一个参数就是域宽,如果后一个参数是负数就理解是左对齐的符号和域宽放在了一起
为什么$skip_atoi$的参数是 $const char**$ ,首先因为$format$ 是 $const$,所以它也是$const$,然后我们需要改变$format$的值,那么参数就得是$format$的指针,所以是$char**$

.prec

精度格式符以“.”开头,后跟十进制整数。可取值如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//获取精度
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(字符)
1
2
3
4
5
6
7
8
9
10
11
//输出字符
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(字符串)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//输出字符串
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函数来构造输出的字符串

1
static char *number(char *str, int num, int base, int size, int precision, int type)

base是进制,type就是flags规定输出的格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#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;
}

然后输出无符号八进制整数就很简单了

1
2
3
case 'o':
str=number(str,va_arg(args,unsigned long),8,field_width,precision,flags);
break;
p (以16进制形式输出指针)
1
2
3
4
5
6
7
8
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进制大小写)
1
2
3
4
5
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位整数)
1
2
3
4
5
6
case 'i':
case 'd':
flags|=SIGN;
case 'u':
str=number(str,va_arg(args,unsigned long),10,field_width,precision,flags);
break;
b (二进制)
1
2
3
case 'b':
str=number(str,va_arg(args,unsigned long),2,field_width,precision,flags);
break;
n (什么也不输出。%n对应的参数是一个指向signed int的指针,在此之前输出的字符数将存储到指针所指的位置)
1
2
3
4
case 'n':
ip=va_arg(args,int *);
*ip=(str-buff);
break;

最后完整的printk代码

kernel/debug/printk.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
#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);
}

现在的目录结构是

本文标题:从零实现一个操作系统-day8

文章作者:

发布时间:2020年04月27日 - 23:04

最后更新:2020年04月29日 - 01:04

原始链接:http://startcraft.cn/post/ea2be1e.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------The End-------------