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]; 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
这里放printk
和 printf
公用的函数,对字符串进行解析和处理
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 = 0
, stdout = 1
, stderr = 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>
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 2
| sudo ./build.sh sudo ./run.sh
|