1.实现printf

之前实现了内核态的printk,在U模式下都是通过sys_write的系统调用来向串口输出数据,因此需要实现一个用户态的printf函数

OS/lib/printf.c

实现跟之前的printk类似,但是系统调用将uart_put需要修改为 sys_write()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <sifanos/os.h>
#include <sifanos/syscall.h>
static char out_buf[1000]; // buffer for vprintf()
static int vprintf(const char* s, va_list vl)
{
int res = _vsnprintf(NULL, -1, s, vl);
_vsnprintf(out_buf, res + 1, s, vl);
sys_write(stdout,out_buf,res + 1);
return res;
}


int printf(const char* s, ...)
{
int res = 0;
va_list vl;
va_start(vl, s);
res = vprintf(s, vl);
va_end(vl);
return res;
}

OS/lib/vsprintf.c

这里放printkprintf公用的函数,对字符串进行解析和处理

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
#include "sifanos/stdio.h"

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;
}

OS/include/stdio.h

声明了一些函数以及定义了文件描述符

在c语言中枚举若无明确定义 则从0开始分配

所以std = 0stdout = 1stderr = 2

stdout 是标准输出流(standard output stream)的缩写,通常用于打印程序的输出信息。标准输出流是一个文件描述符,在大多数系统中,其值为 1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef SOS_STDIO_H__
#define SOS_STDIO_H__

#include <sifanos/os.h>

int _vsnprintf(char * out, size_t n, const char* s, va_list vl);
void panic(char *s);
int printk(const char* s, ...);
int printf(const char* s, ...);

/* 文件描述符 */
typedef enum std_fd_t
{
stdin,
stdout,
stderr,
} std_fd_t;

#endif

OS/include/syscall.h

增加头文件 对函数进行声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifndef SOS_SYSCALL_H__
#define SOS_SYSCALL_H__
#include "types.h"
#include <stddef.h>

/* syscall */
uint64_t __SYSCALL(size_t syscall_id, reg_t arg1, reg_t arg2, reg_t arg3);

#define __NR_write 64
#define __NR_sched_yield 124
#define __NR_exit 93
#define __NR_gettimeofday 169



uint64_t sys_write(size_t fd, const char* buf, size_t len);
uint64_t sys_yield();
uint64_t sys_gettime();

#endif

所以此时OS文件夹的结构如下所示

文件架构

2.测试

makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
SRCS_C = \
sbi.c \
main.c \
printk.c \
trap.c \
syscall.c \
string.c \
app.c \
task.c \
timer.c \
vsprintf.c \
printf.c \

OS/src/app.c

sys_write,全部修改为printf

1
printf(message);

执行

1
2
sudo ./build.sh
sudo ./run.sh

image-20240620151120575