1.printk函数

基于x86架构的简单内核实现-2 | Sifanのblog (liangzhouzz.github.io)在这偏中提到了,在内核态无法使用用户态的printf函数,所以需要编写内核级的printk函数,基于x86简易内核操作中,我们是采用操作寄存器的方式进行读写和打印。

同理,在riscvS模式下 也是无法使用U模式下的printf函数,需要封装位于S模式下的printk函数

可变参数的解释见:封装printf函数 | TimerのBlog (yanglianoo.github.io)

os目录下新增printk.cos.h

OS/os.h

头文件:基本的操作系统头文件,通常用于小型嵌入式系统或内核开发中

  • stddef.h:提供了一些标准定义,如 size_tNULL 等。
  • 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>


/* printk */
extern int printk(const char* s, ...);
extern void panic(char *s);
extern void sbi_console_putchar(int ch);

#endif /* __OS_H__ */

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 函数将执行以下操作:

  1. 初始化 va_list 并将参数列表指向 "world"42
  2. 调用 _vprintf 函数,该函数会将格式化后的字符串 “Hello, world! The answer is 42.\n” 写入 out_buf 并通过 uart_puts 输出到 UART。
  3. 返回写入的字符数。

_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]; // buffer for _vprintf()
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]; // buffer for _vprintf()
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
//extern sbi_console_putchar(int ch);
#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.shrun.sh

结果如下:打印了hello!!!

image-20240611221736082