以下是 JOS 内核控制台输出代码关于 kern/printf.c、 lib/printfmt.c、 kern/console.c 的解析。摘抄自【系统的启动和初始化】一文。
C 语言中解决变参问题的一组宏
1) 用 va_list
可以定义一个 va_list
型的变量,这个变量是指向参数的指针。
2) 用 va_start
宏可以初始化一个 va_list
变量,这个宏有两个参数,第一个是 va_list
变量本身,第二个是可变的参数的前一个参数,是一个固定的参数。
3) 用 va_arg
宏可以返回可变的参数,这个宏也有两个参数,第一个是 va_list
变量,即指向参数的指针,第二个是返回的参数类型。
4) 用 va_end
宏结束可变参数的获取。
cprintf()函数原型
kern/printf.c 之 cprintf()函数的原型
1 | /* |
vcprintf函数原型
kern/printf.c 之 cprintf 函数所调用的 vcprintf 函数的原型
1 | int |
putch函数原型
kern/printf.c 之 putch 函数的原型
1 | /* |
cputchar函数原型
kern/console.c 之 cputchar 函数的原型
1 | void |
cons_putc函数原型
kern/console.c 之 cons_putc 函数的原型
1 | /*将一个字符输出到控制台*/ |
下图表示的是整形参数的 8 到 15 位是如何确定字符输出的格式的: 字符显示属性字节 显存与显示屏的对应关系
可以看到高 4 位决定了字符的背景色,可以有 16 种颜色,同样低四位则决定了字符本
身的颜色,在这里, R 代表红色的色素, G 代表绿色的色素, B 代表蓝色的色素,这三个色素的组合就可以组成 8 种不同的颜色,而 I 则表示颜色是否是高亮的,于是这样便可以有 16 种颜色。
下图表示的是显存与显示屏的对应关系:
cga_putc函数原型
kern/console.c 之 cga_putc 函数的原型
1 | static void |
vprintfmt函数程序流程图
vcprintf (见 kern/printf.c) 函数所调用的 vprintfmt (见 lib/printfmt.c) 函数的程序流程图 vprintfmt程序流程图
上图 5 个格式变量: padc、 width、 precision、 lflag、 altflag。 padc 代表的是填充字符,在初始化的时候 padc 变量会被初始化为空格符,而当程序在显示字符串的 ’%’ 字符后读到 ’-’ 或者 ’0’ 的字符时便会将 ’-’ 或者 ’0’ 赋值给 padc。 width 代表的是打印的一个字符串或者一个数字在屏幕上所占的宽度,而 precision 则特指一个字符串在屏幕上应显示的长度。当打印字符串的时候, padc = ’-’ 代表着字符串需要左对齐,右边补空格, padc =’ ’ 代表字符串右对齐, 而左边由空格补齐, padc = ’0’ 代表字符串右对齐, 左边由 0 补齐。在我们这个实验中当输出数字时会一律的右对齐,左边补 padc,数字显示长度为数字本身的长度。 lfag 变量则是专门在输出数字的时候起作用,在我们这个实验中为了简单起见实际上是不支持输出浮点数的,于是 vprintfmt 函数只能够支持输出整形数,,输出整形数时,当 lflag = 0 时,表示将参数当做 int 型的来输出,当 lflag = 1 时,表示当做 long 型的来输出,而当 lflag = 2 时表示当做 long long 型的来输出。最后, altflag 变量表示当 altflag = 1 时函数若输出乱码则用 ’?’ 代替。
vprintfmt函数打印字符串
lib/printfmt.c 之 vprintfmt 函数打印一个字符串具体是如何实现的
1 | case 's': |
当程序识别了显示字符串中 ’%’ 后的 ’s’ 字符后便从可变参数中读入字符串指针,若指针为空,则让它指向一个 “(null)” 字符串。然后再判断输出是左对齐还是右对齐,若 padc = ’-’ , 表示是左对齐, 否则是右对齐。确认是右对齐的话,按照我们之前所讲的, 用 width 减去字符串实际显示长度便得到需要在左边补空格或 0 的个数,注意到 int strnlen(char *str, int maxlen) 函数原型是计算字符串 str 的 (unsigned int 型)长度,不包括结束符NULL,该长度最大为 maxlen , maxlen 传入 -1 的话会返回字符串的长度。在这之后程序便开始打印字符串本身,可以看到若 precision 大于 0 则显示长度等于 precision 与字符串长度之间的最小值, precision 小于 0 则显示长度等于字符串本身的长度(无论 width 多大)。最后程序判断如果字符串是左对齐的话则在右侧剩余空间补充空格。
printnum函数原型
在打印数字的时候则会用到 printnum 这个函数,该函数的主体如下所示
1 | /*参数 num 代表需要打印出来的整形数, base 代表整形数的进制,其它的参数和 vprintfmt 函数中代表同样的意思。当 num 超过 1 位时函数递归的调用自己本身,这样便可以先打印高位的数字,当 num 只有 1 位时,程序便首先按照右对齐的格式在左侧打印填充字符,然后打印这个数字。*/ |
可以看到无论是打印字符串还是数字,都是将它们分解成一个个单个的字符然后用
putch 函数一个一个的打印出来。所以 putch 函数可谓是显示输出的基本函数。