要对硬盘进行读写数据的操作(这里主要是针对接并行数据线的硬盘),就是要根据ATA的标准来对磁盘控制器里的各种寄存器进行读写操作,例如,要进行读取硬盘数据的操作的话,就需要向命令寄存器发送READ SECTOR(S)或READ SECTOR(S) EXT命令...

    v1.4.0与v1.4.1版本的项目地址:

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

    Dropbox地址:点此进入Dropbox网盘  这两个版本位于zenglOX_v1.4.0与zenglOX_v1.4.1的文件夹中,文件夹里的zip压缩包为源代码,readme.txt为版本的简单说明。

    Google Drive地址:点此进入Google Drive云端硬盘 对应也是zenglOX_v1.4.0与zenglOX_v1.4.1的文件夹。

    sourceforge地址:https://sourceforge.net/projects/zenglox/files  对应也是zenglOX_v1.4.0与zenglOX_v1.4.1的文件夹。

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

    在之前的"zenglOX v1.1.0 通过ATAPI读取光盘里的数据"的文章里,我们对磁盘控制器,以及ATA与ATAPI的标准都做了详细的介绍,要对硬盘进行读写数据的操作(这里主要是针对接并行数据线的硬盘),就是要根据ATA的标准来对磁盘控制器里的各种寄存器进行读写操作,例如,要进行读取硬盘数据的操作的话,就需要向命令寄存器发送READ SECTOR(S)或READ SECTOR(S) EXT命令,如果要对硬盘进行写入数据的操作的话,就需要向命令寄存器发送WRITE SECTOR(S)或WRITE SECTOR(S) EXT命令。其中的READ SECTOR(S) EXT与WRITE SECTOR(S) EXT命令是ATA-ATAPI-6的标准里引入的命令,有关磁盘控制器,及命令寄存器的I/O端口号等内容,请参考v1.1.0版本对应的文章,在v1.1.0版本对应的网盘里,还包含了ATA-ATAPI-5与ATA-ATAPI-6标准的PDF说明文档。

    我们先来看下READ SECTOR(S)即从硬盘里读取一个或多个扇区的命令,该命令的详细说明位于ATA-ATAPI-5.pdf的第161页,以及ATA-ATAPI-6.pdf的第213页 (这里的页数是PDF电子档顶部,分页输入框中的页数),如下图所示:


图1

    在对该图进行解释之前,需要先了解下从ATA驱动器读取扇区时的三种扇区寻址方式:
  • CHS (Cylinder-Head-Sector) :通过磁柱号,磁头号,扇区号来寻址某个扇区(比较古老的一种寻址方式)
  • LBA28 :采用28位的逻辑块的寻址方式,最大可寻址128G的磁盘数据,超过128G的扇区,则需要使用第三种LBA48的寻址方式
  • LBA48 :采用48位的逻辑块的寻址方式,由于我们目前只使用其中的32位(一个整数类型的大小),所以目前可寻址2TB的数据。
    这几种寻址方式在后面介绍代码时再进行详细的解释。

    从上面图1可以看到,在向Command命令寄存器发送0x20命令代码之前,需要先在Sector Count之类的寄存器里准备好输入参数,所需的输入参数如下:
  • Sector Count :需要传输的扇区数,如果是0值则表示传输256个扇区(当一次传输的扇区数超过256时就会产生性能问题)。
  • Sector Number :CHS (Cylinder-Head-Sector) 寻址方式里的Sector起始扇区号,或者,如果是LBA28寻址方式的话,就表示28位逻辑块寻址里的0到7位。
  • Cylinder Low :CHS (Cylinder-Head-Sector) 寻址方式里的Cylinder磁柱号的0到7位,或者,如果是LBA28寻址方式的话,就表示28位逻辑块寻址里的8到15位。
  • Cylinder High :CHS (Cylinder-Head-Sector) 寻址方式里的Cylinder磁柱号的8到15位,或者,如果是LBA28寻址方式的话,就表示28位逻辑块寻址里的16到23位。
  • Device/Head :该寄存器中的位6用于表示是采用LBA28的寻址方式,还是采用CHS的寻址方式,当该位被清零时,则表示采用CHS寻址方式,该位被设置为1时,则表示采用LBA28的寻址方式,位4的DEV位用于选择主从设备,主从设备的概念在v1.1.0的文章里已经解释过了,低4位用于表示CHS寻址方式中的Head磁头号,或者LBA寻址方式的24到27位。
    从上面的输入参数可以看出,READ SECTOR(S)命令只能使用CHS与LBA28的寻址方式,要使用LBA48的寻址方式,则需要用到READ SECTOR(S) EXT命令,该命令是从ATA-ATAPI-6的标准里才引入的,在ATA-ATAPI-5的标准里并没有,READ SECTOR(S) EXT命令位于ATA-ATAPI-6.pdf的第215页,如下图所示:


图2

    上图里比较难理解的地方是Current与Previous。在LBA48的寻址方式里,由于寻址范围的变大,比如,Sector Count一次可传输的最大扇区数从原来的256增加到65536,但是Sector Count之类的寄存器对外还是只有一个字节的大小(可能是为了兼容之前的标准),因此我们需要将扇区数分两次写入到Sector Count寄存器,每次写入一个字节,最后一次写入的即Current,对应Sector count的位0到位7,之前写入的即Previous,对应Sector count的位8到位15。ATA-ATAPI-6的标准里应该为每个寄存器在内部隐式的分配了一个字节的额外空间用于存储Previous高位的字节。

    同理,LBA Low,LBA Mid及LBA High的Current分别用于存储LBA48的位0到位7,位8到位15,以及位16到位23的数据。而LBA Low,LBA Mid及LBA High的Previous则分别用于存储LBA48的位24到位31,位32到位39,以及位40到位47的数据。

    另外,上图里的Device的位6即LBA位必须被设置为1,表示采用LBA48的寻址方式。

    在所需的输入参数准备好后,就可以向Command命令寄存器发送READ SECTOR(S) EXT的命令代码即0x24了。

    至于向硬盘写入数据,就需要用到WRITE SECTOR(S)或WRITE SECTOR(S) EXT命令,WRITE SECTOR(S)命令所需的输入参数与READ SECTOR(S)命令是一样的,如下图所示:
 

图3

    上图位于ATA-ATAPI-5.pdf的第237页,可以看出来,参数都是一样的,只是命令代码为0x30而已。

    同理,WRITE SECTOR(S) EXT命令与READ SECTOR(S) EXT命令的输入参数也是一样的,只是命令代码为0x34而已。

    在了解了硬盘读写相关的命令和相关输入参数后,就可以根据这些参数与命令来编写具体的代码了,与ATA磁盘读写操作相关的函数位于zlox_ata.c文件里:

// Read/Write From ATA Drive, direction == 0 is read, direction == 1 is write
ZLOX_SINT32 zlox_ide_ata_access(ZLOX_UINT8 direction, ZLOX_UINT8 ide_index, ZLOX_UINT32 lba, 
                             ZLOX_UINT8 numsects, ZLOX_UINT8 * buffer)


    上面的zlox_ide_ata_access函数一共有5个参数,第一个direction参数用于表示读操作还是写操作,当direction为0时表示读数据操作,当direction为1时则表示写入数据的操作。第二个ide_index为IDE的设备号,最多可以有4个IDE设备,因此ide_index的有效范围是0到3。第三个lba参数表示从0开始的逻辑块地址,第四个参数numsects表示要读取的扇区数,这里,numsects为ZLOX_UINT8类型,即每次最多可读写256个扇区的数据,最后一个buffer参数为用户提供的缓冲区域,当为读操作时,表示将扇区里的数据读取到buffer指定的缓冲里,当为写操作时,则表示将buffer缓冲里的数据给写入到磁盘的lba对应的扇区里。

    zlox_ide_ata_access函数的开头,在对ide_index的有效范围,以及对应的IDE设备的类型做了简单的判断后,就需要向控制寄存器写入数据来暂时关闭掉ATA设备可能产生的中断请求:

control_orig = zlox_inb(ZLOX_ATA_DCR(bus));
zlox_outb(ZLOX_ATA_DCR(bus), control_orig & ((~2) & 0xff));


    要了解上面代码的含义,就必须先了解控制寄存器里各二进制位的含义,Device Control register(控制寄存器)的相关说明位于ATA-ATAPI-5.pdf的第72页,该寄存器里各二进制位的含义如下图所示:


图4

    上图位于ATA-ATAPI-5.pdf的第73页,当位1即nIEN为1时,ATA设备就不会发送IRQ中断请求,不过上面的代码应该写错了,上面的代码将该位清零了,不过由于目前zlox_ide_ata_access函数是以系统调用的形式进行访问的,在进入系统调用前,已经将所有可屏蔽中断都屏蔽了,所以暂时并不影响,在以后的版本里会对这段代码做调整。

    接着,我们需要选择扇区寻址模式,以及准备好lba相关的参数:

// (I) Select one from LBA48, LBA28 or CHS;
if(lba >= 0x10000000)
{
	// if not support LBA48 , then return -1
	if(!(ide_devices[ide_index].CommandSets & (1 << 26)))
		return -1;

	// LBA48:
	lba_mode  = 2;
	lba_io[0] = (lba & 0x000000FF) >> 0;
	lba_io[1] = (lba & 0x0000FF00) >> 8;
	lba_io[2] = (lba & 0x00FF0000) >> 16;
	lba_io[3] = (lba & 0xFF000000) >> 24;
	lba_io[4] = 0; // lba is integer, so 32-bits are enough to access 2TB.
	lba_io[5] = 0; // lba is integer, so 32-bits are enough to access 2TB.
	head      = 0; // Lower 4-bits of HDDEVSEL are not used here.
}
else if (ide_devices[ide_index].Capabilities & 0x200)
{
	// Drive supports LBA?
	// LBA28:
	lba_mode  = 1;
	lba_io[0] = (lba & 0x00000FF) >> 0;
	lba_io[1] = (lba & 0x000FF00) >> 8;
	lba_io[2] = (lba & 0x0FF0000) >> 16;
	lba_io[3] = 0; // These Registers are not used here.
	lba_io[4] = 0; // These Registers are not used here.
	lba_io[5] = 0; // These Registers are not used here.
	head      = (lba & 0xF000000) >> 24;
} 
else 
{
	// CHS:
	lba_mode  = 0;
	sect      = (lba % 63) + 1;
	cyl       = (lba + 1  - sect) / (16 * 63);
	lba_io[0] = sect;
	lba_io[1] = (cyl >> 0) & 0xFF;
	lba_io[2] = (cyl >> 8) & 0xFF;
	lba_io[3] = 0;
	lba_io[4] = 0;
	lba_io[5] = 0;
	head      = (lba + 1  - sect) % (16 * 63) / (63); // Head number is written to HDDEVSEL lower 4-bits.
}


    上面的第一种LBA48的寻址方式比较简单,没有磁头号,磁柱号等概念,直接将lba参数拆分为4个字节,然后依次写入到lba_io数组的前4项即可,由于lba是整数类型,因此,我们只用到了前4个字节,后两个字节没用到,也就是可寻址2TB的磁盘数据,稍候再将lba_io数组里的这几个字节依次写入到LBA Low,LBA Mid及LBA High寄存器中即可(如上面的图2所示)。

    第二种LBA28的寻址方式里,lba参数的低24位会被拆分为3个字节,依次写入到lba_io数组的前3项,最后4位会作为head磁头号,被写入到上面图1里的Device/Head寄存器的低4位。在该寻址方式中,我们可以将硬盘想象成,含有16个磁头的磁盘,每个磁头可以寻址16777216个扇区(2的24次方)。

    第三种CHS (Cylinder-Head-Sector)寻址方式就没有那么直接了,需要将lba参数对应的逻辑块地址换算成CHS里的Cylinder磁柱号,Head磁头号,以及Sector起始扇区号,要了解这些东东,我们就必须先对磁盘的结构做如下的设想。

    首先我们要将Sector扇区想象成位于环形track磁道上的一块数据:


图5

    上图显示,一个track磁道里有63个扇区,每个扇区的大小为512字节,Sector的扇区号是从1开始的。

    每个Cylinder磁柱又是由16个track磁道组成:
 

图6

    写入磁盘的数据在填写完0号磁道后,就会去填写该磁柱里的1号磁道,以此类推,由于一个磁道有63个扇区,因此当lba逻辑地址为0时,对应0号磁道里的1号扇区,当lba为63时,对应该磁柱的1号磁道的1号扇区。

    Cylinder磁柱里可以嵌套其他的磁柱:
 

图7

    磁柱号是从0开始的,上图显示0号磁柱里嵌套了1号磁柱,1号磁柱还可以继续嵌套其他的磁柱。

    另外,在CHS模型里,数据是将一个磁柱填满后,再去填写其他的磁柱的,由于一个磁柱有16个磁道,每个磁道有63个扇区,因此,一个磁柱有16 * 63 = 1008个扇区,也就是说当lba逻辑块地址为1008时,对应为1号磁柱的0号磁道的1号扇区。

    上面介绍了CHS模型里的Cylinder磁柱,Sector扇区,以及track磁道,至于head磁头则是用于定位Cylinder磁柱里的某个track磁道的,一个磁柱里有16个磁道,因此就有16个head磁头。

    磁头号与磁柱号都是从0开始的,Sector扇区号则是从1开始的。

    根据上面的CHS模型,lba换算为Cylinder磁柱号,Sector扇区号及head磁头号的算法如下:

// CHS:
lba_mode  = 0;
sect      = (lba % 63) + 1;
cyl       = (lba + 1  - sect) / (16 * 63);
lba_io[0] = sect;
lba_io[1] = (cyl >> 0) & 0xFF;
lba_io[2] = (cyl >> 8) & 0xFF;
lba_io[3] = 0;
lba_io[4] = 0;
lba_io[5] = 0;
head      = (lba + 1  - sect) % (16 * 63) / (63); // Head number is written to HDDEVSEL lower 4-bits.


    先通过(lba % 63) + 1取余运算得到扇区号,之所以加1,因为lba是从0开始的,而扇区号则是从1开始的。接着通过(lba + 1  - sect) / (16 * 63)来计算cyl磁柱号,之所以lba + 1要减去sect,是因为磁柱号是从0开始的,由于数据必须在填满了一个磁柱后,再向另一个磁柱去写入数据的,因此,通过除以(16 * 63)即单个磁柱拥有的扇区数,就可以得到磁柱号。有了sect扇区号,就可以将sect设置到lba_io数组的第一个字节里,该字节在稍候会被写入到前面图1里的Sector Number寄存器中。而cyl磁柱号的低8位则被设置到lba_io数组的第二个字节,cyl磁柱号的高8位则被设置到lba_io数组的第三个字节。最后,再通过(lba + 1  - sect) % (16 * 63) / (63)来计算head磁头号,这里通过除以63得到的磁道号就是磁头号。

    从上面的内容可以看到,CHS模型里,可寻址的磁盘大小是由磁柱的数量来决定的,而磁柱号可以由16位的二进制数来表示,因此,CHS可以寻址的最大磁盘大小为65535 * 16 * 63 * 512 / 1024 / 1024 / 1024 约为 31.5G的大小。

    在得到lba_io数组里的参数后,就可以向Sector Count之类的寄存器写入数据了,在写入数据之前,需要先确定ATA驱动器是否处于忙状态,如果处于忙状态,则需要进行等待:

// (II) Wait if the drive is busy;
while ((status = zlox_inb (ZLOX_ATA_COMMAND (bus))) & 0x80)     /* BUSY */
	asm volatile ("pause");


    在之前的v1.1.0版本的文章里,我们介绍过,当从命令寄存器里进行读操作时,读取的实际上是状态寄存器的值,从状态寄存器的最高位就可以判断当前驱动器是否正忙。

    接下来,我们先将head磁头信息,以及主从设备号信息写入到前面图1里的Device/Head寄存器中:

// (III) Select Drive from the controller;
if (lba_mode == 0)
	zlox_outb(ZLOX_ATA_DRIVE_SELECT (bus), ((drive & (1 << 4)) | 0xA0 | head)); // Drive & CHS.
else
	zlox_outb(ZLOX_ATA_DRIVE_SELECT (bus), ((drive & (1 << 4)) | 0xE0 | head)); // Drive & LBA


    然后,再将其他的输入参数写入到对应的寄存器里:

// (IV) Write Parameters;
if (lba_mode == 2) {
	// enable HOB(high order byte) of control register
	zlox_outb(ZLOX_ATA_DCR(bus), zlox_inb(ZLOX_ATA_DCR(bus)) | 0x80);
	zlox_outb(ZLOX_ATA_SECTOR_COUNT(bus), 0);
	zlox_outb(ZLOX_ATA_ADDRESS1(bus), lba_io[3]);
	zlox_outb(ZLOX_ATA_ADDRESS2(bus), lba_io[4]);
	zlox_outb(ZLOX_ATA_ADDRESS3(bus), lba_io[5]);
	// disable HOB
	zlox_outb(ZLOX_ATA_DCR(bus), zlox_inb(ZLOX_ATA_DCR(bus)) & 0x7f);
}
zlox_outb(ZLOX_ATA_SECTOR_COUNT(bus), numsects);
zlox_outb(ZLOX_ATA_ADDRESS1(bus), lba_io[0]);
zlox_outb(ZLOX_ATA_ADDRESS2(bus), lba_io[1]);
zlox_outb(ZLOX_ATA_ADDRESS3(bus), lba_io[2]);


    上面当lba_mode为2时,表示采用LBA48的扇区寻址方式,该寻址方式下,需要将lba_io[3],lba_io[4]及lba_io[5]写入到LBA Low,LBA Mid及LBA High寄存器里,作为这几个寄存器的Previous值(请参考前面的图2),还有将Sector Count寄存器的Previous即高8位的值设置为0 。上面代码里的HOB位是ATA-ATAPI-6标准里在控制寄存器中新增的位,其实,理论上应该不需要添加这段HOB的代码,不过加在这里应该也不影响结果,而且目前的版本用不到超过128G的磁盘数据,所以暂时也不会执行这段代码。

    至于lba_io[0],lba_io[1]与lba_io[2]则会依次写入到Sector Number,Cylinder Low及Cylinder High寄存器中(可以参考前面的图1)。

    在准备好输入参数后,就可以向命令寄存器写入具体的读写命令了:

// (V) Select the command and send it;
if (lba_mode == 0 && direction == 0)
	cmd = 0x20; // READ SECTOR(S) command
if (lba_mode == 1 && direction == 0) 
	cmd = 0x20; // READ SECTOR(S) command
if (lba_mode == 2 && direction == 0) 
	cmd = 0x24; // READ SECTOR(S) EXT command (support from ATA-ATAPI-6)
if (lba_mode == 0 && direction == 1) 
	cmd = 0x30; // WRITE SECTOR(S) command
if (lba_mode == 1 && direction == 1)
	cmd = 0x30; // WRITE SECTOR(S) command
if (lba_mode == 2 && direction == 1) 
	cmd = 0x34; // WRITE SECTOR(S) EXT command (support from ATA-ATAPI-6)
zlox_outb (ZLOX_ATA_COMMAND (bus), cmd);


    上面代码中会根据不同的寻址模式和读写要求,来向命令寄存器中写入不同的命令代码。

    最后,如果是读扇区的操作,则只需循环从数据寄存器里读取出数据到buffer缓冲区域即可,如果是写扇区的操作,也只需将buffer缓冲里的数据依次写入到数据寄存器即可:

// PIO Read.
if (direction == 0)
{
	for (i = 0; i < numsects; i++) 
	{
		while ((status = zlox_inb (ZLOX_ATA_COMMAND (bus))) & 0x80)     /* BUSY */
			asm volatile ("pause");
		while (!((status = zlox_inb (ZLOX_ATA_COMMAND (bus))) & 0x8) && !(status & 0x1))
			asm volatile ("pause");
		/* DRQ or ERROR set */
		if (status & 0x1) {
			return -1;
		}
		/* Read data. */
		zlox_insw (ZLOX_ATA_DATA (bus), (ZLOX_UINT16 *)buffer, words);
		buffer += (words*2);
	}
}
else // PIO Write.
{
	for (i = 0; i < numsects; i++) 
	{
		while ((status = zlox_inb (ZLOX_ATA_COMMAND (bus))) & 0x80)     /* BUSY */
			asm volatile ("pause");
		/* write data. */
		zlox_outsw (ZLOX_ATA_DATA (bus), (ZLOX_UINT16 *)buffer, words);
		buffer += (words*2);
	}
	zlox_outb (ZLOX_ATA_COMMAND (bus), (char []) {0xE7 /*FLUSH CACHE command*/,
					0xE7 /*FLUSH CACHE command*/ ,
					0xEA /*FLUSH CACHE EXT command*/}[lba_mode]);
	while ((status = zlox_inb (ZLOX_ATA_COMMAND (bus))) & 0x80)
		asm volatile ("pause");
}


    另外,从上面的代码里,可以看到,在写扇区操作完毕后,还需要向命令寄存器发送FLUSH CACHE或FLUSH CACHE EXT命令,这样,之前写入的数据才可以真正写入到磁盘上。

    上面采用的是PIO(Programming Input/Output Model 可编程的输入输出模式)来读写数据的,如果想使用DMA(Direct Memory Access 存储器直接访问)的方式来读写磁盘数据的话,可以参考 http://wiki.osdev.org/PCI_IDE_Controller 该链接里的代码。

    有了zlox_ide_ata_access函数,用户态程式就可以通过syscall_ide_ata_access系统调用来读写磁盘数据了,例如:build_initrd_img/ata.c文件里的代码:

/* a tool relate to ata(hard disk) and atapi(cd-rom) device */

#include "common.h"
#include "syscall.h"
#include "ata.h"
#include "kheap.h"

int main(VOID * task, int argc, char * argv[])
{
	......................................................
	else if(argc == 5 && strcmp(argv[1],"-r")==0)
	{
		..................................................
		if(ide_devices[ide_index].Type == IDE_ATA)
		{
			SINT32 ata_ret = syscall_ide_ata_access(0, ide_index, lba, 1, buffer);
			if(ata_ret == -1)
			{
				syscall_monitor_write("\nata read sector failed for ide index [");
				syscall_monitor_write_dec(ide_index);
				syscall_monitor_write("]\n");
				syscall_ufree(buffer);
				return -1;
			}
		}
		..................................................
	}
	else if(argc >= 5 && strcmp(argv[1],"-w")==0)
	{
		..................................................
		ata_ret = syscall_ide_ata_access(1, ide_index, lba, 1, buffer);
		if(ata_ret == -1)
		{
			syscall_monitor_write("\nata write sector failed for ide index [");
			syscall_monitor_write_dec(ide_index);
			syscall_monitor_write("]\n");
			syscall_ufree(buffer);
			return -1;
		}
		..................................................
	}

	......................................................
}


    从上面的代码可以看到,当ata工具使用-r参数时,会通过syscall_ide_ata_access(0, ide_index, lba, 1, buffer)将ide_index对应的IDE设备里的lba扇区的数据读取到buffer缓冲区域,这里将第一个参数设置为0,表示读操作,当使用-w参数时,则会通过syscall_ide_ata_access(1, ide_index, lba, 1, buffer)将buffer缓冲里的数据写入到指定的扇区里,这里将第一个参数设置为1,表示写入扇区的操作。

    以上是v1.4.0版本的相关内容,至于v1.4.1版本里的内容,则可以通过网盘里该版本的readme.txt文件来查看到:


 zenglOX v1.4.1 修复键盘中断时,可能会发送的一些未知字符的BUG

在zlox_keyboard_callback键盘中断处理函数中,当接收到的key扫描码超过scanToAscii_table数组的范围时,使用类似scanToAscii_table[key][4]这样的语句时,
就会访问到scanToAscii_table数组以外的未知数据,从而会向用户程式发送一些包含错误字符的消息,
因此,将原来的类似key_ascii = scanToAscii_table[key][4];的语句调整为
key_ascii = key < scanMaxNum ? scanToAscii_table[key][4] : 0;
这样,当key大于等于scanMaxNum(即scanToAscii_table数组的有效的元素个数)时,就将key_ascii设置为0 。

同时,在build_initrd_img/shell.c文件里,也对接收到的键盘消息进行了判断,只有可显示字符(即ASCII值在32到126之间的字符)才会被添加到input输入数组里。

之前的版本,当shell命令行程式收到键盘中断发送过来的未知字符时,会将这些未知字符给加入到input输入数组里,从而会导致在调用syscall_execve(input)时,出现找不到文件的错误。

该BUG是通过VirtualBox的内置调试器查找出来的。

时间:2014年7月6日
作者:zenglong
官网:www.zengl.com

    上面提到了VirtualBox的内置调试器,在VirtualBox的所有常规的发布版中都包含了内置调试器,其内置调试器的用法和各种可用的调试命令,可以参考 http://www.virtualbox.org/manual/ch12.html#ts_debugger 该链接里的文章。

    例如要启动内置调试器来调试zenglOX的话,可以通过cmd命令行在VirtualBox的安装目录内输入如下命令:


图8

    通过--startvm参数来指定需要启动的虚拟机,在我的VirtualBox里,zenglOX所在虚拟机的名称就是zenglOX,最后还要附加一个--debug的参数,表示开启内置调试器,运行后,就可以看到如下界面:
 

图9

    可以看到,菜单里多了"调试"菜单项,在底部多了一个VBoxDbg的Console控制台,可以在Command输入框里输入各种命令,例如可以输入help命令来查看帮助信息,右侧多了一个VBoxDbg的Statistics的统计窗口,里面对各种硬件相关的数据做了些统计。

    此外,这里还需特别注意的地方是,要使用br命令进行下断点操作的话,需要将zenglOX虚拟机的硬件加速功能关闭掉:
 

图10

    默认情况下,硬件加速功能是打开状态,在打开状态下,br命令即便下了断点也无法中断下来,所以需要将其关闭,而且启用硬件加速时,有时还会出现按键失灵的情况,因为加速情况下,VirtualBox有时就不会向主机发送键盘中断的请求信号,这可能是VirtualBox的BUG。

    最后再介绍下VMware虚拟机软件中,如何配合gdb调试器来调试zenglOX的方法。

    VMware是一款很优秀的虚拟机软件,既可以像VirtualBox那样,有很快的运行速度,又可以像Bochs和qemu那样,使用gdb进行调试。

    可以先在 http://www.vmware.com 的官方网站中下载VMware Workstation,作者下载的是9.0.4的版本,当然它还有10的版本,不过9的版本找Licence Key比较容易,作者下载安装后,测试过的可用的VMware Workstation 9的licence Key为:

4F207-8HJ1M-WZCP8-000N0-92Q6G

    作者还在DropBox里上传了VMware 9.0.4的Windows安装包和对应的licence.txt文件,共享链接地址为:https://www.dropbox.com/sh/23fjq7ogu0fhsoc/AAA50K5l_Dmanrxf0r-6UXcDa 国内用户可能需要通过代理下载,当然,如果官网还继续保留了9的版本的话,就建议从官网直接下载,毕竟官网下载是不需要代理的。

    安装好VMware后,可以先新建一个zenglOX的虚拟机,建立过程如下:

    1,在File菜单下选择New Virtual Machine

    2,在New Virtual Machine Wizard向导界面选择Custom自定义方式,选择Next,进入下一步

    3,在Hardware compatibility(硬件兼容性)中保持默认的Workstation 9.0,选择Next,进入下一步
   
    4,选择zenglOX.iso的光盘镜像:


图11

    在上图里选择好zenglOX.iso后,会弹出警告说检测不到该镜像对应的操作系统类型,这是因为zenglOX是我们自己开发的系统,不是什么主流的系统,当然就检测不到了。选择好镜像后,继续下一步。

    5,在操作系统类型和版本上都选择Other ,进入下一步

    6,在虚拟机名称的输入框内,输入自定义的虚拟机名称,这里就定义为zenglOX好了,然后还要确定好虚拟机的位置。进入下一步

    7,处理器的数目都保持默认的1,进入下一步

    8,虚拟机的内存大小,目前就设置为64M,当前的zenglOX版本里,只会用到16M的物理内存。进入下一步

    9,网络连接就保持默认的NAT类型,目前版本还没有网卡的驱动,进入下一步

    10,SCSI控制器保持默认的BusLogic,进入下一步

    11,保持默认的创建一个新的虚拟磁盘,进入下一步

    12,这一步必须选择IDE类型:
 

图12

    因为,zenglOX里开发的磁盘读写驱动都是针对IDE类型的。继续下一步

    13,磁盘大小就设置为2G就可以了,默认为8G,不过8G的话,在zenglOX里显示磁盘信息时,会显示0M,这是因为8G的话,字节数会超过32位整数的范围,当然只是显示的时候有点问题,不过如果你要访问8G的数据的话,也是可以访问的(不论是CHS还是LBA28寻址模式都可以访问到),如果磁盘支持LBA48的扇区寻址方式的话,要访问2TB的磁盘数据都可以,设置完磁盘大小后,还需要设置Store virtual disk as a single file(将虚拟磁盘存储到单一的一个文件中),继续下一步

    14,设置虚拟磁盘对应的文件名,这里就设置为zenglOX.vmdk,继续下一步

    15,这一步会将前面十几步配置的信息列举为一个清单的形式,如果清单里显示的配置信息有不对的地方,可以通过Back按钮返回到之前的步骤去进行修改和调整。

    16,如果清单确认无误,可以点击Finish完成按钮,这样zenglOX虚拟机就新建好了。

    进行gdb调试前,首先要进入上面第6步所设置的虚拟机位置所在的目录,在该目录中打开zenglOX.vmx文件,这是一个配置文件,在该配置文件底部添加如下内容:

debugStub.listen.guest32="1"

    如果你的主机是64位的机器,就添加debugStub.listen.guest64="1"

    保存配置文件后,就可以使用gdb进行调试了,windows系统下建议使用cygwin,因为安装cygwin时,就可以选择安装gcc,gdb等工具,cygwin的安装方法可以参考 www.linuxidc.com/Linux/2011-06/37588.htm 该链接对应的文章。

    安装好cygwin后,启动Cygwin Terminal即cygwin的控制台,假设我们将zenglOX.iso,zenglOX.bin,还有zenglOX_v1.4.1的源代码都放置在F:\vmwareforzenglOX目录内,那么就可以在cygwin控制台下通过cd命令切换到该目录内,然后输入gdb -q zenglOX.bin来加载zenglOX.bin里的符号信息:


图13

    VMware使用gdb调试时,有个顺序问题:必须先启动gdb,并在gdb里输入target远程调试命令来等待VMware打开端口,然后再启动zenglOX虚拟机,这样,gdb才能中断下来,如果先启动zenglOX虚拟机的话,zenglOX就会一路执行下去了,所以我们先在gdb命令行下输入target命令:

$ gdb -q zenglOX.bin
Reading symbols from /cygdrive/f/vmwareforzenglOX/zenglOX.bin...done.
(gdb) target remote localhost:8832


    如果你的机器是32位的就是8832端口,如果机器是64位的,就是8864端口。

    然后在VMware里,启动zenglOX虚拟机:


图14

    虚拟机启动后,gdb命令行就有反应了,然后就可以通过b命令下断点了,调试方法和Bochs里的一样,当然这里还需要用dir命令来指定zenglOX v1.4.1源代码的位置。

    这里需要注意的是,gdb使用target命令等待8832端口后,必须在短时间内启动zenglOX虚拟机,要不就会出现上图里的Connection timed out的超时提示了。

    当然VMware也有Linux的版本,如果是在Linux下安装VMware的话,就不需要安装Cygwin了,Linux里一般都有gdb调试器。

    以后的版本如果要开发图形界面的话,图形界面的调试当然是首先VMware了,因为它的执行速度是Bochs不能比的,而且它的调试功能也比VirtualBox方便很多。

    从zenglOX v1.4.0开始,ata工具除了可以读取光盘数据外,还可以读写硬盘数据:
 

图15

    上图显示,通过ata -s命令可以看到VMware里,0号IDE设备为ATA磁盘设备,这样我们就可以通过ata -w 命令向0号设备的1号逻辑扇区(即第二个扇区)里写入1,3,4,255这4个数。

    然后就可以通过ata -r 0 1 12命令来查看到刚才写入到扇区里的4个数了,写入的时候是以字节的形式进行写入的。

    以上就是v1.4.0与v1.4.1版本的相关内容。

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

下一篇: zenglOX v1.5.0 zenglfs文件系统, MBR主引导记录, fdisk分区工具及format磁盘格式化工具, file文件目录读写工具等

上一篇: zenglOX v1.3.0 动态链接库, 固定位置的内核栈, double fault(双误异常检测内核栈溢出)

相关文章

zenglOX v2.2.0 ee(easy editor)文本编辑器 C标准库函数 uheap(单独的用户堆空间) atapi驱动BUG zenglfs文件系统BUG 分页BUG 堆算法BUG等修复

zenglOX v1.5.0 zenglfs文件系统, MBR主引导记录, fdisk分区工具及format磁盘格式化工具, file文件目录读写工具等

zenglOX v1.2.0 ISO 9660文件系统

zenglOX v0.0.10 Keyboard(获取键盘的输入)

zenglOX v1.6.0 保护模式下, VGA图形模式驱动程式

zenglOX v0.0.2 VGA输出显示字符串