1.printk函数
基于x86架构的简单内核实现-2 | Sifanのblog (liangzhouzz.github.io)在这偏中提到了,在内核态无法使用用户态的printf函数,所以需要编写内核级的printk函数,基于x86简易内核操作中,我们是采用操作寄存器的方式进行读写和打印。
同理,在riscv
的S
模式下 也是无法使用U
模式下的printf
函数,需要封装位于S
模式下的printk
函数
可变参数的解释见:封装printf函数 | TimerのBlog (yanglianoo.github.io)
在os
目录下新增printk.c
和 os.h
OS/os.h
头文件:基本的操作系统头文件,通常用于小型嵌入式系统或内核开发中
stddef.h
:提供了一些标准定义,如 size_t
、NULL
等。
stdarg.h
:提供了处理变长参数列表的宏和类型,用于实现变长参数函数(如 printk
)。
panic
:在系统遇到严重错误时调用,通常会停止系统运行并输出错误信息。
sbi_console_putchar
:通过 SBI(Supervisor Binary Interface)在控制台上输出字符,通常用于 RISC-V 系统。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #ifndef __OS_H__ #define __OS_H__
#include <stddef.h> #include <stdarg.h>
extern int printk(const char* s, ...); extern void panic(char *s); extern void sbi_console_putchar(int ch);
#endif
|
OS/printk.c
printk函数:
- 对
_vprintf
的封装,用于格式化输出字符串。它接受可变数量的参数,并将它们传递给 _vprintf
进行处理。
res
代表第一个参数的字符串长度,将第一个参数的指针va_list vl
和实际值传递给了_vprintf
va_start
宏用于初始化 vl
以便访问可变参数。它接受两个参数:第一个是 va_list
类型的变量,第二个是最后一个显式参数(在本例中为 s
)。
_vprintf
函数被调用来处理格式化字符串 s
和参数列表 vl
。_vprintf
函数会格式化字符串并将结果输出到 UART,同时返回输出的字符数。
1 2 3 4 5 6 7 8 9
| int printk(const char* s, ...) { int res = 0; va_list vl; va_start(vl, s); res = _vprintf(s, vl); va_end(vl); return res; }
|
例子:
假设你调用 printk
函数如下:
1
| printk("Hello, %s! The answer is %d.\n", "world", 42);
|
printk
函数将执行以下操作:
- 初始化
va_list
并将参数列表指向 "world"
和 42
。
- 调用
_vprintf
函数,该函数会将格式化后的字符串 “Hello, world! The answer is 42.\n” 写入 out_buf
并通过 uart_puts
输出到 UART。
- 返回写入的字符数。
_vprintf函数:
- 在这个函数中,调用了
_vsnprintf
函数,并将结果通过uart_puts
输出,调用了SBI
的控制台输出接口函数sbi_console_putchar
out_buf
是一个静态字符数组,大小为 1000 字节,用于存储格式化后的输出字符串。
- 使用
_vsnprintf
函数来计算格式化后的字符串长度(不实际生成字符串),以便预先检查是否会超出缓冲区大小。
- 第一个参数
NULL
表示不写入任何缓冲区。
- 第二个参数
-1
表示忽略缓冲区大小,只计算长度。
- 第三个参数
s
是格式化字符串。
- 第四个参数
vl
是可变参数列表。
- 第二个
_vsnprintf
函数,将格式化后的字符串写入out_buf
1 2 3 4 5 6 7 8 9 10 11 12
| static char out_buf[1000]; static int _vprintf(const char* s, va_list vl) { int res = _vsnprintf(NULL, -1, s, vl); if (res+1 >= sizeof(out_buf)) { uart_puts("error: output string size overflow\n"); while(1) {} } _vsnprintf(out_buf, res + 1, s, vl); uart_puts(out_buf); return res; }
|
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
| #include "os.h" void uart_puts(char *s) { while (*s) { sbi_console_putchar(*s++); } }
static int _vsnprintf(char * out, size_t n, const char* s, va_list vl) { int format = 0; int longarg = 0; size_t pos = 0; for (; *s; s++) { if (format) { switch(*s) { case 'l': { longarg = 1; break; } case 'p': { longarg = 1; if (out && pos < n) { out[pos] = '0'; } pos++; if (out && pos < n) { out[pos] = 'x'; } pos++; } case 'x': { long num = longarg ? va_arg(vl, long) : va_arg(vl, int); int hexdigits = 2*(longarg ? sizeof(long) : sizeof(int))-1; for(int i = hexdigits; i >= 0; i--) { int d = (num >> (4*i)) & 0xF; if (out && pos < n) { out[pos] = (d < 10 ? '0'+d : 'a'+d-10); } pos++; } longarg = 0; format = 0; break; } case 'd': { long num = longarg ? va_arg(vl, long) : va_arg(vl, int); if (num < 0) { num = -num; if (out && pos < n) { out[pos] = '-'; } pos++; } long digits = 1; for (long nn = num; nn /= 10; digits++); for (int i = digits-1; i >= 0; i--) { if (out && pos + i < n) { out[pos + i] = '0' + (num % 10); } num /= 10; } pos += digits; longarg = 0; format = 0; break; } case 's': { const char* s2 = va_arg(vl, const char*); while (*s2) { if (out && pos < n) { out[pos] = *s2; } pos++; s2++; } longarg = 0; format = 0; break; } case 'c': { if (out && pos < n) { out[pos] = (char)va_arg(vl,int); } pos++; longarg = 0; format = 0; break; } default: break; } } else if (*s == '%') { format = 1; } else { if (out && pos < n) { out[pos] = *s; } pos++; } } if (out && pos < n) { out[pos] = 0; } else if (out && n) { out[n-1] = 0; } return pos; }
static char out_buf[1000]; static int _vprintf(const char* s, va_list vl) { int res = _vsnprintf(NULL, -1, s, vl); if (res+1 >= sizeof(out_buf)) { uart_puts("error: output string size overflow\n"); while(1) {} } _vsnprintf(out_buf, res + 1, s, vl); uart_puts(out_buf); return res; }
int printk(const char* s, ...) { int res = 0; va_list vl; va_start(vl, s); res = _vprintf(s, vl); va_end(vl); return res; }
void panic(char *s) { printk("panic: "); printk(s); printk("\n"); while(1){}; }
|
2.测试
OS/main.c
修改如下:
1 2 3 4 5 6 7
| #include "os.h"
void os_main() { printk("hello!!!"); }
|
OS/Makefile
1 2 3 4 5 6 7
| CFLAGS = -nostdlib -fno-builtin -I/opt/riscv/riscv64-unknown-elf/include -mcmodel=medany
SRCS_C = \ sbi.c \ main.c \ printk.c \
|
执行 build.sh
和run.sh
结果如下:打印了hello!!!