【自制操作系统】以《30天自制操作系统》的03day为基础,脱离繁杂的GUI,做一个工作在文本模式下的命令行操作系统(1)----print函数

 当你学习到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 (;;)
	{
	}
}

 下一次 我们就要让屏幕滚动起来咯(笑)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值