当你学习到03day时 说明你已经进入到了保护模式下 并且使用了C语言进行了开发
由于我们是要在文本模式进行开发 所以请先让显卡进入文本模式,而不是图形模式
然后清空 harimain的所有代码 正式开始我们的开发
首先我们得让光标动起来 不可能让我们自己画一个光标吧 而这个只能用汇编语言实现 我们别无选择
_ASM_call: ;移动光标
mov dx,03d4h;03d4h是索引端口
mov al,0eh;内部的0eh位置存放着光标位置的高八位
out dx,al
inc dx;03d5h是数据端口用于读写数据
in al,dx;读取光标的高八位并且放入bh
mov bh,al
dec dx;这儿开始读取光标位置的低八位放入bl
mov al,0fh;0fh位置存放着光标位置的低八位
out dx,al
inc dx
in al,dx
mov bl,al
mov word bx,[esp+4] ;获取参数中的光标位置
mov dx,03d4h;这段代码将改变后的光标位置写入端口内相应的地方以便下次访问
mov al,0eh;写入光标位置高八位
out dx,al
inc dx
mov al,bh
out dx,al
dec dx
mov al,0fh ;写入光标位置低八位
out dx,al
inc dx
mov al,bl
out dx,al
ret
这名字我取的有点草率 大家可以自己取
大家可以看到 我们的这个函数只有一个参数
有人会说了
“你这不扯呢吗,X Y 怎么看也得有两个参数吧”
这怎么能难倒我们呢 我们用小学就学过的找规律找找不就行了(笑)
首先 大家都知道 我们的文本模式的分辨率是:80x25
也就是说 横向有80个字符 竖向有25个字符
我们发现 使用这个函数 参数如果给80 就会到下一行 也就是说 如果要控制这个光标到Y行 只要将这个Y * 80 就可以了
那么 X呢?
我们又发现:使用这个函数时 Y*80+N 光标就会移动到Y行的第N个位置
那么 转换公式如下:
位置=Y*80+X
我们将它变成代码
void Move_Cursor(int16 x, int16 y)
{
int res = y * 80 + x;
ASM_call(res);
}
(int16=short)
这不就有两个参数了吗 刚刚骂街的人给我出来(bushi)
问题来了 如何清屏 不清屏我们也无法实现打印 print(主要是会变得更复杂)
我们可以通过:*(char*)(b8000+y*160+x) = 一个字符 来向屏幕上输出一个字符 可这并不能用来实现print 倒是能实现我们的清屏(clear)
有人就要问了 i为啥要自增2 而不是1
因为:
在显存(文本模式)中 这80个会占160个字节 结构如下图
void clear()
{
int i;
int j;
for (i = 0; i < 160; i += 2)
{
for (j = 0; j < 25; j++)
{
*(char *)(0xb8000 + j * 160 + i) = ' ';
}
}
Move_Cursor(0, 0);
}
所以 要+=2 而非++
接下来 我们定义四个全局变量
int x = 0;//显示字符的位置
int y = 0;
/*光标的位置*/
int cons_x = 0;
int cons_y = 0;
clear函数也更改为:
void clear()
{
int i;
int j;
for (i = 0; i < 160; i += 2)
{
for (j = 0; j < 25; j++)
{
*(char *)(0xb8000 + j * 160 + i) = ' ';
}
}
x = 0;
y = 0;
cons_x = 0;
cons_y = 0;
Move_Cursor(cons_x, cons_y);
}
接下来 我们便要来写一个重要分支-----putchar 这个函数
学会跑之前得先学会走 输出字符串也同理 输出字符串之前 得先学会输出字符(笑)
我们之前说过可以通过*(char*)(0xb8000+y*160+x)=字符来输出 那么 我们便可以把之前的那几个变量带进去 光标在字符的前面一格就好了 我这里给出putchar的实现
void putchar(char ch)
{
if(x == 160){
y++;
cons_y++;
x = 0;
cons_x = 0;
Move_Cursor(cons_x, cons_y);
}
if (ch == '\n')
{
if (y == 24)
{
/*暂时不做什么*/
return;
}
y++;
//*(char *)(0xb8000 + cons_y * 160 + cons_x) = ' ';
cons_y++;
x = 0;
cons_x = 0;
return;
}
else if (ch == '\0')
{
return;
}
else if (ch == '\b')
{
if (x == 0)
{
return;
}
*(char *)(0xb8000 + y * 160 + x - 2) = ' '; /*显示位置为第23行*/
x -= 2;
cons_x -= 2;
//*(char *)(0xb8000 + cons_y * 160 + cons_x + 4) = ' ';
cons_x += 1;
Move_Cursor(cons_x, cons_y);
return;
}
//*(char *)(0xb8000 + cons_y * 160 + cons_x) = ' ';
cons_x += 1;
Move_Cursor(cons_x, cons_y);
*(char *)(0xb8000 + y * 160 + x) = ch; /*显示位置为第23行*/
x += 2;
}
然后就是putstr 输出一个C风格字符串(须要长度)
有人就要问了 为啥不直接做print呢?
做啥事都得循序渐进嘛 不要心急(笑)
我这里也给出putstr的实现
void putstr(const char *str, int length)
{
int i;
for (i = 0; i < length; i++)
{
if(y == 24 && x >= 160){
/*暂时什么也不做*/
}
if (str[i] == 0x0d)
{
continue;
}
putchar(str[i]);
}
}
然后写个getlength
int getlength(const char *str)
{
int i;
for (i = 0;; ++i)
{
if (str[i] == '\0')
{
return i;
}
}
}
最后才是 print
void print(const char *str)
{
putstr(str, getlength(str));
}
现在 来看看我们的成果!
void HariMain(void)
{
clear();
print("hello, world");
for (;;)
{
}
}
下一次 我们就要让屏幕滚动起来咯(笑)