到目前为止,代码中还有一些重复出现的部分以及存在不够抽象等问题,不符合面向对象的原则,在进一步实现更复杂的功能前,有必要进行代码的整理和重构。
1. 抽象驱动程序
所有硬件加载时都需要驱动程序,有必要抽象出驱动程序类Driver,统一封装驱动程序的共有特点,让所有驱动程序类都继承Driver。同时将所有驱动程序对象放入一个数组中进行统一管理。另外,操作系统每次启动和关闭可能不会清理掉所有数据,所以在每此启动操作系统时重置所有硬件是很有必要的。
driver.h
#ifndef __DRIVER_H
#define __DRIVER_H
class Driver
{
public:
Driver();
~Driver();
virtual void Activate();
virtual void Deactivate();
virtual int Reset();
//这是所有驱动程序的共有功能,均定义为virtual函数,使得在特定驱动程序中可以重写函数内容。
};
class DriverManager
{
public:
DriverManager();
void AddDriver(Driver*); //系统初始化时向数组加入所有驱动程序
void ActivateAll();//遍历数组,依次启动所有驱动程序
private:
Driver* driver[255];//存储驱动程序指针
int numDrivers;//记录驱动程序数,初始化为0
};//DriverManager初始化所有驱动程序,并将所有驱动程序加入到数组中。
#endif
函数实现比较简单,省略。
通过driver.h可以重写键盘和鼠标驱动:
keyboard.h
... ...(头文件)
class KeyboardEventHandler
{
public:
KeyboardEventHandler();
virtual void OnKeyDown(char);
virtual void OnKeyUp(char);
};//将键盘操作抽象出来,供应用程序执行不同的操作。定义键盘点击和抬起操作,暂时不需要实现
class KeyBoardDriver : public InterruptHandler,public Driver
{
public:
KeyBoardDriver(InterruptManager* manager,KeyboardEventHandler* handler);
~KeyBoardDriver();
virtual uint32_t HandleInterrupt(uint32_t esp);
virtual void Activate();//原本构造函数的内容作为Activate函数内容
private:
... ...
KeyboardEventHandler* handler;
};
#endif
mouse.h
... ...(头文件)
class MouseEventHandler
{
public:
MouseEventHandler();
virtual void OnActivate();
virtual void OnMouseDown(uint8_t button);
virtual void OnMouseUp(uint8_t button);
virtual void OnMouseMove(int8_t x,int8_t y);
};//将鼠标操作抽象出来,供应用程序执行不同的操作。定义的鼠标移动,点击和抬起等函数,暂时不需要实现
class MouseDriver : public InterruptHandler,public Driver
{
public:
MouseDriver(InterruptManager* manager,MouseEventHandler* handler);
~MouseDriver();
virtual uint32_t HandleInterrupt(uint32_t esp);
virtual void Activate();//原本构造函数的内容作为Activate函数内容
private:
... ...
MouseEventHandler* handler;
};
#endif
2. 重写鼠标键盘操作
将原本的鼠标键盘操作重写到kernel.cpp中,新类继承自KeyboardEventHandler,这样使得无需直接操纵屏幕,只使用现成的程序接口即可完成对外设的操作。当后续需要鼠标键盘的其他操作时只需要定义其他继承自KeyboardEventHandler/MouseEventHandler的类即可。
class PrintKeyboardEventHandler : public KeyboardEventHandler
{
public:
void OnKeyDown(char c){
char* foo = (char*)" ";
foo[0]=c;
printf(foo);
}
};//只是简单的输出字符
class MouseToConsole : public MouseEventHandler
{
public:
MouseToConsole(){}
// : x(40), y(12)
void OnActivate(){
uint16_t* VideoMemory = (uint16_t*)0xb8000;
VideoMemory[y*80+x] = ((VideoMemory[y*80+x]&0xf000) >> 4) | ((VideoMemory[y*80+x]&0x0f00)<<4)|
(VideoMemory[y*80+x]&0x00ff);
}
void OnMouseMove(int8_t nx,int8_t ny){//传入偏移量nx,ny
uint16_t* VideoMemory = (uint16_t*)0xb8000;
VideoMemory[y*80+x] = ((VideoMemory[y*80+x]&0xf000) >> 4) | ((VideoMemory[y*80+x]&0x0f00)<<4)| (VideoMemory[y*80+x]&0x00ff);
x += nx; if(x<0) x=0; else if(x>=80) x=79;
y += ny; if(y<0) y=0; else if(y>=25) y=24;
VideoMemory[y*80+x] = ((VideoMemory[y*80+x]&0xf000) >> 4) | ((VideoMemory[y*80+x]&0x0f00)<<4)|(VideoMemory[y*80+x]&0x00ff);
}
private:
int8_t x,y;
};
3. 定义printfHex
项目中多次使用到打印16进制数(如打印中断号),在kernel.cpp中封装为函数:
void printfHex(uint8_t char)
{
char* foo = (char*)"00";
const char* hex = "0123456789ABCDEF";
foo[0] = hex[(char >> 4)&0x0f];
foo[1] = hex[char & 0x0f];
printf(foo);
}
4.整理目录结构
创建include文件夹和src文件夹分别存储库函数(.h)和源码(.cpp),同时将有关驱动与硬件等不同代码分开存放,使整个工程文件更加标准化:
在makefile中创建obj文件夹存放所有中间生成的.o文件:
makefile
... ...
objects = obj/loader.o \
obj/kernel.o \
obj/gdt.o \
obj/hardwarecommunication/port.o \
obj/hardwarecommunication/interrupts.o \
obj/hardwarecommunication/interruptstubs.o \
obj/drivers/keyboard.o \
obj/drivers/mouse.o \
obj/drivers/driver.o
obj/%.o: src/%.cpp
mkdir -p $(@D)
g++ $(GPPRAMS) -o $@ -c $<
... ...
.PHONY: clean
clean:
rm -rf mykernel.bin mykernel.iso obj #每次clean都清理整个obj文件夹
5.使用命名空间
命名空间是一个声明性区域,为其内部的标识符(类型、函数和变量等的名称)提供一个范围。 命名空间用于将代码组织到逻辑组中,还可用于避免名称冲突,尤其是在基本代码包括多个库时。 命名空间可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。总之,使用命名空间是项目工程的标准操作。
使用namespace关键字将include下每一个文件夹都设立单独的命名空间,同时都嵌套在myos的总命名空间下,在.cpp中使用using关键字声明使用哪些命名空间中的函数即可。
6.inline函数改写port
使用inline关键字,重新定义端口读写函数为内联函数,以加快端口的调用和读取。 inline函数与普通函数的区别在于,当编译器处理调用内联函数的语句时,不会将该语句编译成函数调用的指令,而是直接将整个函数体的代码插人调用语句处,就像整个函数体在调用处被重写了一遍一样。这一点有点类似于宏定义。中心思想是以空间换时间
当越来越多的设备被调用时,不能像鼠标键盘一样一直使用硬编码的方式调用外设,需要一种更快捷,更标准的方法:这时便引入新的硬件——PCI总线。他通过使用基址寄存器实现动态申请I/O端口和地址空间(图片来自网络)
PCI是外部设备互联标准的缩写,是局部总线的一种标准,是intel架构计算机重要的组成部分。 (参考微机原理)作为一种局部总线,我们关注到它的两个特点:
PCI的基本配置空间有256B,分为头标区(64B)和设备相关区(192B)。头标区通过多个寄存器来提供与该设备有关的信息,共三类,分别对应PCI-CardBus(用于笔记本电脑),PCI-PCI和PCI-设备。其中最重要的是第0类:PCI-设备桥。三种配置空间的通用header:(https://wiki.osdev.org/Pci)
PCI-设备桥配置空间:
PCI配置信息命令与读写主要由两个32位端口实现,分别是(0xcf8)的 CONFIG_ADDRESS,和(0xcfc)的CONFIG_DATA。CONFIG_ADDRESS 指定需要访问的配置地址,而对 CONFIG_DATA 的访问将实际生成配置访问并将数据传入或传出 CONFIG_DATA 寄存器。
PCI控制器的结构为:最多可连接8条总线,每条总线最多连接32个设备,每个设备最多可以使用8种功能:
因此,每次读写操作都需要用3bit确定bus,5bit确定device和3bit确定function,这是CONFIG_ADDRESS结构:(https://wiki.osdev.org/Pci)
Bit 31 | Bits 30-24 | Bits 23-16 | Bits 15-11 | Bits 10-8 | Bits 7-0 |
---|---|---|---|---|---|
Enable Bit | Reserved | Bus Number | Device Number | Function Number | Register Offset |
由于每次只能读写配置信息中32b的数据,所以需要Regiseter Offset指定读取的偏移量。
pci.h
#ifndef __MYOS__HARDWARECOMMUNICATION__PCI_H
#define __MYOS__HARDWARECOMMUNICATION__PCI_H
#include "hardwarecommunication/interrupts.h"
#include "common/types.h"
#include "hardwarecommunication/port.h"
#include "drivers/driver.h"
namespace myos
{
namespace hardwarecommunication
{
class PCI_DeviceDescriptor
{
public:
PCI_DeviceDescriptor();
~PCI_DeviceDescriptor();
myos::common::uint32_t portBase;
myos::common::uint32_t interrupt;
myos::common::uint8_t bus;
myos::common::uint8_t device;
myos::common::uint8_t function;
myos::common::uint16_t device_id;
myos::common::uint16_t vendor_id;
myos::common::uint8_t class_id;
myos::common::uint8_t subclass_id;
myos::common::uint8_t interface_id;
myos::common::uint8_t reversion;
};//参照上图定义PCI描述符,每个描述符都对应一个bus上的一个device上的一个function。
class PCI_ConnectController
{
public:
PCI_ConnectController();
~PCI_ConnectController();
myos::common::uint32_t Read(myos::common::uint8_t bus,myos::common::uint8_t deveice, myos::common::uint8_t function, myos::common::uint8_t registeroffset);
//定义某个特定function的读操作。
void Write(myos::common::uint8_t bus, myos::common::uint8_t deveice,myos::common::uint8_t function, myos::common::uint8_t registeroffset,myos::common::uint32_t value );
//定义某个function的写操作
bool DeviceHasFunctions(myos::common::uint8_t bus, myos::common::uint8_t device);
//判断某个device是否有function,这将加速对某一function的选择
void SelectDrivers(myos::drivers::DriverManager* driverManager);
//选择驱动程序使得PCI可以直接和驱动程序通信而无需CPU参与
PCI_DeviceDescriptor GetDeviceDescriptor(myos::common::uint8_t bus, myos::common::uint8_t device, myos::common::uint8_t function);
//获取PCI描述符
private:
Port32Bit dataPort;
Port32Bit commandPort;
};
}
}
pci.cpp
#include "hardwarecommunication/pci.h"
using namespace myos::drivers;
using namespace myos::common;
using namespace myos::hardwarecommunication;
PCI_DeviceDescriptor::PCI_DeviceDescriptor(){}
PCI_DeviceDescriptor::~PCI_DeviceDescriptor(){}
PCI_ConnectController::PCI_ConnectController()
: dataPort(0xcfc),
commandPort(0xcf8){}//初始化PCI控制器的两个端口
PCI_ConnectController::~PCI_ConnectController(){}
uint32_t PCI_ConnectController::Read(uint8_t bus, uint8_t function, uint8_t device, uint8_t registeroffset)
{
uint32_t id = 1<<31 | ((bus&0xff)<<16) | ((device&0x1f)<<11) | ((function&0x07)<<8) | (registeroffset&0xfc);//最后两位为0,第一位为1
//根据CONFIG_ADDRESS结构拼接
commandPort.Write(id);
uint32_t result = dataPort.Read();
return result >> (8*(registeroffset%4));
}
void PCI_ConnectController::Write(uint8_t bus, uint8_t device, uint8_t function,uint8_t registeroffset,uint32_t value)
{
uint32_t id = 1<<31 | ((bus&0xff)<<16) | ((device&0x1f)<<11) | ((function&0x07)<<8) | (registeroffset&0xfc);
commandPort.Write(id);
dataPort.Write(value);
}//和Read一样先获取CONFIG_ADDRESS,再写入value。
bool PCI_ConnectController::DeviceHasFunctions(common::uint8_t bus, common::uint8_t device){
return Read(bus, device, 0, 0x0E) & (1<<7);//只需返回读取到的功能0的第7位是否为1即可。
}
void printf(char* str);
void printfHex(uint8_t);
void PCI_ConnectController::SelectDrivers(DriverManager* driverManager)
{
for(uint16_t bus = 0; bus < 8; bus++){
for(uint8_t device = 0; device < 32; device++){
int numFunctions = DeviceHasFunctions(bus, device) ? 8 : 1;
for(int function = 0; function < numFunctions; function++){
//三重循环依次选择每一个bus,每一个device上每一个function
PCI_DeviceDescriptor dev = GetDeviceDescriptor(bus, device, function);
if(dev.vendor_id == 0x0000 || dev.vendor_id == 0xFFFF) continue;
//如果没有设备则vender id将为全0或全1
printf("PCI BUS "); printfHex(bus & 0xFF);
printf(", DEVICE "); printfHex(device & 0xFF);
printf(", FUNCTION "); printfHex(function & 0xFF);
printf(" = VENDOR "); printfHex((dev.vendor_id & 0xFF00) >> 8); printfHex(dev.vendor_id & 0xFF);
//为了打印16位使用两次printfHex
printf(", DEVICE "); printfHex((dev.device_id & 0xFF00) >> 8); printfHex(dev.device_id & 0xFF);
printf("\n");
}//选择并打印出所有bus,device,function。
}}}
PCI_DeviceDescriptor PCI_ConnectController::GetDeviceDescriptor(uint8_t bus, uint8_t device, uint8_t function)
{
PCI_DeviceDescriptor result;
result.bus = bus;
result.device = device;
result.function = function;
result.vendor_id = Read(bus, device, function, 0x00);
result.device_id = Read(bus, device, function, 0x02);
result.class_id = Read(bus, device, function, 0x0b);
result.subclass_id = Read(bus, device, function, 0x0a);
result.interface_id = Read(bus, device, function, 0x09);
result.revision = Read(bus, device, function, 0x08);
result.interrupt = Read(bus, device, function, 0x3c);
return result;
}//根据PCI配置信息通过调整偏移量记录相应内容。
最后,在kernel.cpp中启动PCI:
... ...
PCI_ConnectController PCIController;
PCIController.SelectDrivers(&drvManager);
drvManager.ActivateAll();
Interrupts.Activate();
while(1);
}
启动操作系统将看到所有设备与厂商的信息,可以定位到每一种特定的设备,这无论是对进一步操纵其他外设,还是对于精准定位设备bug并获取有用的信息,都是非常友好的。
基址寄存器是实现PCI功能的核心:由上图 (Header type 0x0) 可以看到:header从0x10开始,有6个双字作为基址寄存器,作用为:
下面在我们的PCI中实现获取基址寄存器。
pci.h
... ...(头)
enum BaseAddressRegisterType
{
MemoryMapping = 0,
InputOutput = 1
};//枚举类型。分别对应两种功能
class BaseAddressRegister
{
public:
bool prefetchable;
myos::common::uint8_t* address;
myos::common::uint32_t size;
BaseAddressRegisterType type;
};//分别对应上图的四个部分
class PCI_ConnectController
{
public:
... ...
myos::drivers::Driver* GetDriver(PCI_DeviceDescriptor dev, myos::hardwarecommunication::InterruptManager* interrupts);
//获取驱动
BaseAddressRegister GetBaseAddressRegister(myos::common::uint16_t bus, myos::common::uint16_t device, myos::common::uint16_t function, myos::common::uint16_t bar);
//获取基址寄存器入口
private:
... ...
};
pci.cpp
void PCI_ConnectController::SelectDrivers(DriverManager* driverManager,InterruptManager* interrupts, InterruptManager* interrupts)
{//增加中断参数
... ...
if(dev.vendor_id == 0x0000 || dev.vendor_id == 0xFFFF) continue;
for(int barNum = 0; barNum < 6; barNum++){//一共有6个基址寄存器
BaseAddressRegister bar = GetBaseAddressRegister(bus, device, function, barNum);
if(bar.address && (bar.type == InputOutput))
dev.portBase = (uint32_t)bar.address;//address为bar的高字节,
}
Driver* driver = GetDriver(dev, interrupts);
if(driver != 0)
driverManager->AddDriver(driver);//如果有则加入到drivermanager
... ...
}
... ...
BaseAddressRegister PCI_ConnectController::GetBaseAddressRegister(uint16_t bus, uint16_t device, uint16_t function, uint16_t bar)
{
BaseAddressRegister result;
uint32_t headertype = Read(bus, device, function, 0x0E) & 0x7F;
int maxBARs = 6 - (4*headertype); //根据headertype判断使用多少个基址寄存器(最多6个)
if(bar >= maxBARs)
return result;
uint32_t bar_value = Read(bus, device, function, 0x10 + 4*bar);//得到32位寄存器的内容
result.type = (bar_value & 0x1) ? InputOutput : MemoryMapping;//根据最后一位判断是哪种基址寄存器
uint32_t temp;
if(result.type == MemoryMapping){//如果是内存映射寄存器
switch((bar_value >> 1) & 0x3){//右移一位取低两位
case 0: // 32 Bit Mode
case 1: // 20 Bit Mode
case 2: // 64 Bit Mode
break;
}
}
else{ // 如果是InputOutput
result.address = (uint8_t*)(bar_value & ~0x3);
result.prefetchable = false;
}
return result;
}
Driver* PCI_ConnectController::GetDriver(PCI_DeviceDescriptor dev, InterruptManager* interrupts)
{
Driver* driver = 0;
......//这里只是简单的根据vendor id,class id等信息打印出特定设备信息。
return driver;
}
显示适配器就是我们所说的显卡,是显示器和主机连接的装置,VGA(video graphics adaptor)是IBM推出的彩色显示适配器,与大多数现今的显卡兼容,是自制操作系统编写图形界面的很好选择。最高分辨率640×350,颜色最多256k种。
通常使用VGA的方法是在bios调用0x13中断,但由于我们使用grub引导程序直接跳过了bios阶段,这样使得对VGA的操作变得十分困难:即仍然同之前一样,以直接操作硬件(写入端口)的方式操作,也不需要借助PCI,以操作文本的方式写入像素,比较简陋的实现图形界面功能。
(注意:viktor(原作者)说:在没有系统保护的情况下,理论上来说,将错误的数据传入显卡可能导致硬件损坏甚至发生爆炸)(https://wiki.osdev.org/Pci)
在vga.h中定义所需的端口和方法(略),每种模式端口写入内容是固定的,具体规则不作研究。总之这种操作vga的方法无趣且危险。
vga.cpp
#include
using namespace myos::common;
using namespace myos::drivers;
VideoGraphicsArray::VideoGraphicsArray() :
miscPort(0x3c2),//miscellaneous output register.
crtcIndexPort(0x3d4),
crtcDataPort(0x3d5),
sequencerIndexPort(0x3c4),
sequencerDataPort(0x3c5),
graphicsControllerIndexPort(0x3ce),
graphicsControllerDataPort(0x3cf),// indexed registers
attributeControllerIndexPort(0x3c0),
attributeControllerReadPort(0x3c1),
attributeControllerWritePort(0x3c0),
attributeControllerResetPort(0x3da){} //初始化所有VGA要使用的端口
VideoGraphicsArray::~VideoGraphicsArray(){}
void VideoGraphicsArray::WriteRegisters(uint8_t* registers)//绕过bios启用vga
{
// misc:miscellaneous output register.
miscPort.Write(*(registers++));
// sequencer
for(uint8_t i = 0; i < 5; i++)
{
sequencerIndexPort.Write(i);
sequencerDataPort.Write(*(registers++));
}
// cathode ray tube controller:阴极射线管控制
crtcIndexPort.Write(0x03);
crtcDataPort.Write(crtcDataPort.Read() | 0x80);//第七位置1
crtcIndexPort.Write(0x11);
crtcDataPort.Write(crtcDataPort.Read() & ~0x80);
registers[0x03] = registers[0x03] | 0x80;
registers[0x11] = registers[0x11] & ~0x80;
for(uint8_t i = 0; i < 25; i++){
crtcIndexPort.Write(i);
crtcDataPort.Write(*(registers++));
}
// graphics controller
for(uint8_t i = 0; i < 9; i++){
graphicsControllerIndexPort.Write(i);
graphicsControllerDataPort.Write(*(registers++));
}
// attribute controller:属性控制
for(uint8_t i = 0; i < 21; i++) {
attributeControllerResetPort.Read();
attributeControllerIndexPort.Write(i);
attributeControllerWritePort.Write(*(registers++));
}
attributeControllerResetPort.Read();
attributeControllerIndexPort.Write(0x20);
}//像寄存器写入固定内容。各寄存器功能:https://wiki.osdev.org/VGA_Hardware
bool VideoGraphicsArray::SupportsMode(uint32_t width, uint32_t height, uint32_t colordepth)//颜色深度:用多少位表示颜色
{
return width == 320 && height == 200 && colordepth == 8;
}//设置支持的模式:这里只给出一种模式:320×200,256种颜色
bool VideoGraphicsArray::SetMode(uint32_t width, uint32_t height, uint32_t colordepth)
{
if(!SupportsMode(width, height, colordepth))
return false;
unsigned char g_320x200x256[] =
{
/* MISC */
0x63,
/* SEQ */
0x03, 0x01, 0x0F, 0x00, 0x0E,
/* CRTC */
0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0xBF, 0x1F,
0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x9C, 0x0E, 0x8F, 0x28, 0x40, 0x96, 0xB9, 0xA3,
0xFF,
/* GC */
0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F,
0xFF,
/* AC */
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x41, 0x00, 0x0F, 0x00, 0x00
};
WriteRegisters(g_320x200x256);
return true;
}//这是320×200模式固定要像各个寄存器中写入的内容
uint8_t* VideoGraphicsArray::GetFrameBufferSegment()//获取video rom memory
{
graphicsControllerIndexPort.Write(0x06);
uint8_t segmentNumber = graphicsControllerDataPort.Read() & (3<<2);
switch(segmentNumber){
default:
case 0<<2: return (uint8_t*)0x00000;
case 1<<2: return (uint8_t*)0xA0000;
case 2<<2: return (uint8_t*)0xB0000;
case 3<<2: return (uint8_t*)0xB8000;
}
}
void VideoGraphicsArray::PutPixel(int32_t x, int32_t y, uint8_t colorIndex)
{//真正写入像素点函数
if(x < 0 || 320 <= x|| y < 0 || 200 <= y)
return;
uint8_t* pixelAddress = GetFrameBufferSegment() + 320*y + x;//获取像素地址:像素地址由帧缓冲区段开始,硬编码方式写入
*pixelAddress = colorIndex;//在像素地址处写入一个像素
}
uint8_t VideoGraphicsArray::GetColorIndex(uint8_t r, uint8_t g, uint8_t b)
{
if(r == 0x00 && g == 0x00 && b == 0x00) return 0x00; // black
if(r == 0x00 && g == 0x00 && b == 0xA8) return 0x01; // blue
if(r == 0x00 && g == 0xA8 && b == 0x00) return 0x02; // green
if(r == 0xA8 && g == 0x00 && b == 0x00) return 0x04; // red
if(r == 0xFF && g == 0xFF && b == 0xFF) return 0x3F; // white
return 0x00;
}//这是由rgb颜色转换到ColorIndex(使用8位描述一种颜色)的方法,暂时只写入这几种颜色。
void VideoGraphicsArray::PutPixel(int32_t x, int32_t y, uint8_t r, uint8_t g, uint8_t b)
{
PutPixel(x,y, GetColorIndex(r,g,b));
}//在x,y位置写入一个像素(一个ColoreIndex)。
void VideoGraphicsArray::FillRectangle(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint8_t r, uint8_t g, uint8_t b)
{
for(int32_t Y = y; Y < y+h; Y++)
for(int32_t X = x; X < x+w; X++)
PutPixel(X, Y, r, g, b);
}//画一个长方形 :)
最后在kernel.cpp中使用两层循环把所有像素写入:(在虚拟机中速度很慢)
... ...
VideoGraphicsArray vga;
drvManager.ActivateAll();
Interrupts.Activate();
vga.SetMode(320,200,8);
for(uint32_t y=0;y<200;y++){
for(uint32_t x=0;x<320;x++){
vga.PutPixel(x,y,0x00,0x00,0xa8);
}
}
while(1);
}