课程目标
使用C语言设计一组打印函数,为后续进一步开发内核功能做准备!
内核中的屏幕打印模块
接口设计
接口之间的关系
void PrintChar(char c)
功能定义:在屏幕上打印一个字符
实现原理:直接在显存对应位置写入数据
这里用内嵌汇编的方式实现这个函数,是因为用汇编实现这个函数比较简单。
C语言中的内嵌汇编
内嵌汇编示例
注意
因为使用了gcc编译器,所以内嵌汇编使用的是 AT&T 汇编格式 (与 nasm 汇编格式不同)
int PrintString(const char* s);
实现方式
- 循环调用PrintChar知道遇到0结束符
int PrintIntHex(unsigned int n);
实现方式
- 将参数n转换为对应的16进制数字符串
- 调用 PrintString() 打印转换得到的字符串
int PrintIntDec(int n);
保护模式下的光标追踪
内核屏幕操作
screen.h
#ifndef SCREEN_H
#define SCREEN_H
#define SCREEN_WIDTH 80
#define SCREEN_HEIGHT 25
typedef enum
{
SCREEN_GRAY = 0x07,
SCREEN_BLUE = 0x09,
SCREEN_GREEN = 0x0A,
SCREEN_RED = 0x0C,
SCREEN_YELLOW = 0x0E,
SCREEN_WHITE = 0x0F
}PrintColor;
void ClearScreen();
int SetPrintPos(short w, short h);
void SetPrintColor(PrintColor c);
int PrintChar(char c);
int PrintString(const char* s);
int PrintIntDec(int n);
int PrintIntHex(unsigned int n);
#endif
screen.c
#include "kernel.h"
#include "screen.h"
static int gPosW = 0;
static int gPosH = 0;
static char gColor = SCREEN_WHITE;
void ClearScreen()
{
int w = 0;
int h = 0;
SetPrintPos(0, 0);
for(h = 0; h < SCREEN_HEIGHT; h++)
{
for(w = 0; w < SCREEN_WIDTH; w++)
{
PrintChar(' ');
}
}
SetPrintPos(0, 0);
}
int SetPrintPos(short w, short h)
{
int ret = 0;
if(ret = ((w >= 0) && (w < SCREEN_WIDTH) && (h >= 0) && (h < SCREEN_HEIGHT)))
{
short bx = h * SCREEN_WIDTH + w;
gPosW = w;
gPosH = h;
asm volatile(
"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"
"movw $0x03D4, %%dx\n"
"movb $0x0F, %%al\n"
"outb %%al, %%dx\n"
"movw $0x03D5, %%dx\n"
"movb %%bl, %%al\n"
"outb %%al, %%dx\n"
:
: "r"(bx)
: "ax", "bx", "dx"
);
}
return ret;
}
void SetPrintColor(PrintColor c)
{
gColor = c;
}
int PrintChar(char c)
{
int ret = 0;
if((c == '\n') || (c == '\r'))
{
ret = SetPrintPos(0, gPosH + 1);
}
else
{
int pw = gPosW;
int ph = gPosH;
if((pw >= 0) && (pw < SCREEN_WIDTH) && (ph >= 0) && (ph < SCREEN_HEIGHT))
{
int edi = (SCREEN_WIDTH * ph + pw) * 2;
char ah = gColor;
char al = c;
asm volatile(
"movl %0, %%edi\n"
"movb %1, %%ah\n"
"movb %2, %%al\n"
"movw %%ax, %%gs:(%%edi)"
"\n"
:
: "r"(edi), "r"(ah), "r"(al)
: "ax", "edi"
);
pw++;
if(pw == SCREEN_WIDTH)
{
pw = 0;
ph += 1;
}
SetPrintPos(pw, ph);
ret = 1;
}
}
return ret;
}
int PrintString(const char* s)
{
int ret = 0;
if(s != NULL)
{
while(*s)
{
ret += PrintChar(*s++);
}
}
else
{
ret = -1;
}
return ret;
}
int PrintIntHex(unsigned int n)
{
int i = 0;
char hex[11] = {'0', 'x', 0};
for(i = 9; i >= 2; i--)
{
int temp = n & 0xF;
if(temp < 10)
{
hex[i] = ('0' + temp);
}
else
{
hex[i] = ('A' + temp - 10);
}
n >>= 4;
}
return PrintString(hex);
}
int PrintIntDec(int n)
{
int ret = 0;
if(n < 0)
{
ret += PrintChar('-');
PrintIntDec(-n);
}
else
{
if(n < 10)
{
ret += PrintChar('0' + n);
}
else
{
ret += PrintIntDec(n / 10);
ret += PrintIntDec(n % 10);
}
}
return ret;
}
需要在跳转到内核程序之前,先设置好相应的段寄存器。
小结
gcc编译器只支持 AT&T 格式的内嵌汇编
通过内嵌汇编的方式可实现 PrintChar() 函数
PrintChar() 是其他屏幕打印函数的基础
通过操作 0x03D4 与 0x03D5 端口对光标位置进行设置