在对硬盘进行读写操作时,第一件要做的事就是对硬盘进行分区工作,也就是向MBR(硬盘的第一个扇区)里的分区表中写入每个分区的起始扇区号等信息,有关MBR的结构可以参考...

    v1.5.0版本的项目地址:

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

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

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

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

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

    在对硬盘进行读写操作时,第一件要做的事就是对硬盘进行分区工作,也就是向MBR(硬盘的第一个扇区)里的分区表中写入每个分区的起始扇区号等信息,有关MBR的结构可以参考 http://wiki.osdev.org/MBR_%28x86%29 该链接里的文章,从这篇文章中,可以看到,MBR的格式如下表所示:

Offset
字节偏移值
Size (bytes)
字节大小
Description
描述
0 436 (to 446, if you need a little extra)
436字节(如果不需要下面10字节的disk ID字段的话则可以扩充到446字节)
MBR Bootstrap (flat binary executable code)
MBR引导程序(二进制可执行代码)
0x1b4 10字节 Optional "unique" disk ID
可选的唯一的磁盘ID
0x1be 64字节(下面4个分区表项的总字节大小) MBR Partition Table, with 4 entries (below)
MBR分区表,包含4个分区表项目
0x1be 16字节 First partition table entry
第一个分区表项
0x1ce 16字节 Second partition table entry
第二个分区表项
0x1de 16字节 Third partition table entry
第三个分区表项
0x1ee 16字节 Fourth partition table entry
第四个分区表项
0x1fe 2 (0x55, 0xAA) "Valid bootsector" signature bytes
一般是0x55, 0xAA的两个字节,用于表示该扇区是有效的启动扇区

    可以看到,从MBR的0字节偏移位置处开始的436字节的部分,是MBR的引导程序,像dos系统下的fdisk程式就会向该部分写入一段启动代码,启动代码的大概执行流程可以参考 http://wiki.osdev.org/MBR_%28x86%29#Traditional_MBR 该链接对应的内容。

    启动代码后面是可选的disk ID字段,该字段没有标准的含义,如果不需要的话,可以不使用,或者合并到第一个引导程序部分。

    在zenglOX里的fdisk程式并没有使用上面介绍的引导程序部分和disk ID字段,也没有将MBR的最后两个字节修改为0X55、0xAA(因为目前是从光盘启动的,不需要从硬盘来启动,如果要从硬盘启动,就需要还写入一个类似于grub引导器的东东),唯一用到的就是接下来要介绍的4个分区表部分。

    由于MBR扇区只有512字节,所以只能写入4个分区表的信息,要设置更多的分区,则需要使用Extended Partitions(扩展分区),有关MBR分区表的结构以及扩展分区的相关内容可以参考 http://wiki.osdev.org/Partition_Table 该链接对应的文章,在该篇文章中,可以看到MBR分区表的结构如下所示:

Element (offset)
字段的字节偏移值
Size
字节大小
Description
描述
0 1个字节 Boot indicator bit flag: 0 = no, 0x80 = bootable (or "active")
启动引导位,0表示普通的非引导分区,0x80则表示该分区是可引导分区,也就是该分区里包含启动引导相关的代码
1 1个字节 Starting head
起始磁头号
2 6 bits
6个二进制位
Starting sector (Bits 6-7 are the upper two bits for the Starting Cylinder field.)
起始扇区号(该字段所在字节的低6位表示起始扇区号,位6和位7则用于下面的起始柱面号)
3 10 bits
10个二进制位
Starting Cylinder
起始柱面号
4 1个字节 System ID
系统ID值,用于标识分区所使用的文件系统
5 1个字节 Ending Head
结束磁头号
6 6 bits
6个二进制位
Ending Sector (Bits 6-7 are the upper two bits for the ending cylinder field)
结束扇区号(同样的,所在字节的低6位表示扇区号,位6和位7则用于下面的柱面号)
7 10 bits
10个二进制位
Ending Cylinder
结束柱面号
8 dword
4个字节
Relative Sector (to start of partition -- also equals the partition's starting LBA value)
该分区的起始扇区的lba(逻辑块地址)
12 dword
4个字节
Total Sectors in partition
该分区拥有的总扇区数

    虽然有这么多的字段,但是zenglOX的fdisk工具只用到了其中的三个字段:System ID(标识文件系统)字段,Relative Sector(起始扇区的lba)字段,Total Sectors(总扇区数)字段。其他的像Starting head(起始磁头号)等字段都没使用,这是因为上一篇文章所介绍的zlox_ide_ata_access即ATA磁盘读写操作相关的函数只是通过lba来进行寻址的。

    System ID字段通过数值来表示该分区所使用的文件系统类型,通过 http://www.win.tue.nl/~aeb/partitions/partition_types-1.html 该链接可以查看到不同文件系统所对应的具体数值,下面只列举部分内容:

ID  Name
0x00 Empty
0x01 DOS 12-bit FAT
0x02 XENIX root
0x03 XENIX /usr
0x04 DOS 3.0+ 16-bit FAT (up to 32M)
0x05 DOS 3.3+ Extended Partition
0x06 DOS 3.31+ 16-bit FAT (over 32M)
0x07 OS/2 IFS (e.g., HPFS)
0x07 Windows NT NTFS
0x07 exFAT
0x07 Advanced Unix
0x07 QNX2.x pre-1988 (see below under IDs 4d-4f)
0x08 OS/2 (v1.0-1.3 only)
0x08 AIX boot partition
0x08 SplitDrive
0x08 Commodore DOS
0x08 DELL partition spanning multiple drives
0x08 QNX 1.x and 2.x ("qny")
0x09 AIX data partition
0x09 Coherent filesystem
0x09 QNX 1.x and 2.x ("qnz")
0x0a OS/2 Boot Manager
0x0a Coherent swap partition
0x0a OPUS
0x0b WIN95 OSR2 FAT32
0x0c WIN95 OSR2 FAT32, LBA-mapped
0x0d SILICON SAFE
0x0e WIN95: DOS 16-bit FAT, LBA-mapped
0x0f WIN95: Extended partition, LBA-mapped
0x10 OPUS
0x11 Hidden DOS 12-bit FAT
0x11 Leading Edge DOS 3.x logically sectored FAT
0x12 Configuration/diagnostics partition
0x14 Hidden DOS 16-bit FAT <32M
0x14 AST DOS with logically sectored FAT
0x16 Hidden DOS 16-bit FAT >=32M
0x17 Hidden IFS (e.g., HPFS)
0x18 AST SmartSleep Partition
0x19 Unused
0x1b Hidden WIN95 OSR2 FAT32
0x1c Hidden WIN95 OSR2 FAT32, LBA-mapped
0x1e Hidden WIN95 16-bit FAT, LBA-mapped
0x20 Unused
0x21 Reserved
0x21 Unused
0x22 Unused
0x23 Reserved
0x24 NEC DOS 3.x
0x26 Reserved
0x27 PQservice
0x27 Windows RE hidden partition
0x27 MirOS partition
0x27 RouterBOOT kernel partition
.............................................


    从上面的内容中可以看到,0x23是保留的值,zenglfs文件系统使用的就是0x23的保留值。

    了解了MBR和分区表的结构后,zenglOX的fdisk工具的源代码就不难理解了,该工具的源代码文件为build_initrd_img/fdisk.c文件,该文件里和设置分区信息相关的代码如下:

/* fdisk -- 磁盘分区工具 */
.......................................................
int main(VOID * task, int argc, char * argv[])
{
	.......................................................
	FDISK_PARAM param = {0};
	/*
	通过parse_param函数,将用户的参数设置到param结构体变量中,
	例如,start参数的值会被设置到param.start成员,
	num参数的值会被设置到param.num成员
	*/
	if(parse_param(&param, argc, argv) == -1)
		return -1;
	UINT8 *buffer = (UINT8 *)syscall_umalloc(ATA_SECTOR_SIZE+5);
	UINT32 ide_index = param.hd;
	UINT32 lba = 0; // MBR
	IDE_DEVICE * ide_devices = (IDE_DEVICE *)syscall_ata_get_ide_info();
	.......................................................

	/*
	通过syscall_ide_ata_access系统调用来读取0号扇区(MBR)的
	数据到buffer缓冲里,lba(逻辑块地址)在之前
	已经被设置为了0
	*/
	SINT32 ata_ret = syscall_ide_ata_access(0, ide_index, lba, 1, buffer); // read MBR
	.......................................................

	MBR_PT * partition = (MBR_PT *)(buffer + MBR_PT_START);
	/*
	通过用户设置的pt参数来定位到buffer缓冲中
	的指定分区
	*/
	partition += (param.pt - 1);
	/*
	将不需要的flag(分区引导标志)字段设置为0,
	以及将不需要的head(起始磁头号),sec_cyl(起始柱面号)等
	设置为0xff(二进制位的值全部设置为1,以表示无效的值),
	fs_type即文件系统类型字段设置为用户type参数所指定的值,
	startLBA即起始逻辑块地址字段设置为用户start参数所指定
	的值,secNum即分区拥有的总扇区数字段设置为用户num参数
	所指定的值。
	*/
	partition->flag = 0;
	partition->head = partition->end_head = (param.num !=0) ? 0xff : 0;
	partition->sec_cyl = partition->end_sec_cyl = (param.num !=0) ? 0xffff : 0;
	partition->fs_type = param.type;
	partition->startLBA = param.start;
	partition->secNum = param.num;

	/*
	对用户设置的起始扇区号进行必要的调整,让其按照2个扇区
	进行对齐,因为zenglOX里使用的逻辑块都是2个磁盘扇区的大小
	*/
	partition->startLBA = (partition->startLBA % 2 == 0) ? partition->startLBA : 
					(partition->startLBA + 1);
	partition->secNum = (partition->secNum % 2 == 0) ? partition->secNum : 
					(partition->secNum + 1);

	/*
	对用户设置的参数进行必要的检测,不符合要求的设置则返回错误信息。
	*/
	if(partition->secNum != 0 && partition->startLBA == 0)
		partition->startLBA = 2;
	if(partition->secNum != 0 && 
		(partition->startLBA + partition->secNum - 1) > ide_devices[ide_index].Size)
	{
		syscall_monitor_write("your startLBA + secNum out of range, ide_index [");
		syscall_monitor_write_dec(ide_index);
		syscall_monitor_write("] Last LBA is ");
		syscall_monitor_write_dec(ide_devices[ide_index].Size);
		syscall_monitor_put('\n');
		syscall_ufree(buffer);
		return -1;
	}
	if(check_partition(partition, (MBR_PT *)(buffer + MBR_PT_START)) == -1)
	{
		syscall_ufree(buffer);
		return -1;
	}

	/*
	最后通过syscall_ide_ata_access系统调用将buffer缓冲区里设置过
	的分区表信息写入到硬盘的MBR里。
	*/
	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;
	}
	else
		syscall_monitor_write("\nfdisk write MBR success , you can use \"fdisk -l ide_index\" to see it! \n");

	syscall_ufree(buffer);
	return 0;
	.......................................................
}


    上面函数里棕色的注释是这里额外添加的,在源文件中暂时没有。在向MBR里写入分区表信息后,可以通过fdisk -l命令来查看MBR的分区表信息,fdisk -l命令的代码也位于上面fdisk.c的main函数中:

/* fdisk -- 磁盘分区工具 */

.......................................................
int main(VOID * task, int argc, char * argv[])
{
.......................................................
	if(strcmp(argv[1],"-l")==0)
	{
		UINT32 ide_index = strToUInt(argv[2]);
		UINT32 lba = 0; // MBR
		UINT8 * buffer = (UINT8 *)syscall_umalloc(ATA_SECTOR_SIZE+5);
		IDE_DEVICE * ide_devices = (IDE_DEVICE *)syscall_ata_get_ide_info();
		.......................................................
		
		/*
		通过syscall_ide_ata_access系统调用来读取0号扇区(MBR)的
		数据到buffer缓冲里,lba(逻辑块地址)在之前
		已经被设置为了0
		*/
		SINT32 ata_ret = syscall_ide_ata_access(0, ide_index, lba, 1, buffer); // read MBR
		.......................................................
		/*
		将buffer(即MBR缓冲)里的分区表的起始位置赋值给partition指针变量
		*/
		MBR_PT * partition = (MBR_PT *)(buffer + MBR_PT_START);
		SINT32 i;
		/*
		通过for循环,将4个分区表的信息给依次显示出来
		*/
		for(i=0;i < 4;i++,partition++)
		{
			syscall_monitor_write("[");
			syscall_monitor_write_dec(i+1);
			syscall_monitor_write("] start LBA: ");
			syscall_monitor_write_dec(partition->startLBA);
			syscall_monitor_write(" Total Sectors: ");
			syscall_monitor_write_dec(partition->secNum);
			if(partition->secNum != 0)
			{
				syscall_monitor_write(" [");
				/*
				通过分区的总扇区数来计算出分区的尺寸大小,
				并根据尺寸大小来设置具体的单位,比如MB,
				KB以及Byte
				*/
				if(partition->secNum * 512 / 1024 / 1024 != 0)
				{
					syscall_monitor_write_dec(partition->secNum * 512 / 1024 / 1024);
					syscall_monitor_write("MB]");
				}
				else if(partition->secNum * 512 / 1024 != 0)
				{
					syscall_monitor_write_dec(partition->secNum * 512 / 1024);
					syscall_monitor_write("KB]");
				}
				else
				{
					syscall_monitor_write_dec(partition->secNum * 512);
					syscall_monitor_write("Byte]");
				}
			}
			syscall_monitor_write(" filesystem: ");
			/*
			根据分区表的System ID字段即下面的
			partition->fs_type成员的值来判断分区所设置
			的文件系统类型,zenglfs文件系统的类型值为0x23,
			即下面MBR_FS_TYPE_ZENGLFS宏所对应的值。如果fs_type
			类型值为MBR_FS_TYPE_EMPTY即0的话,就说明该分区表是
			empty(空的)。其他的文件系统类型值,目前统一显示为other,
			以表示zenglOX目前不能识别和处理的分区类型。
			*/
			switch(partition->fs_type)
			{
			case MBR_FS_TYPE_ZENGLFS:
				syscall_monitor_write(" zenglfs");
				break;
			case MBR_FS_TYPE_EMPTY:
				syscall_monitor_write(" empty");
				break;
			default:
				syscall_monitor_write(" other");
				break;
			}
			syscall_monitor_write("\n");
		}

		syscall_ufree(buffer);
		return 0;
	}
.......................................................
}


    fdisk工具的使用方法如下图所示:


图1

    从上图中可以看到,一开始,0号IDE硬盘的4个分区表都为空,通过fdisk -hd 0 -pt 1 -type zenglfs -start 8 -num 123456命令,就可以将0号IDE硬盘的第一个分区设置为zenglfs的文件系统类型,该分区的起始扇区号为8,扇区总数为123456,相当于60M的分区大小。至于IDE硬盘具体的设备号可以通过ata -s命令来查看到。

    在分区后,要使用该分区的话,还必须通过format工具对其进行格式化操作,要进行格式化操作,就必须先了解zenglfs文件系统的结构。

    zenglfs文件系统是参考ext2文件系统的,ext2文件系统可以参考 http://wiki.osdev.org/Ext2 该链接里的文章,zenglfs去掉了ext2里的一些字段,同时调整了Group块组信息和位图信息,具体结构如下图所示:
 

图2

    0号逻辑块是Reserved(预留的),从1号逻辑块开始的第一个部分是super block(超级块),第2部分是Group组信息部分,第3部分是block bitmap即逻辑块的位图信息(位图里的二进制1表示已分配的逻辑块,二进制0表示未分配的逻辑块),第4部分是inode bitmap即文件节点的位图信息(位图里的二进制1表示已分配的文件节点,二进制0表示未分配的文件节点),第5部分是inode array即文件节点的数组,该数组里的每一项都记录了一个文件节点的具体字段信息,剩余的部分则都是用于存储文件或目录的内容的。

    文件系统里所使用的逻辑块都是1024字节的大小,相当于2个磁盘扇区的大小。

    在zlox_zenglfs.h头文件和build_initrd_img/format.h头文件里,都包含了super block超级块的结构体定义,只不过format.h头文件里的结构体定义没有ZLOX的前缀而已:

/*zlox_zenglfs.h -- the zenglfs header*/

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

#define ZLOX_ZLFS_SUPER_BLOCK_SIGN 0x53464c5a // ZLFS

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

typedef struct _ZLOX_ZLFS_SUPER_BLOCK
{
	ZLOX_UINT32 sign;
	ZLOX_UINT32 startLBA;
	ZLOX_UINT32 TotalBlock;
	ZLOX_UINT32 TotalInode;
	ZLOX_UINT32 GroupAddr;
	ZLOX_UINT32 GroupCount;
	ZLOX_UINT32 GroupBlocks;
	ZLOX_UINT32 BlockBitMapBlockAddr;
	ZLOX_UINT32 BlockMapBlocks;
	ZLOX_UINT32 InodeBitMapBlockAddr;
	ZLOX_UINT32 InodeMapBlocks;
	ZLOX_UINT32 InodeTableBlockAddr;
	ZLOX_UINT32 allocBlocks;
	ZLOX_UINT32 allocInodes;
} ZLOX_ZLFS_SUPER_BLOCK;

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


    上面结构体中,第1个sign字段是zenglfs文件系统的标识符,有效值是0x53464c5a,对应的ASCII码为ZLFS。在mount工具挂载分区时,会对sign字段进行检测,以判断该分区是否是有效的zenglfs文件系统。

    第2个startLBA表示该分区在MBR分区表中所设置的起始扇区号。下面提到的逻辑块地址是从上面图2所示的Reserved预留块开始的,并且是扇区大小的两倍即1024字节,因此这些逻辑块地址需要乘以2,再加上startLBA才能得到对应的磁盘扇区的lba值,ATA驱动程序才能根据该扇区lba值读取出指定的数据。

    第3个TotalBlock字段表示该分区一共有多少个逻辑块,由于逻辑块是磁盘扇区大小的两倍,因此,我们只需将MBR分区表中设置的该分区的总扇区数除以2,就可以得到该文件系统的总逻辑块数。

    第4个TotalInode字段表示该分区一共可以存储多少个文件节点,每个文件节点对应一个目录或一个文件。

    第5个GroupAddr字段表示group数组的逻辑块地址,该数组由连续的group组成,每个group负责统计一个逻辑块位图和一个文件节点位图里的二进制位的分配情况,单个group对应的结构体,定义在zlox_zenglfs.h的头文件和build_initrd_img/format.h头文件里:

/*zlox_zenglfs.h -- the zenglfs header*/

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

typedef struct _ZLOX_GROUP_INFO
{
	ZLOX_UINT32	allocBlocks;
	ZLOX_UINT32	allocInodes;
} ZLOX_GROUP_INFO;


    上面的allocBlocks表示该group负责的逻辑块位图中一共分配了多少个逻辑块,allocInodes则表示goup负责的文件节点位图中一共分配了多少个文件节点。

    super block的第6个GroupCount字段表示group数组里的group数量。

    第7个GroupBlocks字段表示group数组占用了多少个逻辑块,因为zenglfs文件系统在实际操作时,一次只会读取一个逻辑块(即两个磁盘扇区)的数据,因此,当group数组占用了两个逻辑块时,就需要分两次读取两个逻辑块,才能遍历这个group数组里的所有group结构。

    第8个BlockBitMapBlockAddr字段表示逻辑块位图数组的起始逻辑块地址,该数组由连续的逻辑块位图组成。

    第9个BlockMapBlocks字段表示逻辑块位图数组所占用的逻辑块数量,每个逻辑块可以存储一个逻辑块位图。

    第10个InodeBitMapBlockAddr字段表示文件节点位图数组的起始逻辑块地址,该数组由连续的文件节点位图组成。

    第11个InodeMapBlocks字段表示文件节点位图数组所占用的逻辑块数量,每个逻辑块可以存储8个文件节点位图。

    第12个InodeTableBlockAddr字段表示文件节点数组的起始逻辑块地址,该数组由连续的文件节点组成,文件节点的具体结构将在下面进行解释。

    第13个allocBlocks字段表示当前文件系统一共分配了多少逻辑块。

    第14个allocInodes字段表示当前文件系统一共分配了多少个文件节点。

    上面的super block的第12个InodeTableBlockAddr字段是文件节点数组的起始逻辑块地址,该数组里,每个文件节点的结构定义在zlox_zenglfs.h中:

#define ZLOX_ZLFS_INODE_TYPE_DIRECTORY 0x4000
#define ZLOX_ZLFS_INODE_TYPE_REGULAR 0x8000

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

struct _ZLOX_INODE_DATA
{
	ZLOX_UINT16 type;
	ZLOX_UINT32 size;
	ZLOX_UINT32 totalblk;
	ZLOX_UINT32 dirent_block;
	ZLOX_UINT32 dirent_idx;
	ZLOX_UINT32 dir_inode;
	ZLOX_UINT32 dir_item_num;
	ZLOX_UINT32 blockAddr[12];
	ZLOX_UINT32 SinglyBlockAddr;
	ZLOX_UINT32 DoublyBlockAddr;
	ZLOX_UINT32 TriplyBlockAddr;
	ZLOX_UINT8 reserve[42];
} __attribute__((packed));

typedef struct _ZLOX_INODE_DATA ZLOX_INODE_DATA;


    上面的ZLOX_INODE_DATA结构体中,第1个type字段表示文件节点的类型,当为0x4000时,表示该文件节点是一个目录,当为0x8000时,表示该文件节点是一个普通的文件。第2个size字段表示文件节点的内容大小(以字节为单位),第3个totalblk字段表示该文件节点的内容一共占用了多少个逻辑块。第4个dirent_block字段表示该文件节点的目录入口所在的逻辑块地址(有关目录入口的相关内容将在后面进行介绍),第5个dirent_idx字段表示该文件节点的目录入口在逻辑块中的索引值,之所以需要dirent_block和dirent_idx字段,是为了方便在删除文件节点时,可以快速的找到对应的目录入口,然后将目录入口里的信息给清理掉。

    第6个dir_inode字段,表示父目录的文件节点号,文件节点号是从1开始的,将节点号减一,就可以得到文件节点在文件节点数组中的索引值。

    第7个dir_item_num字段表示当前目录所拥有的子节点数(包括子目录和子文件的数量),比如,假设某个目录下有两个文件,那么该目录的文件节点的dir_item_num字段就会是2 。

    第8个blockAddr数组里的每个整数表示一个内容数据的逻辑块地址,该数组有12个元素,因此可以存储12个逻辑块地址,从这里可以看出来,文件和目录节点的内容可以分散在非连续的逻辑块中,读取节点内容时,只需根据逻辑块地址,将数据依次读取出来即可,这样就有利于动态的分配和释放数据,也有利于动态的扩容数据和收缩数据。但是该数组只有12个元素,因此只能分配到12个逻辑块,也就是12K字节的数据,要分配更多的逻辑块,就需要使用下面的SinglyBlockAddr(一级间接逻辑块),DoublyBlockAddr(二级间接逻辑块地址)以及TriplyBlockAddr(三级间接逻辑块地址)

    第9个SinglyBlockAddr字段表示一级间接逻辑块地址,该地址对应的逻辑块里可以存储256个整数,每个整数可以表示一个逻辑块地址,这样就可以分配到256K字节的数据。

    第10个DoublyBlockAddr字段表示二级间接逻辑块地址,该地址对应的逻辑块里可以存储256个整数,每个整数表示一个一级间接逻辑块地址,因此,就可以分配到256 * 256k = 65536k = 64M的数据。

    第11个TriplyBlockAddr字段表示三级间接逻辑块地址,该逻辑块里的每个整数可以表示一个二级间接逻辑块地址,因此,就可以分配到256 * 256 * 256k = 16777216k = 16384M = 16G的数据。因此,理论上,这种结构的单个文件的最大尺寸是16G多,当然还要看具体的读写操作的算法,目前的读写算法是先将要写入的数据全部缓存到内存,再将内存里的数据写入磁盘,因此,zenglfs目前可写入文件的实际大小是受限于内存大小的。如果将读写算法调整为每次只写入一部分数据,分批将数据写入到磁盘的话,就可以让文件达到理论上的最大尺寸。

    最后一个reserve字段是预留的部分,因为每个文件节点都是128字节的大小,因此预留部分目前就是42个字节。

    对于文件节点而言,节点内容就是文件实际的数据。对于目录节点而言,其节点内容是由Directory Entry即目录入口所组成的数组。每个目录入口的结构体定义如下(也位于zlox_zenglfs.h的头文件中):

struct _ZLOX_ZLFS_DIR_ENTRY
{
	ZLOX_UINT32 inode;
	ZLOX_UINT16 type;
	ZLOX_UINT8 namelength;
	ZLOX_CHAR name[57];
} __attribute__((packed));

typedef struct _ZLOX_ZLFS_DIR_ENTRY ZLOX_ZLFS_DIR_ENTRY;


    上面结构体的第1个inode字段表示该目录入口对应的文件节点的节点号。第2个type字段表示文件节点的类型,当为0x4000时,表示文件节点是一个目录,当为0x8000时,表示文件节点是一个普通的文件。第3个namelength字段表示节点文件名的有效长度,最后一个name字段表示节点文件名的字符数组。

    以上就是zenglfs文件系统的结构,format工具就是根据这些结构来对文件系统进行相关格式化操作的。

    format工具的源代码位于build_initrd_img/format.c文件里,和格式化相关的代码如下:

/* format -- 磁盘格式化工具 */

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

int main(VOID * task, int argc, char * argv[])
{
..................................................

	FORMAT_PARAM param = {0};
	/*
	先通过parse_param函数将用户输入的参数设置到
	param结构体变量。
	*/
	if(parse_param(&param, argc, argv) == -1)
		return -1;
	UINT8 *buffer = (UINT8 *)syscall_umalloc(ATA_SECTOR_SIZE * 2);
	UINT32 ide_index = param.hd;
	UINT32 lba = 0; // MBR
	IDE_DEVICE * ide_devices = (IDE_DEVICE *)syscall_ata_get_ide_info();
	..................................................
	
	/*
	将lba对应的0号扇区即MBR里的数据读取到buffer缓冲
	*/
	SINT32 ata_ret = syscall_ide_ata_access(IDE_ATA_READ, ide_index, lba, 1, buffer); // read MBR
	..................................................

	MBR_PT * partition_ptr = (MBR_PT *)(buffer + MBR_PT_START);
	partition_ptr += (param.pt - 1);
	MBR_PT partition = (*partition_ptr);
	/*
	因为要为inode array节点数组预留一段空间,每个
	节点占用128字节,而节点数组是1024的倍数(一个节点位图为128
	字节即1024个二进制位,每个二进制代表一个文件节点的占用情况),
	因此节点数组最少需要预留1024 * 128 = 131072即128K字节的空间,
	在节点数组前还有5个部分如super block部分,
	每个部分按最小1K即一个逻辑块来算,
	那么,就需要128K + 5K = 133K,此外,文件节点还需要一些空间来存储
	内容数据,因此最小的分区大小至少要200K以上。
	而下面的450个扇区的尺寸为450 * 512 = 230400即225K字节的大小,
	符合要求,所以,目前就以450个扇区作为最小的可供格式化的分区扇区数。
	*/
	if(partition.secNum < 450)
	{
		syscall_monitor_write("this partition is too small , must at least have 450 sectors \n");
		syscall_ufree(buffer);
		return -1;
	}
	else if(partition.fs_type != MBR_FS_TYPE_ZENGLFS)
	{
		syscall_monitor_write("this partition is not zenglfs \n");
		syscall_ufree(buffer);
		return -1;
	}
	/*
	partition.startLBA + 2就可以跳过分区的Reserved预留块,而
	定位到super block超级块
	*/
	lba = partition.startLBA + 2;
	/*将超级块的内容读取出来,因为一个逻辑块是2个扇区的大小,因此,
	下面就一次读取了2个扇区的数据*/
	syscall_ide_ata_access(IDE_ATA_READ, ide_index, lba, 2, buffer); // read superblock
	SUPER_BLOCK * superblock_ptr = (SUPER_BLOCK *)buffer;
	/*下面对超级块的各个字段依次进行设置*/
	superblock_ptr->sign = SUPER_BLOCK_SIGN;
	superblock_ptr->startLBA = partition.startLBA;
	superblock_ptr->TotalBlock = partition.secNum / 2;
	UINT32 group = superblock_ptr->TotalBlock / (1024 * 8) + ((superblock_ptr->TotalBlock % (1024 * 8) != 0) ? 1 : 0);
	superblock_ptr->TotalInode = group * 128 * 8;
	superblock_ptr->GroupAddr = 2;
	superblock_ptr->GroupCount = group;
	superblock_ptr->GroupBlocks = (group / 128) + ((group % 128 !=0) ? 1 : 0);
	superblock_ptr->BlockBitMapBlockAddr = superblock_ptr->GroupAddr + superblock_ptr->GroupBlocks;
	superblock_ptr->BlockMapBlocks = group;
	superblock_ptr->InodeBitMapBlockAddr = superblock_ptr->BlockBitMapBlockAddr + group;
	superblock_ptr->InodeMapBlocks = (group / 8) + ((group % 8 !=0) ? 1 : 0);
	superblock_ptr->InodeTableBlockAddr = superblock_ptr->InodeBitMapBlockAddr + (group / 8) + ((group % 8 !=0) ? 1 : 0);
	superblock_ptr->allocBlocks = 2 + superblock_ptr->GroupBlocks + superblock_ptr->BlockMapBlocks + 
					superblock_ptr->InodeMapBlocks + group * 128;
	superblock_ptr->allocInodes = 1;
	syscall_ide_ata_access(IDE_ATA_WRITE, ide_index, lba, 2, buffer);
	SUPER_BLOCK superblock = (*superblock_ptr);
	UINT32 i,j,t = superblock.allocBlocks, gsz = (1024 / sizeof(GROUP_INFO));
	GROUP_INFO * groupinfo;
	BOOL needClear = FALSE;
	memset(buffer, 0, ATA_SECTOR_SIZE * 2);
	/*设置group数组里的各个group信息*/
	for(i = 0;i < superblock.GroupBlocks;i++)
	{
		lba = (superblock.GroupAddr + i) * 2 + superblock.startLBA;
		groupinfo = (GROUP_INFO *)buffer;
		if(i == 0)
		{
			groupinfo->allocInodes = 1;
			needClear = TRUE;
		}
		if(t != 0)
		{
			for(j=0;t != 0 && j < gsz;j++,groupinfo++)
			{
				if(t >= 8192)
				{
					groupinfo->allocBlocks = 8192;
					t -= 8192;
				}
				else if(t < 8192)
				{
					groupinfo->allocBlocks = t;
					t = 0;
				}
			}
			needClear = TRUE;
		}
		syscall_ide_ata_access(IDE_ATA_WRITE, ide_index, lba, 2, buffer);
		if(needClear == TRUE)
		{
			memset(buffer, 0, ATA_SECTOR_SIZE * 2);
			needClear = FALSE;
		}
	}
	t = superblock.allocBlocks;
	/*因为super block等需要占用一些逻辑块,因此将逻辑块位图中
	对应的二进制位设置为占用状态*/
	for(i = 0;i < superblock.BlockMapBlocks;i++)
	{
		lba = (superblock.BlockBitMapBlockAddr + i) * 2 + superblock.startLBA;
		if(t >= 8192)
		{
			memset(buffer, 0xff, ATA_SECTOR_SIZE * 2);
			t -= 8192;
			needClear = TRUE;
		}
		else if(t > 0 && t < 8192)
		{
			for(j = 0;j < t;j++)
				set_bitmap((UINT32 *)buffer, j);
			t = 0;
			needClear = TRUE;
		}
		syscall_ide_ata_access(IDE_ATA_WRITE, ide_index, lba, 2, buffer);
		if(needClear == TRUE)
		{
			memset(buffer, 0, ATA_SECTOR_SIZE * 2);
			needClear = FALSE;
		}
	}
	/*format在格式化时会设置一个root根目录的文件节点,因此,
	下面将文件节点位图中对应的二进制位设置为占用状态*/
	for(i = 0;i < superblock.InodeMapBlocks;i++)
	{
		lba = (superblock.InodeBitMapBlockAddr + i) * 2 + superblock.startLBA;
		if(i == 0)
		{
			set_bitmap((UINT32 *)buffer, 0);
			needClear = TRUE;
		}
		syscall_ide_ata_access(IDE_ATA_WRITE, ide_index, lba, 2, buffer);
		if(needClear == TRUE)
		{
			memset(buffer, 0, ATA_SECTOR_SIZE * 2);
			needClear = FALSE;
		}
	}
	INODE_DATA * root_inode = (INODE_DATA *)buffer;
	/*设置root根目录的文件节点,其实就是简单的将该文件节点
	的类型设置为目录类型,至于文件节点的内容则只有在使用过程
	中才会进行分配*/
	root_inode->type = INODE_TYPE_DIRECTORY;
	lba = superblock.InodeTableBlockAddr * 2 + superblock.startLBA;
	syscall_ide_ata_access(IDE_ATA_WRITE, ide_index, lba, 2, buffer); // write root inode
	syscall_ufree(buffer);
	syscall_monitor_write("\nformat success , you can use \"format -l ide_index ptnum\" to see it! \n");
	return 0; 
..................................................
}


    在格式化操作成功后,就可以通过format -l命令来查看分区的超级块信息了,如下图所示:


图3

    上图显示,在fdisk设置了0号IDE硬盘的1号分区后,就可以通过format -hd 0 -pt 1 -type zenglfs命令将该分区格式化为zenglfs文件系统,该命令里的-hd参数用于设置硬盘的IDE设备号,-pt参数用于设置分区号(分区号从1开始),-type用于指定需要格式化的文件系统类型。在格式化成功后,就可以通过format -l 0 1命令来查看到该分区的super block超级块和Group组的信息了。如果不想显示Group Info信息,或者想控制Group Info所显示的Group信息数量,则可以在末尾再添加一个数字,例如:format -l 0 1 0 最后一个0表示显示的Group信息数为0,即不显示Group Info信息。

    在格式化后,就可以通过mount工具来挂载zenglfs文件系统的分区到hd目录下了,和挂载zenglfs文件系统相关的代码位于build_initrd_img/mount.c文件里:

// mount.c -- 挂载文件系统的程式

#include "common.h"
#include "syscall.h"

/*
mount工具可以加载cdrom到iso目录,也可以将硬盘里格式化过的zenglfs分区给加载到hd目录,
当使用mount iso命令时,会通过syscall_mount_iso()系统调用将cdrom加载到iso目录
当使用类似 mount hd 0 1 命令时,则可以将0号IDE硬盘的1号分区给加载到hd目录
iso和hd是内核固定设置好的目录,目前不可以加载到其他的目录名上
*/

int main(VOID * task, int argc, char * argv[])
{
	UNUSED(task);
	if(argc == 2 && strcmp(argv[1],"iso")==0)
	{
		int ret = syscall_mount_iso();
		if(ret != 0)
			syscall_monitor_write("mount iso to [iso] success! you can use \"ls iso\" to see "
						"the contents of iso directory");
		else
			syscall_monitor_write("mount iso failed...");
	}
	else if(argc == 4 && strcmp(argv[1],"hd")==0)
	{
		UINT32 ide_index = strToUInt(argv[2]);
		UINT32 pt = strToUInt(argv[3]);
		int ret = syscall_mount_zenglfs(ide_index, pt);
		if(ret != 0)
			syscall_monitor_write("mount to [hd] success! you can use \"ls hd\" to see "
						"the contents of hd directory");
	}
	else 
		syscall_monitor_write("usage: mount [iso][hd ide_index pt]");
	return 0;
}


    从上面的代码,可以看到,通过syscall_mount_zenglfs系统调用可以将指定的分区挂载到hd目录上。

    在使用mount挂载硬盘里的分区时,需要指定硬盘的IDE设备号和分区号,如下图所示:


图4

    在mount挂载成功后,通过ls命令,就可以看到hd目录了。

    当使用mount工具将硬盘分区挂载到hd目录后,就可以使用file工具对hd目录进行读写文件或创建文件,创建子目录,删除文件,删除子目录,重命名文件,重命名子目录的操作,

    例如:file hd/hello命令表示在hd目录内创建一个空的hello文件,然后使用ls hd命令就可以在hd目录中查看到刚才创建的hello文件了,通过ls hd/hello命令可以看到该hello文件的length尺寸为0,如下图所示:
 

图5

    通过file ata hd/ata2命令可以将ramdisk里的ata文件拷贝到hd目录内,同时,在hd目录里的文件名为ata2,接着就可以通过hd/ata2命令来执行ata工具的操作了,如下图所示:
 

图6

    通过file -rm hd/ata2命令就可以将刚才创建的ata2文件给删除掉。file工具里为了简化起见,目录是在创建文件时,一起被创建的,在你自定义的程式里,也可以根据file里的原理来单独创建某目录。

    例如:file ata hd/dir/ata命令表示将ata文件拷贝到hd目录里的dir目录内,如果dir目录不存在,则会创建该目录。

    使用-rm参数删除目录时,必须先将目录里的所有内容删除,再删除该目录,当然在你自定义的程式里,也可以通过递归方法,让工具自动将目录里的文件删除,再将目录删除,file工具目前为了简化起见,并没有这么做。

    通过file -rename hd/hello hello2命令可以将hd目录的hello文件重命名为hello2,如果hello是一个目录的话,就将该目录重命名为hello2,如果重命名的名称里包含斜杠'/',则'/'会被zenglfs替换为下划线'_' 。

    下图显示了上面几个命令的用法:
 

图7

    在mount工具挂载zenglfs文件系统时,会调用zlox_zenglfs.c文件里的zlox_mount_zenglfs函数来完成加载工作:

// 挂载硬盘分区到hd目录下
ZLOX_FS_NODE * zlox_mount_zenglfs(ZLOX_UINT32 ide_index, ZLOX_UINT32 pt)
{
	........................................................
	zenglfs_root = (ZLOX_FS_NODE *)zlox_kmalloc(sizeof(ZLOX_FS_NODE));
	zlox_memset((ZLOX_UINT8 *)zenglfs_root, 0 , sizeof(ZLOX_FS_NODE));
	zlox_strcpy(zenglfs_root->name, "hd");
	zenglfs_root->mask = zenglfs_root->uid = zenglfs_root->gid = zenglfs_root->length = 0;
	zenglfs_root->inode = 1;
	zenglfs_root->flags = ZLOX_FS_DIRECTORY;
	zenglfs_root->read = 0;
	zenglfs_root->write = 0;
	zenglfs_root->open = 0;
	zenglfs_root->close = 0;
	zenglfs_root->readdir = &zlox_zenglfs_readdir;
	zenglfs_root->writedir = &zlox_zenglfs_writedir;
	zenglfs_root->finddir = &zlox_zenglfs_finddir;
	zenglfs_root->ptr = 0;
	zenglfs_root->impl = 0;

	zenglfs_ide_index = ide_index;
	zlox_kfree(buffer);
	return zenglfs_root;
}


    可以看到,zlox_mount_zenglfs函数其实就是创建了一个ZLOX_FS_NODE类型的zenglfs_root的文件节点,然后将该节点的节点号设置为1,以表示根目录节点,将zenglfs_root的flags成员设置为ZLOX_FS_DIRECTORY表示该节点是一个目录,再将readdir,writedir及finddir成员设置为相应的目录读写查找函数即可。

    在file之类的工具对hd目录所在的zenglfs文件系统进行写目录操作时(比如创建子目录,或创建目录里的文件时),会调用zlox_zenglfs.c文件的zlox_zenglfs_writedir函数。

    在file之类的工具对hd目录进行读写文件的操作时,会调用zlox_zenglfs.c文件的zlox_zenglfs_read或zlox_zenglfs_write函数。

    当对hd目录进行列表或查找文件及子目录的操作时,会调用zlox_zenglfs.c文件的zlox_zenglfs_readdir或zlox_zenglfs_finddir_ext函数,

    当对hd目录进行删除文件或删除子目录的操作时,会调用zlox_zenglfs.c文件的zlox_zenglfs_remove函数。

    当对hd目录里的子文件或子目录进行重命名操作时,会调用zlox_zenglfs.c文件的zlox_zenglfs_rename函数。

    在unmount工具卸载zenglfs文件系统时,则会调用zlox_zenglfs.c文件的zlox_unmount_zenglfs函数。

    在zlox_zenglfs.c文件的开头定义了类似zenglfs_cache_inode,zenglfs_cache_inode_blk,zenglfs_cache_group之类的缓存结构,定义这些结构是为了能缓存磁盘里的数据,以减少频繁的磁盘I/O读写操作。

    这些函数其实就是围绕着上面介绍过的zenglfs文件系统结构来进行各种读写,添加删除等操作的,限于篇幅,这里就不一一介绍了,请读者根据zenglfs文件系统的结构和源代码,再配合gdb调试器,进行调试分析。

    最后,v1.5.0版本还对zlox_keyboard.c文件做了调整,让其可以向任务发送上下左右的按键消息:

// zlox_keyboard.c implement interrupt and functions of keyboard

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

static ZLOX_VOID zlox_keyboard_callback(/*ZLOX_ISR_REGISTERS * regs*/)
{
	ZLOX_UINT32 key = zlox_inb(0x60);
	ZLOX_UINT32 key_ascii = 0;	
	ZLOX_UINT32 key_code = 0;
	ZLOX_UINT32 scanMaxNum = sizeof(scanToAscii_table) / (8 * 4);
	
	if(press_key == 0xE0)
	{
		switch(key)
		{
		case 0x48:
			key_code = ZLOX_MKK_CURSOR_UP_PRESS;
			break;
		case 0x50:
			key_code = ZLOX_MKK_CURSOR_DOWN_PRESS;
			break;
		case 0x4B:
			key_code = ZLOX_MKK_CURSOR_LEFT_PRESS;
			break;
		case 0x4D:
			key_code = ZLOX_MKK_CURSOR_RIGHT_PRESS;
			break;
		default:
			key_code = 0;
			break;
		}
		press_key = 0;
	}
	else if(key == 0xE0)
	{
		press_key = key;
	}

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

	if(key_code != 0)
	{
		ZLOX_TASK_MSG ascii_msg = {0};
		ascii_msg.type = ZLOX_MT_KEYBOARD;
		ascii_msg.keyboard.type = ZLOX_MKT_KEY;
		ascii_msg.keyboard.key = key_code;
		zlox_send_tskmsg(input_focus_task,&ascii_msg);
	}
	..................................................
}


    当某一次按键中断的扫描码为0xE0时,如果下一次按键中断的扫描码为0x48,则表示用户按了上键,当为0x50,则表示按了下键,当为0x4B,则表示按了左键,当为0x4D,则表示按了右键。最后,会将这些按键转为消息发送给具有输入焦点的任务。

    由于可以接收按键,shell命令行程式,就可以根据上下键来回滚历史命令记录,当然目前只能向上回滚一次历史,build_initrd_img目录的shell.c文件里和按键相关的代码如下:

// shell.c -- 命令行程式

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

char input_for_up[MAX_INPUT]={0};
char input_for_down[MAX_INPUT]={0};

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

int main(VOID * task, int argc, char * argv[])
{
...................................................

	switch(msg.keyboard.key)
	{
	case MKK_CURSOR_UP_PRESS:
		if(strlen(input_for_up) == 0)
			break;
		strcpy(input_for_down, input);
		replace_input(input,input_for_up,&count);
		isinUp = TRUE;
		break;
	case MKK_CURSOR_DOWN_PRESS:
		if(isinUp == TRUE)
		{
			replace_input(input,input_for_down,&count);
			isinUp = FALSE;
		}
		break;
	default:
		break;
	}

	continue;
...................................................
}


    当用户按了上键时,shell会将input_for_up里记录的内容替换掉当前input输入字符串的内容,当用户按了下键时,就将input_for_down里的内容替换掉input。

    因此,shell目前可以实现最近一次的历史回滚。

    以上就是v1.5.0版本的相关内容。

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

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

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

相关文章

zenglOX v0.0.7 VFS(虚拟文件系统)与initrd(初始化ram disk)

zenglOX v0.0.6 使用Heap(堆)动态分配和释放内存

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

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

zenglOX v3.1.0 Sound Blaster 16

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