Freeldr提供了对fat12、fat32、fatx、ntfs等文件系统的只读功能。这部分代码主要集中在boot\freeldr\freeldr\fs\fs.c文件中。 首先计算机加电后会把mbr读取到物理内存的0x7c00位置,mbr搜索活动分区并加载活动分区根目录下的Freeldr.sys文件。加载后跳入Freeldr入口start。Freeldr进行32为初始化后跳入主初始化函数BootMain(boot\freeldr\freeldr\Freeldr.c)中。
VOID BootMain (LPSTR CmdLine) {...... MachInit (CmdLine);FsInit ();...... RunLoader ();}
BootMain会对硬件(MachInit)和文件系统(FsInit)进行检测和初始化。所有准备工作进行完毕后就会调用RunLoader进行系统的加载工作。 Fs初始化和DEVICE、FILEDATA结构 下面看一下文件系统的初始化 FsInit(boot\freeldr\freeldr\fs\fs.c)
VOID FsInit (VOID) {ULONG i; RtlZeroMemory (FileData, sizeof (FileData));for (i = 0 ; i < MAX_FDS; i++)FileData[i].DeviceId = (ULONG)-1 ; InitializeListHead (&DeviceListHead);}
FsInit初始化FileData数组。和一个和磁盘分区相关的链表DeviceListHead。 首先fs.c维护了一个MAX_FDS(60)大小的数组 static FILEDATA FileData[MAX_FDS];
typedef struct tagDEVVTBL { ARC_CLOSE Close; ARC_GET_FILE_INFORMATION GetFileInformation; ARC_OPEN Open; ARC_READ Read; ARC_SEEK Seek; LPCWSTR ServiceName; } DEVVTBL; typedef struct tagFILEDATA { ULONG DeviceId; ULONG ReferenceCount; const DEVVTBL* FuncTable; const DEVVTBL* FileFuncTable; VOID* Specific; } FILEDATA;
每一个成功打开的文件会返回一个文件句柄,这个句柄实际上就是FileData数组的索引。所以每个打开的文件都有一个对应的FileData。这个结构就类似windows中的FILE_OBJECT FileData中DeviceId是文件所在磁盘的句柄。这个句柄同样也是FileData数组的索引,通过这个句柄可以找到”磁盘文件”,对”磁盘文件”的读写就是直接对相应的磁盘或磁盘分区的读写。类似Windows中直接对磁盘分区进行CreateFile返回的句柄。”磁盘文件”的DeviceId没有意义。 ReferenceCount是该文件的引用计数。 FuncTable这是一个函数数组指针,里面存放了对文件进行读写、SEEK等操作的函数指针。 FileFuncTable只对”磁盘文件”有意义。当Freeldr确定了磁盘文件对应的分区的分区格式后,会把与分区格式相关的函数指针数组放到这个字段里面。如Fat12分区”磁盘文件”的FileFuncTable字段存放的就是FatFuncTable指针。 Specific存放于文件有关的结构。磁盘文件就是DISKCONTEXT指针,fat12下的文件就是FAT_FILE_INFO指针 等等。 之后是DeviceListHead,这是DEVICE结构的链表头
typedef struct tagDEVICE { LIST_ENTRY ListEntry; const DEVVTBL* FuncTable; CHAR* Prefix; ULONG DeviceId; ULONG ReferenceCount; } DEVICE;
用户电脑中的每一个硬盘和硬盘中的每一分区都对应了一个DEVICE结构。 FuncTable里面存放了对该分区进行读写等操作的指针,对于硬盘而言这个数组就是DiskVtbl。 Prefix是该分区或硬盘的ArcName。(如multi(0)disk(0)rdisk(0)partition(0))。Freeldr中的文件路径都是Arc形式的路径。而且0号分区代表整个硬盘,真正的分区从1号开始。如multi(0)disk(0)rdisk(0)partition(0)便代表第0块硬盘本身。multi(0)disk(0)rdisk(0)partition(1)代表第0块硬盘的第0个分区。 通过DeviceId字段可以找到该DEVICE的文件句柄。这个字段和FILEDATA相互配合,使系统可以遍历DEVICE结构快速找到某个分区的文件句柄。 DEVICE(磁盘及分区)的检测 上面说到Freeldr操作的路径都是存储在DEVICE结构中的ArcPath。那么这些DEVICE是怎么来的呢? 首先我们看一下DEVICE的注册函数,FsRegisterDevice(boot\freeldr\freeldr\fs\fs.c)
VOID FsRegisterDevice (CHAR* Prefix, const DEVVTBL* FuncTable) {DEVICE* pNewEntry; ULONG dwLength; dwLength = strlen (Prefix) + 1 ; pNewEntry = MmHeapAlloc (sizeof (DEVICE) + dwLength); if (!pNewEntry)return ;pNewEntry->FuncTable = FuncTable; pNewEntry->ReferenceCount = 0 ; pNewEntry->Prefix = (CHAR*)(pNewEntry + 1 ); memcpy (pNewEntry->Prefix, Prefix, dwLength);InsertHeadList (&DeviceListHead, &pNewEntry->ListEntry);}
这么函数非常简单。Prefix就是Arc路径,FuncTable是操作这个分区(磁盘)对应的函数数组。FsRegisterDevice生成了一个DEVICE结构,把ArcName和FuncTable复制进去。之后连入了DeviceListHead链表。 那么又是谁调用的FsRegisterDevice呢?是DetectBiosDisks(boot\freeldr\freeldr\arch\i386\hardware.c)函数。虽然这一部分已经不属于FS的范畴,还是在这里简单讲一下便于理解。这里我略去了不必要的代码。 DetectBiosDisks的调用顺序是 RunLoader -> MachHwDetect (PcHwDetect)-> DetectISABios -> DetectBiosDisks
static VOIDDetectBiosDisks (PCONFIGURATION_COMPONENT_DATA BusKey) {BOOLEAN BootDriveReported = FALSE; ULONG i; ULONG DiskCount = GetDiskCount (BusKey); CHAR BootPath[512 ]; ...... for (i = 0 ; i < DiskCount; i++){ ULONG Size; CHAR Identifier[20 ]; ...... if (BootDrive == 0x80 + i)BootDriveReported = TRUE; GetHarddiskIdentifier (Identifier, 0x80 + i);} }
首先使用GetDiskCount从Freeldr注册表的System键中读取硬盘总数。System键的初始化在DetectSystem(freeldr\freeldr\arch\i386\hardware.c)中,一会儿再看。 于是进入一个for循环,为每个硬盘调用GetHarddiskIdentifier函数。在BIOS中硬盘号是从0x80开始的,所以GetHarddiskIdentifier的硬盘号加了0x80。 GetHarddiskIdentifier的作用是为制定硬盘生成一个唯一的ID,并通过Identifier参数返回。但这个函数名起得并不好,因为生成ID其实只是这个函数的功能之一。另外的一大功能是检测硬盘,并且为硬盘本身和硬盘分区调用FsRegisterDevice函数进行注册。通过这个注册后硬盘才能真正被文件系统识别。 freeldr\freeldr\arch\i386\hardware.c中
static VOIDGetHarddiskIdentifier (PCHAR Identifier, ULONG DriveNumber) {PMASTER_BOOT_RECORD Mbr; ULONG *Buffer; ULONG i; ULONG Checksum; ULONG Signature; CHAR ArcName[256 ]; PARTITION_TABLE_ENTRY PartitionTableEntry; if (!MachDiskReadLogicalSectors (DriveNumber, 0ULL , 1 , (PVOID)DISKREADBUFFER)){ DPRINTM (DPRINT_HWDETECT, "Reading MBR failed\n" );return ;} Buffer = (ULONG*)DISKREADBUFFER; Mbr = (PMASTER_BOOT_RECORD)DISKREADBUFFER; Signature = Mbr->Signature; DPRINTM (DPRINT_HWDETECT, "Signature: %x\n" , Signature);Checksum = 0 ; for (i = 0 ; i < 128 ; i++){ Checksum += Buffer[i]; } Checksum = ~Checksum + 1 ; DPRINTM (DPRINT_HWDETECT, "Checksum: %x\n" , Checksum);reactos_arc_disk_info[reactos_disk_count].Signature = Signature; reactos_arc_disk_info[reactos_disk_count].CheckSum = Checksum; sprintf (ArcName, "multi(0)disk(0)rdisk(%lu)" , reactos_disk_count);strcpy (reactos_arc_strings[reactos_disk_count], ArcName);reactos_arc_disk_info[reactos_disk_count].ArcName = reactos_arc_strings[reactos_disk_count]; reactos_disk_count++; sprintf (ArcName, "multi(0)disk(0)rdisk(%lu)partition(0)" , DriveNumber - 0x80 );FsRegisterDevice (ArcName, &DiskVtbl);i = 1 ; DiskReportError (FALSE);while (DiskGetPartitionEntry (DriveNumber, i, &PartitionTableEntry)){ if (PartitionTableEntry.SystemIndicator != PARTITION_ENTRY_UNUSED){ sprintf (ArcName, "multi(0)disk(0)rdisk(%lu)partition(%lu)" , DriveNumber - 0x80 , i);FsRegisterDevice (ArcName, &DiskVtbl);} i++; } DiskReportError (TRUE);Identifier[0 ] = Hex[(Checksum >> 28 ) & 0x0F ]; Identifier[1 ] = Hex[(Checksum >> 24 ) & 0x0F ]; Identifier[2 ] = Hex[(Checksum >> 20 ) & 0x0F ]; Identifier[3 ] = Hex[(Checksum >> 16 ) & 0x0F ]; Identifier[4 ] = Hex[(Checksum >> 12 ) & 0x0F ]; Identifier[5 ] = Hex[(Checksum >> 8 ) & 0x0F ]; Identifier[6 ] = Hex[(Checksum >> 4 ) & 0x0F ]; Identifier[7 ] = Hex[Checksum & 0x0F ]; Identifier[8 ] = '-' ; Identifier[9 ] = Hex[(Signature >> 28 ) & 0x0F ]; Identifier[10 ] = Hex[(Signature >> 24 ) & 0x0F ]; Identifier[11 ] = Hex[(Signature >> 20 ) & 0x0F ]; Identifier[12 ] = Hex[(Signature >> 16 ) & 0x0F ]; Identifier[13 ] = Hex[(Signature >> 12 ) & 0x0F ]; Identifier[14 ] = Hex[(Signature >> 8 ) & 0x0F ]; Identifier[15 ] = Hex[(Signature >> 4 ) & 0x0F ]; Identifier[16 ] = Hex[Signature & 0x0F ]; Identifier[17 ] = '-' ; Identifier[18 ] = 'A' ; Identifier[19 ] = 0 ; }
函数首先使用MachDiskReadLogicalSectors读取指定硬盘的MBR。对于PC机而言MachDiskReadLogicalSectors使用int 13h中断实现对硬盘的读操作。里面包括了16、32位代码的互转,和本节内容无关,以后再做说明。 MBR结构为。详细信息可以参考(http://en.wikipedia.org/wiki/Master_boot_record )
typedef struct _MASTER_BOOT_RECORD { UCHAR MasterBootRecordCodeAndData[0x1b8 ]; ULONG Signature; USHORT Reserved; PARTITION_TABLE_ENTRY PartitionTable[4 ]; USHORT MasterBootRecordMagic; } MASTER_BOOT_RECORD, *PMASTER_BOOT_RECORD;
GetHarddiskIdentifier在获取了Signature、计算了Checksum后 。 sprintf(ArcName, “multi(0)disk(0)rdisk(%lu)partition(0)”, DriveNumber - 0x80); FsRegisterDevice(ArcName, &DiskVtbl); 生成对应硬盘的ArcName,使用FsRegisterDevice注册这块硬盘,这个函数我们已经看过。注意这里Partition为0,所以0号分区实际表示硬盘本身。 之后
i = 1 ; while (DiskGetPartitionEntry (DriveNumber, i, &PartitionTableEntry)){ if (PartitionTableEntry.SystemIndicator != PARTITION_ENTRY_UNUSED){ sprintf (ArcName, "multi(0)disk(0)rdisk(%lu)partition(%lu)" , DriveNumber - 0x80 , i);FsRegisterDevice (ArcName, &DiskVtbl);} i++; }
DiskGetParititionEntry将会解析DriveNumber对应磁盘的分区表,填充第i个分区的信息到PartitionTableEntry结构。如果分区存在则使用FsRegisterDevice注册分区。 使用刚才计算的CheckSum和Signature组合一个ID返回给调用者。其实这个ID没有被使用过。。。 最后看一下调用FsRegisterDevice时的第二个参数DiskVtbl
static const DEVVTBL DiskVtbl = {DiskClose, DiskGetFileInformation, DiskOpen, DiskRead, DiskSeek, };
这里面包含了对磁盘扇区读写的全部函数。我们之后再介绍。 至此硬盘的及硬盘分区的注册完成。 执行完DetectBiosDisks后,DeviceListHead里面就存放了当前计算机所有的磁盘和分区对应的DEVICE结构。 文件系统的识别和文件的打开 这时Fs模块已经知道的硬盘数量,分区信息。下面来看看一个文件的打开流程。 首先,Freeldr使用的是Arc路径,IDE硬盘以multi(0)disk(0)rdisk(n)开头,文件也是以Arc路径表示的。打开文件的函数在Freeldr\Freeldr\fs\fs.c中。这函数比较长,我们分段阅读。 Freeldr\Freeldr\fs\fs.c
LONG ArcOpen (CHAR* Path, OPENMODE OpenMode, ULONG* FileId) {...... *FileId = MAX_FDS; FileName = strrchr (Path, ')' ); if (!FileName)return EINVAL;FileName++; dwCount = 0 ; for (p = Path; p != FileName; p++)if (*p == '(' && *(p + 1 ) == ')' )dwCount++; dwLength = FileName - Path + dwCount; if (dwCount != 0 ){ DeviceName = MmHeapAlloc (FileName - Path + dwCount); if (!DeviceName)return ENOMEM;for (p = Path, q = DeviceName; p != FileName; p++){ *q++ = *p; if (*p == '(' && *(p + 1 ) == ')' )*q++ = '0' ; } } else DeviceName = Path; ......
这个函数有三个参数Path是文件名的Arc路径,如multi(0)disk(0)rdisk(0)partition(1)Freeldr.sys就表示C盘中的Freeldr.sys文件。 OpenMode是打开模式(OpenReadOnly、OpenReadWrite等)。 如果打开成功,文件句柄将通过FileId参数返回。 首先这一部分代码分理出Arc磁盘路径中的”()”替换成”(0)”并存入DeviceName中,如multi()disk()rdisk()partition(1)Freeldr.sys处理后,DeviceName将指向multi(0)disk(0)rdisk(0)partition(1)。注意这个DeviceName是不以NULL结尾的。。。这是个很蛋疼的设计。 FileName会指向Arc路径中的文件名部分,上面的例子将是Freeldr.sys。 文件打开分为两步,第一步是开个文件所在的设备、创建设备的句柄。第二部才是打开文件本身。 这里是第一步打开设备的代码。
...... pEntry = DeviceListHead.Flink; while (pEntry != &DeviceListHead){ pDevice = CONTAINING_RECORD (pEntry, DEVICE, ListEntry); if (strncmp (pDevice->Prefix, DeviceName, dwLength) == 0 ){ if (pDevice->ReferenceCount == 0 ){ for (DeviceId = 0 ; DeviceId < MAX_FDS; DeviceId++)if (!FileData[DeviceId].FuncTable)break ;if (DeviceId == MAX_FDS)return EMFILE;FileData[DeviceId].FuncTable = pDevice->FuncTable; ret = pDevice->FuncTable->Open (pDevice->Prefix, DeviceOpenMode, &DeviceId); if (ret != ESUCCESS){ FileData[DeviceId].FuncTable = NULL ; return ret;} else if (!*FileName){ *FileId = DeviceId; pDevice->ReferenceCount++; return ESUCCESS;} FileData[DeviceId].FileFuncTable = FatMount (DeviceId); if (!FileData[DeviceId].FileFuncTable)FileData[DeviceId].FileFuncTable = NtfsMount (DeviceId); if (!FileData[DeviceId].FileFuncTable)FileData[DeviceId].FileFuncTable = Ext2Mount (DeviceId); if (!FileData[DeviceId].FileFuncTable){ pDevice->FuncTable->Close (DeviceId); FileData[DeviceId].FuncTable = NULL ; return ENODEV;} pDevice->DeviceId = DeviceId; } else { DeviceId = pDevice->DeviceId; } pDevice->ReferenceCount++; break ;} pEntry = pEntry->Flink; } if (pEntry == &DeviceListHead)return ENODEV;
一个循环,遍历DEVICE链表,找到DEVICE->Prefix (磁盘、分区的Arc路径,上一节说过)和刚刚分解出来的DeviceName相等的节点。如果没有则函数直接失败。 找到DEVICE节点后判断DEVICE->ReferenceCount是否为0。这个代表该DEVICE被打开的次数,如果ReferenceCount不为0,说明DEVICE已经被打开。那个直接从Device->DeviceId中获得设备的文件句柄。可以看出无论打开一个设备多少次,只会有ReferenceCount的变化,而句柄都是相同的。所以如果设备打开两次,SEEK时会相互影响。读写之前最好重新调用SEEK函数。 当DEVICE->ReferenceCount为0时是Freeldr需要调用进行打开和文件系统的识别。我们仔细读读。
for (DeviceId = 0 ; DeviceId < MAX_FDS; DeviceId++)if (!FileData[DeviceId].FuncTable)break ;if (DeviceId == MAX_FDS)return EMFILE;
首先在FileData数组中找到空闲项,数组的索引即将成为设备句柄。
FileData[DeviceId].FuncTable = pDevice->FuncTable; ret = pDevice->FuncTable->Open (pDevice->Prefix, DeviceOpenMode, &DeviceId); if (ret != ESUCCESS){ FileData[DeviceId].FuncTable = NULL ; return ret;} else if (!*FileName){ *FileId = DeviceId; pDevice->ReferenceCount++; return ESUCCESS;}
之后把DEVICE中存储的设别操作函数数组FuncTable赋值给对应FileData中的FuncTable。之后对该句柄的读写操作将直接传递给FileData.FuncTable中的函数。 调用FuncTable->Open打开设备。上面我们看过这个函数实际是freeldr\freeldr\arch\i386\hardware.c中的DiskOpen。 打开成功后,如果FileName(需要打开的文件名)为空,说明这次请求只打开设备,于是直接返回设备的句柄。 如果不为空,则下面开始识别分区格式,打开文件的操作。 在继续读ArcOpen函数前我们先看看DiskOpen在打开设备时都做了什么。
ArcOpen -> DiskOpen (freeldr\freeldr\arch\i386\hardware.c) static LONG DiskOpen (CHAR* Path, OPENMODE OpenMode, ULONG* FileId) {...... if (!DissectArcPath (Path, FileName, &DriveNumber, &DrivePartition))return EINVAL;if (DrivePartition == 0xff ){ SectorSize = 2048 ; } else { SectorSize = 512 ; } if (DrivePartition != 0xff && DrivePartition != 0 ){ if (!DiskGetPartitionEntry (DriveNumber, DrivePartition, &PartitionTableEntry))return EINVAL;SectorOffset = PartitionTableEntry.SectorCountBeforePartition; SectorCount = PartitionTableEntry.PartitionSectorCount; } Context = MmHeapAlloc (sizeof (DISKCONTEXT)); if (!Context)return ENOMEM;Context->DriveNumber = DriveNumber; Context->SectorSize = SectorSize; Context->SectorOffset = SectorOffset; Context->SectorCount = SectorCount; Context->SectorNumber = 0 ; FsSetDeviceSpecific (*FileId, Context);return ESUCCESS;}
这个函数非常简单,使用DissectArcPath根据设备的Arc路径分解出文件名FileName、BIOS驱动器号DriveNumber、和分区号DrivePartition(第0个分区的编号是1,0代表整个硬盘) 之后确定扇区大小,分区开始的扇区号、分区扇区数等信息,存入DISKCONTEXT结构。使用FsSetDeviceSpecific和FildId相关联。 还记得FILEDATA的结构么?FsSetDeviceSpecific就是填充里面的Specific指针 :)
VOID FsSetDeviceSpecific (ULONG FileId, VOID* Specific) {if (FileId >= MAX_FDS || !FileData[FileId].FuncTable)return ;FileData[FileId].Specific = Specific; }
实际上DiskOpen的作用就是获得该设备(分区)的基本信息——BIOS驱动器号、扇区大小、开始扇区号、扇区数量和当前读写指针(SectorNumber)。生成DISKCONTENT结构使用FsSetDeviceSpecific和FileID绑定。 现在我们回到ArcOpen函数,希望你还记得 :)
FileData[DeviceId].FileFuncTable = FatMount (DeviceId); if (!FileData[DeviceId].FileFuncTable)FileData[DeviceId].FileFuncTable = NtfsMount (DeviceId); if (!FileData[DeviceId].FileFuncTable)FileData[DeviceId].FileFuncTable = Ext2Mount (DeviceId); if (!FileData[DeviceId].FileFuncTable){ pDevice->FuncTable->Close (DeviceId); FileData[DeviceId].FuncTable = NULL ; return ENODEV;} pDevice->DeviceId = DeviceId;
现在FileData[DeviceId]已经代表刚刚打开的设备了,开始挂载分区。啥叫挂载分区,就是让文件系统提供个接口,能让我们操作分区里面的文件。而这个接口就是个DEVVTBL指针,和直接操作硬盘的接口一样,只不过这次这个可以操作文件了。如果分区识别成功,XxxMount函数将会返回另外一个DEVVTBL指针数组,这个指针赋值给设备对象的FileFuncTable成员。使用这个指针数组就可以在文件级别操作了。比如打开freeldr.sys文件就可以调用FileData[DeviceId].FileFuncTable->open函数。FileFuncTable和FuncTable是不同的哦! :) 注意FileFuncTable其实是不直接使用的,这个指针的作用是为之后打开的文件对应的FileData.FileTable赋值。于是操作文件和操作磁盘都是用对应的FileData.FileTable,实现形式上的统一。而且这种架构还可以轻易的实现将一个文件虚拟成为一个分区,只要为文件对象调用XxxMount并且给FileFuncTable域赋值就可以了,非常易于扩展。Freeldr并没有实现这种功能,文件的FileData.FileFuncTable没有使用~ 下面我们就来看看第二步,打开文件 首先为文件找一个空闲的FileData
for (i = 0 ; i < MAX_FDS; i++)if (!FileData[i].FuncTable)break ;if (i == MAX_FDS)return EMFILE;
跳过文件名开始的 “" 字符
if (*FileName == '\\' )FileName++;
我们前面说的,为FileData.FuncTable赋值。FileData.DeviceId是文件所在分区的句柄。FuncTable内部函数将通过这个句柄调用读写分区内容,为用户提供文件的读写接口。
FileData[i].FuncTable = FileData[DeviceId].FileFuncTable; FileData[i].DeviceId = DeviceId; *FileId = i; ret = FileData[i].FuncTable->Open (FileName, OpenMode, FileId); if (ret != ESUCCESS){ FileData[i].FuncTable = NULL ; *FileId = MAX_FDS; }
至此打开文件的操作结束。这里略去了XxxMount和文件的Open函数。以后再说。 :)