代码拉取完成,页面将自动刷新
{
"dir" : [
],
"src" : [
"print.c"
],
"inc" : [
"include"
]
}
; 光标位置:(x, y)
; 光标位置须按照 bx = (80 * y + x) 公式转换一下放入 bx 寄存器中,比如设置光标位置 (0, 2): mov bx, (80 * 2 + 0)
; 设置光标高 8 位:先把数据 0x0E 写入端口 0x03D4,然后把 bh 写入端口 0x03D5
mov dx, 0x03D4
mov al, 0x0E
out dx, al
mov dx, 0x03D5
mov al, bh
out dx, al
; 设置光标低 8 位:先把数据 0x0F 写入端口 0x03D4,然后把 bl 写入端口 0x03D5
mov dx, 0x03D4
mov al, 0x0F
out dx, al
mov dx, 0x03D5
mov al, bl
out dx, al
// 光标位置:(x, y)
// 光标位置须按照 bx = (SCREEN_WIDTH * y + x) 公式转换一下放入 bx 寄存器中
U16 bx_c = SCREEN_WIDTH * y + x;
asm volatile(
// 设置光标高 8 位:先把数据 0x0E 写入端口 0x03D4,然后把 bh 写入端口 0x03D5
"movw %0, %%bx\n"
"movw $0x03D4, %%dx\n"
"movb $0x0E, %%al\n"
"outb %%al, %%dx\n"
"movw $0x03D5, %%dx\n"
"movb %%bh, %%al\n"
"outb %%al, %%dx\n"
// 设置光标低 8 位:先把数据 0x0F 写入端口 0x03D4,然后把 bl 写入端口 0x03D5
"movw $0x03D4, %%dx\n"
"movb $0x0F, %%al\n"
"outb %%al, %%dx\n"
"movw $0x03D5, %%dx\n"
"movb %%bl, %%al\n"
"outb %%al, %%dx\n"
:
: "b"(bx_c) // 相当于:bx = bx_c,等号左边是汇编中的寄存器 bx,等号右边是 C 代码中的变量
: "ax", "dx" // 告诉 gcc 编译器,ax,dx 寄存器被内嵌汇编使用,需要 gcc 自动添加保护和恢复操作(入栈和出栈)
// 明明 ax, bx, dx 三个寄存器都被使用了,为什么这里却只有 ax 和 dx 寄存器?
// "b"(bx_c) 中使用约束名 "b" 时 gcc 就已经自动对 bx 进行保护和恢复操作,所以这里就不需要重复了
);
E_RET SetCursorPos(U16 x, U16 y)
{
U16 bx_c = 0;
// 对传入的坐标进行合法性检查
if(x >= SCREEN_WIDTH || y >= SCREEN_WIDTH)
return E_ERR;
CursorPosX = x;
CursorPosY = y;
// 光标位置须按照 bx = (80 * y + x) 公式转换一下放入 bx 寄存器中
bx_c = 80 * y + x;
asm volatile(
// 设置光标高 8 位:先把数据 0x0E 写入端口 0x03D4,然后把 bh 写入端口 0x03D5
"movw %0, %%bx\n"
"movw $0x03D4, %%dx\n"
"movb $0x0E, %%al\n"
"outb %%al, %%dx\n"
"movw $0x03D5, %%dx\n"
"movb %%bh, %%al\n"
"outb %%al, %%dx\n"
// 设置光标低 8 位:先把数据 0x0F 写入端口 0x03D4,然后把 bl 写入端口 0x03D5
"movw $0x03D4, %%dx\n"
"movb $0x0F, %%al\n"
"outb %%al, %%dx\n"
"movw $0x03D5, %%dx\n"
"movb %%bl, %%al\n"
"outb %%al, %%dx\n"
:
: "b"(bx_c) // 相当于:bx = bx_c,等号左边是汇编中的寄存器 bx,等号右边是 C 代码中的变量
: "ax", "dx" // 告诉 gcc 编译器,ax,dx 寄存器被内嵌汇编使用,需要 gcc 自动添加保护和恢复操作(入栈和出栈)
// 明明 ax, bx, dx 三个寄存器都被使用了,为什么这里却只有 ax 和 dx 寄存器?
// "b"(bx_c) 中使用约束名 "b" 时 gcc 就已经自动对 bx 进行保护和恢复操作,所以这里就不需要重复了
);
return E_OK;
}
void SetFontColor(E_FONT_COLOR color)
{
FontColor = color;
}
U32 edi_c = (SCREEN_WIDTH * CursorPosY + CursorPosX) * 2;
U08 ah_c = FontColor;
U08 al_c = ch;
asm volatile(
"movl %0, %%edi\n" // edi:显存中的偏移量
"movb %1, %%ah\n" // 显示属性
"movb %2, %%al\n" // 要打印的字符
"movw %%ax, %%gs:(%%edi)" // 把显示数据放入显存
:
: "D"(edi_c), "r"(ah_c), "r"(al_c) // %0 替换成 edi_c,"D" 表示使用 edi 寄存器; %1 替换成 ah_c; %2 替换成 al_c;
: "ax"
);
void PrintChar(U08 ch)
{
U32 edi_c = (80 * CursorPosY + CursorPosX) * 2;
U08 ah_c = FontColor;
U08 al_c = ch;
// 每打印一个字符,光标都往后移动一个位置
CursorPosX++;
// 如果光标达到最大横坐标值了,则设置光标到下一行起始位置
if(CursorPosX >= SCREEN_WIDTH)
{
CursorPosX = 0;
CursorPosY++;
}
if('\n' == al_c || '\r' == al_c) // 遇到换行,则光标位置设置成下一行的起始位置处
{
CursorPosX = 0;
CursorPosY++;
}
else
{
// 打印一个字符
asm volatile(
"movl %0, %%edi\n" // edi:显存中的偏移量
"movb %1, %%ah\n" // 显示属性
"movb %2, %%al\n" // 要打印的字符
"movw %%ax, %%gs:(%%edi)" // 把显示数据放入显存
:
: "D"(edi_c), "r"(ah_c), "r"(al_c) // %0 替换成 edi_c,"D" 表示使用 edi 寄存器; %1 替换成 ah_c; %2 替换成 al_c;
: "ax"
);
}
// 更新光标位置
SetCursorPos(CursorPosX, CursorPosY);
}
#define NULL ((void*)0)
E_RET PrintString(U08 const *str)
{
// 检查 *str 是否为空
if(NULL == str)
return E_ERR;
while(*str)
PrintChar(*str++);
return E_OK;
}
static const U08 IntToStr[10] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
void PrintIntDec(U32 num)
{
if(num < 10) // 递归出口条件
PrintChar(IntToStr[num]); // ①
else
{
PrintIntDec(num / 10); // ②
PrintIntDec(num % 10); // ③
}
}
PrintIntDec(12345 / 10);
PrintIntDec(12345 % 10); // 把它当中普通语句
PrintIntDec(1234 / 10);
PrintIntDec(1234 % 10); // 把它当中普通语句
PrintIntDec(12345 % 10); // 把它当中普通语句
PrintIntDec(123 / 10);
PrintIntDec(123 % 10); // 把它当中普通语句
PrintIntDec(1234 % 10); // 把它当中普通语句
PrintIntDec(12345 % 10); // 把它当中普通语句
PrintIntDec(12 / 10);
PrintIntDec(12 % 10); // 把它当中普通语句
PrintIntDec(123 % 10); // 把它当中普通语句
PrintIntDec(1234 % 10); // 把它当中普通语句
PrintIntDec(12345 % 10); // 把它当中普通语句
void PrintIntDec(S32 num)
{
// 如果 num 为负数,则先把负号 '-' 打印出来; num = -num: 取一个负数的绝对值
if(num < 0)
{
PrintChar('-');
num = -num;
}
if(num < 10) // 递归出口条件
PrintChar(IntToStr[num]);
else
{
PrintIntDec(num / 10);
PrintIntDec(num % 10);
}
}
static const U08 IntToStr[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
void PrintIntHex(S32 num)
{
U08 print[12] = { 0 }; // 初始化时必须清零,最大占 12 字节,比如 "-0x12345678\0"
U08 i = 0;
U08 tmp = 0;
if(num >= 0) // 正数
{
print[0] = '0';
print[1] = 'x';
for(i = 9; i >= 2; i--)
{
tmp = num & 0xF;
num = num >> 4;
print[i] = IntToStr[tmp];
}
}
else // 负数
{
print[0] = '-';
print[1] = '0';
print[2] = 'x';
num = -num; // 将负数取绝对值(去掉负号)
for(i = 10; i >= 3; i--)
{
tmp = num & 0xF;
num = num >> 4;
print[i] = IntToStr[tmp];
}
}
PrintString(print);
}
void ClearScreen(void)
{
U32 i = 0;
SetCursorPos(0, 0);
for(i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++)
PrintChar(' ');
SetCursorPos(0, 0);
}
int printf(const char * format, ...)
int scanf(const char *format, ...)
代码:
#include <stdio.h>
#include <stdarg.h>
void test_func(int arg1, ...)
{
va_list va;
va_start(va, arg1);
printf("arg1 = %d\n", arg1);
printf("arg2 = %d\n", va_arg(va, int));
printf("arg3 = %d\n", va_arg(va, int));
printf("arg4 = %d\n", va_arg(va, int));
va_end(va);
}
int main(void)
{
test_func(1, 2, 3, 4);
return 0;
}
运行结果:
arg1 = 1
arg2 = 2
arg3 = 3
arg4 = 4
由于我们实现的是一个操作系统,所以不可能包含库中 stdarg.h 头文件,一切都要自己实现。于是先创建一个 "stdarg.h" 头文件,放入 KOS 工程 "include" 目录下,其内容包括如下定义:
typedef U08 * va_list;
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(U32) - 1) & ~(sizeof(U32) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
了解了C语言可变参数函数后,通用打印函数的主体实现就很简单了,通过遍历字符串 fmt,寻找 '%', '%' 之前的部分直接通过字符打印函数打印出来,找到 '%' 后,再根据 '%' 后面的第一个字符判断打印类型,从而调用上面我们已经实现好的各种类型的打印函数
函数实现框架如下
E_RET printk(const char * fmt, ...)
{
...
// 遍历字符串 fmt
for(; *fmt; ++fmt)
{
// 寻找格式转换字符 '%', 如果不是 '%', 则直接打印出来
if(*fmt != '%')
{
PrintChar(*fmt);
continue;
}
// 如果找到了 '%', 则取 '%' 后的第一个字符
++fmt;
switch (*fmt) // 根据 '%' 后的字符类型采取不同的打印接口
{
// 分别调用对应的打印接口
case 'c': // 字符
case 's': // 字符串
case 'x': // 十六进制
case 'd': // 十进制
}
}
...
}
以上接口函数具体实现代码见: print.c
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。