自制操作系统日志——第五天

自制操作系统日志——第五天

今天是该系列的第五天。今天的任务就是1、采用结构体形式;2、利用像素矩阵的方法进行字符串的显示;3、GDT/IDT的初始化。



一、利用结构体表示变量的集合

在前一天中,为了方便学习我们都是直接将数字写入程序当中的。但是,对于我们来说,这种习惯是极其不好的,因为这都是我们在设定了采用VRAM画面模式下已知了屏幕分辨率的情况下进行设定的。如果画面进行了更改,那么我们就要把用到的部分都要修改,实在是过于麻烦。因此最好直接从asmhead.asm当中,取得这些关于VRAM的值是更加好的做法!

但是,如果我们直接利用在主程序中定义一个变量来进行获取asmhead中的值的话,那我们需要声明的变量也太多了。看起来也不够简洁,因此,建议使用结构体的形式进行读取。如下述:

//与asmhead中的bootinfo内容对齐
struct BOOTINFO    /* 0x0ff0-0x0fff */
{
	char cyls;/* 启动区读磁盘读到此为止 */
    char leds;/* 启动时键盘的LED的状态 */
    char vmode;/* 显卡模式为多少位彩色 */
    char reserve;
	short scrnx,scrny;/* 画面分辨率 */
	char *vram;	/*显卡入口地址*/
};//将变量声明集中起来,该变量一共12字节,作为一个整体的新变量BootInfo,可以给其他进行声明;记住按照asmhead顺序写变量

这样子就很简单明了的知道了这些变量的含义,而且我们还可以利用将这个结构体进行声明,并赋予这个结构体一个初始的地址值,方便指向我们在asmhead当中用到的那12个字节的地址空间,具体如下:

void HariMain(void)
{
	char *vram;
	int xsize,ysize;
	struct BootInfo *binfo;

//为了从asmhead.nas中保存下来的值中取出,以防止当画面模式改变后出现异常。因为直接使用320 这些固定的可能会出错,而我们在.nas中会预先设定号模式,直接从这取比较保险
    
  binfo = ( struct BootInfo *) 0x0ff0; 
    .......
  }

这里,我们就把bootinfo这个结构体的起始地址指向了0x0ff0 ,这里也是我们在asmhead当中使用到的起始空间。至于结构体里的变量就按samhead里的地址顺序进行声明:
在这里插入图片描述
这里,当我们使用上述的方式后,我们可以使用以下两种,来取得结构体里的值:

(*binfo).scrnx
binfo->scrnx

二、像素字符阵

对于在OS的屏幕上如何显示字符?这里可能是一个比较困难的地方,因为这里我们已经不能调用bios进行显示了,那么我们就换一个思路进行开发测试一下看看。

我们可以采用一个8x16的字符矩阵进行显示256个字符(ascii),如下图所示:
在这里插入图片描述
比如,上述的A字符,我们可以就用以下这个16进制的数组来表达:(对应上图的二进制)

	static char font_A[16] = {
		0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24,
		0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00
	};

然后,在写下这个函数进行显示输出:

void putfont8(char *vram, int xsize, int x, int y, char c, char *font)
{
	int i;
	char *p, d /* data */;
	for (i = 0; i < 16; i++) {               //字符像素点阵的行数循环 
		p = vram + (y + i) * xsize + x;      //字符像素点阵的开始位置
		d = font[i];
		if ((d & 0x80) != 0) { p[0] = c; }   //p代表着字符像素点阵一行的那8位
		if ((d & 0x40) != 0) { p[1] = c; }
		if ((d & 0x20) != 0) { p[2] = c; }
		if ((d & 0x10) != 0) { p[3] = c; }
		if ((d & 0x08) != 0) { p[4] = c; }
		if ((d & 0x04) != 0) { p[5] = c; }
		if ((d & 0x02) != 0) { p[6] = c; }
		if ((d & 0x01) != 0) { p[7] = c; }
	}
	return;
}

这里的几个if 是根据a字符的8x16这个矩阵中需要呈现的像素点进行判断的!举例来说:
例如0x80 换算成二进制就是 1000 000 ,任意的二进制数与之相与的结果,都只可能是最高位上是1 。因此,当我们已经建立字符数组font_A后,font_A中的每一个8位数据都会与之相与,只有当font_A[13]=0xe7(a字符中只有这个最高位是1)时,结果才会为0,此时就要在这个地方填入颜色。

让我们试着输出一下吧!
在这里插入图片描述

当然,为了能够将ascii中的字符串都显示出来,我们可以一个个这样子设置,但是如果这样子搞,可能会花去我们很多的时间。因此以下我们使用kiyoto先生开发好的hankaku.txt字符文本文件来装载在系统里。

我们需要将文本文件读入进来,并于bootpac.obj进行链接,因此这里我们修改以下makefile文件,方便输出对应的obj以及进行连接:

#增加部分:
hankaku.bin : hankaku.txt Makefile
	$(MAKEFONT) hankaku.txt hankaku.bin

hankaku.obj : hankaku.bin Makefile
	$(BIN2OBJ) hankaku.bin hankaku.obj _hankaku
#修改部分:
bootpack.bim : bootpack.obj naskfunc.obj  hankaku.obj Makefile
	$(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map \
		bootpack.obj naskfunc.obj hankaku.obj

在这里插入图片描述
然后,我们再在c的主函数下添加:

extern char hankaku[4096]; 

由于该文件是依照ascii码进行编制的,因此我们想要找到某个字符数据的话需要找到对应的地址,例如,a字符的ascii码为0x41,则地址为 “hankaku+0x41*16”

继续进一步的为了方便显示字符串,制作如下代码(注意,这里字符串调用的显示字符的函数名一定要对应,我这里因为之前没对应上,总是出现莫名其妙的bug):

void putfonts8_asc(char *vram, int xsize, int x, int y, char c, char *s) //s指向字符串开头的位置,因此可以直接使用s进行读取字符串的每一个单独字符; 以ascii编码
{
	extern char hankaku[4096];
	for(;*s != 0x00; s++)
	{
		putfont8(vram, xsize, x, y, c, hankaku + *s*16); //c语言中,函数都是以0x00结尾的!!
		x += 8;
	}
	return;
}

然后,让我们看看主函数中的调用:

putfonts8_asc(binfo->vram, binfo->scrnx,  8,  8, COL8_FFFFFF, "ABC 123");
putfonts8_asc(binfo->vram, binfo->scrnx, 31, 31, COL8_000000, "Yuan OS.");
putfonts8_asc(binfo->vram, binfo->scrnx, 30, 30, COL8_FFFFFF, "Yuan OS.");

make run 以下:
在这里插入图片描述

很好,接下来进一步!我们试试看能不能将变量显示出来呢?
这里,我们调用编译器自带的sprintf函数。在该编译器中,sprintf不像printf函数需要调用到操作系统的大小(我们本来就是建操作系统,怎么可能要用到那种调用操作系统的函数),sprintf函数作用是将数据作为字符串输出到内存当中。

void HariMain(void)
{
	char s[40];
	int mx,my;
	struct BootInfo *binfo = ( struct BootInfo *) 0x0ff0 ;
	//extern char hankaku[4096]; //16x256 共256个字符

   init_palette();
   init_screen( binfo->vram, binfo->scrnx, binfo->scrny);

	putfonts8_asc(binfo->vram, binfo->scrnx,  8,  8, COL8_FFFFFF, "ABC 123");
	putfonts8_asc(binfo->vram, binfo->scrnx, 31, 31, COL8_000000, "Yuan OS.");
  putfonts8_asc(binfo->vram, binfo->scrnx, 30, 30, COL8_FFFFFF, "Yuan OS.");

	sprintf(s, "scrnx = %d",binfo->scrnx);
	putfonts8_asc(binfo->vram, binfo->scrnx, 16, 64, COL8_FFFFFF, s);
   //sprintf(s, "scrnx = %d ", binfo->scrnx);//sprintf(地址,格式,值, 值...) 地址就是该字符串存放在内存当中的地址; 格式就是想要存放的字符串,如果当中有%d之类的则会自动进行置换
	for(;;)   
	{
	     io_hlt();
	    //自建的hlt函数,源目标程序在naskfunc.nas中,c语言本身没有hlt这个命
	}
}

在这里插入图片描述

三、像素鼠标阵

在前面我们利用了矩阵形式成功的显示了字符,那么接下来我们进一步的利用像素矩阵进行显示鼠标:
在这里插入图片描述如图所示,建立了一个16x16的鼠标矩阵。使用原理字符矩阵一样,于是编写一个函数先进行初始化:

void init_mouse_cursor8(char *mouse, char bc) //准备鼠标指针 16x16 ;bc就是背景色
{
	static char cursor[16][16]={
		"**************..",
		"*OOOOOOOOOOO*...",
		"*OOOOOOOOOO*....",
		"*OOOOOOOOO*.....",
		"*OOOOOOOO*......",
		"*OOOOOOO*.......",
		"*OOOOOOO*.......",
		"*OOOOOOOO*......",
		"*OOOO**OOO*.....",
		"*OOO*..*OOO*....",
		"*OO*....*OOO*...",
		"*O*......*OOO*..",
		"**........*OOO*.",
		"*..........*OOO*",
		"............*OO*",
		".............***"
	};
	int x,y;

	for(y = 0; y < 16; y++)
	{
		for(x = 0; x < 16; x++)
		{
			if(cursor[y][x] == '*'){
				mouse[x+y*16] = COL8_000000;
			}
		    if(cursor[y][x] == 'O'){
				mouse[x+16*y] = COL8_FFFFFF;
			}
		    if(cursor[y][x] == '.'){
				mouse[x+16*y] = bc;
			}
		}
	}
	return;
}

这里我们将颜色信息等等,都放在了传进来的char *mouse指向的内存数据当中,要想写入VRAM显示器当中,我们还需要编写一个填入的函数:

void putblock8_8(char *vram, int vxsize, int pxize, 
     int pysize, int px0, int py0, char *buf, int bxsize) 
	  /*将鼠标的背景色显示出来;
	  vram与vxsize是关于VRAM信息的,值分别是0xa000与320;
	  pxsize与pysize是显示图形的大小,即鼠标像素阵,16x16;
	  px0与py0是指定图形在画面上显示的位置;
	  buf与bxsize是指定图形的存放地址和每一行含有的像素数(为后面准备,这里是与pxsize一样)
	  */
	 {
		int x,y;
		for(y=0; y < pysize ; y++)
		{
			for(x=0; x < pxize; x++)
			{
				vram[(py0+y)*vxsize+(px0+x)] = buf[y*bxsize+x];
			}
		}
		return;
	 }

然后,我们在主函数调用一下就行:

void HariMain(void)
{
	char s[40], mcursor[256];
	int mx,my;
	struct BootInfo *binfo = ( struct BootInfo *) 0x0ff0 ;
	//extern char hankaku[4096]; //16x256 共256个字符

    init_palette();
    init_screen( binfo->vram, binfo->scrnx, binfo->scrny);


    /* 显示鼠标 */
	mx = (binfo->scrnx - 16) / 2; /* 计算画面的中心坐标*/
	my = (binfo->scrny - 28 - 16) / 2;
	init_mouse_cursor8(mcursor, COL8_008484);
	putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
	....
	}

在这里插入图片描述在这里插入图片描述
当然,由于这里还没有做其他的设定,因此之做出来个中看不中用的鼠标。为了让他动起来我们还需要再多做一些事情。

四、GDT和IDT的初始化

为了方便后续的代码理解,这里我会提前介绍以下GDT与IDT,如果不需要的可以直接跳过该部分。。。

**GDT:**全局段号记录表。其就是指当计算机采用段式存储时,计算机会将整个内存分为一个个的段方便进行管理。然后,GDT就是用于存储每一个段信息的记录表,通过此记录表计算机能够轻易的了解到每一个段的具体信息。每一个段都主要有以下几个信息部分:

  • 段地址大小
  • 段地址的起始地址
  • 段的管理属性

cpu中规定要用8字节的数据来表示这些信息。 按照上述分类,我们进行如下解释:

段地址: 段的地址空间大小为32位(4字节),在cpu中被视为段的基址。因此在下面的结构体中我们使用了base这个变量名。在这个结构体中base分为low(2字节)、mid(1字节)、high(1字节)三个段组成。因此,在下述的初始化中,只需按顺序填入对应的数值即可!!(程序代码中利用移位来达到填入对应位置)

这里可能会有人问,为什么要分成三个段?害,这当然都是80286兼容(老古董了)!

**段大小(段上限):**一个段按理来说最大有4GB,如果直接用此表示,那么我们又要使用4字节,这明显是不可取的毕竟我们还有管理属性没有填入! 因此,我们利用20位作为段的上限。

但是这里如果只采用20位的话,那么我们只能最大寻址到1mb,实在是有点小。因此,在段的属性中我们设置了一个属性G的标志位,当G=1时,这个上限的地址limit的单位就不在是字节了,而是以4kb为单位。至此,就可以有1MB*4KB = 4GB

在代码的结构体中,limit(上限)分为了limit_low与limit_high总共3字节,共24位,因此我们会将limit_high的高四位作为段属性填入!!

段的管理属性: 最后会有12位作为段的属性。再加上前面的4位一共16位,因此代码中,可以利用ar这个变量把他们当作16位进行处理!

ar的高四位称之为拓展访问权:这四位就是"GD" 其中G就是前面说的单位问题 。 D则是指段的模式:当D=1时是32位,当D=0时是16位。

ar的低八位是:
0x00:未使用记录表
0x92:系统专用,可读写不可执行
0x9a:系统专用,可执行可读,不可写
0xf2:应用程序用,可读写不可执行
0xfa:应用程序用,可执行可读,不可写

所谓系统专用,就是不给运行在系统之上的应用程序调用的段。

然后,我们的程序呢,就是先将这些数据整齐有序的放在内存的某个地方,然后将内存的起始地址,和设定的有效个数放在cpu的专用寄存器GDTR当中。

IDT与GDT是大同小异的具体可以看后续的代码注释了。

代码:boorpack.c新增:

struct SEGMENT_DESCRIPTOR{
	short limit_low, base_low;
	char base_mid, access_right;
	char limit_high, base_high;
}; //存放8字节的GDT内容

struct GATE_DESCRIPTOR
{
	short offset_low, selector;
	char dw_count, access_right;
	short offset_high;
}; //存放8字节的IDT内容

void init_gdtidt(void)
{
	struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) 0x00270000; //使用0x00270000~0x0027ffff
	struct GATE_DESCRIPTOR    *idt = (struct GATE_DESCRIPTOR    *) 0x0026f800; //使用0x0026f800~0x0026ffff
	int i;

	//GDT初始化
	for (i = 0; i < 8192; i++)
	{
		set_segmdesc(gdt + i , 0, 0, 0); //这里gdt由于表示的是8字节的结构体,因此i每增加一次,地址就+8
	}
	set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092);//设定gdt的第一个段为全局,方便cpu掌管内存
	set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a);//设定2号段大小为512kb,用于bootpack.hrb
	load_gdtr(0xffff, 0x00270000);//利用汇编给GDTR寄存器存储数值

	/* IDT初始化 */
	for (i = 0; i < 256; i++) {
		set_gatedesc(idt + i, 0, 0, 0);
	}
	load_idtr(0x7ff, 0x0026f800);

    return;
}

void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)
{
	if (limit > 0xfffff) {
		ar |= 0x8000; /* G_bit = 1 */
		limit /= 0x1000;
	}
	sd->limit_low    = limit & 0xffff;
	sd->base_low     = base & 0xffff;
	sd->base_mid     = (base >> 16) & 0xff;
	sd->access_right = ar & 0xff;
	sd->limit_high   = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0);
	sd->base_high    = (base >> 24) & 0xff;
	return;
}

void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar)
{
	gd->offset_low   = offset & 0xffff;
	gd->selector     = selector;
	gd->dw_count     = (ar >> 8) & 0xff;
	gd->access_right = ar & 0xff;
	gd->offset_high  = (offset >> 16) & 0xffff;
	return;
}

总结

今天的任务到此结束,明天我们加吧劲继续吧IDT与GDT搞定!!内容可能会比较难,大家结合书本再查询资料吧。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值