已完成实验
已完成实验链接
简介
实验 21. 实现 printf
总结
-
简化系统调用和中断,用 eax 代表调用号参数,ebx,ecx,edx 来代表参数(syscall.c kernel.s)
-
添加 write 的系统调用接口(syscall.c, syscall-init.c, print.s)
注意:要更改 print.s 中清屏的地址
-
添加 printf 函数接口
主要代码
syscall.h
syscall.c
// 文件: syscall.c
// 时间: 2024-08-01
// 来自: ccj
// 描述: 用户系统调用,把调用号和参数写入寄存器,执行int 0x80,从eax拿到返回值#include "syscall.h"/// 系统调用,进入0x80之前
// 1. 把调用号和参数写入寄存器
// eax = NUMBER;
// ebx = ARG1;
// ecx = ARG2;
// edx = ARG3;
// 2. int 0x80
// 3. retval = eax
#define _syscall0(NUMBER) \({ \int retval; \asm volatile("int $0x80" : "=a"(retval) : "a"(NUMBER) : "memory"); \retval; \})#define _syscall1(NUMBER, ARG1) \({ \int retval; \asm volatile("int $0x80" : "=a"(retval) : "a"(NUMBER), "b"(ARG1) : "memory"); \retval; \})#define _syscall2(NUMBER, ARG1, ARG2) \({ \int retval; \asm volatile("int $0x80" : "=a"(retval) : "a"(NUMBER), "b"(ARG1), "c"(ARG2) : "memory"); \retval; \})#define _syscall3(NUMBER, ARG1, ARG2, ARG3) \({ \int retval; \asm volatile("int $0x80" \: "=a"(retval) \: "a"(NUMBER), "b"(ARG1), "c"(ARG2), "d"(ARG3) \: "memory"); \retval; \})/// @brief 返回当前任务pid
/// @return
uint32_t getpid() { return _syscall0(SYS_GETPID); }/// @brief 写入字符串
/// @param fd
/// @param buf 字符串
/// @param count 字符数量
/// @return
uint32_t write(char* str) { return _syscall1(SYS_WRITE, str); }
kernel.s
syscall-init.c
print.s
stdio.c
// 文件: stdio.c
// 时间: 2024-08-01
// 来自: ccj
// 描述: printf,%符号转义#include "stdio.h"
#include "interrupt.h"
#include "global.h"
#include "string.h"
#include "syscall.h"
#include "print.h"#define va_start(ap, v) ap = (va_list)(&v)
// v是字符串首地址
// &v就是字符串首地址的内存地址,也就是栈顶
// ap指向了栈顶#define va_arg(ap, t) *((t*)(ap += 4)) // ap指向下一个参数并返回其值
// t是 type 类型
#define va_end(ap) ap = NULL // 清除ap/* 将整型转换成字符(integer to ascii) */
static void itoa(uint32_t value, char** buf_ptr_addr, uint8_t base) {uint32_t m = value % base; // 求模,最先掉下来的是最低位uint32_t i = value / base; // 取整if (i) { // 如果倍数不为0则递归调用。itoa(i, buf_ptr_addr, base);}if (m < 10) { // 如果余数是0~9*((*buf_ptr_addr)++) = m + '0'; // 将数字0~9转换为字符'0'~'9'} else { // 否则余数是A~F*((*buf_ptr_addr)++) = m - 10 + 'A'; // 将数字A~F转换为字符'A'~'F'}
}/* 将参数ap按照格式format输出到字符串str,并返回替换后str长度 */
uint32_t vsprintf(char* str, const char* format, va_list ap) {char* buf_ptr = str;const char* index_ptr = format;char index_char = *index_ptr; // 格式字符串中的字符int32_t arg_int;char* arg_str;while (index_char) {if (index_char != '%') { // 如果不是%,那么直接复制到str*(buf_ptr++) = index_char;index_char = *(++index_ptr);continue;}index_char = *(++index_ptr); // index_char是&,那么得到%后面的字符做转换switch (index_char) {case 's': // %s处理arg_str = va_arg(ap, char*); // 拿到字符串首地址strcpy(buf_ptr, arg_str); // 复制到strbuf_ptr += strlen(arg_str); // str指针增加index_char = *(++index_ptr); // 拿到%之后的字符继续循环break;case 'c':*(buf_ptr++) = va_arg(ap, char);index_char = *(++index_ptr);break;case 'd':arg_int = va_arg(ap, int);/* 若是负数, 将其转为正数后,再正数前面输出个负号'-'. */if (arg_int < 0) {arg_int = 0 - arg_int;*buf_ptr++ = '-';}itoa(arg_int, &buf_ptr, 10);index_char = *(++index_ptr);break;case 'x':arg_int = va_arg(ap, int);itoa(arg_int, &buf_ptr, 16);index_char = *(++index_ptr); // 跳过格式字符并更新index_charbreak;}}return strlen(str);
}/* 同printf不同的地方就是字符串不是写到终端,而是写到buf中 */
uint32_t sprintf(char* buf, const char* format, ...) {va_list args;uint32_t retval;va_start(args, format);retval = vsprintf(buf, format, args);va_end(args);return retval;
}/* 格式化输出字符串format */
uint32_t printf(const char* format, ...) {va_list args;char buf[1024] = {0}; // 用于存储拼接后的字符串va_start(args, format); // 使args指向formatvsprintf(buf, format, args);va_end(args);return write(buf);
}
main.c
// 文件: main.c
// 时间: 2024-07-19
// 来自: ccj
// 描述: 内核从此处开始#include "print.h"
#include "init.h"
#include "thread.h"
#include "interrupt.h"
#include "console.h"
#include "process.h"
#include "syscall.h"
#include "syscall-init.h"
#include "stdio.h"// 两个内核线程
void k_thread_a(void*);
void k_thread_b(void*);
// 两个用户进程
void u_prog_a(void);
void u_prog_b(void);int main(void) {put_str("I am kernel\n");init_all();process_execute(u_prog_a, "user_prog_a");process_execute(u_prog_b, "user_prog_b");console_put_str("main_pid:0x");console_put_int(sys_getpid());console_put_char('\n');thread_start("k_thread_a", 31, k_thread_a, "argA ");thread_start("k_thread_b", 31, k_thread_b, "argB ");intr_enable(); // 打开中断,使时钟中断起作用while (1) {};return 0;
}// 内核线程函数
void k_thread_a(void* arg) {console_put_str("thread_a_pid:0x");console_put_int(sys_getpid());console_put_char('\n');while (1) {}
}
void k_thread_b(void* arg) {console_put_str("thread_b_pid:0x");console_put_int(sys_getpid());console_put_char('\n');while (1) {}
}// 测试用户进程
void u_prog_a(void) {printf("u_%s_pid:0x%d%c", "prog_a", getpid(), '\n');while (1) {}
}
void u_prog_b(void) {printf("u_%s_pid:0x%d%c", "prog_b", getpid(), '\n');while (1) {}
}