首页 理论教育 操作系统实现之路-Video对象

操作系统实现之路-Video对象

时间:2023-10-21 理论教育 版权反馈
【摘要】:Video对象层中的主要对象就是Video对象,系统中的每个显示设备,对应一个Video对象。在Hello China V1.75的实现中,只有一个Video对象—全局Video对象。如果这个函数失败,则直接导致Video对象的Initialize函数失败,从而导致GUI模块加载失败。初始化完成之后,Video对象就可以被更上层的应用代码直接使用了。在当前版本的实现中,Video对象的功能比较简单,只实现了画点、画线、画圆等基础函数。

操作系统实现之路-Video对象

Video对象层中的主要对象就是Video对象,系统中的每个显示设备,对应一个Video对象。一个典型的例子就是,在个人计算机中,会有显示器设备、打印机设备、投影仪设备等。每个这样的设备,对应一个显示对象。显示对象的具体实现是由设备的驱动程序完成的,在实现Video对象的时候,具体的功能代码,与硬件关系紧密,不同的Video对象会存在较大差异。但是每个Video对象的实现,必须按照预先定义的接口进行,否则无法被更上层(通用绘制层)调用。

程序员来说,解释概念的最直观方法,就是展示代码。下面是Video对象的预定义代码:

978-7-111-41444-5-Chapter11-10.jpg

978-7-111-41444-5-Chapter11-11.jpg

当前的定义比较简单,基本上是按照flat display memory的模型来定制的。但是可以对其进行扩展,添加更多的功能。重点关注下列几个预定义函数:

(1)Initialize/UnInitialize函数,其中第一个是在GUI模块初始化的时候被调用,第二个则是在Video对象被卸载的时候调用。对显示卡来说,应该在Initialize函数中,对硬件进行初始化,获得硬件的相关信息,并把关键的信息填到Video的几个变量中。最主要的是pBaseAddress、BitsPerPixel等。这些变量的含义都是自解释的。

(2)DrawPixel、DrawLine等函数。这些函数实现了最基本的绘制功能,上层模块通过调用这些功能,实现更复杂的绘制操作。DrawPixel和GetPixel是必须实现的,其他诸如画线、画椭圆等,可选择实现。如果实现了,GUI的通用绘制层会直接调用,如果没有实现,通用绘制层会通过调用DrawPixel函数来自行实现,这时候的效率,可能不如Video对象实现的高。因为Video对象在实现的时候,可以调过自身的硬件机制来实现绘制功能,而通用绘制层的实现,则完全是基于软件的。通过这里的描述,读者会更进一步理解显示卡对CPU在图形操作上的处理分担(offload)功能。

(3)MouseToScreen函数,这是必须实现的。这个函数把鼠标的坐标数值,换算成屏幕的坐标数值。因为通常情况下,鼠标的横纵坐标最大为255,而屏幕的分辨率则是变化的。我们在操作窗口元素的时候,是以屏幕坐标为基础的,而用户输入,则是以鼠标坐标为基础。这样就必须实现这两者之间的转换。

在Hello China V1.75的实现中,只有一个Video对象—全局Video对象。该对象对应屏幕显示设备。这个对象直接定义在源代码中,这样其他层次(比如通用绘制层)就可直接调用它提供的功能方法了:

978-7-111-41444-5-Chapter11-12.jpg

978-7-111-41444-5-Chapter11-13.jpg

上面的定义,初始化了Initialize等函数,但是对于pBaseAddress等,并没有设置。这些变量的设置,是在Initialize函数中实现的。下面详细解释Initialize函数的实现。

Initialize函数的实现代码如下:

978-7-111-41444-5-Chapter11-14.jpg

978-7-111-41444-5-Chapter11-15.jpg

Video对象初始化的过程比较简单,主要有三个关键点,代码中分别用(1)、(2)、(3)表示。分别介绍如下:

(1)首先调用SwitchToGraphic函数,试图切入图形模式的0x118显示模式。该函数首先切换到实模式,然后通过0x10号中断调用,设置显示卡工作模式为0x118,并获取对应模式的相关信息,存储到VBE_INFO_START定义的内存处,然后再切换回保护模式。如果一切操作都是成功的,则返回TRUE,任何一个环节失败就返回FALSE。如果这个函数失败,则直接导致Video对象的Initialize函数失败,从而导致GUI模块加载失败。这里需要注意的是,Hello China在加载GUI模块的时候,就已经进入保护模式了。这时若要调用0x10 BIOS中断设置显示模式,则必须重新返回实模式。

(2)SwitchToGraphic函数成功后,说明已经切换到0x118号的图形模式,该模式的相关信息被保存在VBE_INFO_START定义的内存地址处。该处存放了一个VBE_MODE_INFO结构的信息块,其中的physbaseptr变量,就是我们关注的flat display memory的起始地址。需要注意的是,这个地址是显卡显存的物理地址,这个地址由BIOS在初始化显卡时设置。缺省情况下Hello China启用了VMM(虚拟内存管理,详情可参考第5章)功能,任何一块可用的内存空间,必须经VMM管理器建立对应的页目录和页表才能访问,否则会引发异常。因此在获得该地址后,必须调用VirtualAlloc函数,来为显存建立页目录和页表。关于VirtualAlloc函数的使用,在第5章有详细介绍。正常情况下应该不会失败,因为显存地址不会被占用。但是也有可能出现显存地址被预先占用的情况,这时候就会导致VirtualAlloc失败,从而导致Video初始化失败。如果读者对VirtualAlloc的使用方法不了解,也无需在这里花费过多时间,可以先认为这一步不存在,以免影响对GUI部分的理解。(www.xing528.com)

(3)另外一个需要设置的是显存的大小。在0x118模式下,显存的大小是3MB。正常情况下,显存的大小也应该是从VBE信息块中获取的。但是为了实现上的方便,直接用预定义的常数DISPLAY_MEMORY_LENGTH来初始化显存大小。一般情况下,这是没有问题的。

初始化完成之后,Video对象就可以被更上层的应用代码直接使用了。在当前版本的实现中,Video对象的功能比较简单,只实现了画点、画线、画圆等基础函数。下面是画点函数的实现,非常简单。

978-7-111-41444-5-Chapter11-16.jpg

该函数的功能是在屏幕坐标(x,y)处,画一个颜色是color的点。函数首先计算出这个坐标所对应的显存地址,然后把颜色代码写入显存即可。这样显卡硬件就会在(x,y)处点亮对应的颜色。

其他函数,比如画线函数,则是使用了一些成熟的计算机图形学算法,调用DrawPixel来实现画图功能。如何快速有效地画出一条直线,甚至不使用浮点运算功能,是计算机图形学的重要课题之一。比如比较有名的画线算法,是Bresenham算法,这个算法快速且效果好,又不用浮点数支持,非常高效。Hello China V1.75 GUI的画线和画圆算法,就使用了该算法。这个算法的详细思想,可参考相关资料,或者到网上搜索

到此为止,GUI部分与硬件相关的内容介绍完毕,相信读者们已经建立起一个清晰的脉络。此后的所有内容,基本都是硬件无关的,它们只会调用Video对象提供的服务,硬件操作完全由Video对象屏蔽。为了进一步加深读者理解,我们简单描述一下GUI模块加载过程的开始部分,这样会把Video对象的初始化等动作所在的位置说明得更加清楚。

Hello China V1.75的GUI模块是在字符shell模式下加载的。在字符操作模式下,用户输入gui命令并回车后,字符shell会在C:\PTHOUSE目录下寻找hcngui.bin模块。如果找不到,则加载失败,重新回到字符shell。如果能够找到hcngui.bin模块,则字符shell会读取该模块到内存,然后进行合法性检查,主要是检查该模块的开始部分是不是一个合法的Hello China外围模块。如果检查失败,则放弃加载,返回字符shell。如果检查顺利通过,则字符shell会以hcngui.bin的起始地址为入口点,创建一个名字为“GUI”的核心线程,然后等待该线程运行结束(通过调用WaitForThisObject,等待GUI线程运行结束)。需要注意的是,字符shell本身就是一个核心线程,在完成GUI线程的创建后,系统中至少存在三个核心线程:字符shell线程、GUI线程、空闲idle线程。

GUI线程会马上被调度运行,这时候正式进入GUI模块的初始化过程。下面是初始化的部分代码:

978-7-111-41444-5-Chapter11-17.jpg

978-7-111-41444-5-Chapter11-18.jpg

上述Init函数即是GUI模块的初始化函数,也是GUI模块被加载后调用的第一个函数。该函数首先初始化Video对象(在上述代码位置(1)处),如果初始化失败,则直接进入出错处理过程,取消GUI的进一步初始化。代码中Video对象的Initialize函数,就是前面部分讲解的Video初始化函数。在这个函数内,完成切换到图形模式、初始化显存地址、为显存分配页目录和页表等工作。

如果Video对象初始化成功,则继续初始化系统中的其他对象,比如窗口管理器对象等。这些对象的详细初始化过程,在后续章节中会一一展开。全局对象初始化完毕,GUI模块会创建几个核心线程如GUIRAWIT线程、GUISHELL线程等,这些线程的用途,会在后面详细介绍,现在不必理会。核心线程创建完毕,GUI线程就会进入等待状态(等待GUIRAWIT等线程运行结束),正式的GUI功能由GUIRAWIT、GUISHELL等线程完成。一旦GUIRAWIT等核心线程运行结束(由用户驱动,比如用户在图形模式下按了“CTRL+ALT+DEL”组合键,就会导致GUIRAWIT线程自然结束),GUI线程会重新被调度运行,于是就进行资源清除工作,然后正式退出图形模式。

这里提到的一些线程,可能会让读者糊涂,图11-2清晰地说明了这些线程的等待关系。因为这些线程非常重要,所以读者必须搞清楚它们之间的关系,否则理解GUI的后续内容将存在一定困难。

978-7-111-41444-5-Chapter11-19.jpg

图11-2 GUI关键线程生命周期示意

图中阴影部分指的是线程处于阻塞等待状态,非阴影部分指的是线程处于正常运行状态。这种以核心线程为基础的设计方式,可有效地把GUI这个复杂的功能模块,分解为相互独立又相互协作的独立功能模块,具备很强的伸缩性和灵活性。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈