从零开始搭建环境编写操作系统 AT&T GCC (四)绘制界面
2017-08-04 08:28
375 查看
昨天写了一晚上的文章说没就没了???!!!一夜回到解放前啊,csdn不应该做出点补偿么……从头再来,也没有什么关系嘛。
今天的主要内容是整理文件,改正之前的几个错误点,简单讲一下VGA,然后写几个绘制界面的函数。
一、整理文件
在system.s文件夹创建一个functions.s的文件,这个文件用于补充c语言不能编写的内容,比如底层硬件的in和out操作,关闭中断cli操作,暂停hlt操作。system和loader两个文件夹下的makefile我都进行了修改,使得系统可以进入gdb和monitor调试模式,便于发现系统的问题,首先将makefile贴上来。
loader makefile
qemu-system-i386 -fda $(BIN_DIR)/loader.img -boot a -gdb tcp::1234 -monitor stdio ,这一行改动最大,-gdb tcp::1234是启动远程gdb调试,具体使用方法等单独拿出一节来讲,-monitor stdio是使用标准输入输出进行monitor调试,这个调试功能没有gdb功能丰富,但是可以观察所有registers寄存器的状态,还是必须要用的。
system makefile
这个makefile的主要改动就是增加了functions.s的编译
二、改正错误
1、如果继续我之前的环境编写下去,就会发现c语言中全局变量和静态变量无法使用,昨天用了大半天的时间查找这个问题,在这里记录一下变量的存储方式。
全局变量和静态变量是存储在.data段和.bss段内的,已经初始化的变量放在.data段内,未初始化的变量放在.bss段内。局部变量使用栈的方式存储。所以无法使用静态变量一定是向.data段的寻址出现了问题,寻址问题一般出现在段的划分上,恍然大悟,之前把ds的基址直接放在了0xa0000的位置上,寻址的时候自然也是以这个位置作为首地值,这个问题还没有想出一个好的解决方法,暂时把ds的基址定为0,把段限定为4G,就是数据段的地址与内存物理地址一一对应,lds文件要这么写:
2、当程序越写越大,就会发现出现各种奇怪的错误,后来一条条指令debug终于发现了第二个弱智错误,我把GDT表放在了0x8400处,程序从0x8200处开始运行,只有0x200,512字节的空间可以放下我们的程序,否则就会把GDT覆盖,这种错误也是醉了,不知道刚开始怎么想的。所以我把GDT表放到了0x90000处,这样就有接近64KB的空间放我们的这个小系统了,当系统再大时,我们就在loader中初始化gdt,然后把程序放到更大的内存空间里,现在就暂时这样写了。另外,GDT#1即代码段的段限只有512字节,已经忘记刚开始为什么要这么设计了,把它改成ffff,把段限改成64KB。再检查一下,ss有28KB,cs有64KB,ds有4G,没问题了。
吸取经验,要把系统有关的所有重要信息记录下来,要不然写到后面就忘记了。
再看一下system.s:
三、简单讲一下VGA
VGA是很久以前制定的一套标准,所以分辨率是很低的,现在的高分辨标准由VESA制定,称为SVGA,超级VGA,其提供了大量的bios驱动,具体的实现方法我也没有研究过,感兴趣的百度吧,这里就暂时用《30天自制操作系统》上的操作方式,等后期全部完成后可以再研究这个方面。
VGA的修改画面额方式非常简单,就是向0xa0000这个内存地址存入数据即可,0xa0000~0x0affff这一段内存地址放有显存,这一段内存地址存储的数据和画面上的像素点的颜色一一对应,所以只需要向这一段地址写入数据,就可以改变画面上的颜色,这一种UI方式跟ARM还是很相似的。
四、具体实现
1、functions.s的代码
这一段代码中实现了out,in,cli,hlt,标志位的存取这些C语言不能直接实现的功能。out,in由于操作的位数不同,所以提供了不同的操作函数。
这里还有一点可以提一下,就是函数的传参数方式,不同的平台有完全不同的传参方式,比如arm平台用r0,r1,r2……等传参数,x64平台用通用寄存器传参,但是x86平台是用栈的方式进行参数传递的,函数的参数从右到左依次入栈,被调用的函数就可以从栈中取出所需要的参数,返回值传入eax寄存器。
2、main.c代码
前面提到了,如果想要改变画面的颜色,只需要往0xa0000~0xaffff这个地址中存数据就可以了,每一个地址对应了一个像素块的颜色,一个地址对应一个字节,所以我们只有256种颜色可以使用,如果你这么想就错了,因为vga给我们提供了一个功能叫做调色板,调色板是这样一个东西:有256个编号,每个编号对应一个颜色。设置的时候,可以设置“1号颜色为#FFFFFF,2号颜色为#FFFFCC,……”,显卡会保存这些设置。然后我们只需要向地址中存入几号颜色,对应的像素就会称为相应的颜色。我们现在使用的模式是320*200的窗口大小,内存地址横向扫描,也就是说一行一行的存储,0xa0000是左上角(0,0)点的颜色,0xa0001是左上角(1,0)点的颜色,所以某一个像素对应的内存地址很容易计算出来:
行×320+列+0xa0000就是对应的像素内存了,至于怎么画图就是数学问题了。
至于怎么操作调色板呢,vga给出了很标准的步骤,在这插上一嘴,当不会实现某种功能的时候,去官网上找手册,手册永远是最清晰最准确的。因为官方文档是英文的(我查过了,讲的非常详细),所以把中文的操作步骤给出来,极度简化版:
1、记录中断许可标志的值;
2、将中断许可标志置为0,禁止中断;
3、将调色板号码写入0x03c8,然后按照R G B的顺序写入0x03c9.
4、复位中断许可标志
调色板到此也完成了。我随便设置了几个颜色。
我写了最简单的画方块的函数,因为画圆,画直线都涉及数学算法,大家要开动脑筋自己想啦。
main.c完整代码:
这是我随便画的三个方块
今天的主要内容是整理文件,改正之前的几个错误点,简单讲一下VGA,然后写几个绘制界面的函数。
一、整理文件
在system.s文件夹创建一个functions.s的文件,这个文件用于补充c语言不能编写的内容,比如底层硬件的in和out操作,关闭中断cli操作,暂停hlt操作。system和loader两个文件夹下的makefile我都进行了修改,使得系统可以进入gdb和monitor调试模式,便于发现系统的问题,首先将makefile贴上来。
loader makefile
run : loader.bin dd if=$(BIN_DIR)/loader.bin of=$(BIN_DIR)/loader.img bs=512 count=1 dd if=$(BIN_DIR)/system.bin of=$(BIN_DIR)/system.img bs=1474048 count=1 conv=sync dd if=$(BIN_DIR)/system.img of=$(BIN_DIR)/loader.img bs=512 seek=1 # qemu-system-i386 -fda $(BIN_DIR)/loader.img -boot a -gdb tcp::1234 -S -monitor stdio qemu-system-i386 -fda $(BIN_DIR)/loader.img -boot a -gdb tcp::1234 -monitor stdio loader.bin : loader.o Makefile loader.lds ld -M --oformat binary -m elf_i386 -o $(BIN_DIR)/loader.bin $(OBJ_DIR)/loader.o -T loader.lds loader.o : loader.s Makefile as --32 loader.s -o $(OBJ_DIR)/loader.o
qemu-system-i386 -fda $(BIN_DIR)/loader.img -boot a -gdb tcp::1234 -monitor stdio ,这一行改动最大,-gdb tcp::1234是启动远程gdb调试,具体使用方法等单独拿出一节来讲,-monitor stdio是使用标准输入输出进行monitor调试,这个调试功能没有gdb功能丰富,但是可以观察所有registers寄存器的状态,还是必须要用的。
system makefile
system.bin : system.o Makefile system.lds main.o functions.o ld -M -m elf_i386 -o $(BIN_DIR)/system.elf \ $(OBJ_DIR)/system.o $(OBJ_DIR)/functions.o $(OBJ_DIR)/main.o \ -T system.lds objcopy -O binary $(BIN_DIR)/system.elf $(BIN_DIR)/system.bin system.o : system.s Makefile as --32 system.s -o $(OBJ_DIR)/system.o main.o : main.c Makefile gcc -c main.c -o $(OBJ_DIR)/main.o -m32 functions.o : functions.s Makefile as --32 functions.s -o $(OBJ_DIR)/functions.o
这个makefile的主要改动就是增加了functions.s的编译
二、改正错误
1、如果继续我之前的环境编写下去,就会发现c语言中全局变量和静态变量无法使用,昨天用了大半天的时间查找这个问题,在这里记录一下变量的存储方式。
全局变量和静态变量是存储在.data段和.bss段内的,已经初始化的变量放在.data段内,未初始化的变量放在.bss段内。局部变量使用栈的方式存储。所以无法使用静态变量一定是向.data段的寻址出现了问题,寻址问题一般出现在段的划分上,恍然大悟,之前把ds的基址直接放在了0xa0000的位置上,寻址的时候自然也是以这个位置作为首地值,这个问题还没有想出一个好的解决方法,暂时把ds的基址定为0,把段限定为4G,就是数据段的地址与内存物理地址一一对应,lds文件要这么写:
SECTIONS { . = 0x8200; .text : { *(.text) } .data : { *(.data) } .bss : { *(.bss) } }
2、当程序越写越大,就会发现出现各种奇怪的错误,后来一条条指令debug终于发现了第二个弱智错误,我把GDT表放在了0x8400处,程序从0x8200处开始运行,只有0x200,512字节的空间可以放下我们的程序,否则就会把GDT覆盖,这种错误也是醉了,不知道刚开始怎么想的。所以我把GDT表放到了0x90000处,这样就有接近64KB的空间放我们的这个小系统了,当系统再大时,我们就在loader中初始化gdt,然后把程序放到更大的内存空间里,现在就暂时这样写了。另外,GDT#1即代码段的段限只有512字节,已经忘记刚开始为什么要这么设计了,把它改成ffff,把段限改成64KB。再检查一下,ss有28KB,cs有64KB,ds有4G,没问题了。
吸取经验,要把系统有关的所有重要信息记录下来,要不然写到后面就忘记了。
再看一下system.s:
.code16 .section .text .set CYLS, 0X0FF0 .set LEDS, 0X0FF1 .set VMODE, 0X0FF2 .set SCRNX, 0X0FF4 .set SCRNY, 0X0FF6 .set VRAM, 0X0FF8 start: movb $0x13, %al movb $0x00, %ah int $0x10 movb $8, VMODE movw $320, SCRNX movw $200, SCRNY movl $0x000a000, VRAM movb $0x02, %ah int $0x16 mov %al, LEDS movw %cs, %ax movw %ax, %ds movw %ax, %es movw %ax, %ss movw $0x100, %sp ########################################start 32 ############set GDT movl gdt_base, %eax ############0# empty GDT movl $0x00000000, 0(%eax) movl $0x00000000, 4(%eax) ############1# code GDT movl $0x8200ffff, 8(%eax) movl $0x00409a00, 12(%eax) ############2# data GDT movl $0x0000ffff, 16(%eax) movl $0x00cf9200, 20(%eax) ############3# stack GDT movl $0x00007a00, 24(%eax) movl $0x00409600, 28(%eax) #close interrupt cli lgdt gdt_size #configuration #turn on A2 inb $0x92, %al orb $0x02, %al outb %al, $0x92 #configure CR0 movl %cr0, %eax orl $1, %eax movl %eax, %cr0 #protect mode start ljmp $0x0008, $(start_protect-start) #16位描述子:32位位移 start_protect: .code32 movw $0x0010, %ax movw %ax, %ds movw $0x0010, %ax movw %ax, %ss movl $0x00007a00, %esp call SysMain gdt_size: .word 31 gdt_base: .long 0x9000 #定义GDT位置
三、简单讲一下VGA
VGA是很久以前制定的一套标准,所以分辨率是很低的,现在的高分辨标准由VESA制定,称为SVGA,超级VGA,其提供了大量的bios驱动,具体的实现方法我也没有研究过,感兴趣的百度吧,这里就暂时用《30天自制操作系统》上的操作方式,等后期全部完成后可以再研究这个方面。
VGA的修改画面额方式非常简单,就是向0xa0000这个内存地址存入数据即可,0xa0000~0x0affff这一段内存地址放有显存,这一段内存地址存储的数据和画面上的像素点的颜色一一对应,所以只需要向这一段地址写入数据,就可以改变画面上的颜色,这一种UI方式跟ARM还是很相似的。
四、具体实现
1、functions.s的代码
.code32 .section .text .global FunctionHlt .global FunctionOut8, FunctionOut16, FunctionOut32 .global FuntionIn8, FuntionIn16, FuntionIn32 .global FunctionCli, FuntionSti .global FunctionLoadEflags, FunctionStoreEflags FunctionHlt: #void FunctionHlt(void) hlt ret FunctionOut8: #void FunctionOut8(int port, int data) movl 4(%esp), %edx movb 8(%esp), %al outb %al, %dx ret FunctionOut16: #void FunctionOut16(int port, int data) movl 4(%esp), %edx movw 8(%esp), %ax outw %ax, %dx ret FunctionOut32: #void FunctionOut32(int port, int data) movl 4(%esp), %edx movl 8(%esp), %eax outl %eax, %dx ret FuntionIn8: #int FunctionIn8(int port) movl 4(%esp), %edx movb $0, %al inb %dx, %al ret FuntionIn16: #int FunctionIn16(int port) movl 4(%esp), %edx movw $0, %ax inw %dx, %ax ret FuntionIn32: #int FunctionIn32(int port) movl 4(%esp), %edx movl $0, %eax inl %dx, %eax ret FunctionCli: #void FunctionCli(void) cli ret FuntionSti: #void FunctionSti(void) sti ret FunctionLoadEflags: #int FunctionLoadEflags(void) pushf pop %eax ret FunctionStoreEflags: #void FunctionStoreEflags(int eflags) mov 4(%esp), %eax push %eax popf ret
这一段代码中实现了out,in,cli,hlt,标志位的存取这些C语言不能直接实现的功能。out,in由于操作的位数不同,所以提供了不同的操作函数。
这里还有一点可以提一下,就是函数的传参数方式,不同的平台有完全不同的传参方式,比如arm平台用r0,r1,r2……等传参数,x64平台用通用寄存器传参,但是x86平台是用栈的方式进行参数传递的,函数的参数从右到左依次入栈,被调用的函数就可以从栈中取出所需要的参数,返回值传入eax寄存器。
2、main.c代码
前面提到了,如果想要改变画面的颜色,只需要往0xa0000~0xaffff这个地址中存数据就可以了,每一个地址对应了一个像素块的颜色,一个地址对应一个字节,所以我们只有256种颜色可以使用,如果你这么想就错了,因为vga给我们提供了一个功能叫做调色板,调色板是这样一个东西:有256个编号,每个编号对应一个颜色。设置的时候,可以设置“1号颜色为#FFFFFF,2号颜色为#FFFFCC,……”,显卡会保存这些设置。然后我们只需要向地址中存入几号颜色,对应的像素就会称为相应的颜色。我们现在使用的模式是320*200的窗口大小,内存地址横向扫描,也就是说一行一行的存储,0xa0000是左上角(0,0)点的颜色,0xa0001是左上角(1,0)点的颜色,所以某一个像素对应的内存地址很容易计算出来:
行×320+列+0xa0000就是对应的像素内存了,至于怎么画图就是数学问题了。
至于怎么操作调色板呢,vga给出了很标准的步骤,在这插上一嘴,当不会实现某种功能的时候,去官网上找手册,手册永远是最清晰最准确的。因为官方文档是英文的(我查过了,讲的非常详细),所以把中文的操作步骤给出来,极度简化版:
1、记录中断许可标志的值;
2、将中断许可标志置为0,禁止中断;
3、将调色板号码写入0x03c8,然后按照R G B的顺序写入0x03c9.
4、复位中断许可标志
调色板到此也完成了。我随便设置了几个颜色。
void InitPalette() { int i,eflags; static unsigned char table_rgb [PALETTE_NUMBER*3] = { 0xFF,0xFF,0xFF, //0、白色 0xDD,0xDD,0xDD, 0xC0,0xC0,0xC0, 0x96,0x96,0x96, 0x80,0x80,0x80, 0x64,0x64,0x64, 0x4B,0x4B,0x4B, 0x24,0x24,0x24, 0x00,0x00,0x00, //8.黑色 0xFF,0xFF,0x00, //9、黄色 0xFF,0xCC,0x00, //10、淡橙色 0xFF,0x99,0x00, //11、深橙色 0xFF,0x66,0x00, //12、更深的橙色 0xFF,0x00,0x00 //13、红色 }; eflags = FunctionLoadEflags(); FunctionCli(); FunctionOut8(0x03c8,0); for (i=1;i<=PALETTE_NUMBER;i++) { FunctionOut8(0x03c9,table_rgb[3*i-3]); FunctionOut8(0x03c9,table_rgb[3*i-2]); FunctionOut8(0x03c9,table_rgb[3*i-1]); } FunctionStoreEflags(eflags); return; }
我写了最简单的画方块的函数,因为画圆,画直线都涉及数学算法,大家要开动脑筋自己想啦。
main.c完整代码:
extern void FunctionCli(void);
extern void FunctionOut8(int port, int data);
extern int FunctionLoadEflags(void);
extern void FunctionStoreEflags(int eflags);
void InitPalette();
void DrawRectangle(int x1,int y1,int x2,int y2,int color);
#define PALETTE_NUMBER 14
void SysMain()
{
InitPalette();
DrawRectangle(10,10,100,100,10);
DrawRectangle(50,50,150,150,11);
DrawRectangle(100,100,200,200,12);
while(1);
}
void InitPalette() { int i,eflags; static unsigned char table_rgb [PALETTE_NUMBER*3] = { 0xFF,0xFF,0xFF, //0、白色 0xDD,0xDD,0xDD, 0xC0,0xC0,0xC0, 0x96,0x96,0x96, 0x80,0x80,0x80, 0x64,0x64,0x64, 0x4B,0x4B,0x4B, 0x24,0x24,0x24, 0x00,0x00,0x00, //8.黑色 0xFF,0xFF,0x00, //9、黄色 0xFF,0xCC,0x00, //10、淡橙色 0xFF,0x99,0x00, //11、深橙色 0xFF,0x66,0x00, //12、更深的橙色 0xFF,0x00,0x00 //13、红色 }; eflags = FunctionLoadEflags(); FunctionCli(); FunctionOut8(0x03c8,0); for (i=1;i<=PALETTE_NUMBER;i++) { FunctionOut8(0x03c9,table_rgb[3*i-3]); FunctionOut8(0x03c9,table_rgb[3*i-2]); FunctionOut8(0x03c9,table_rgb[3*i-1]); } FunctionStoreEflags(eflags); return; }
void DrawRectangle(int x1,int y1,int x2,int y2,int color)
{
int i,j;
char *p;
for (i=y1;i<=y2;i++)
{
for (j=x1;j<=x2;j++)
{
p = (char*)(0xa0000 + 320 * i + j);
*p = color;
}
}
return;
}
这是我随便画的三个方块
相关文章推荐
- 从零开始搭建环境编写操作系统 AT&T GCC (二)从实模式到保护模式
- 从零开始搭建环境编写操作系统 AT&T GCC (八)使用键盘和滚轮鼠标
- 从零开始搭建环境编写操作系统 AT&T GCC (一)搭建环境和测试环境
- 从零开始搭建环境编写操作系统 AT&T GCC (九)内存管理
- 从零开始搭建环境编写操作系统 AT&T GCC (五)显示鼠标和字符
- 从零开始搭建环境编写操作系统 AT&T GCC (三)引入C语言
- 从零开始搭建环境编写操作系统 AT&T GCC (七)GDB调试和-monitor
- 读书笔记:Orange's 一个操作系统的实现(1) - 实验环境搭建和引导扇区的编写
- 《Orange's 一个操作系统的实现》读书手记2--- [ 搭建你的工作环境]
- 《Orange's 一个操作系统的实现》读书手记2--- [ 搭建你的工作环境]
- ARM-Tiny6410-开发环境搭建-Hardware && Linux && arm-linux-gcc
- Notepad++ & MinGW(gcc...) 编译环境搭建
- Orange's一个操作系统的实现的开发环境的搭建
- 《Orange's 一个操作系统的实现》学习笔记(一) 实验环境搭建
- orange's 一个操作系统的实现 实验环境搭建
- orange's一个操作系统的实现--环境搭建
- arm-linux-gcc4.4.3 交叉编译环境搭建&错误修正 fedora 19(KDE)
- 从零开始学android<android开发环境的搭建.一.>
- Oragne's 一个操作系统的实现的环境搭建【ubutu+win7】
- Windows操作系统下ionic开发环境搭建