实现简易版的printf函数
屏幕的输入输出函数主要的功能还是dubug,我们模仿标准库来实现,标准库的printf基于vsprintf
1 | int vsprintf(const char *format, va_list arg) |
先看看printk的内容
1 | void printk(const char *format, ...) |
可以看出vsprint返回值是输出的字符个数,它的工作过程是格式化要输出的字符串,buff最后就是可以输出的字符串
vprintf
参考:https://www.cnblogs.com/zhoug2020/p/7676586.html
格式字符串的格式如下
1 | %[flags][width][.prec][length]type |
flags
flags的格式如上图,我们可以在程序中用二进制位来表示,先来定义这些标志的二进制位
1 |
开始实现vsprintf的flags部分的判断
1 | for (;*format;++format) |
这一段就是将标志位对应的那一段置1,当然如果没有’%’就直接输出就完事了
width
用十进制整数来表示输出的最少位数。若实际位数多于指定的宽度,则按实际位数输出,若实际位数少于定义的宽度则补以空格或0。width的可能取值如下:
1 |
|
如果是一个数字,那么直接将字符转换成数字就是域宽,如果是*号,那么后一个参数就是域宽,如果后一个参数是负数就理解是左对齐的符号和域宽放在了一起
为什么$skip_atoi$的参数是 $const char**$ ,首先因为$format$ 是 $const$,所以它也是$const$,然后我们需要改变$format$的值,那么参数就得是$format$的指针,所以是$char**$
.prec
精度格式符以“.”开头,后跟十进制整数。可取值如下:
1 | //获取精度 |
处理跟获取域宽差不多,但是负值就忽略了
type
length我们就直接忽略了,毕竟是简易的printf,type有很多,我们一个个来
c(字符)
1 | //输出字符 |
先判断左对齐的标志位是否为1,不然就是右对齐要在左边填空格直到长度为域宽-1
s(字符串)
1 | //输出字符串 |
%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 |
|
然后输出无符号八进制整数就很简单了
1 | case 'o': |
p (以16进制形式输出指针)
1 | case 'p': |
x和X (16进制大小写)
1 | case 'x': |
i和d和u (有符号32位整数和无符号32位整数)
1 | case 'i': |
b (二进制)
1 | case 'b': |
n (什么也不输出。%n对应的参数是一个指向signed int的指针,在此之前输出的字符数将存储到指针所指的位置)
1 | case 'n': |
最后完整的printk代码
kernel/debug/printk.c
1 |
|
现在的目录结构是