从v3.0.0版本开始,zenglOX开始进入桌面环境,在内核中实现了窗口管理器,整个桌面其实就是一个窗口。v3.0.0版本是使用Qemu来作为开发调试用的模拟器的。Bochs的渲染速度以及执行性能都不如Qemu,不太适合进行图形化界面的开发,当然,v3.0.0及v3.0.1的版本也可以在Bochs上正常运行...

    页面导航:
项目下载地址:

    v3.0.0与v3.0.1版本的项目地址:

    github.com地址:https://github.com/zenglong/zenglOX (只包含git提交上来的源代码)

    Dropbox地址:点此进入Dropbox网盘 

    Google Drive地址:点此进入Google Drive云端硬盘

    sourceforge地址:https://sourceforge.net/projects/zenglox/files 

    在上面的三个网盘中(不包括github),这两个版本的相关文件,分别位于zenglOX_v3.0.0与zenglOX_v3.0.1的文件夹中。

    在zenglOX_v3.0.0文件夹中,zenglOX_v3.0.0.zip压缩包里包含v3.0.0版本的源代码,qemu-2.2.0.tar.bz2则为qemu模拟器的源代码,下面的readme.md说明文档中会介绍qemu的编译安装方法,zenglOX.iso为光盘镜像(可以直接放到virtualBox或VMware下进行测试,下面的说明文档里也会介绍),grub.cfg,initrd.img及zenglOX.bin这三个文件可以写入U盘,这样就可以从U盘启动,从而在真机上运行zenglOX,将Grub2写入U盘的方法,也会在readme.md文档里进行说明。

    在zenglOX_v3.0.1文件夹中,只包含zenglOX_v3.0.1.zip源代码压缩包,和readme.txt的说明文档,v3.0.1版本对应的iso镜像等文件,需要读者自行编译源码来生成。

    Dropbox与Google Drive如果正常途径访问不了,则需要使用代理访问。

readme.md说明文档:

    在v3.0.0与v3.0.1的源代码根目录中都包含了readme.md文件,该文件的内容如下(下面的信息可能跟readme.md文件不完全一致,作者只会将一些关键内容给显示出来,同时也会做一些必要的补充):

    从v3.0.0版本开始,zenglOX开始进入桌面环境,在内核中实现了窗口管理器,整个桌面其实就是一个窗口。

    截图如下(如果下面的图片无法显示的话,可以直接查看源码根目录下的screenshot目录,所有截图都放在该目录内):


图1
 
 

图2
 
 

图3
 

图4

    前两幅图是VirtualBox与VMware下的截图,第三幅图是在笔记本上,通过U盘启动时的截图,第四幅图是在作者的台式机上,通过U盘启动时的截图。(将Grub2写入U盘的方法,在下面会进行介绍。在作者的笔记本上,鼠标键盘都可以正常使用,但是在作者的台式机上,只有键盘可以正常工作,鼠标估计是走的USB通道,没有通过PS/2 second port对应的IRQ来发送中断信号)

    VirtualBox与VMware中运行时,所需要的zenglOX.iso镜像可以直接从sourceforge网盘对应的v3.0.0的文件夹里下载到。

    在之前的zenglOX v1.0.0版本对应的文章里,介绍过VirtualBox中新建zenglOX虚拟机的详细步骤。在zenglOX v1.4.0与v1.4.1版本对应的文章里,介绍过如何在VMware中新建zenglOX虚拟机的详细步骤。

    在VirtualBox中,请开启硬件加速功能,因为在开启硬件加速功能时,图形界面的渲染速度要比非硬件加速时要快很多,在新建虚拟机时,VirtualBox默认就已经打开了加速功能。

    v3.0.0版本是使用Qemu来作为开发调试用的模拟器的。Bochs的渲染速度以及执行性能都不如Qemu,不太适合进行图形化界面的开发,当然,v3.0.0及v3.0.1的版本也可以在Bochs上正常运行,只要你的Bochs可以运行v2.4.0的版本,就可以直接运行v3.0.0与v3.0.1的版本。下面会介绍Qemu的安装和使用方法。

    这里需要特别注意的地方是:无论是用Bochs还是用Qemu,你都必须在真实的机子上安装了Linux发行版后,再在这些发行版里安装Bochs或Qemu。

    如果你使用VirtualBox虚拟机安装的Linux发行版,然后再在发行版中安装Bochs或Qemu的话,Bochs与Qemu里的鼠标很可能用不了。因为Linux发行版在VirtualBox里运行时,会给Bochs与Qemu传递错误的鼠标位移值,这个值通常会是几万的很大的值,通过调试Qemu的源代码就会发现这个问题。

    作者一开始为了方便使用windows里的资源,就将ubuntu之类的系统装在了VirtualBox里,但是这样一来,ubuntu中安装的Qemu和Bochs的鼠标都无法正常工作。最后作者将ubuntu装在真实的机子上,让它和windows双启动,再安装Bochs与Qemu,这样一来,鼠标就可以正常工作了。

    如果读者有自己的hobby OS,并且想加入鼠标驱动的话,就需要注意这个问题。

    系统在进入桌面前,会在一个1024x768的黑色屏幕上显示一些内核信息,显示完信息后,会等待3秒才进入桌面,每过一秒都会在下面显示两个小数点,显示出6个小数点后,就会进入桌面。

    进入桌面后,在桌面的左上角有一个黑底白边的正方形。用鼠标单击该框框,可以启动term。term就是v3.0.0版本中实现的带窗口界面的终端程式。如果鼠标用不了(一般是在真实的机子上,通过U盘启动时,有可能出现这种情况。因为现在的机子上,鼠标很多都不再走PS/2的通道。在虚拟机上,比如bochs, qemu, VirtualBox以及VMware中,经过作者的测试,都可以正常使用鼠标和键盘)。鼠标用不了时,在出现桌面时,按F2键也可以启动term。

    启动term后,如果你在v3.0.0之前的版本里,已经在VirtualBox或VMware里新建过了zenglOX虚拟机的话。估计磁盘已经分区格式化过了,并且可能在磁盘里残留了之前版本的应用程式,比如:ee编辑器,zengl脚本程式等。那么你在mount iso和mount hd ...命令后,还需要通过isoget -all命令将ee编辑器,zengl脚本程式,以及libc.so动态库文件进行更新。因为libc.so库文件里将使用syscall_cmd_window_write之类的系统调用来显示数据到对应的窗口中,原来的syscall_monitor_write系统调用,只会将信息暂时显示到屏幕上,这些暂时信息是可以被鼠标或窗口给擦掉的!而且不更新磁盘数据的话,之前版本中的ee编辑器是无法正常工作的,旧版的libc.so以及ee编辑器运行时,可能会当掉你的系统!

    如果磁盘没有进行过分区格式化的操作的话,可以参考zenglOX v1.5.0版本对应的官方文章,该文章里介绍了如何使用fdisk与format工具来进行分区格式化操作。

    在桌面具有输入焦点时,按上下键可以适当的调节鼠标的scale,你可以将scale看成鼠标移动速度的一个因子。在鼠标驱动里,在scale大于1时,当鼠标位移值超过一个指定的数值时,就会将实际位移值乘以scale,来得出最终的位移值。(只要在桌面上单击一下,桌面对应的窗口就会具有输入焦点了。另外,当启动一个带窗口的应用程式,如term时,这些窗口也会自动获取到输入焦点,其他的失去输入焦点的窗口就需要通过单击来重新获取到输入焦点)。

    调节鼠标scale时,桌面程式会将scale信息通过syscall_monitor_write系统调用直接显示到屏幕上,这些信息是暂时的,你可以用鼠标或别的窗口将它擦掉。或者在桌面具有输入焦点时,按F5键来刷新整个屏幕以将这些信息给擦掉。

    桌面上具有标题栏的窗口都可以使用鼠标来拖动,就跟windows下拖动窗口的方式一样,直接拖动标题栏即可。目前窗口只有关闭按钮,没有最大化与最小化按钮

    下面来看下, 如何搭建Qemu的开发调试环境:

    首先参考之前v0.0.1版本对应的官方文章,搭建好交叉编译环境(请使用slackware或ubuntu之类的linux系统,bochs可以安装,也可以不安装,因为v3.0.0版本使用的是Qemu来开发的。如果安装bochs的话,还需要按照v2.0.0版本的要求加入e1000网卡的支持,以及按照v2.4.0版本的要求,修改bochs-2.6里的pci_ide.cc文件,启动startBochs时,还需要root权限,如果你之前已经搭建好了交叉编译环境的话,可以跳过这一步)

    接着,从sourceforge网盘的v3.0.0文件夹里下载 qemu-2.2.0.tar.bz2 文件,假设我们下载到了Downloads目录中:

[email protected]:~/Downloads$ ls
qemu-2.2.0.tar.bz2 
[email protected]:~/Downloads$ 


    接着解压 qemu-2.2.0.tar.bz2 文件:

[email protected]:~/Downloads$ tar xvf qemu-2.2.0.tar.bz2
[email protected]:~/Downloads$ ls
qemu-2.2.0  qemu-2.2.0.tar.bz2 
[email protected]:~/Downloads$ 


    解压后,应该在Downloads目录中会多出一个qemu-2.2.0的文件夹(如上所示)。接着创建qemu-build目录(编译时的中间文件都会被生成到该目录内):

[email protected]:~/Downloads$ mkdir qemu-build


    cd进入qemu-build目录:

[email protected]:~/Downloads$ cd qemu-build/
[email protected]:~/Downloads/qemu-build$ 


    在进行configure配置之前,我们需要先安装qemu所需的一些依赖项(下面以ubuntu为例进行说明):

[email protected]:~/Downloads/qemu-build$ sudo apt-get install libsdl1.2-dev


    上面的libsdl1.2-dev为sdl库,因为qemu启动时需要SDL库来显示界面,如果没有安装sdl库的话,启动qemu时就会出现:VNC server running on`127.0.0.1:5900'的信息,然后qemu就停在这条信息上,没反应了。

[email protected]:~/Downloads/qemu-build$ sudo apt-get install autoconf automake libtool


    以上是安装automake之类的工具,防止配置时出现:./autogen.sh: 4: autoreconf: not found 之类的错误。

[email protected]:~/Downloads/qemu-build$ sudo apt-get install zlib1g-dev


    安装zlib库和相关的头文件,防止配置时出现:Error: zlib check failed,  Make sure to have the zlib libs and headers installed. 的错误。

[email protected]:~/Downloads/qemu-build$ sudo apt-get install libglib2.0-dev


    安装glib库,防止配置时出现:ERROR: glib-2.12 required to compile QEMU 的错误。

    以上是作者安装qemu时出现的一些依赖问题,如果读者有别的依赖没安装的话,当出现错误时,请根据错误信息在网络中搜索解决方案。

    安装完依赖项后,就可以进行qemu的配置和编译了:

[email protected]:~/Downloads/qemu-build$ ../qemu-2.2.0/configure --target-list=i386-softmmu --enable-debug --disable-pie
[email protected]:~/Downloads/qemu-build$ make
[email protected]:~/Downloads/qemu-build$ sudo make install
[email protected]:~/Downloads/qemu-build$ qemu-system-i386 --version
QEMU emulator version 2.2.0, Copyright (c) 2003-2008 Fabrice Bellard
[email protected]:~/Downloads/qemu-build$ 


    上面configure配置时的--target-list=i386-softmmu是让make编译时,只生成x86的qemu模拟器,如果不指定该参数的话,就会将arm之类的模拟器也生成一遍。那就会编译很久了,而且我们也不需要arm之类的测试平台。

    --enable-debug参数可以让你调试qemu的源代码(它会保留调试所需的符号信息)。

    --disable-pie参数也是用于调试的,它会让生成的qemu的可执行文件的指令代码不是与位置无关的,只有这样才能正常调试qemu的源代码。没有这个参数的话,gdb调试qemu源代码时会出现问题。

    在configure配置生成Makefile文件后,就可以通过make命令来进行编译了。编译完后,可以通过 sudo make install 来安装qemu到系统中。

    qemu默认会被安装到 /usr/local/bin 的位置处。安装时需要root权限,因此上面就用到了sudo命令, 安装完后, 可以通过qemu-system-i386 --version来查看qemu的版本号信息。如果版本号是2.2.0的话, 就说明你已经将qemu正确的安装到系统中了。

    zenglOX内核源代码的编译过程, 与之前版本是一样的:

[email protected]:~/zenglOX$ unzip -d zenglOX_v3.0.0 zenglOX_v3.0.0.zip 
[email protected]:~/zenglOX$ cd zenglOX_v3.0.0
[email protected]:~/zenglOX/zenglOX_v3.0.0$ make
[email protected]:~/zenglOX/zenglOX_v3.0.0$ make iso
[email protected]:~/zenglOX/zenglOX_v3.0.0$ chmod +x startQemu 
[email protected]:~/zenglOX/zenglOX_v3.0.0$ ./startQemu 


    上面是v3.0.0版本的编译过程(v3.0.1版本也是一样的编译过程)。上面直接用makemake iso命令就可以生成zenglOX.bin,initrd.img及zenglOX.iso文件。

    此外,上面的 chmod +x startQemu 命令是让startQemu脚本文件具有可执行权限,./startQemu命令在启动qemu时,qemu会像之前版本中的bochs一样等待gdb的连接(处于暂停状态)。

    然后另起一个终端,使用gdb来连接:

[email protected]:~/zenglOX/zenglOX_v3.0.0$ gdb -q zenglOX.bin
Reading symbols from /home/zengl/zenglOX/zenglOX_v3.0.0/zenglOX.bin...done.
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
0x0000fff0 in ?? ()
(gdb) b zlox_kernel_main 
Breakpoint 1 at 0x10003c: file zlox_kernel.c, line 36.
(gdb) c
Continuing.

Breakpoint 1, zlox_kernel_main (mboot_ptr=0x10000, initial_stack=524032)
    at zlox_kernel.c:36
36	{
(gdb) c
Continuing.


    上面gdb连接时也是用的1234端口,因为startQemu脚本里设置的就是1234 。上面在一开始时,通过b命令来设置了断点,当中断下来后,直接c命令继续执行,你也可以不下断点,让qemu一路执行下去。稍后需要下断点时,可以在gdb里通过 ctrl + c 的组合键将qemu中断下来。

    如果要用bochs来运行的话,只需要 chmod +x startBochs ,然后 sudo ./startBochs 运行即可,bochs运行时的相关注意事项,请参考v3.0.0之前的版本的官方文章。

    如果要将zenglOX写入U盘,然后通过U盘启动的话,需要先将linux中的grub2安装到U盘中(只有grub2才能启动到VBE图形模式,传统的grub在没打相关的patch补丁之前,无法启动到VBE图形模式)。

    将grub2写入U盘的方法, 可以参考 http://www.kissthink.com/archive/ru-he-zai--i-n-u-x-zhi-zuo-qi-dong--pan--shi-yong-g-r-u-b-huo-g-r-u-b-2-.html 该链接对应的文章(这个链接地址好长啊!)

    例如作者的U盘被ubuntu自动挂载到了 /media/KINGSTON 目录,并且,U盘在系统中的设备节点为/dev/sdb ,则输入如下命令:

[email protected]:~/zenglOX$ sudo grub-install --force --no-floppy --root-directory=/media/KINGSTON/ /dev/sdb


    上面通过grub-install命令将grub2安装到U盘,安装好后,此时,在你的U盘根目录下应该存在一个"boot"目录。

    将zenglOX源代码根目录中的grub.cfg文件放置到U盘的boot/grub目录内,再将编译生成的initrd.img与zenglOX.bin文件放置到boot目录中。(grub.cfg、initrd.img及
zenglOX.bin文件在sourceforge网盘的v3.0.0的文件夹内也有,可以直接下载到,这些是作者编译生成的文件)

    这样,就可以用U盘来启动zenglOX了。重启机子,在BIOS中将U盘设置为启动盘,再重启就可以先看到grub2的菜单界面,接着就会进入zenglOX了。不过真机上,只能使用ramdisk里的程式。

    此外,qemu在v3.0.0与v3.0.1版本中,使用的是user(用户模式)的网络,只能ping到网关。要ping外网的话,需要用到tun/tap,由于配置比较麻烦,所以放到以后的版本再做。

    bochs, virtualbox及vmware下,应该可以ping到外网的ip。(zenglOX中和网络相关的内容请参考 zenglOX v2.0.0版本相关的官方文章)

    v3.0.0与v3.0.1版本中,窗口管理器的核心代码位于源码根目录下的zlox_my_windows.c文件里。桌面程式的源代码位于 build_initrd_img/desktop.c 文件中。term程式的
源代码位于build_initrd_img/term.c文件中。鼠标相关的驱动程式则位于源码根目录下的zlox_mouse.c文件中。

    以上就是readme.md文件的主要内容,在网盘的zenglOX_v3.0.1的文件夹里,还有一个readme.txt文件,该文件里的内容是v3.0.1版本的相关说明信息,主要内容如下:

v3.0.1 版本对应的readme.txt说明文档:

    该readme.txt文档只存在于网盘中,在zenglOX的源代码里并没有,该文档只是用于说明v3.0.1版本所做的一些改动。

    v3.0.1版本主要针对上一个v3.0.0版本中的BUG,对zlox_task.c文件中的zlox_switch_task函数里的任务调度算法进行了调整:新创建的任务具有最高优先级,会被优先执行,其次就是有待处理的消息的任务,最后才是没有任何需要处理的消息的任务。

    新增了kill工具,可以用于终止一些与kill所在的任务没有任何"血缘关系"的任务。比如kill的父任务,或kill的父任务的父任务等,这些与kill具有"血缘关系"的任务是kill所不能终止的,而其他的非"血缘关系"的任务,都可以被kill所终止。kill在终止一个任务时,它还会将该任务的子任务也终止掉。

    v3.0.1版本中,当出现一些严重的系统错误时,比如:分页错误、double fault错误、内核堆溢出等错误时,会以红底白字(即红色背景,白色前景)来显示错误信息,并且这些错误信息里的每个字符之间不再有空隙,这样可以让错误信息不会受到窗口背景的干扰。

    ps命令新增了一个-x参数,通过ps -x命令就可以显示出每个任务的消息数。如果发现某个任务的消息一直没被处理的话,就可以使用kill工具将其终止掉。

    目前的shutdown关机程式只能对bochs及virtualBox起作用,在其他模拟器上,或真机上,则只会显示出一条 "zenglOX is shutdown now , you can power off!" 的红底白字信息,然后你就需要手动来关闭电源。

源码解析:

    下面将对v3.0.1的最新版本中的源代码来进行分析。

    要让grub2启动到VBE图形模式,最关键的地方,就在于zlox_boot.s文件中对Multiboot header(多启动头)的设置:

.................................
.section .zlox_multiboot
.align 4
.global _zlox_boot_mb_header
_zlox_boot_mb_header:
  .long ZLOX_MBOOT_MAGIC
  .long ZLOX_MBOOT_FLAGS
  .long ZLOX_MBOOT_CHECKSUM

  .long _zlox_boot_mb_header
  .long _code
  .long _bss
  .long _end
  .long _zlox_boot_start
  .long 0
  .long 1024
  .long 768
  .long 32

.................................


    上面代码中,.long 0 会指示grub2启动到linear graphics mode(也就是VBE图形模式),如果将0改成1的话则会是默认的text mode(文本模式),.long 1024 则用于指明水平分辨率为1024像素,.long 768用于指明垂直分辨率为768像素。.long 32用于指明图形界面中的每个像素都会占用32位(即4个字节)的显存。

    读者可以在 http://www.gnu.org/software/grub/manual/multiboot/multiboot.html 该链接对应的"The layout of Multiboot header"部分,看到Multiboot header的标准结构如下:

3.1.1 The layout of Multiboot header

The layout of the Multiboot header must be as follows:

Offset 	Type 	Field Name 	Note
0 	u32 	magic 	        required
4 	u32 	flags 	        required
8 	u32 	checksum 	required
12 	u32 	header_addr 	if flags[16] is set
16 	u32 	load_addr 	if flags[16] is set
20 	u32 	load_end_addr 	if flags[16] is set
24 	u32 	bss_end_addr 	if flags[16] is set
28 	u32 	entry_addr 	if flags[16] is set
32 	u32 	mode_type 	if flags[2] is set
36 	u32 	width 	        if flags[2] is set
40 	u32 	height 	        if flags[2] is set
44 	u32 	depth 	        if flags[2] is set

The fields 'magic', 'flags' and 'checksum' are defined in Header magic fields, 
the fields 'header_addr', 'load_addr', 'load_end_addr', 'bss_end_addr' and 'entry_addr'
 are defined in Header address fields, and the fields 
'mode_type', 'width', 'height' and 'depth' 
are defined in Header graphics fields. 


    上面的mode_type用于指示grub2之类的启动引导器是启动到VBE图形模式,还是启动到文本模式,width用于指明水平分辨率,height指明垂直分辨率,depth指明位深,具体含义可以参考 Header graphics fields 部分,该部分的内容如下:

...............................................
The meaning of each is as follows:

mode_type
    Contains '0' for linear graphics mode or '1' for EGA-standard text mode. 
    Everything else is reserved for future expansion. Note that the boot loader may set a text mode, 
    even if this field contains '0'.
width
    Contains the number of the columns. This is specified in pixels in a graphics mode, 
    and in characters in a text mode. The value zero indicates that the OS image has no preference.
height
    Contains the number of the lines. This is specified in pixels in a graphics mode, 
    and in characters in a text mode. The value zero indicates that the OS image has no preference.
depth
    Contains the number of bits per pixel in a graphics mode, and zero in a text mode. 
    The value zero indicates that the OS image has no preference. 


    在grub2启动到VBE图形模式后,我们还需要知道显存实际的物理内存地址,只有知道显存的物理内存地址,我们才能向显存中写入数据,有就是在图形界面上绘制东西。

    要知道显存的起始物理内存地址的话,需要先在zlox_boot.s的多启动头部分,进行如下设置:

#zlox_boot.s -- zenglOX kernel start assemble
.equ ZLOX_MBOOT_PAGE_ALIGN,1 #Load Kernel on a page boundary
.equ ZLOX_MBOOT_GETMEM_INFO,1<<1 #Tell MBoot provide your kernel with memory info
.equ ZLOX_MBOOT_GETVBE_INFO,1<<2
.equ ZLOX_MBOOT_GETOTHER_INFO,1<<16
.equ ZLOX_MBOOT_MAGIC,0x1BADB002 #Multiboot Magic value

.equ ZLOX_MBOOT_FLAGS,ZLOX_MBOOT_PAGE_ALIGN | ZLOX_MBOOT_GETMEM_INFO | ZLOX_MBOOT_GETVBE_INFO | ZLOX_MBOOT_GETOTHER_INFO
.equ ZLOX_MBOOT_CHECKSUM,-(ZLOX_MBOOT_MAGIC + ZLOX_MBOOT_FLAGS)

.section .zlox_multiboot
.align 4
.global _zlox_boot_mb_header
_zlox_boot_mb_header:
  .long ZLOX_MBOOT_MAGIC
  .long ZLOX_MBOOT_FLAGS
............................................


    上面在多启动头的ZLOX_MBOOT_FLAGS字段加入了ZLOX_MBOOT_GETVBE_INFO,也就是将上面介绍的Multiboot header(多启动头标准)里的flags字段的位2设置为1,我们可以在 Header magic fields 链接中查看到flags字段的二进制位定义:

'magic'
    The field 'magic' is the magic number identifying the header, which must be the hexadecimal value 0x1BADB002.
'flags'
    The field 'flags' specifies features that the OS image requests or requires of an boot loader. Bits 0-15 indicate requirements; if the boot loader sees any of these bits set but doesn't understand the flag or can't fulfill the requirements it indicates for some reason, it must notify the user and fail to load the OS image. Bits 16-31 indicate optional features; if any bits in this range are set but the boot loader doesn't understand them, it may simply ignore them and proceed as usual. Naturally, all as-yet-undefined bits in the 'flags' word must be set to zero in OS images. This way, the 'flags' fields serves for version control as well as simple feature selection.

    If bit 0 in the 'flags' word is set, then all boot modules loaded along with the operating system must be aligned on page (4KB) boundaries. Some operating systems expect to be able to map the pages containing boot modules directly into a paged address space during startup, and thus need the boot modules to be page-aligned.

    If bit 1 in the 'flags' word is set, then information on available memory via at least the 'mem_*' fields of the Multiboot information structure (see Boot information format) must be included. If the boot loader is capable of passing a memory map (the 'mmap_*' fields) and one exists, then it may be included as well.

    If bit 2 in the 'flags' word is set, information about the video mode table (see Boot information format) must be available to the kernel.

    If bit 16 in the 'flags' word is set, then the fields at offsets 12-28 in the Multiboot header are valid, and the boot loader should use them instead of the fields in the actual executable header to calculate where to load the OS image. This information does not need to be provided if the kernel image is in elf format, but it must be provided if the images is in a.out format or in some other format. Compliant boot loaders must be able to load images that either are in elf format or contain the load address information embedded in the Multiboot header; they may also directly support other executable formats, such as particular a.out variants, but are not required to.
'checksum'
    The field 'checksum' is a 32-bit unsigned value which, when added to the other magic fields (i.e. 'magic' and 'flags'), must have a 32-bit unsigned sum of zero.


    既然我们已经在zlox_boot.s中要求了grub2为我们提供相关的video视频信息(包括显存的起始物理地址在内),那么这些视频信息存放在哪里呢,这就需要查看zlox_multiboot.h的头文件了,该头文件的内容如下:

/* zlox_multiboot.h -- Declares the multiboot info structure.*/

#ifndef _ZLOX_MULTIBOOT_H_
#define _ZLOX_MULTIBOOT_H_

struct _ZLOX_MULTIBOOT
{
	ZLOX_UINT32 flags;
	ZLOX_UINT32 mem_lower;
	ZLOX_UINT32 mem_upper;
	ZLOX_UINT32 boot_device;
	ZLOX_UINT32 cmdline;
	ZLOX_UINT32 mods_count;
	ZLOX_UINT32 mods_addr;
	ZLOX_UINT32 num;
	ZLOX_UINT32 size;
	ZLOX_UINT32 addr;
	ZLOX_UINT32 shndx;
	ZLOX_UINT32 mmap_length;
	ZLOX_UINT32 mmap_addr;
	ZLOX_UINT32 drives_length;
	ZLOX_UINT32 drives_addr;
	ZLOX_UINT32 config_table;
	ZLOX_UINT32 boot_loader_name;
	ZLOX_UINT32 apm_table;
	ZLOX_UINT32 vbe_control_info;
	ZLOX_UINT32 vbe_mode_info;
	ZLOX_UINT32 vbe_mode;
	ZLOX_UINT32 vbe_interface_seg;
	ZLOX_UINT32 vbe_interface_off;
	ZLOX_UINT32 vbe_interface_len;
}  __attribute__((packed));

struct _ZLOX_MULTIBOOT_VBE_INFO {
	ZLOX_UINT16 attributes;
	ZLOX_UINT8  winA, winB;
	ZLOX_UINT16 granularity;
	ZLOX_UINT16 winsize;
	ZLOX_UINT16 segmentA, segmentB;
	ZLOX_UINT32 realFctPtr;
	ZLOX_UINT16 pitch;

	ZLOX_UINT16 Xres, Yres;
	ZLOX_UINT8  Wchar, Ychar, planes, bpp, banks;
	ZLOX_UINT8  memory_model, bank_size, image_pages;
	ZLOX_UINT8  reserved0;

	ZLOX_UINT8  red_mask, red_position;
	ZLOX_UINT8  green_mask, green_position;
	ZLOX_UINT8  blue_mask, blue_position;
	ZLOX_UINT8  rsv_mask, rsv_position;
	ZLOX_UINT8  directcolor_attributes;

	ZLOX_UINT32 physbase;
	ZLOX_UINT32 reserved1;
	ZLOX_UINT16 reserved2;
} __attribute__ ((packed));

typedef struct _ZLOX_MULTIBOOT ZLOX_MULTIBOOT;

typedef struct _ZLOX_MULTIBOOT_VBE_INFO ZLOX_MULTIBOOT_VBE_INFO;

#endif //_ZLOX_MULTIBOOT_H_


    grub2会给我们的内核提供一个ZLOX_MULTIBOOT结构体的指针,在该指针指向的结构体中,有一个vbe_mode_info字段,该字段的值是上面头文件里的ZLOX_MULTIBOOT_VBE_INFO结构体的指针值,在该结构体里的physbase字段就是我们所需要的显存的起始的物理内存地址了。

    可以在zlox_kernel.c文件的zlox_kernel_main函数中看到physbase字段的使用:

/*zlox_kernel.c Defines the C-code kernel entry point, calls initialisation routines.*/

..............................................

//zenglOX kernel main entry
ZLOX_SINT32 zlox_kernel_main(ZLOX_MULTIBOOT * mboot_ptr, ZLOX_UINT32 initial_stack)
{
	..............................................
	lfb_vid_memory = (ZLOX_UINT8 *)((ZLOX_MULTIBOOT_VBE_INFO *)(mboot_ptr->vbe_mode_info))->physbase;
	..............................................
}


    上面提到的ZLOX_MULTIBOOT结构体是参考的 Boot-information-format 该链接里的Multiboot information structure结构来定义的。而ZLOX_MULTIBOOT_VBE_INFO结构体则是参考的 http://www.virtualbox.org/svn/vbox/trunk/src/VBox/Devices/Graphics/BIOS/vbe.h 该链接里的ModeInfoBlock结构体来定义的。

    在某些论坛上,作者看到,grub2里的video模块是可选的(grub2有可能不会自动加载video模块),所以有必要在grub.cfg文件中手动加入video模块:

insmod vbe
insmod vga
insmod video
set timeout=5

menuentry "zenglOX" {
	multiboot /boot/zenglOX.bin
	module	/boot/initrd.img
}


    需要特别注意的地方是:虽然grub2里面还有video_bochs和video_cirrus之类的模块,但是这些模块不能加入到zenglOX的grub.cfg文件中,如果手动加入的话,图形界面启动后,会什么都看不到了。

    此外,ZLOX_MULTIBOOT结构体中还有两个字段比较重要:mem_lowermem_upper字段,这两个字段的值相加可以得到机子的物理内存的尺寸信息,虽然这个信息比实际的物理内存要稍微小一点,但是对于实际的应用来说,已经足够了,可以在zlox_kernel_main函数中看到这两个字段的应用:

//zenglOX kernel main entry
ZLOX_SINT32 zlox_kernel_main(ZLOX_MULTIBOOT * mboot_ptr, ZLOX_UINT32 initial_stack)
{
	......................................
	// 开启分页,并创建堆
	zlox_init_paging_start((ZLOX_UINT32)(mboot_ptr->mem_lower + mboot_ptr->mem_upper));
	......................................
}


    在zlox_init_paging_start函数中(该函数位于zlox_paging.c文件里),会根据物理内存的尺寸来创建对应的frames(物理位图):

/* zlox_paging.c Defines the interface for and structures relating to paging.*/

.........................................................

ZLOX_VOID zlox_init_paging_start(ZLOX_UINT32 total_phymem)
{
	ZLOX_UINT32 i;
	// The size of physical memory. For the moment we 
	// assume it is 32MB big.
	//ZLOX_UINT32 mem_end_page = 0x1000000;
	ZLOX_UINT32 mem_end_page = 0x8000000;

	if(total_phymem > 0 && total_phymem < (0x8000000 / 1024))
		mem_end_page = total_phymem * 1024;

	nframes = mem_end_page / 0x1000;
	frames = (ZLOX_UINT32 *)zlox_kmalloc(nframes/8);
	zlox_memset((ZLOX_UINT8 *)frames, 0, nframes/8);
        .........................................................
}


    当物理内存超过128M时,最多也只会使用128M的内存。

    zenglOX和鼠标相关的驱动程式位于zlox_mouse.c文件中,要理解鼠标驱动的原理,可以参考 http://wiki.osdev.org/PS/2_Mouse 该链接对应的文章。zlox_mouse.c文件中的代码是参照 http://forum.osdev.org/viewtopic.php?t=10247 该论坛里的开源的示例代码来写的(对示例代码做了一些改动,让鼠标数据获取的更准确些)。

    在图形界面中显示的字符,都是依据zlox_term_font.h头文件里的点阵位图来显示的,该头文件是从 toaruos内核中移植过来的,包括zlox_mouse.c里的改动过的代码其实也是参考的toaruos内核里的鼠标驱动,toaruos也是一个hobby OS(是一位日本程式员从头开发的一个OS),他的界面中大量用到了PNG图片,而zenglOX目前只用到了bmp位图,因此,他的窗口界面更加华丽一些。也有不少业余爱好者在参与其中的开发工作,不过目前,toaruos只能在qemu中运行,要运行到VirtualBox或其他模拟器上,需要做很多额外工作,因为他的内核是直接写入到磁盘中的,是从磁盘来启动内核的,zenglOX则为了方便起见,一直是从光盘中启动内核的(或者U盘中)。

    toaruos的github地址为:https://github.com/klange/toaruos (该内核的很多驱动与zenglOX一样,是从osdev或者网络的其他地方移植过来的,有兴趣的读者可以对其进行研究,不过需要注意的地方是:一定要在真机上安装Linux发行版,然后在这些发行版里测试toaruos,这个问题上面提到过,因为在VirtualBox虚拟机上安装的linux发行版会给qemu传递错误的鼠标位移值)。

    zenglOX里和窗口管理器相关的源代码,位于zlox_my_windows.c文件中,该文件里的代码是v3.0.0版本中的开发重点,作者的大部分精力都用在这个文件上了(花了数不清的debug时间,因为每个像素都不能出错),窗口管理器中最重要的结构就是 ZLOX_MY_WINDOW 结构,该结构定义在zlox_my_windows.h的头文件里:

// zlox_my_windows.h Defines some structures relating to the windows.

#ifndef _ZLOX_MY_WINDOWS_H_
#define _ZLOX_MY_WINDOWS_H_

#include "zlox_common.h"

typedef struct _ZLOX_MY_WINDOW ZLOX_MY_WINDOW;

#include "zlox_task.h"

typedef ZLOX_SINT32 (*ZLOX_MY_WINDOW_CALLBACK) (ZLOX_VOID * msg, ZLOX_VOID * my_window);

typedef struct _ZLOX_MY_RECT
{
	ZLOX_SINT32 x;
	ZLOX_SINT32 y;
	ZLOX_SINT32 width;
	ZLOX_SINT32 height;
} ZLOX_MY_RECT;

struct _ZLOX_MY_WINDOW
{
	ZLOX_SINT32 x;
	ZLOX_SINT32 y;
	ZLOX_SINT32 width;
	ZLOX_SINT32 height;
	ZLOX_UINT32 * bitmap;
	ZLOX_MY_WINDOW_CALLBACK mywin_callback;
	ZLOX_VOID * task;
	ZLOX_BOOL need_update;
	ZLOX_MY_RECT update_rect;
	ZLOX_MY_WINDOW * next;
	ZLOX_MY_WINDOW * prev;
	ZLOX_VOID * kbd_task;
	ZLOX_BOOL has_title;
	ZLOX_BOOL has_cmd;
	ZLOX_MY_RECT title_rect;
	ZLOX_MY_RECT close_btn_rect;
	ZLOX_MY_RECT cmd_rect;
	ZLOX_SINT32 cmd_cursor_x;
	ZLOX_SINT32 cmd_cursor_y;
	ZLOX_UINT32 cmd_font_color;
	ZLOX_UINT32 cmd_back_color;
	ZLOX_BOOL cmd_single_line_out;
};

.............................................

#endif // _ZLOX_MY_WINDOWS_H_


    每个窗口在内核中都有一个相应的ZLOX_MY_WINDOW结构体,窗口和窗口之间,会通过该结构体里的next与prev字段构成一个双向链表结构。

    在这个双向链表结构中,越靠近后面的窗口,越显示在上方,因此,链表结构的最后一个窗口就是置顶的窗口,链表中的第一个窗口就是最底部的桌面所对应的窗口。

    当我们单击某个非桌面的窗口时,我们只需将该窗口对应的ZLOX_MY_WINDOW结构体移动到链表的最后位置即可。

    当某个应用需要创建自己的窗口时,只需要调用syscall_create_my_window系统调用即可,该系统调用最后会执行zlox_my_windows.c文件里的zlox_create_my_window函数:

ZLOX_SINT32 zlox_create_my_window(ZLOX_MY_WINDOW * my_window)
{
	ZLOX_MY_WINDOW * mywin = (ZLOX_MY_WINDOW *)zlox_kmalloc(sizeof(ZLOX_MY_WINDOW));
	zlox_memset((ZLOX_UINT8 *)mywin, 0, sizeof(ZLOX_MY_WINDOW));
	ZLOX_SINT32 pix_byte = lfb_resolution_b / 8;
	mywin->x = my_window->x;
	mywin->y = my_window->y;
	mywin->width = my_window->width;
	mywin->height = my_window->height;
	mywin->bitmap = (ZLOX_UINT32 *)zlox_kmalloc(my_window->width * my_window->height * pix_byte);
	mywin->mywin_callback = my_window->mywin_callback;
	mywin->task = my_window->task;
	mywin->kbd_task = my_window->task;
	((ZLOX_TASK *)mywin->task)->mywin = mywin;
	if(mywin_list_header == ZLOX_NULL)
	{
		mywin_list_header = mywin;
		mywin_list_end = mywin;
	}
	else
	{
		ZLOX_MY_WINDOW * tmpwin = mywin_list_header;
		for(;;)
		{
			if(tmpwin->next == ZLOX_NULL)
			{
				tmpwin->next = mywin;
				mywin->prev = tmpwin;
				break;
			}
			else
			{
				tmpwin = tmpwin->next;
			}
		}
		mywin_list_end = mywin;
	}
	ZLOX_TASK_MSG msg = {0};
	msg.type = ZLOX_MT_CREATE_MY_WINDOW;
	zlox_send_tskmsg((ZLOX_TASK *)mywin->task,&msg);
	return (ZLOX_SINT32)mywin;
}

ZLOX_SINT32 zlox_dispatch_win_msg(ZLOX_TASK_MSG * msg, ZLOX_MY_WINDOW * my_window)
{
	switch(msg->type)
	{
	case ZLOX_MT_CREATE_MY_WINDOW:
		...........................................
			my_window->mywin_callback(msg, my_window);
		...........................................
		break;
	case ZLOX_MT_CLOSE_MY_WINDOW:
	case ZLOX_MT_MOUSE:
	case ZLOX_MT_KEYBOARD:
		my_window->mywin_callback(msg, my_window);
		...........................................
		break;
	default:
		break;
	}
	return -1;
}


    上面代码里,会通过zlox_kmalloc函数为ZLOX_MY_WINDOW结构体创建堆空间,并且为该结构体中的bitmap字段创建对应的画布,用户程式里,会先将需要绘制的图像绘制到该bitmap(画布)上,再由内核更新到实际的显存中,在该结构体里,还有一个mywin_callback的用户窗口回调函数字段,之所以要引入回调函数,是因为,窗口的画布是创建在内核堆中的,用户程式要访问这些内核堆数据的话,需要先通过syscall_dispatch_win_msg系统调用进入内核的zlox_dispatch_win_msg函数,在该函数里,再通过mywin_callback的用户窗口回调函数来读写内核数据,这样,在内核绕了一圈以后,用户的回调函数就具有足够的权限来访问内核中的数据了。

    在zlox_my_windows.c文件的开头,定义了一个mouse_bitmap_byte的字节数组,该数组里的数据是鼠标的位图数据,是用winhex工具从screenshot目录的mouse.bmp文件里提取出来的,有关位图的格式,可以参考之前的 zenglOX v1.6.0 版本对应的官方文章。

    在移动鼠标时,鼠标驱动会将鼠标的位移信息,传递给zlox_my_windows.c文件里的zlox_update_for_mymouse函数,然后由该函数进行鼠标的绘制工作:

ZLOX_SINT32 zlox_update_for_mymouse(ZLOX_TASK_MSG * msg)
{
	ZLOX_MY_RECT mouse_tmp_rect;
	mouse_tmp_rect.x = my_mouse_x;
	mouse_tmp_rect.y = my_mouse_y;
	mouse_tmp_rect.width = my_mouse_w;
	mouse_tmp_rect.height = my_mouse_h;
	ZLOX_BOOL is_release_mouse = (!(msg->mouse.state & 0x01) && (my_mouse_left_press == ZLOX_TRUE));
	//if(!is_release_mouse)
	//{
        // 先根据鼠标的原坐标,将原位置处的鼠标给擦掉
		zlox_draw_mywin_list(mywin_list_header, mywin_list_end, &mouse_tmp_rect);
	//}

	my_mouse_x += msg->mouse.rel_x;
	my_mouse_y -= msg->mouse.rel_y;
	if(my_mouse_x < 0)
		my_mouse_x = 0;
	else if(my_mouse_x >= lfb_resolution_x - 3)
		my_mouse_x = lfb_resolution_x - 3;
	if(my_mouse_y < 0)
		my_mouse_y = 0;
	else if(my_mouse_y >= lfb_resolution_y - 3)
		my_mouse_y = lfb_resolution_y - 3;

        // 鼠标刚开始左键单击时,会调用zlox_mymouse_first_click函数,
        // 当按左键拖动鼠标时,会调用zlox_mymouse_drag函数,
        // 当释放鼠标时,会调用zlox_mymouse_release函数。
	if(msg->mouse.state & 0x01)
	{
		if(my_mouse_left_press == ZLOX_FALSE) // first click
		{
			zlox_mymouse_first_click(msg);
		}
		else // drag mouse
		{
			zlox_mymouse_drag(msg);
		}
	}
	else if(is_release_mouse)
	{
		zlox_mymouse_release(msg);
	}
	//if(!is_release_mouse)
	//{
        // 在新的坐标位置处绘制鼠标。
		mouse_tmp_rect.x = my_mouse_x;
		mouse_tmp_rect.y = my_mouse_y;
		mouse_tmp_rect.width = my_mouse_w;
		mouse_tmp_rect.height = my_mouse_h;
		zlox_draw_my_mouse(&mouse_tmp_rect);
	//}
	return 0;
}


    上面函数里,棕色的注释是此处为了起说明作用,额外添加的,在源代码中并没有。

    命令行的显示是在内核中完成的,如果交由用户程式来做的话,会产生大量的消息,因为子任务要向父任务的窗口里显示信息的话,它只能通过传递消息的方式,那样的话,在命令行中每显示一个字符或字符串就会产生一个消息,这样会降低图形显示的效率。

    在前面提到的ZLOX_MY_WINDOW的窗口结构体中,和命令行相关的字段如下:

struct _ZLOX_MY_WINDOW
{
        .....................................
	ZLOX_BOOL has_cmd;
	.....................................
	ZLOX_MY_RECT cmd_rect;
	ZLOX_SINT32 cmd_cursor_x;
	ZLOX_SINT32 cmd_cursor_y;
	ZLOX_UINT32 cmd_font_color;
	ZLOX_UINT32 cmd_back_color;
	ZLOX_BOOL cmd_single_line_out;
};


    其中,has_cmd字段用于指明该窗口是否具有命令行的显示区域,cmd_rect用于指明命令行在窗口中的矩形区域,cmd_cursor_x用于指明命令行光标的x坐标,cmd_cursor_y用于指明命令行光标的y坐标,cmd_font_color用于指明命令行字体的前景色,cmd_back_color用于指明命令行字体的背景色,cmd_single_line_out用于指明是否单行显示(即不换行显示,主要用于ee编辑器)。

    在zlox_my_windows.c文件里,和命令行显示有关的函数为:zlox_cmd_window_write函数,zlox_cmd_window_write_hex函数,zlox_cmd_window_write_dec函数等。例如:zlox_cmd_window_write函数就会将用户传递过来的字符串信息给显示到对应的命令行窗口的指定区域中。 要确定某个任务的命令行窗口,可以通过zlox_my_windows.c文件中的zlox_set_cmd_window函数来确定,这几个函数都有对应的系统调用。

    和窗口相关的消息,定义在zlox_task.h的头文件里:

typedef enum _ZLOX_MSG_TYPE
{
	ZLOX_MT_KEYBOARD,
	ZLOX_MT_TASK_FINISH,
	ZLOX_MT_NET_PACKET,
	ZLOX_MT_MOUSE,
	ZLOX_MT_CREATE_MY_WINDOW,
	ZLOX_MT_CLOSE_MY_WINDOW,
}ZLOX_MSG_TYPE;


    其中,ZLOX_MT_MOUSE为与鼠标相关的消息(比如鼠标单击,drag拖动等消息),ZLOX_MT_CREATE_MY_WINDOW为创建窗口的消息,ZLOX_MT_CLOSE_MY_WINDOW为点击窗口关闭按钮时所产生的消息。这些消息对于用户程式而言,则定义在build_initrd_img目录下的task.h头文件里:

typedef enum _MSG_TYPE
{
	MT_KEYBOARD,
	MT_TASK_FINISH,
	MT_NET_PACKET,
	MT_MOUSE,
	MT_CREATE_MY_WINDOW,
	MT_CLOSE_MY_WINDOW,
}MSG_TYPE;


    可以看到,用户程式中所使用的消息名,只是少了 ZLOX_ 的前缀而已。

    上面讲解的代码,主要在v3.0.0版本中就已经编写好了,v3.0.1的版本则主要是对zlox_task.c文件里的zlox_switch_task函数的任务调度算法进行了调整:

ZLOX_VOID zlox_switch_task()
{
	.....................................................
	// 统计所有任务的需要处理的消息总数, 同时检测是否有新建的任务
	do
	{
		msg_total_count += tmp_task->msglist.count;
		if(tmp_task != ready_queue && tmp_task->args == 0 &&
			tmp_task->status == ZLOX_TS_RUNNING)
		{
			new_task = tmp_task;
			break;
		}
		tmp_task = tmp_task->next;
	}while(tmp_task != ZLOX_NULL);

	tmp_task = ZLOX_NULL;

	//如果有新建的任务, 则优先运行新任务
	if(new_task != ZLOX_NULL)
	{
		current_task = new_task;
	}
	else
	{
		do
		{
			// Get the next task to run.
			current_task = current_task->next;
			// If we fell off the end of the linked list start again at the beginning.
			if (!current_task) 
				current_task = ready_queue;

			// 当任务处于运行状态下, 具有需要处理的消息的任务会被优先执行
			if(current_task->status == ZLOX_TS_RUNNING)
			{
				if(current_task->msglist.count > 0)
					break;
				else if(msg_total_count > 0)
				{
					if(current_task == orig_task)
					{
						current_task = tmp_task;
						break;
					}
					else
					{
						if(tmp_task == ZLOX_NULL)
							tmp_task = current_task;
						continue;
					}
				}
				else
					break;
			}
			// 如果当前任务有需要进行结束的子任务的话,就唤醒该任务
			else if(current_task->status == ZLOX_TS_WAIT && current_task->msglist.finish_task_num > 0)
			{
				current_task->status = ZLOX_TS_RUNNING;
				break;
			}
			// 如果任务列表里所有的任务都不是运行状态,则将第一个任务唤醒,并切换到第一个任务
			else if(current_task == orig_task)
			{
				current_task = ready_queue;
				current_task->status = ZLOX_TS_RUNNING;
				break;
			}
		}while(ZLOX_TRUE);
	}
	.....................................................
}


    从上面的代码中,可以看到:新创建的任务具有最高优先级,会被优先执行。其次就是有待处理的消息的任务具有中等优先级,最后才是没有任何需要处理的消息的任务具有最低优先级。

    限于篇幅,作者不能对源代码一一解释,读者可以通过gdb调试器来进行分析。

文章中的相关链接:

    http://www.kissthink.com/archive/ru-he-zai--i-n-u-x-zhi-zuo-qi-dong--pan--shi-yong-g-r-u-b-huo-g-r-u-b-2-.html 该链接中介绍了将grub2写入U盘的方法。

    http://www.gnu.org/software/grub/manual/multiboot/multiboot.html 该链接中介绍了Multiboot标准。

    http://www.virtualbox.org/svn/vbox/trunk/src/VBox/Devices/Graphics/BIOS/vbe.h 该链接里定义了Multiboot中vbe_mode_info对应的结构体。

    http://wiki.osdev.org/PS/2_Mouse 该链接中介绍了鼠标驱动相关的理论知识。

    http://forum.osdev.org/viewtopic.php?t=10247 该链接中介绍了一个鼠标驱动的示例代码。

    https://github.com/klange/toaruos toaruos项目的github地址。

    如果有链接地址无法直接访问的话,请使用代理访问。

    OK,就到这里,休息,休息一下 o(∩_∩)o~~

    时间,你不开拓它,它就悄悄长出青苔,爬上你生命的庭院,把你一生掩埋。
—— unknown
 
上下篇

下一篇: zenglOX v3.0.2 PNG(Portable Network Graphics) and BUG Fix

上一篇: zenglOX v2.4.0 DMA(Direct Memory Access)

相关文章

zenglOX v2.3.0 移植zengl嵌入式脚本语言

zenglOX v0.0.8 Multitask(多任务)

zenglOX v2.0.0 E1000系列网卡驱动, PCI驱动, PS/2控制器驱动, 以太网,ARP,IP,UDP,DHCP,ICMP协议, dhcp,arp,ipconf,ping,lspci命令行程式

zenglOX v1.4.0与v1.4.1 通过ATA驱动读写硬盘里的数据, BUG修复, VirtualBox与VMware的调试功能

zenglOX v1.0.0 shell(命令行程式及各种小工具)

zenglOX v3.1.0 Sound Blaster 16