[14]Windows内核情景分析 --- 文件系统
文件系统
一台机器上可以安装很多物理介质来存放资料(如磁盘、光盘、软盘、U盘等)。各种物理介质千差万别,都配备有各自的驱动程序,为了统一地访问这些物理介质,windows设计了文件系统机制。应用程序要访问存储在那些物理介质上的资料时,无需直接访问,只需借助文件系统即可对其有效访问。各种物理介质的存储方式千差万别,文件系统则按照‘文件’的概念,把要存储的资料以文件为单位进行存放,然后,读取的时候也以文件为单位进行读取。当应用程序要访问资料时,只需指明‘文件名+文件内偏移’,文件系统自然就能找到实际要访问的东西物理存放在物理介质内的何处。 这样,系统就能有效的管理物理介质上的资料。
【说到底,文件系统本质上其实就是 一种如何组织数据在磁盘上进行存放的方式而已】
比如,应用程序指定要读取f文件内偏移为o处的一个字节内容,文件系统会计算出偏移o落在该文件的哪一个簇中,以及簇内偏移co。然后算出该f文件的那个簇在磁盘上的位置,加上簇内偏移co,便可得出具体的磁盘偏移了(不过这个磁盘偏移仍只是一个逻辑磁盘偏移,即卷偏移,下层的磁盘驱动会再将卷偏移转换为物理磁盘偏移,最终再将物理磁盘偏移转换成盘片号、柱面号、扇区号三维的物理地址)
分区:磁盘设备才有分区。不过,光盘、U盘、软盘等整个设备也看做是一个分区。
磁盘卷:即上面的磁盘分区。磁盘卷设备对象名像\Device\HarddiskN\PartitionN形式
文件卷:挂载(即绑定)在分区上的文件系统卷。文件卷设备对象都是无名对象
每个磁盘卷都有一个C,D,E之类的盘符,由系统分配。
系统在启动初始化阶段,会为本机上的所有物理存储介质中的所有分区分配盘符,我们看看是如何分配的。
VOID //给所有分区分配盘符
xHalIoAssignDriveLetters(IN PLOADER_PARAMETER_BLOCK LoaderBlock,
IN PSTRING NtDeviceName,
OUT PUCHAR NtSystemPath,
OUT PSTRING NtSystemPathString)
{
Status = RtlAnsiStringToUnicodeString(&BootDevice,NtDeviceName,TRUE);
//获取系统中的硬件配置信息(磁盘、光盘、软盘个数)
ConfigInfo = IoGetConfigurationInformation();
RDiskCount = xHalpGetRDiskCount();//获取可引导磁盘个数(指物理磁盘)
Buffer1 = (PWSTR)ExAllocatePool(PagedPool,64 * sizeof(WCHAR));
Buffer2 = (PWSTR)ExAllocatePool(PagedPool,32 * sizeof(WCHAR));
PartialInformation = (PKEY_VALUE_PARTIAL_INFORMATION)ExAllocatePool(PagedPool,
sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(REG_DISK_MOUNT_INFO));
// DiskMountInfo表示盘符分配信息
DiskMountInfo = (PREG_DISK_MOUNT_INFO) PartialInformation->Data;
RtlInitUnicodeString(&UnicodeString1, L"\\Registry\\Machine\\SYSTEM\\MountedDevices");
InitializeObjectAttributes(&ObjectAttributes,&UnicodeString1,OBJ_CASE_INSENSITIVE,
NULL,NULL);
//这个键记录了上次开机时各个盘符的分配信息
ZwOpenKey(&hKey,KEY_ALL_ACCESS,&ObjectAttributes);
//为每个磁盘的第一个分区创建一个符号链接
for (i = 0; i < ConfigInfo->DiskCount; i++)
{
swprintf(Buffer1,L"\\Device\\Harddisk%d\\Partition0",i);
RtlInitUnicodeString(&UnicodeString1,Buffer1);
InitializeObjectAttributes(&ObjectAttributes,&UnicodeString1,0,NULL,NULL);
Status = ZwOpenFile(&FileHandle,FILE_READ_DATA | SYNCHRONIZE,&ObjectAttributes,
&StatusBlock,FILE_SHARE_READ,FILE_SYNCHRONOUS_IO_NONALERT);
if (NT_SUCCESS(Status))
{
ZwClose(FileHandle);
swprintf(Buffer2,L"\\??\\PhysicalDrive%d",i);
RtlInitUnicodeString(&UnicodeString2,Buffer2);
IoCreateSymbolicLink(&UnicodeString2,&UnicodeString1);
}
}
//分区表数组
LayoutArray = ExAllocatePool(NonPagedPool,ConfigInfo->DiskCount * sizeof(PDRIVE_LAYOUT_INFORMATION));
RtlZeroMemory(LayoutArray,ConfigInfo->DiskCount * sizeof(PDRIVE_LAYOUT_INFORMATION));
for (i = 0; i < ConfigInfo->DiskCount; i++)
{
swprintf(Buffer1, L"\\Device\\Harddisk%d\\Partition0",i);
RtlInitUnicodeString(&UnicodeString1,Buffer1);
xHalQueryDriveLayout(&UnicodeString1,&LayoutArray[i]);//读取每个物理磁盘的分区表
for (j = 0; j < LayoutArray[i]->PartitionCount; j++)
LayoutArray[i]->PartitionEntry[j].RewritePartition = FALSE;//初始为尚未分配盘符
}
//将那些上次开机时分配过的盘符(记录在注册表中) 试图再次 分配给相同分区
if (hKey)
{
for (k = 2; k < 26; k++) //C-Z
{
swprintf(Buffer1, DiskMountString, L'A' + k);
RtlInitUnicodeString(&UnicodeString1, Buffer1);
Status = ZwQueryValueKey(hKey,&UnicodeString1,KeyValuePartialInformation,
PartialInformation,sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(REG_DISK_MOUNT_INFO), &Length);
if (NT_SUCCESS(Status) && PartialInformation->Type == REG_BINARY &&
PartialInformation->DataLength == sizeof(REG_DISK_MOUNT_INFO))
{
{
BOOLEAN Found = FALSE;
for (i = 0; i < ConfigInfo->DiskCount; i++)
{
if (LayoutArray[i] && LayoutArray[i]->Signature &&
LayoutArray[i]->Signature == DiskMountInfo->Signature)//原磁盘
{
for (j = 0; j < LayoutArray[i]->PartitionCount; j++)
{
//if 分区位置没变(也即分区表没有变化)
if(LayoutArray[i]->PartitionEntry[j].StartingOffset.QuadPart == DiskMountInfo->StartingOffset.QuadPart)
{
//if 分区没损坏 && 尚未分配盘符
If(IsRecognizedPartition(LayoutArray[i]->PartitionEntry[j].PartitionType) &&
!LayoutArray[i]->PartitionEntry[j].RewritePartition)
{
swprintf(Buffer2,
L"\\Device\\Harddisk%d\\Partition%d",i, LayoutArray[i]->PartitionEntry[j].PartitionNumber);
RtlInitUnicodeString(&UnicodeString2,Buffer2);
//分配指定盘符给指定分区
Found = HalpAssignDrive(&UnicodeString2,//分区
k,//指定盘符
DOSDEVICE_DRIVE_FIXED,
DiskMountInfo->Signature,
DiskMountInfo->StartingOffset,
NULL,&BootDevice,NtSystemPath);
LayoutArray[i]->PartitionEntry[j].RewritePartition = TRUE;//标记该分区已分配盘符
}
break;
}
}
}
}
}
}
}
}
//上面分配了了那些记录的盘符后,再继续为尚未分配盘符的分区分配盘符
//下面先给第一个磁盘的引导分区分配盘符
if (RDiskCount > 0)
{
Status = xHalpGetDiskNumberFromRDisk(0, &DiskNumber);//第一个可引导的磁盘号
if (NT_SUCCESS(Status) && DiskNumber < ConfigInfo->DiskCount &&LayoutArray[DiskNumber])
{
for (j = 0; j < NUM_PARTITION_TABLE_ENTRIES && j < LayoutArray[DiskNumber]->PartitionCount; j++)
{
if ((LayoutArray[DiskNumber]->PartitionEntry[j].BootIndicator == TRUE) && IsRecognizedPartition(LayoutArray[DiskNumber]->PartitionEntry[j].PartitionType))
{
if (LayoutArray[DiskNumber]->PartitionEntry[j].RewritePartition == FALSE)
{
swprintf(Buffer2,L"\\Device\\Harddisk%lu\\Partition%d",DiskNumber,
LayoutArray[DiskNumber]->PartitionEntry[j].PartitionNumber);
RtlInitUnicodeString(&UnicodeString2,Buffer2);
HalpAssignDrive(&UnicodeString2,//引导分区
AUTO_DRIVE,//自动从C-Z的顺序选择一个未用盘符
DOSDEVICE_DRIVE_FIXED,
LayoutArray[DiskNumber]->Signature,
LayoutArray[DiskNumber]->PartitionEntry[j].StartingOffset,
hKey,&BootDevice,NtSystemPath);
LayoutArray[DiskNumber]->PartitionEntry[j].RewritePartition = TRUE;
}
break;
}
}
}
}
//再为每个可引导磁盘的主分区分配盘符
for (RDisk = 0; RDisk < RDiskCount; RDisk++)
{
Status = xHalpGetDiskNumberFromRDisk(RDisk, &DiskNumber);
if (NT_SUCCESS(Status) && DiskNumber < ConfigInfo->DiskCount &&
LayoutArray[DiskNumber])
{
for (j = 0; (j < NUM_PARTITION_TABLE_ENTRIES) && (j < LayoutArray[DiskNumber]->PartitionCount); j++)
{
if (LayoutArray[DiskNumber]->PartitionEntry[j].RewritePartition == FALSE &&
IsRecognizedPartition(LayoutArray[DiskNumber]->PartitionEntry[j].PartitionType))
{
swprintf(Buffer2,L"\\Device\\Harddisk%d\\Partition%d",DiskNumber,
LayoutArray[DiskNumber]->PartitionEntry[j].PartitionNumber);
RtlInitUnicodeString(&UnicodeString2,Buffer2);
HalpAssignDrive(&UnicodeString2,
AUTO_DRIVE,//自动分配盘符
DOSDEVICE_DRIVE_FIXED,
LayoutArray[DiskNumber]->Signature,
LayoutArray[DiskNumber]->PartitionEntry[j].StartingOffset,
hKey,&BootDevice,NtSystemPath);
LayoutArray[DiskNumber]->PartitionEntry[j].RewritePartition = TRUE;
}
}
}
}
//再为每个可引导磁盘的所有扩展分区分配盘符
for (RDisk = 0; RDisk < RDiskCount; RDisk++)
{
Status = xHalpGetDiskNumberFromRDisk(RDisk, &DiskNumber);
if (NT_SUCCESS(Status) &&DiskNumber < ConfigInfo->DiskCount &&LayoutArray[DiskNumber])
{
for (j = NUM_PARTITION_TABLE_ENTRIES; j < LayoutArray[DiskNumber]->PartitionCount; j++)
{
if (IsRecognizedPartition(LayoutArray[DiskNumber]->PartitionEntry[j].PartitionType) &&
LayoutArray[DiskNumber]->PartitionEntry[j].RewritePartition == FALSE &&
LayoutArray[DiskNumber]->PartitionEntry[j].PartitionNumber != 0)
{
swprintf(Buffer2,L"\\Device\\Harddisk%d\\Partition%d",DiskNumber,
LayoutArray[DiskNumber]->PartitionEntry[j].PartitionNumber);
RtlInitUnicodeString(&UnicodeString2,Buffer2);
HalpAssignDrive(&UnicodeString2,
AUTO_DRIVE,
DOSDEVICE_DRIVE_FIXED,
LayoutArray[DiskNumber]->Signature,
LayoutArray[DiskNumber]->PartitionEntry[j].StartingOffset,
hKey,&BootDevice,NtSystemPath);
LayoutArray[DiskNumber]->PartitionEntry[j].RewritePartition = TRUE;
}
}
}
}
//再为剩余所有非可引导盘的所有分区分配盘符
for (DiskNumber = 0; DiskNumber < ConfigInfo->DiskCount; DiskNumber++)
{
if (LayoutArray[DiskNumber])
{
for (j = 0; (j < NUM_PARTITION_TABLE_ENTRIES) && (j < LayoutArray[DiskNumber]->PartitionCount); j++)
{
if (LayoutArray[DiskNumber]->PartitionEntry[j].RewritePartition == FALSE &&
IsRecognizedPartition(LayoutArray[DiskNumber]->PartitionEntry[j].PartitionType))
{
swprintf(Buffer2,L"\\Device\\Harddisk%d\\Partition%d",DiskNumber,
LayoutArray[DiskNumber]->PartitionEntry[j].PartitionNumber);
RtlInitUnicodeString(&UnicodeString2,Buffer2);
HalpAssignDrive(&UnicodeString2,
AUTO_DRIVE,
DOSDEVICE_DRIVE_FIXED,
LayoutArray[DiskNumber]->Signature,
LayoutArray[DiskNumber]->PartitionEntry[j].StartingOffset,
hKey,&BootDevice,NtSystemPath);
LayoutArray[DiskNumber]->PartitionEntry[j].RewritePartition = TRUE;
}
}
}
}
//再为所有扩展分区分配盘符
for (DiskNumber = 0; DiskNumber < ConfigInfo->DiskCount; DiskNumber++)
{
if (LayoutArray[DiskNumber])
{
for (j = NUM_PARTITION_TABLE_ENTRIES; j < LayoutArray[DiskNumber]->PartitionCount; j++)
{
if (IsRecognizedPartition(LayoutArray[DiskNumber]->PartitionEntry[j].PartitionType) &&
LayoutArray[DiskNumber]->PartitionEntry[j].RewritePartition == FALSE &&
LayoutArray[DiskNumber]->PartitionEntry[j].PartitionNumber != 0)
{
swprintf(Buffer2,L"\\Device\\Harddisk%d\\Partition%d",DiskNumber,
LayoutArray[DiskNumber]->PartitionEntry[j].PartitionNumber);
RtlInitUnicodeString(&UnicodeString2,Buffer2);
HalpAssignDrive(&UnicodeString2,
AUTO_DRIVE,
DOSDEVICE_DRIVE_FIXED,
LayoutArray[DiskNumber]->Signature,
LayoutArray[DiskNumber]->PartitionEntry[j].StartingOffset,
hKey,&BootDevice,NtSystemPath);
LayoutArray[DiskNumber]->PartitionEntry[j].RewritePartition = TRUE;
}
}
}
}
//再为所有可移动磁盘分配盘符
for (i = 0; i < ConfigInfo->DiskCount; i++)
{
if (LayoutArray[i])
{
if (LayoutArray[i]->PartitionCount == 1 &&
LayoutArray[i]->PartitionEntry[0].PartitionType == 0)
{
swprintf(Buffer2,L"\\Device\\Harddisk%d\\Partition1",i);
RtlInitUnicodeString(&UnicodeString2,Buffer2);
HalpAssignDrive(&UnicodeString2,AUTO_DRIVE,DOSDEVICE_DRIVE_REMOVABLE,
0,RtlConvertLongToLargeInteger(0),
hKey,&BootDevice,NtSystemPath);
}
}
}
//为所有软盘分配盘符
for (i = 0; i < ConfigInfo->FloppyCount; i++)
{
swprintf(Buffer1,L"\\Device\\Floppy%d",i);
RtlInitUnicodeString(&UnicodeString1,Buffer1);
HalpAssignDrive(&UnicodeString1, (i < 2) ? i : AUTO_DRIVE,
DOSDEVICE_DRIVE_REMOVABLE,0,RtlConvertLongToLargeInteger(0),
hKey,&BootDevice,NtSystemPath);
}
//为所有光盘分配盘符
for (i = 0; i < ConfigInfo->CdRomCount; i++)
{
swprintf(Buffer1,L"\\Device\\CdRom%d",i);
RtlInitUnicodeString(&UnicodeString1,Buffer1);
HalpAssignDrive(&UnicodeString1,AUTO_DRIVE,DOSDEVICE_DRIVE_CDROM,
0,RtlConvertLongToLargeInteger(0),
hKey,&BootDevice,NtSystemPath);
}
}
如上,该函数会为所有分区分配盘符。具体的分配工作由下面的函数完成
BOOLEAN
HalpAssignDrive(IN PUNICODE_STRING PartitionName,//目标分区
IN ULONG DriveNumber,//手动指定盘符 或 自动分配
IN UCHAR DriveType,//驱动器类型
IN ULONG Signature,//驱动器签名
IN LARGE_INTEGER StartingOffset,//分区的位置
IN HANDLE hKey,//记录盘符分配情况的键
IN PUNICODE_STRING BootDevice,//引导分区的名字
OUT PUCHAR NtSystemPath)
{
WCHAR DriveNameBuffer[16];
UNICODE_STRING DriveName;
ULONG i;
NTSTATUS Status;
REG_DISK_MOUNT_INFO DiskMountInfo;
if ((DriveNumber != AUTO_DRIVE) && (DriveNumber < 26))
{
//DriveMap:盘符分配位图
if ((ObSystemDeviceMap->DriveMap & (1 << DriveNumber)) != 0)//if已分配
return FALSE;
}
else
{
DriveNumber = AUTO_DRIVE;
for (i = 2; i < 26; i++)
{
if ((ObSystemDeviceMap->DriveMap & (1 << i)) == 0)//寻找一个空闲的未分配盘符
{
DriveNumber = i;
break;
}
}
if (DriveNumber == AUTO_DRIVE)
return FALSE;
}
ObSystemDeviceMap->DriveMap |= (1 << DriveNumber);
ObSystemDeviceMap->DriveType[DriveNumber] = DriveType;
/* Build drive name */
swprintf(DriveNameBuffer,L"\\??\\%C:",'A' + DriveNumber);
RtlInitUnicodeString(&DriveName,DriveNameBuffer);
//关键,为指定分区创建一个符号链接,就是为其分配一个盘符
Status = IoCreateSymbolicLink(&DriveName,PartitionName);
//if 需要将盘符分配信息保存在注册表中(以便下次把这个盘符分配给同样的分区)
if (hKey && DriveType == DOSDEVICE_DRIVE_FIXED && Signature)
{
DiskMountInfo.Signature = Signature;
DiskMountInfo.StartingOffset = StartingOffset;
swprintf(DriveNameBuffer, DiskMountString, L'A' + DriveNumber);
RtlInitUnicodeString(&DriveName, DriveNameBuffer);
Status = ZwSetValueKey(hKey,&DriveName,0,REG_BINARY,&DiskMountInfo,
sizeof(DiskMountInfo));
}
//if 这是一个引导分区
if (RtlCompareUnicodeString(PartitionName, BootDevice, FALSE) == 0)
*NtSystemPath = (UCHAR)('A' + DriveNumber);//记录系统盘符
return TRUE;
}
典型的,C盘符会分配第一个磁盘的第一个分区。这样,\??\C: 就会链接指向 \Device\Harddisk0\Partition0。\??\C:\Windows\Explorer.exe 就会链接指向\Device\Harddisk0\Partition0\Windows\Explorer.exe。
当每个分区分配了盘符后,应用程序就可以指定一个文件全路径,调用CreateFile创建、打开文件(相对路径会被自动转换成全路径)。CreateFile函数的实质作用与这个函数的命名矛盾。或者说,这个函数的命名有问题,似乎应该改为OpenFile才能表达这个函数的意思。因为CreateFile的用途是用来打开一个文件,如果文件不存在,就先创建,再打开,所以‘打开’才是其最终目的。我们看看文件是如何打开的。
CreateFile函数实质是用来打开任意有名称的内核对象的,‘打开文件 ’的说法其实也不对,应该叫‘打开卷设备对象,创建一个文件对象,返回文件句柄’。
综上:CreateFile其实改名为OpenKernelObject更贴切。
注意文件对象与文件这两种概念的区别。文件就是指物理介质上的一个个文件,而文件对象则是每次打开设备时创建生成的一个内核对象,记录了那次的打开上下文。因此,文件对象实质上是‘打开描述符’。这一点务必要注意区分。
HANDLE WINAPI CreateFileW ( //打开内核对象
LPCWSTR lpFileName,//全路径、相对路径、UNC路径、设备名、符号链接名等
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile)//模板文件仅用来复制EA附加属性
{
OBJECT_ATTRIBUTES ObjectAttributes;
IO_STATUS_BLOCK IoStatusBlock;
UNICODE_STRING NtPathU;
HANDLE FileHandle;
NTSTATUS Status;
ULONG FileAttributes, Flags = 0;
PVOID EaBuffer = NULL;
ULONG EaLength = 0;
switch (dwCreationDisposition)
{
case CREATE_NEW://强制创建一个文件(原文件不能存在)
dwCreationDisposition = FILE_CREATE;
break;
case CREATE_ALWAYS://原文件存在就覆盖
dwCreationDisposition = FILE_OVERWRITE_IF;
break;
case OPEN_EXISTING://原文件必须存在
dwCreationDisposition = FILE_OPEN;
break;
case OPEN_ALWAYS://原文件不存在就创建
dwCreationDisposition = FILE_OPEN_IF;
break;
case TRUNCATE_EXISTING://原文件存在就清空内容
dwCreationDisposition = FILE_OVERWRITE;
break;
}
//这个函数可用于打开控制台输入输出缓冲
if (0 == _wcsicmp(L"CONOUT$", lpFileName)|| 0 == _wcsicmp(L"CONIN$", lpFileName))
{
return OpenConsoleW(lpFileName,dwDesiredAccess,
lpSecurityAttributes ? lpSecurityAttributes->bInheritHandle : FALSE,
FILE_SHARE_READ | FILE_SHARE_WRITE);
}
if (!(dwFlagsAndAttributes & FILE_FLAG_OVERLAPPED))//同步模式
Flags |= FILE_SYNCHRONOUS_IO_NONALERT;//使用同步IO模式
if(dwFlagsAndAttributes & FILE_FLAG_WRITE_THROUGH)
Flags |= FILE_WRITE_THROUGH;
if(dwFlagsAndAttributes & FILE_FLAG_NO_BUFFERING)//不经过文件缓冲,直接读写磁盘
Flags |= FILE_NO_INTERMEDIATE_BUFFERING;
if(dwFlagsAndAttributes & FILE_FLAG_RANDOM_ACCESS)
Flags |= FILE_RANDOM_ACCESS;
if(dwFlagsAndAttributes & FILE_FLAG_SEQUENTIAL_SCAN)
Flags |= FILE_SEQUENTIAL_ONLY;
if(dwFlagsAndAttributes & FILE_FLAG_DELETE_ON_CLOSE)//文件句柄关完就删除磁盘文件(临时文件)
Flags |= FILE_DELETE_ON_CLOSE;
if(dwFlagsAndAttributes & FILE_FLAG_BACKUP_SEMANTICS) 。。。
if(dwFlagsAndAttributes & FILE_FLAG_OPEN_REPARSE_POINT)
Flags |= FILE_OPEN_REPARSE_POINT;
if(dwFlagsAndAttributes & FILE_FLAG_OPEN_NO_RECALL)
Flags |= FILE_OPEN_NO_RECALL;
FileAttributes = (dwFlagsAndAttributes & (FILE_ATTRIBUTE_VALID_FLAGS & ~FILE_ATTRIBUTE_DIRECTORY));
//文件句柄默认总是可用于WaitFor和查询属性
dwDesiredAccess |= SYNCHRONIZE | FILE_READ_ATTRIBUTES;
//将DOS相对路径补全为DOS全路径,然后转换为NT全路径格式,因为win32需要DOS格式,而内核只能识别NT格式的路径(即\??\开头的NT路径)。下面的RtlDosPathNameToNtPathName函数具体的可将:
C:\路径 转换成 \??\C:\路径
\\主机\共享名\路径 转换成 \??\UNC\主机\共享名\路径
\\.\符号链接 转换成 \??\符号链接
相对路径 转换成 全路径 再转换成 \??\全路径
if (!RtlDosPathNameToNtPathName_U (lpFileName,&NtPathU,NULL,NULL))
{
SetLastError(ERROR_PATH_NOT_FOUND);
return INVALID_HANDLE_VALUE;
}
if (hTemplateFile != NULL) //复制模板文件的EA附加属性
{
FILE_EA_INFORMATION EaInformation;
for (;;)
{
//查询附加属性块的大小
Status = NtQueryInformationFile(hTemplateFile,&IoStatusBlock,&EaInformation,
sizeof(FILE_EA_INFORMATION),FileEaInformation);
if (NT_SUCCESS(Status) && (EaInformation.EaSize != 0))
{
EaBuffer = RtlAllocateHeap(RtlGetProcessHeap(),0,EaInformation.EaSize);
//查询附加属性块的内容
Status = NtQueryEaFile(hTemplateFile,&IoStatusBlock,EaBuffer,
EaInformation.EaSize,FALSE,NULL,0,NULL,TRUE);
if (NT_SUCCESS(Status))
{
EaLength = EaInformation.EaSize;
break;
}
Else。。。
}
else
break;
}
}
InitializeObjectAttributes(&ObjectAttributes,&NtPathU,0,NULL,NULL);
if (lpSecurityAttributes)
{
if(lpSecurityAttributes->bInheritHandle)
ObjectAttributes.Attributes |= OBJ_INHERIT;
ObjectAttributes.SecurityDescriptor = lpSecurityAttributes->lpSecurityDescriptor;
}
if(!(dwFlagsAndAttributes & FILE_FLAG_POSIX_SEMANTICS))
ObjectAttributes.Attributes |= OBJ_CASE_INSENSITIVE;
//关键。调用这个系统服务
Status = NtCreateFile (&FileHandle,//返回内部创建的那个文件对象的句柄
dwDesiredAccess,
&ObjectAttributes,//NT路径与其他属性
&IoStatusBlock,NULL,
FileAttributes,
dwShareMode,
dwCreationDisposition,
Flags,
EaBuffer,//附加属性
EaLength);
if (!NT_SUCCESS(Status))
{
if (Status == STATUS_OBJECT_NAME_COLLISION && dwCreationDisposition == FILE_CREATE)
SetLastError( ERROR_FILE_EXISTS );
else
SetLastErrorByStatus (Status);
return INVALID_HANDLE_VALUE;
}
if (dwCreationDisposition == FILE_OPEN_IF)
SetLastError(IoStatusBlock.Information == FILE_OPENED ? ERROR_ALREADY_EXISTS : 0);
else if (dwCreationDisposition == FILE_OVERWRITE_IF)
SetLastError(IoStatusBlock.Information == FILE_OVERWRITTEN ? ERROR_ALREADY_EXISTS : 0);
return FileHandle;//返回的文件句柄 就是 那个文件对象的句柄
}
CreateFile内部会调用系统服务NtCreateFile,继续看(以打开文件C:\Windows\Explorer.exe为例)
NTSTATUS
NtCreateFile(PHANDLE FileHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,//文件路径为‘\??\C:\Windows\Explorer.exe’
PIO_STATUS_BLOCK IoStatusBlock,
PLARGE_INTEGER AllocateSize,
ULONG FileAttributes,
ULONG ShareAccess,
ULONG CreateDisposition,
ULONG CreateOptions,
PVOID EaBuffer,
ULONG EaLength)
{
return IoCreateFile(FileHandle,
DesiredAccess,
ObjectAttributes,
IoStatusBlock,
AllocateSize,
FileAttributes,
ShareAccess,
CreateDisposition,
CreateOptions,
EaBuffer,
EaLength,
CreateFileTypeNone,//文件类型为普通文件
NULL,//传NULL
0);//传0
}
进入IO管理器
NTSTATUS
IoCreateFile(OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,//NT路径与其他属性
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PLARGE_INTEGER AllocationSize OPTIONAL,//文件创建的初始分配大小
IN ULONG FileAttributes,
IN ULONG ShareAccess,
IN ULONG Disposition,
IN ULONG CreateOptions,
IN PVOID EaBuffer OPTIONAL,
IN ULONG EaLength,
IN CREATE_FILE_TYPE CreateFileType,
IN PVOID ExtraCreateParameters OPTIONAL,
IN ULONG Options)
{
KPROCESSOR_MODE AccessMode;
HANDLE LocalHandle = 0;
LARGE_INTEGER SafeAllocationSize;
PVOID SystemEaBuffer = NULL;
NTSTATUS Status;
OPEN_PACKET OpenPacket;
ULONG EaErrorOffset;
if (Options & IO_NO_PARAMETER_CHECKING)
AccessMode = KernelMode;//内核模式发出的系统调用可以不用检查参数
else
AccessMode = ExGetPreviousMode();//采用实际的模式
if (AccessMode != KernelMode)//用户模式的调用请求必须检查参数
{
_SEH2_TRY
{
ProbeForWriteHandle(FileHandle);
ProbeForWriteIoStatusBlock(IoStatusBlock);
if (AllocationSize)
SafeAllocationSize = ProbeForReadLargeInteger(AllocationSize);
else
SafeAllocationSize.QuadPart = 0;
if ((EaBuffer) && (EaLength))
{
ProbeForRead(EaBuffer,EaLength,sizeof(ULONG));
SystemEaBuffer = ExAllocatePoolWithTag(NonPagedPool,EaLength,TAG_EA);
RtlCopyMemory(SystemEaBuffer, EaBuffer, EaLength);
Status = IoCheckEaBufferValidity(SystemEaBuffer, EaLength,&EaErrorOffset);
if (!NT_SUCCESS(Status))
{
ExFreePoolWithTag(SystemEaBuffer, TAG_EA);
_SEH2_YIELD(return Status);
}
}
}
。。。
}
Else //来自内核模式的调用
{
/* Check if this is a device attach */
if (CreateOptions & IO_ATTACH_DEVICE_API)
{
Options |= IO_ATTACH_DEVICE;
CreateOptions &= ~IO_ATTACH_DEVICE_API;
}
if (AllocationSize)
SafeAllocationSize = *AllocationSize;
else
SafeAllocationSize.QuadPart = 0;
if ((EaBuffer) && (EaLength))
{
SystemEaBuffer = ExAllocatePoolWithTag(NonPagedPool,EaLength,TAG_EA);
RtlCopyMemory(SystemEaBuffer, EaBuffer, EaLength);
Status = IoCheckEaBufferValidity(SystemEaBuffer,EaLength,&EaErrorOffset);
}
}
//上面的操作,将参数复制到内核空间
//下面构造一个打开请求包
RtlZeroMemory(&OpenPacket, sizeof(OPEN_PACKET));
OpenPacket.Type = IO_TYPE_OPEN_PACKET; OpenPacket.Size = sizeof(OPEN_PACKET);
OpenPacket.OriginalAttributes = *ObjectAttributes;
OpenPacket.AllocationSize = SafeAllocationSize;
OpenPacket.CreateOptions = CreateOptions;
OpenPacket.FileAttributes = (USHORT)FileAttributes;
OpenPacket.ShareAccess = (USHORT)ShareAccess;
OpenPacket.EaBuffer = SystemEaBuffer;
OpenPacket.EaLength = EaLength;
OpenPacket.Options = Options;
OpenPacket.Disposition = Disposition;
OpenPacket.CreateFileType = CreateFileType;
OpenPacket.MailslotOrPipeParameters = ExtraCreateParameters;
IopUpdateOperationCount(IopOtherTransfer);
//下面的函数用来打开设备对象。在解析文件路径的过程中,当解析到C:时会链接指向\Device\Harddisk0\Partition0\Windows\Explorer.exe新路径重新开始解析。当解析到Partition0时,发现\Device\Harddisk0\Partition0是一种设备对象,就会调用IopParseDevice函数来解析后面的剩余路径Windows\Explorer.exe。IopParseDevice函数内部则会创建一个文件对象和一个irp,将irp发给这个分区设备。
Status = ObOpenObjectByName(ObjectAttributes,NULL,AccessMode,NULL,
DesiredAccess,&OpenPacket,
&LocalHandle);//这个LocalHandle返回文件句柄
//if 打开失败
if (!(NT_SUCCESS(Status)) || (OpenPacket.ParseCheck != TRUE))
{
/* Check if Ob thinks well went well */
if (NT_SUCCESS(Status))
{
ZwClose(LocalHandle);
Status = STATUS_OBJECT_TYPE_MISMATCH;
}
/* Now check the Io status */
if (!NT_SUCCESS(OpenPacket.FinalStatus))
{
Status = OpenPacket.FinalStatus;
/* Check if it was only a warning */
if (NT_WARNING(Status))
{
IoStatusBlock->Information = OpenPacket.Information;
IoStatusBlock->Status = OpenPacket.FinalStatus;
}
}
else if ((OpenPacket.FileObject) && (OpenPacket.ParseCheck != 1))
{
OpenPacket.FileObject->DeviceObject = NULL;
ObDereferenceObject(OpenPacket.FileObject);
}
}
Else //解析成功,得到了一个文件句柄
{
OpenPacket.FileObject->Flags |= FO_HANDLE_CREATED;
*FileHandle = LocalHandle;//返回给用户
IoStatusBlock->Information = OpenPacket.Information;
IoStatusBlock->Status = OpenPacket.FinalStatus;
Status = OpenPacket.FinalStatus;
}
return Status;
}
如上,我们看看剩余路径Windows\Explorer.exe在IopParseDevice中是如何解析的。
NTSTATUS
IopParseDevice(IN PVOID ParseObject,//磁盘卷设备或文件卷设备对象
IN PVOID ObjectType,
IN OUT PACCESS_STATE AccessState,
IN KPROCESSOR_MODE AccessMode,
IN ULONG Attributes,
IN OUT PUNICODE_STRING CompleteName,//完整路径
IN OUT PUNICODE_STRING RemainingName,//相对于ParseObject或起点目录的剩余路径
IN OUT PVOID Context,//OPEN_PACKET
IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL,
OUT PVOID *Object)//返回解析得到的文件对象
{
POPEN_PACKET OpenPacket = (POPEN_PACKET)Context;
// OriginalDeviceObject最终用来表示磁盘卷设备对象
PDEVICE_OBJECT OriginalDeviceObject = (PDEVICE_OBJECT)ParseObject;
PDEVICE_OBJECT DeviceObject;//IRP_MJ_CREATE irp 发往的卷设备对象
PDEVICE_OBJECT OwnerDevice;//栈顶设备
PVPB Vpb = NULL;
BOOLEAN DirectOpen = FALSE, OpenCancelled, UseDummyFile;
BOOLEAN VolumeOpen = FALSE;
BOOLEAN AccessGranted, LockHeld = FALSE;
PPRIVILEGE_SET Privileges = NULL;
*Object = NULL;
其他局部变量省略。。。
//RelatedFileObject表示搜索的起点目录,如果这个函数是由IopParseFile调用的,那么RelatedFileObject就可能指向用户给定的搜索起点目录,否则为NULL
if (OpenPacket->RelatedFileObject)
OriginalDeviceObject = OpenPacket->RelatedFileObject->DeviceObject;
Status = IopCheckDeviceAndDriver(OpenPacket, OriginalDeviceObject);
if (!NT_SUCCESS(Status))
{
OpenPacket->FinalStatus = Status;
return Status;
}
权限检查略。。。
//如果是QueryInformationFile/DeleteFile等操作,不用创建文件对象,直接使用已有的那个公共文件对象
UseDummyFile = ((OpenPacket->QueryOnly) || (OpenPacket->DeleteOnly));
if (!(RemainingName->Length) &&!(OpenPacket->RelatedFileObject) &&
((DesiredAccess & ~(SYNCHRONIZE |FILE_READ_ATTRIBUTES |READ_CONTROL |
ACCESS_SYSTEM_SECURITY |WRITE_OWNER |WRITE_DAC)) == 0) && !(UseDummyFile))
{
DirectOpen = TRUE;//标记要直接打开磁盘卷进行访问,无需使用文件系统
}
if (!(DirectOpen) &&!(RemainingName->Length) &&!(OpenPacket->RelatedFileObject) &&
((wcsstr(CompleteName->Buffer, L"Harddisk"))) && !(UseDummyFile))
{
DirectOpen = TRUE;//也标记是要直接打开原始的磁盘卷设备
}
if ((OpenPacket->RelatedFileObject) &&
!(OpenPacket->RelatedFileObject->Flags & FO_DIRECT_DEVICE_OPEN))
{
DeviceObject = ParseObject;//此时ParseObject一定是一个文件卷设备
if (OpenPacket->RelatedFileObject->Vpb)
{
Vpb = OpenPacket->RelatedFileObject->Vpb;
InterlockedIncrement((PLONG)&Vpb->ReferenceCount);
}
}
else
{
DeviceObject = OriginalDeviceObject;// OriginalDeviceObject为磁盘卷
if ((OriginalDeviceObject->Vpb) && !(DirectOpen))
{
//获得磁盘卷上面挂载的文件卷(如果当前尚未挂载,就挂载一个文件卷上去)
Vpb = IopCheckVpbMounted(OpenPacket,OriginalDeviceObject, RemainingName,&Status);
if (!Vpb) return Status;
DeviceObject = Vpb->DeviceObject;//此时DeviceObject为文件卷设备
}
if (DeviceObject->AttachedDevice)
DeviceObject = IoGetAttachedDevice(DeviceObject);//栈顶的文件卷设备或磁盘卷设备
}
//经过上面的折腾后,DeviceObject一定是文件卷(除非直接打开,那么DeviceObject就是磁盘卷),IRP_MJ_CREATE将发送给它。如果有文件系统过滤驱动绑定了文件卷,此时,DeviceObject就是过滤驱动中的设备对象,Irp将首先发给它。
Irp = IoAllocateIrp(DeviceObject->StackSize, TRUE);
Irp->RequestorMode = AccessMode;
Irp->Flags = IRP_CREATE_OPERATION |IRP_SYNCHRONOUS_API |IRP_DEFER_IO_COMPLETION;
Irp->Tail.Overlay.Thread = PsGetCurrentThread();
Irp->UserIosb = &IoStatusBlock;
Irp->MdlAddress = NULL;
Irp->PendingReturned = FALSE;
Irp->UserEvent = NULL;
Irp->Cancel = FALSE;
Irp->CancelRoutine = NULL;
Irp->Tail.Overlay.AuxiliaryBuffer = NULL;
SecurityContext.SecurityQos = SecurityQos;
SecurityContext.AccessState = AccessState;
SecurityContext.DesiredAccess = AccessState->RemainingDesiredAccess;
SecurityContext.FullCreateOptions = OpenPacket->CreateOptions;
StackLoc = (PEXTENDED_IO_STACK_LOCATION)IoGetNextIrpStackLocation(Irp);
StackLoc->Control = 0;
switch (OpenPacket->CreateFileType)
{
case CreateFileTypeNone:
StackLoc->MajorFunction = IRP_MJ_CREATE;//最常见
StackLoc->Parameters.Create.EaLength = OpenPacket->EaLength;
StackLoc->Flags = (UCHAR)OpenPacket->Options;
StackLoc->Flags |= !(Attributes & OBJ_CASE_INSENSITIVE) ? SL_CASE_SENSITIVE: 0;
break;
case CreateFileTypeNamedPipe:
StackLoc->MajorFunction = IRP_MJ_CREATE_NAMED_PIPE;
StackLoc->Parameters.CreatePipe.Parameters =
OpenPacket->MailslotOrPipeParameters;
break;
case CreateFileTypeMailslot:
StackLoc->MajorFunction = IRP_MJ_CREATE_MAILSLOT;
StackLoc->Parameters.CreateMailslot.Parameters =
OpenPacket->MailslotOrPipeParameters;
break;
}
Irp->Overlay.AllocationSize = OpenPacket->AllocationSize;
Irp->AssociatedIrp.SystemBuffer = OpenPacket->EaBuffer;
StackLoc->Parameters.Create.Options = (OpenPacket->Disposition << 24) |
(OpenPacket->CreateOptions & 0xFFFFFF);
StackLoc->Parameters.Create.FileAttributes = OpenPacket->FileAttributes;//此处包含ACL
StackLoc->Parameters.Create.ShareAccess = OpenPacket->ShareAccess;
StackLoc->Parameters.Create.SecurityContext = &SecurityContext;
if (!UseDummyFile)//最常见
{
InitializeObjectAttributes(&ObjectAttributes,NULL,Attributes,NULL,NULL);
//关键。看到没,CreateFile内部会创建一个文件对象,然后返回其句柄
Status = ObCreateObject(KernelMode,IoFileObjectType,//关键
&ObjectAttributes,AccessMode,NULL,
sizeof(FILE_OBJECT),//关键
0,0, (PVOID*)&FileObject);
RtlZeroMemory(FileObject, sizeof(FILE_OBJECT));
if (OpenPacket->CreateOptions &
(FILE_SYNCHRONOUS_IO_ALERT | FILE_SYNCHRONOUS_IO_NONALERT))
{
FileObject->Flags |= FO_SYNCHRONOUS_IO;//标记是同步方式打开
if (OpenPacket->CreateOptions & FILE_SYNCHRONOUS_IO_ALERT)
FileObject->Flags |= FO_ALERTABLE_IO;
}
if (FileObject->Flags & FO_SYNCHRONOUS_IO)
KeInitializeEvent(&FileObject->Lock, SynchronizationEvent, FALSE);
if (OpenPacket->CreateOptions & FILE_NO_INTERMEDIATE_BUFFERING)
FileObject->Flags |= FO_NO_INTERMEDIATE_BUFFERING;
if (OpenPacket->CreateOptions & FILE_WRITE_THROUGH)
FileObject->Flags |= FO_WRITE_THROUGH;
if (OpenPacket->CreateOptions & FILE_SEQUENTIAL_ONLY)
FileObject->Flags |= FO_SEQUENTIAL_ONLY;
if (OpenPacket->CreateOptions & FILE_RANDOM_ACCESS)
FileObject->Flags |= FO_RANDOM_ACCESS;
}
Else //简单的文件信息查询操作和删除操作 使用内部预先创建的那个公共文件对象
{
DummyFileObject = OpenPacket->DummyFileObject;
RtlZeroMemory(DummyFileObject, sizeof(DUMMY_FILE_OBJECT));
FileObject = (PFILE_OBJECT)&DummyFileObject->ObjectHeader.Body;
DummyFileObject->ObjectHeader.Type = IoFileObjectType;
DummyFileObject->ObjectHeader.PointerCount = 1;
}
FileObject->Type = IO_TYPE_FILE;
FileObject->Size = sizeof(FILE_OBJECT);
//文件对象内部的这个字段表示当初NtCreateFile指定的搜索起点目录
FileObject->RelatedFileObject = OpenPacket->RelatedFileObject;
//这个字段则表示当初CreateFile时打开的那个设备对象
FileObject->DeviceObject = OriginalDeviceObject;//此例中为磁盘卷
if (DirectOpen) FileObject->Flags |= FO_DIRECT_DEVICE_OPEN;
if (!(Attributes & OBJ_CASE_INSENSITIVE))
FileObject->Flags |= FO_OPENED_CASE_SENSITIVE;
Irp->Tail.Overlay.OriginalFileObject = FileObject;//关键
StackLoc->FileObject = FileObject; //关键
if (RemainingName->Length)
{
FileObject->FileName.MaximumLength = RemainingName->Length + sizeof(WCHAR);
FileObject->FileName.Buffer = ExAllocatePoolWithTag(PagedPool,
FileObject->FileName.MaximumLength, TAG_IO_NAME);
}
//文件对象的这个FileName字段表示的是相对于盘符的路径或者相对于起点目录的路径,不是全路径,这个要注意。我们说文件对象表示对设备的一次打开上下文,记录了打开信息。这个FileName就是其中最重要的上下文信息,表示当次打开的是哪个文件
RtlCopyUnicodeString(&FileObject->FileName, RemainingName);
//文件对象内部自带的事件对象,用于同步IO
KeInitializeEvent(&FileObject->Event, NotificationEvent, FALSE);
OpenPacket->FileObject = FileObject;//返回创建的文件对象给用户
IopQueueIrpToThread(Irp);//将irp挂入线程的pending irp列表
//此时的Device要么是文件卷,要么是磁盘卷,若非直接打开,就是文件卷,从而将进入相应文件系统驱动的irp派遣处理函数。文件系统分很多种,如FAT32、Ntfs等,本文只关注FAT32文件系统。
Status = IoCallDriver(DeviceObject, Irp);
if (Status == STATUS_PENDING)//if irp未完成
{
//可看出,CreateFile函数本身总是同步返回的,只有ReadFile、WriteFile等函数才有异步一说。
KeWaitForSingleObject(&FileObject->Event,Executive,KernelMode,FALSE,NULL);
Status = IoStatusBlock.Status; //完成结果
}
Else //若已完成
{
KeRaiseIrql(APC_LEVEL, &OldIrql);
IoStatusBlock = Irp->IoStatus;
Status = IoStatusBlock.Status;//完成结果
FileObject->Event.Header.SignalState = 1;
IopUnQueueIrpFromThread(Irp);
if ((Irp->Flags & IRP_BUFFERED_IO) && (Irp->Flags & IRP_DEALLOCATE_BUFFER))
ExFreePool(Irp->AssociatedIrp.SystemBuffer);
IoFreeIrp(Irp);
KeLowerIrql(OldIrql);
}
OpenPacket->Information = IoStatusBlock.Information;//返回完成结果
if (!NT_SUCCESS(Status)) 。。。
//OwnerDevice此时为栈顶文件卷设备 或 栈顶磁盘卷设备
OwnerDevice = IoGetRelatedDeviceObject(FileObject);
if (OwnerDevice != DeviceObject)//若irp 完成后,栈顶发生变化
{
if (Vpb) IopDereferenceVpb(Vpb);
Vpb = FileObject->Vpb;//使用新的Vpb
if (Vpb) InterlockedIncrement((PLONG)&Vpb->ReferenceCount);
}
if (!UseDummyFile)
{
/* Check if this was a volume open */
if ((!FileObject->RelatedFileObject ||
FileObject->RelatedFileObject->Flags & FO_VOLUME_OPEN)
&&
!(FileObject->FileName.Length))
{
//if OwnerDevice是一种文件系统cdo
if ((OwnerDevice->DeviceType == FILE_DEVICE_DISK_FILE_SYSTEM) ||
(OwnerDevice->DeviceType == FILE_DEVICE_CD_ROM_FILE_SYSTEM) ||
(OwnerDevice->DeviceType == FILE_DEVICE_TAPE_FILE_SYSTEM) ||
(OwnerDevice->DeviceType == FILE_DEVICE_FILE_SYSTEM))
{
FileObject->Flags |= FO_VOLUME_OPEN;//标记这个文件对象当初是打开的cdo
}
}
ObReferenceObject(FileObject);
*Object = FileObject;//返回创建的文件对象
OpenPacket->FinalStatus = IoStatusBlock.Status;//返回完成结果
OpenPacket->ParseCheck = TRUE;//标记解析正确
return OpenPacket->FinalStatus;
}
Else //若是一个简单的一次性查询请求 或者 文件删除请求
{
if (OpenPacket->QueryOnly)//如果用户是要查询文件信息
{
if (!OpenPacket->FullAttributes)//if 是要查询基本信息
{
FileBasicInfo = ExAllocatePoolWithTag(NonPagedPool,sizeof(*FileBasicInfo),);
if (FileBasicInfo)
{
Status = IoQueryFileInformation(FileObject,FileBasicInformation,
sizeof(*FileBasicInfo),FileBasicInfo,
&ReturnLength);
if (NT_SUCCESS(Status))
{
RtlCopyMemory(OpenPacket->BasicInformation,FileBasicInfo,
ReturnLength);
}
ExFreePool(FileBasicInfo);
}
}
else
{
Status = IoQueryFileInformation(FileObject,FileNetworkOpenInformation,
sizeof(FILE_NETWORK_OPEN_INFORMATION),OpenPacket->NetworkInformation,
&ReturnLength);
}
}
//文件查询操作和删除操作都是一次性的,不需要维系一个文件对象给用户
IopDeleteFile(FileObject);
OpenPacket->FileObject = NULL;
OpenPacket->FinalStatus = Status;
OpenPacket->ParseCheck = TRUE;
return Status;
}
}
如上,这个IopParseDevice就是用来解析剩余路径,返回文件对象给用户。实际上用户可以有两种方式指定路径。
1、 磁盘卷+相对于磁盘卷的剩余路径。如C: + \Windows\Explorer.exe
2、 起点目录+相对于起点目录的剩余路径。如 Windows + Explorer.exe
应用程序使用CreateFile只能按第一种方式指定路径
驱动程序使用ZwCreateFile,可以按两种方式指定路径,当驱动程序使用第二种方式指定目标路径调用ZwCreateFile时,会在内部调用IopParseFile函数来解析路径,而IopParseFile内部也会调用IopParseDevice来解析路径,此时,IopParseDevice的第一个参数就是文件卷,OpenPacket结构的RelatedFileObject字段就表示搜索起点目录对象了。
IopParseDevice它会在内部创建一个文件对象,构造一个IRP_MJ_CREATE发给文件卷或磁盘卷设备。若是直接打开磁盘卷,就将irp发给磁盘卷设备,否则,将irp发给挂载在磁盘卷上面的文件卷。假设磁盘卷C盘格式化成了FAT32格式,那么它上面挂载的就是FAT32文件系统卷,那么该irp将发给FAT32文件系统中的卷设备去处理,从而使控制逻辑进入FAT32文件系统驱动内部的派遣函数。
那FAT32文件系统是如何处理CreateFile irp的(我将IRP_MJ_CREATE称为CreateFile irp)?在FAT32中,处理这个irp的函数为VfatCreateFile,这个函数留待以后分析。
上面说过,当驱动程序员按第二种方式指定路径,调用ZwCreateFile时,将会在内部调用IopParseFile来解析路径,我们看:
NTSTATUS
IopParseFile(IN PVOID ParseObject,//文件系统中的目录 (也即搜索的起点目录)
IN PVOID ObjectType,
IN OUT PACCESS_STATE AccessState,
IN KPROCESSOR_MODE AccessMode,
IN ULONG Attributes,
IN OUT PUNICODE_STRING CompleteName,
IN OUT PUNICODE_STRING RemainingName,//相对于起点目录的剩余路径
IN OUT PVOID Context OPTIONAL,// OPEN_PACKET
IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL,
OUT PVOID *Object)
{
PVOID DeviceObject;
POPEN_PACKET OpenPacket = (POPEN_PACKET)Context;
if (!IopValidateOpenPacket(OpenPacket)) return STATUS_OBJECT_TYPE_MISMATCH;
DeviceObject = IoGetRelatedDeviceObject(ParseObject);//获得文件对象关联的文件卷设备
OpenPacket->RelatedFileObject = ParseObject;//果然,记录了搜索的起点目录
//因为打开文件,本质是也是一种打开设备操作,打开的是磁盘卷设备,因此,也会调用IopParseDevice来完成解析任务,不过,第一个参数传的不是磁盘卷而是文件卷而已。
return IopParseDevice(DeviceObject,//此时这个是文件卷,不再是磁盘卷了
ObjectType,AccessState,AccessMode,Attributes,
CompleteName,RemainingName,
OpenPacket,SecurityQos,Object);
}
题外话:IopParseDevice在发irp前,若发现磁盘卷上面当时尚未挂载有文件卷,就会试图挂载。下面的函数就是用来检测和挂载的。顺便,我们还可以看到:磁盘卷的挂载时机是在第一次打开磁盘卷中的文件时进行挂载的。
PVPB //返回该磁盘卷的VPB,若尚未挂载,就先挂载
IopCheckVpbMounted(IN POPEN_PACKET OpenPacket,
IN PDEVICE_OBJECT DeviceObject,//磁盘卷
IN PUNICODE_STRING RemainingName,
OUT PNTSTATUS Status)
{
BOOLEAN Alertable, Raw;
KIRQL OldIrql;
PVPB Vpb = NULL;
IoAcquireVpbSpinLock(&OldIrql);
//如果是直接打开磁盘卷进行访问,就不需要文件系统,挂载一个模拟的RAW原始文件系统即可
Raw = !RemainingName->Length && !OpenPacket->RelatedFileObject;
Alertable = (OpenPacket->CreateOptions & FILE_SYNCHRONOUS_IO_ALERT) ? TRUE: FALSE;
while (!(DeviceObject->Vpb->Flags & VPB_MOUNTED))//一直等到挂载
{
IoReleaseVpbSpinLock(OldIrql);
//为指定磁盘卷挂载一个对应文件系统的文件卷。这个函数留待以后看。
*Status = IopMountVolume(DeviceObject,Raw,FALSE,Alertable,&Vpb);
if (!(NT_SUCCESS(*Status)) || (*Status == STATUS_USER_APC) ||
(*Status == STATUS_ALERTED))
{
IopDereferenceDeviceObject(DeviceObject, FALSE);
if (!NT_SUCCESS(*Status)) return NULL;
*Status = STATUS_WRONG_VOLUME;
return NULL;
}
IoAcquireVpbSpinLock(&OldIrql);
}
Vpb = DeviceObject->Vpb;
if (Vpb->Flags & VPB_LOCKED)
{
*Status = STATUS_ACCESS_DENIED;
Vpb = NULL;
}
else
Vpb->ReferenceCount++;
IoReleaseVpbSpinLock(OldIrql);
return Vpb;
}
重温一下文件对象结构体的定义:
typedef struct _FILE_OBJECT //设备对象打开上下文
{
CSHORT Type;
CSHORT Size;
//表示本文件对象当初CreateFile打开的那个设备对象
PDEVICE_OBJECT DeviceObject; //磁盘卷、普通设备
PVPB Vpb;//上面磁盘卷的卷参数块
PVOID FsContext;//FCB,FAT32文件系统中是一个VFATFCB结构指针
PVOID FsContext2;//附加FCB,FAT32文件系统中是一个VFATCCB结构指针
PSECTION_OBJECT_POINTERS SectionObjectPointer;
PVOID PrivateCacheMap;
NTSTATUS FinalStatus;
struct _FILE_OBJECT *RelatedFileObject;//起点目录(注意不一定是父目录)
BOOLEAN LockOperation;
BOOLEAN DeletePending;
BOOLEAN ReadAccess;
BOOLEAN WriteAccess;
BOOLEAN DeleteAccess;
BOOLEAN SharedRead;
BOOLEAN SharedWrite;
BOOLEAN SharedDelete;
ULONG Flags;
UNICODE_STRING FileName;//相对于盘符的路径 或者 相对于起点目录的路径
LARGE_INTEGER CurrentByteOffset;//文件偏移指针,同步IO方式会自动管理文件偏移指针
volatile ULONG Waiters;
volatile ULONG Busy;
PVOID LastLock;
KEVENT Lock;
KEVENT Event;//自带的内置事件对象
volatile PIO_COMPLETION_CONTEXT CompletionContext;
KSPIN_LOCK IrpListLock;
LIST_ENTRY IrpList;
volatile PVOID FileObjectExtension;
} FILE_OBJECT, *PFILE_OBJECT;
FAT32文件系统介绍:
FAT32文件系统是一种FAT文件系统,位于FastFat.sys中。这个驱动内部集成了FAT12、FAT16、FAT32三种文件系统。FAT是文件分配表的意思(File Allocate Table缩写),FAT文件系统中,在每个磁盘卷的开头两个簇中有一个文件分配表(又叫簇表,是一个数组),这个表记录了每个文件各自分配到的所有簇。每个表项是个简单的整数值,表项索引即是簇号,表项里面存放的值则是下一个簇号。这样,一个文件分配的所有簇都能通过这个值找到,换句话说,每个文件的簇链表,记录在这个簇表中,所以又叫文件分配表。既然每个文件都有一个簇链表,那么每个文件的第一个簇号势必也必须记录,这样,才能根据第一个簇号,沿着簇链表,顺藤摸瓜找到该文件分配的所有簇。
当然,簇表是FAT文件系统特有的,FAT文件系统利用簇表的机制来寻址每个文件在磁盘上的位置。
簇表中每个表项的值有三种意义:
0表示该簇空闲,尚未分配给任何文件,处于游离状态
1~0x0ffffff7表示 该簇链接的下一个簇号
其它值都表示该簇是文件中的最后一簇,一般用0xfffffff表示。
簇就是将磁盘(实际上是逻辑磁盘,即分区、卷)划分成物理连续的块,至于如何划分,即每个簇的大小设为多少,则是格式化的时候决定的,每个簇的大小记录在磁盘分区开头的引导块中。
根据簇号自然可得出那块簇中的第一个扇区在磁盘上的扇区号,即簇的起始扇区号。下面的函数就是这样。
ULONGLONG //获得指定簇的起始扇区号
ClusterToSector(PDEVICE_EXTENSION DeviceExt,ULONG Cluster)
{
return DeviceExt->FatInfo.dataStart +
( (Cluster - 2) * DeviceExt->FatInfo.SectorsPerCluster);
}
如上。dataStart表示卷中数据区的起始扇区号,SectorsPerCluster则表示每个簇包含的扇区数,这两个值都记录在每个磁盘卷开头的引导块中。每个物理磁盘卷内部,开头两个簇用来存放格式化信息、文件分配表等基本信息,因此那块区域叫基本信息区,后面的区域才是用来存放文件数据的。因此,dataStart其实又表示基本区占去的扇区个数,即基本扇区数。
下面的函数用来获取指定簇的下一个簇号
NTSTATUS
GetNextCluster(PDEVICE_EXTENSION DeviceExt,//XX文件系统中的文件卷的设备扩展
ULONG CurrentCluster,//指定簇
PULONG NextCluster)//OUT
{
NTSTATUS Status;
if (CurrentCluster == 0) return(STATUS_INVALID_PARAMETER);
ExAcquireResourceSharedLite(&DeviceExt->FatResource, TRUE);
//调用具体文件系统的函数,FAT12、FAT16、FAT32各不相同,FAT32中是FAT32GetNextCluster
Status = DeviceExt->GetNextCluster(DeviceExt, CurrentCluster, NextCluster);
ExReleaseResourceLite(&DeviceExt->FatResource);
return(Status);
}
NTSTATUS
FAT32GetNextCluster(PDEVICE_EXTENSION DeviceExt,ULONG CurrentCluster,PULONG NextCluster)
{
PVOID BaseAddress;
ULONG FATOffset;
ULONG ChunkSize;
PVOID Context;
LARGE_INTEGER Offset;
ULONG next;
FATOffset = CurrentCluster * sizeof(ULONG);//获得对应簇表项在FAT表的偏移
ChunkSize = CACHEPAGESIZE(DeviceExt);//扇区大小,一般512
Offset.QuadPart = ROUND_DOWN(FATOffset, ChunkSize);//获得对应簇表项相对FAT表的偏移扇区数
//将簇表项所在的整个扇区读入内存,返回地址在BaseAddress中(可见磁盘以扇区为IO单位)
if(!CcMapData(DeviceExt->FATFileObject, &Offset, ChunkSize, 1, &Context, &BaseAddress))
return STATUS_UNSUCCESSFUL;
next = (*(PULONG)((char*)BaseAddress + (FATOffset % ChunkSize))) & 0x0fffffff;
if (next >= 0xffffff8 && next <= 0xfffffff)//这个范围都表示没有下一个簇
next = 0xffffffff;//统一转换成-1
CcUnpinData(Context);
*NextCluster = next;//返回
return (STATUS_SUCCESS);
}
相应的,在创建文件,为文件分配簇时,会调用下面的函数,将该文件的各个簇链接起来。
NTSTATUS
WriteCluster(PDEVICE_EXTENSION DeviceExt,ULONG ClusterToWrite,ULONG NewValue)
{
NTSTATUS Status;
ULONG OldValue;
ExAcquireResourceExclusiveLite (&DeviceExt->FatResource, TRUE);
//FAT32中是FAT32WriteCluster函数
Status = DeviceExt->WriteCluster(DeviceExt, ClusterToWrite, NewValue, &OldValue);
if (DeviceExt->AvailableClustersValid)
{
//AvailableClusters表示卷中的可用空闲簇数
if (OldValue && NewValue == 0)
InterlockedIncrement((PLONG)&DeviceExt->AvailableClusters);//多出一块空闲簇
else if (OldValue == 0 && NewValue)
InterlockedDecrement((PLONG)&DeviceExt->AvailableClusters);//少一块空闲簇
}
ExReleaseResourceLite(&DeviceExt->FatResource);
return(Status);
}
NTSTATUS
FAT32WriteCluster(PDEVICE_EXTENSION DeviceExt,ULONG ClusterToWrite,
ULONG NewValue,PULONG OldValue)
{
PVOID BaseAddress;
ULONG FATOffset;
ULONG ChunkSize;
PVOID Context;
LARGE_INTEGER Offset;
PULONG Cluster;
ChunkSize = CACHEPAGESIZE(DeviceExt);
FATOffset = (ClusterToWrite * 4);
Offset.QuadPart = ROUND_DOWN(FATOffset, ChunkSize);
//将簇表项所在扇区读入内存,并锁定
if(!CcPinRead(DeviceExt->FATFileObject, &Offset, ChunkSize, 1, &Context, &BaseAddress))
return STATUS_UNSUCCESSFUL;
Cluster = ((PULONG)((char*)BaseAddress + (FATOffset % ChunkSize)));
*OldValue = *Cluster & 0x0fffffff;
*Cluster = (*Cluster & 0xf0000000) | (NewValue & 0x0fffffff);//修改成指定值
CcSetDirtyPinnedData(Context, NULL); //标记为脏
CcUnpinData(Context); //解除锁定,回写到磁盘
return(STATUS_SUCCESS);
}
当一个文件刚刚创建时,没有分配任何簇。一旦向文件写数据,就会为文件分配第一个簇,以后若文件体积增长时,也可能需要分配新簇。下面的函数就是用来扩充文件,分配新簇的
NTSTATUS
NextCluster(PDEVICE_EXTENSION DeviceExt,
ULONG FirstCluster,
PULONG CurrentCluster,//指定簇号
BOOLEAN Extend)//是否扩充文件
{
if (FirstCluster == 1)//特殊处理
{
(*CurrentCluster) += DeviceExt->FatInfo.SectorsPerCluster;
return(STATUS_SUCCESS);
}
else
{
if (Extend) //扩充
return GetNextClusterExtend(DeviceExt, (*CurrentCluster), CurrentCluster);
else //查询
return GetNextCluster(DeviceExt, (*CurrentCluster), CurrentCluster);
}
}
如上,这个函数有两种用法。一种是查询指定簇的下一个簇,另一种是扩充文件分配新簇(这时候CurrentCluster参数必须传入文件的最后一个簇才能起到分配新簇的作用)
NTSTATUS
GetNextClusterExtend(PDEVICE_EXTENSION DeviceExt,ULONG CurrentCluster,
PULONG NextCluster)
{
NTSTATUS Status;
ExAcquireResourceExclusiveLite(&DeviceExt->FatResource, TRUE);
if (CurrentCluster == 0)//如果该文件刚刚创建,尚未分配任何簇
{
ULONG NewCluster;
//分配一个空闲的簇,作为该文件的第一个簇
Status = DeviceExt->FindAndMarkAvailableCluster(DeviceExt, &NewCluster);
if (!NT_SUCCESS(Status))
{
ExReleaseResourceLite(&DeviceExt->FatResource);
return Status;
}
*NextCluster = NewCluster;
ExReleaseResourceLite(&DeviceExt->FatResource);
return(STATUS_SUCCESS);
}
Status = DeviceExt->GetNextCluster(DeviceExt, CurrentCluster, NextCluster);
if ((*NextCluster) == 0xFFFFFFFF)//看到没,必须传递最后一个簇
{
ULONG NewCluster;
//分配一个新簇
Status = DeviceExt->FindAndMarkAvailableCluster(DeviceExt, &NewCluster);
if (!NT_SUCCESS(Status))
{
ExReleaseResourceLite(&DeviceExt->FatResource);
return Status;
}
WriteCluster(DeviceExt, CurrentCluster, NewCluster);//链上去
*NextCluster = NewCluster;//返回新分配的簇
}
ExReleaseResourceLite(&DeviceExt->FatResource);
return(Status);
}
//在指定磁盘卷中查找一块空闲的簇
NTSTATUS
FAT32FindAndMarkAvailableCluster (PDEVICE_EXTENSION DeviceExt, PULONG Cluster)
{
ChunkSize = CACHEPAGESIZE(DeviceExt);
FatLength = (DeviceExt->FatInfo.NumberOfClusters + 2);
*Cluster = 0;
StartCluster = DeviceExt->LastAvailableCluster;
//两次循环。第一次从上次分配到的空闲簇继续往后搜索空闲簇,若找不到,再折转回去,从开头开始搜索,这样提高搜索效率
for (j = 0; j < 2; j++)
{
for (i = StartCluster; i < FatLength;)
{
Offset.QuadPart = ROUND_DOWN(i * 4, ChunkSize);
if(!CcPinRead(DeviceExt->FATFileObject, &Offset, ChunkSize, 1, &Context, &BaseAddress))
return STATUS_UNSUCCESSFUL;
Block = (PULONG)((ULONG_PTR)BaseAddress + (i * 4) % ChunkSize);
BlockEnd = (PULONG)((ULONG_PTR)BaseAddress + ChunkSize);
while (Block < BlockEnd && i < FatLength)
{
if ((*Block & 0x0fffffff) == 0)
{
DeviceExt->LastAvailableCluster = *Cluster = i;//记录上次分配到的空闲簇号
*Block = 0x0fffffff;//新分配的簇一定是文件中的最后一个簇
CcSetDirtyPinnedData(Context, NULL);
CcUnpinData(Context);//回写磁盘
if (DeviceExt->AvailableClustersValid)
InterlockedDecrement((PLONG)&DeviceExt->AvailableClusters);
return(STATUS_SUCCESS);
}
Block++;
i++;
}
CcUnpinData(Context);
}
FatLength = StartCluster;
StartCluster = 2;
}
return (STATUS_DISK_FULL);
}
当文件刚刚创建后分配第一个簇时,需要把这个文件的第一个簇的簇号记录起来,以便以后遍历查找该该文件的所有簇。FAT表中记录的仅仅是磁盘卷中所有簇之间的链接关系。在每个目录中,还记录了该目录下每个文件的信息,其中就包括起始簇号。目录(文件夹)是一种特殊的文件,目录中存放的就是里面各个文件的信息,用一个文件描述符来描述。目录的文件内容其实就是一个文件描述符数组而已,每个表项就是一个文件描述符,又叫目录项。我们看看目录项的结构定义
struct _FATDirEntry
{
union
{
struct { unsigned char Filename[8], Ext[3]; };//8.3形式的DOS文件名
//注意:若首字节是0xe5,则表示该目录项被删除,该元素处于空闲状态
unsigned char ShortName[11];//短文件名
};
unsigned char Attrib;//文件属性:系统,只读,隐藏,存档,目录等属性
unsigned char lCase;
unsigned char CreationTimeMs;
unsigned short CreationTime,CreationDate,AccessDate;
unsigned short FirstClusterHigh; //第一个簇号(高位部分)
unsigned short UpdateTime; //上次更新时间
unsigned short UpdateDate; //上次更新日期
unsigned short FirstCluster; //关键。该文件的第一个簇号(低位部分)
unsigned long FileSize;//文件大小,看到没,4B整数,FAT32最大只支持4GB的文件
};
目录项中的ShortName表示短文件名,但是一个文件必须使用长文件名才能精确表示。但是ShortName只能存放十一哥字符,因此,需要使用其它方案来记录长文件名。FAT32系统的解决办法便是将长文件名分散保存在多个slot结构中。这个slot结构体的大小刚好与目录项结构体的大小刚好一样,因此,一个具有长文件名的文件的文件描述符 往往由一个目录项和N个连续的slot组合在一起而构成。
struct _slot
{
unsigned char id; //低5位表示slot序号,位6表示是否是第一个slot
WCHAR name0_4[5]; //前5个字符
unsigned char attr; //0xf就表示本结构是一个slot,而不是目录项
unsigned char reserved; //固定为0
unsigned char alias_checksum; //各个slot都记录着该文件的短名校验和
WCHAR name5_10[6]; //6个字符
unsigned char start[2]; //起始簇号
WCHAR name11_12[2]; //2个字符
};
一个slot结构可以存放5+6+2共13个宽字符,如果一个长文件名超过了24个字符,就需要使用多个slot。每添加一个slot便能多描述13个字符。需要多少个slot由文件名的长度决定。
这个slot结构与目录项结构的大小刚好完全一样,而且与目录项结构位于同一数组中,那么,如何区分数组元素是一个目录项还是一个slot呢? 那就是靠attr字段的作用来区分。另外:如果一个文件有slot,那么在数组中,先排放的是各个slot,然后才是目录项,也即每个文件的slot在目录项前面。
注意:一个目录也是一种文件,它的内容就是一个目录项(混合着slot)的数组。
明白了slot的作用,我们看下文件的查找过程.当用户调用CreateFile要创建或打开一个文件时,比如C:\Windows\Explorer.exe,FAT32文件系统会检测磁盘上是否有该文件。下面的函数就是用来在指定目录中根据文件名查找文件的,并为其创建一个FCB再返回给用户。(一个FCB就代表一个打开的磁盘文件)
NTSTATUS
vfatDirFindFile (
PDEVICE_EXTENSION pDeviceExt,//文件卷
PVFATFCB pDirectoryFCB,//指定目录
PUNICODE_STRING FileToFindU,//文件名(不含路径)
PVFATFCB * pFoundFCB)//返回创建的FCB
{
NTSTATUS status;
PVOID Context = NULL;
PVOID Page = NULL;
BOOLEAN First = TRUE;
VFAT_DIRENTRY_CONTEXT DirContext;
//#define MAX_PATH 260 缘由于此
WCHAR LongNameBuffer[260];//20个slot
WCHAR ShortNameBuffer[13];//13*20
BOOLEAN FoundLong = FALSE;
BOOLEAN FoundShort = FALSE;
DirContext.DirIndex = 0;//当前目录项或slot
DirContext.LongNameU.Buffer = LongNameBuffer;
DirContext.LongNameU.Length = 0;
DirContext.LongNameU.MaximumLength = sizeof(LongNameBuffer);
DirContext.ShortNameU.Buffer = ShortNameBuffer;
DirContext.ShortNameU.Length = 0;
DirContext.ShortNameU.MaximumLength = sizeof(ShortNameBuffer);
while (TRUE)
{
/* 关键,获取第N个目录项的长短文件名,返回到DirContext中,对于FAT32,这个函数是
FATGetNextDirEntry */
status = pDeviceExt->GetNextDirEntry(&Context,&Page,pDirectoryFCB,&DirContext,
First);
First = FALSE;
if (status == STATUS_NO_MORE_ENTRIES)
return STATUS_OBJECT_NAME_NOT_FOUND;//表示文件不存在,但中间目录都存在
if (!NT_SUCCESS(status))
return status;
DirContext.LongNameU.Buffer[DirContext.LongNameU.Length / sizeof(WCHAR)] = 0;
DirContext.ShortNameU.Buffer[DirContext.ShortNameU.Length / sizeof(WCHAR)] = 0;
if (!ENTRY_VOLUME(pDeviceExt, &DirContext.DirEntry))
{
//先比长文件名
FoundLong = RtlEqualUnicodeString(FileToFindU, &DirContext.LongNameU, TRUE);
if (FoundLong == FALSE) //再比短名
{
FoundShort = RtlEqualUnicodeString(FileToFindU, &DirContext.ShortNameU, TRUE);
}
//如果指定目录下确实有这个文件
if (FoundLong || FoundShort)
{
status = vfatMakeFCBFromDirEntry (pDeviceExt,pDirectoryFCB,&DirContext,
pFoundFCB);
CcUnpinData(Context);
return status;
}
}
DirContext.DirIndex++;//下一个目录项
}
return STATUS_OBJECT_NAME_NOT_FOUND;
}
NTSTATUS //获取指定目录下第N个目录项的长、短文件名
FATGetNextDirEntry(PVOID *pContext,PVOID *pPage,
IN PVFATFCB pDirFcb,//指定目录
PVFAT_DIRENTRY_CONTEXT DirContext,//第N个目录项 IN、OUT
BOOLEAN First)//是否第一次
{
ULONG dirMap;
PWCHAR pName;
LARGE_INTEGER FileOffset;
PFAT_DIR_ENTRY fatDirEntry;
slot * longNameEntry;
ULONG index;
UCHAR CheckSum, shortCheckSum;
USHORT i;
BOOLEAN Valid = TRUE;
BOOLEAN Back = FALSE;
DirContext->LongNameU.Buffer[0] = 0;
FileOffset.u.HighPart = 0;
FileOffset.u.LowPart = ROUND_DOWN(DirContext->DirIndex * sizeof(FAT_DIR_ENTRY),PAGE_SIZE);
// FileOffset为指定目录项所在页面 在目录文件内部的偏移
//if 首次 或者 刚好要翻页读取下一页
if (*pContext == NULL || (DirContext->DirIndex % FAT_ENTRIES_PER_PAGE) == 0)
{
if (*pContext != NULL)
CcUnpinData(*pContext);
//读入目录项所在的页面到内存中,页面地址返回到pPage中
if (FileOffset.u.LowPart >= pDirFcb->RFCB.FileSize.u.LowPart ||
!CcMapData(pDirFcb->FileObject, &FileOffset, PAGE_SIZE, TRUE, pContext, pPage))
{
*pContext = NULL;
return STATUS_NO_MORE_ENTRIES;
}
}
//获得内存中该目录项的位置
fatDirEntry = (PFAT_DIR_ENTRY)(*pPage) + DirContext->DirIndex % FAT_ENTRIES_PER_PAGE;
longNameEntry = (slot*) fatDirEntry;//先假设该项为一个slot
dirMap = 0;
//第一次的时候,用户指定的DirIndex可能指向了中间的某个slot 或者 目录项,需要退回到该文件的第一个slot处。
if (First)
{
while (DirContext->DirIndex > 0 &&
!FAT_ENTRY_DELETED(fatDirEntry) && //退回已删除目录项处
((!FAT_ENTRY_LONG(fatDirEntry) && !Back) || //两个目录项中间无slot
(FAT_ENTRY_LONG(fatDirEntry) && !(longNameEntry->id & 0x40)))) //退到第一个slot
{
DirContext->DirIndex--;//退一步
Back = TRUE;//标记曾经退过
//if 刚好要退回上一个页面的最后一条目录项处,就读入上一个页面到内存
if ((DirContext->DirIndex % FAT_ENTRIES_PER_PAGE) == FAT_ENTRIES_PER_PAGE - 1)
{
CcUnpinData(*pContext);
FileOffset.u.LowPart -= PAGE_SIZE;
if (FileOffset.u.LowPart >= pDirFcb->RFCB.FileSize.u.LowPart ||
!CcMapData(pDirFcb->FileObject, &FileOffset, PAGE_SIZE, TRUE, pContext,pPage))
{
*pContext = NULL;
return STATUS_NO_MORE_ENTRIES;
}
fatDirEntry = (*pPage) + DirContext->DirIndex % FAT_ENTRIES_PER_PAGE;
longNameEntry = (slot*) fatDirEntry;
}
else
{
fatDirEntry--;
longNameEntry--;
}
}
//上面的while退回到第一个slot处或者上一个目录项处
//if 退到的是上一个目录项处,则需要向前进一步
if (Back && !FAT_ENTRY_END(fatDirEntry) &&
(FAT_ENTRY_DELETED(fatDirEntry) || !FAT_ENTRY_LONG(fatDirEntry)))
{
DirContext->DirIndex++;
if ((DirContext->DirIndex % FAT_ENTRIES_PER_PAGE) == 0) 。。。
else
{
fatDirEntry++;
longNameEntry++;
}
}
}
DirContext->StartIndex = DirContext->DirIndex;
CheckSum = 0;
//遍历该文件的所有slot和目录项,将其中的长、短文件名拷贝返回给用户
while (TRUE)
{
if (FAT_ENTRY_END(fatDirEntry))
{
CcUnpinData(*pContext);
*pContext = NULL;
return STATUS_NO_MORE_ENTRIES;
}
if (FAT_ENTRY_DELETED(fatDirEntry))
{
dirMap = 0;
DirContext->LongNameU.Buffer[0] = 0;
DirContext->StartIndex = DirContext->DirIndex + 1;
}
else
{
if (FAT_ENTRY_LONG(fatDirEntry)) //if 该项是个slot
{
if (dirMap == 0) //if 该文件的第一个slot
{
RtlZeroMemory(DirContext->LongNameU.Buffer, DirContext->LongNameU.MaximumLength);
CheckSum = longNameEntry->alias_checksum;
Valid = TRUE;
}
index = (longNameEntry->id & 0x1f) - 1;//后5位的slot序号 转为 索引
dirMap |= 1 << index;
pName = DirContext->LongNameU.Buffer + 13 * index;
//看到没,分段拷贝该slot内部各处的文件名片段
RtlCopyMemory(pName, longNameEntry->name0_4, 5 * sizeof(WCHAR));
RtlCopyMemory(pName + 5, longNameEntry->name5_10, 6 * sizeof(WCHAR));
RtlCopyMemory(pName + 11, longNameEntry->name11_12, 2 * sizeof(WCHAR));
if (CheckSum != longNameEntry->alias_checksum)
Valid = FALSE;//各slot中的校验和必须一致,否则就表示已损坏无效
}
Else //该项是个目录项
{
shortCheckSum = 0;
for (i = 0; i < 11; i++)
{
shortCheckSum = (((shortCheckSum & 1) << 7) | ((shortCheckSum & 0xfe) >> 1))
+ fatDirEntry->ShortName[i];
}
//校验和不对,就表示slot有损坏,就无法获得长文件名
if (shortCheckSum != CheckSum && DirContext->LongNameU.Buffer[0])
DirContext->LongNameU.Buffer[0] = 0;
if (Valid == FALSE)
DirContext->LongNameU.Buffer[0] = 0;
RtlCopyMemory (&DirContext->DirEntry.Fat, fatDirEntry, sizeof (FAT_DIR_ENTRY));
break;
}
}
DirContext->DirIndex++;//下一条
if ((DirContext->DirIndex % FAT_ENTRIES_PER_PAGE) == 0) 。。。
else
{
fatDirEntry++;
longNameEntry++;
}
}
DirContext->LongNameU.Length = wcslen(DirContext->LongNameU.Buffer) * sizeof(WCHAR);
//生成、返回短名给用户
vfat8Dot3ToString(&DirContext->DirEntry.Fat, &DirContext->ShortNameU);
if (DirContext->LongNameU.Length == 0)
RtlCopyUnicodeString(&DirContext->LongNameU, &DirContext->ShortNameU);
return STATUS_SUCCESS;
}
上面的函数看起来复杂,实质上功能很简单。上面牵涉的几个宏定义如下:
#define FAT_ENTRY_DELETED(DirEntry) ((DirEntry)->Filename[0] == 0xe5) //该目录项已被删除
#define FAT_ENTRY_END(DirEntry) ((DirEntry)->Filename[0] == 0) //最后一个空白的目录项
#define FAT_ENTRY_LONG(DirEntry) (((DirEntry)->Attrib & 0x3f) == 0x0f) //slot标志
当从磁盘的某个目录中删除一个文件时,会相应的释放那个文件占用的簇 以及 它所在目录中相应的目录项(注意可能这个文件名很长,那还要删除它的所有连续slot,也即需要删除从开始slot到目录项处的那一块连续区域,即为那块连续区域中的每个条目打上删除标记),下面的函数就是用来做文件删除工作的
NTSTATUS FATDelEntry(IN PDEVICE_EXTENSION DeviceExt,IN PVFATFCB pFcb)
{
ULONG CurrentCluster = 0, NextCluster, i;
PVOID Context = NULL;
LARGE_INTEGER Offset;
PFAT_DIR_ENTRY pDirEntry = NULL;
Offset.u.HighPart = 0;
// startIndex表示第一个slot的位置,dirIndex表示最后那个目录项的位置
for (i = pFcb->startIndex; i <= pFcb->dirIndex; i++)
{
//if 刚好翻页
if (Context == NULL || ((i * sizeof(FAT_DIR_ENTRY)) % PAGE_SIZE) == 0)
{
if (Context)
{
CcSetDirtyPinnedData(Context, NULL);
CcUnpinData(Context);
}
Offset.u.LowPart = (i * sizeof(FAT_DIR_ENTRY) / PAGE_SIZE) * PAGE_SIZE;
//读入该项所在的页面到内存
CcPinRead(pFcb->parentFcb->FileObject, &Offset, sizeof(FAT_DIR_ENTRY), TRUE,
&Context, (PVOID*)&pDirEntry);
}
//打上0xe5删除标记
pDirEntry[i % (PAGE_SIZE / sizeof(FAT_DIR_ENTRY))].Filename[0] = 0xe5;
if (i == pFcb->dirIndex)
{
//关键。获得该文件的起始簇号
CurrentCluster = vfatDirEntryGetFirstCluster(DeviceExt,
(PDIR_ENTRY)&pDirEntry[i % (PAGE_SIZE / sizeof(FAT_DIR_ENTRY))]);
}
}
if (Context)
{
CcSetDirtyPinnedData(Context, NULL);
CcUnpinData(Context);
}
//遍历该文件的簇,一一释放
while (CurrentCluster && CurrentCluster != 0xffffffff)
{
GetNextCluster(DeviceExt, CurrentCluster, &NextCluster);
WriteCluster(DeviceExt, CurrentCluster, 0);//标记成空闲
CurrentCluster = NextCluster;
}
return STATUS_SUCCESS;
}
如上,文件的删除操作涉及文件本身的释放 和 目录项的删除操作,下面再看一下在目录中创建文件的操作。CreateFile这个函数在文件不存在的情况下会先创建文件,再打开文件。若是第一次打开文件,就会创建一个FCB,以后别的进程再打开此文件时,只需查找、返回先前创建的那个FCB
static NTSTATUS
FATAddEntry( //创建文件
IN PDEVICE_EXTENSION DeviceExt,//文件卷
IN PUNICODE_STRING NameU,//文件名(不是路径)
IN PVFATFCB* Fcb,//返回新创建文件的FCB
IN PVFATFCB ParentFcb,//父目录
IN ULONG RequestedOptions,
IN UCHAR ReqAttr)//文件属性:如只读、隐藏等属性
{
PVOID Context = NULL;
PFAT_DIR_ENTRY pFatEntry;
slot *pSlots;
USHORT nbSlots = 0, j, posCar;
PUCHAR Buffer;
BOOLEAN needTilde = FALSE, needLong = FALSE;
BOOLEAN lCaseBase = FALSE, uCaseBase, lCaseExt = FALSE, uCaseExt;
ULONG CurrentCluster;
LARGE_INTEGER SystemTime, FileOffset;
NTSTATUS Status = STATUS_SUCCESS;
ULONG size;
long i;
OEM_STRING NameA;
CHAR aName[13];
BOOLEAN IsNameLegal;
BOOLEAN SpacesFound;
VFAT_DIRENTRY_CONTEXT DirContext;
WCHAR LongNameBuffer[LONGNAME_MAX_LENGTH + 1];
WCHAR ShortNameBuffer[13];
DirContext.LongNameU = *NameU;
//根据文件名长度计算出该文件将占用的目录项和slot总数。
nbSlots = (DirContext.LongNameU.Length / sizeof(WCHAR) + 12) / 13 + 1;
Buffer = ExAllocatePoolWithTag(NonPagedPool, (nbSlots - 1) * sizeof(FAT_DIR_ENTRY));
RtlZeroMemory(Buffer, (nbSlots - 1) * sizeof(FAT_DIR_ENTRY));
pSlots = (slot *) Buffer;//pSlots不包含目录项
NameA.Buffer = aName;
NameA.Length = 0;
NameA.MaximumLength = sizeof(aName);
DirContext.ShortNameU.Buffer = ShortNameBuffer;
DirContext.ShortNameU.Length = 0;
DirContext.ShortNameU.MaximumLength = sizeof(ShortNameBuffer);
RtlZeroMemory(&DirContext.DirEntry.Fat, sizeof(FAT_DIR_ENTRY));//目录项初始为空
//检查用户给定的是不是合法的8.3形式文件名
IsNameLegal = RtlIsNameLegalDOS8Dot3(&DirContext.LongNameU, &NameA, &SpacesFound);
//如果不是 或者 文件名中含有空格
if (!IsNameLegal || SpacesFound)
{
GENERATE_NAME_CONTEXT NameContext;
VFAT_DIRENTRY_CONTEXT SearchContext;
WCHAR ShortSearchName[13];
needTilde = TRUE;
needLong = TRUE;//表示需要slot
RtlZeroMemory(&NameContext, sizeof(GENERATE_NAME_CONTEXT));
SearchContext.LongNameU.Buffer = LongNameBuffer;
SearchContext.LongNameU.MaximumLength = sizeof(LongNameBuffer);
SearchContext.ShortNameU.Buffer = ShortSearchName;
SearchContext.ShortNameU.MaximumLength = sizeof(ShortSearchName);
for (i = 0; i < 100; i++) //尝试100次,生成一个不冲突的短名
{
//根据长名生成一个短名
RtlGenerate8dot3Name(&DirContext.LongNameU, FALSE, &NameContext, &DirContext.ShortNameU);
DirContext.ShortNameU.Buffer[DirContext.ShortNameU.Length / sizeof(WCHAR)] = 0;
SearchContext.DirIndex = 0;
Status = FindFile(DeviceExt, ParentFcb, &DirContext.ShortNameU, &SearchContext, TRUE);
if (!NT_SUCCESS(Status))//if 不冲突
break;
}
IsNameLegal = RtlIsNameLegalDOS8Dot3(&DirContext.ShortNameU, &NameA, &SpacesFound);
aName[NameA.Length]=0;//此时NameA为短名
}
Else //用户给的就是8.3形式短名,检查基本名部分和扩展名部分是否大小写混杂了
{
aName[NameA.Length] = 0;
for (posCar = 0; posCar < DirContext.LongNameU.Length / sizeof(WCHAR); posCar++)
{
if (DirContext.LongNameU.Buffer[posCar] == L'.')
break;
}
RtlDowncaseUnicodeString(&DirContext.ShortNameU, &DirContext.LongNameU, FALSE);
DirContext.ShortNameU.Buffer[DirContext.ShortNameU.Length / sizeof(WCHAR)] = 0;
uCaseBase = wcsncmp(DirContext.LongNameU.Buffer,
DirContext.ShortNameU.Buffer, posCar) ? TRUE : FALSE;
if (posCar < DirContext.LongNameU.Length/sizeof(WCHAR))
{
i = DirContext.LongNameU.Length / sizeof(WCHAR) - posCar;
uCaseExt = wcsncmp(DirContext.LongNameU.Buffer + posCar,
DirContext.ShortNameU.Buffer + posCar, i) ? TRUE : FALSE;
}
else
uCaseExt = FALSE;
RtlUpcaseUnicodeString(&DirContext.ShortNameU, &DirContext.LongNameU, FALSE);
DirContext.ShortNameU.Buffer[DirContext.ShortNameU.Length / sizeof(WCHAR)] = 0;
lCaseBase = wcsncmp(DirContext.LongNameU.Buffer,
DirContext.ShortNameU.Buffer, posCar) ? TRUE : FALSE;
if (posCar < DirContext.LongNameU.Length / sizeof(WCHAR))
{
i = DirContext.LongNameU.Length / sizeof(WCHAR) - posCar;
lCaseExt = wcsncmp(DirContext.LongNameU.Buffer + posCar,
DirContext.ShortNameU.Buffer + posCar, i) ? TRUE : FALSE;
}
else
lCaseExt = FALSE;
//if 基本名部分大小写混杂了 或者 扩展名部分大小写混杂了仍需要slot长名
if ((lCaseBase && uCaseBase) || (lCaseExt && uCaseExt))
needLong = TRUE;
}
memset(DirContext.DirEntry.Fat.ShortName, ' ', 11);
for (i = 0; i < 8 && aName[i] && aName[i] != '.'; i++)
DirContext.DirEntry.Fat.Filename[i] = aName[i];
if (aName[i] == '.')
{
i++;//不保存点号
for (j = 0; j < 3 && aName[i]; j++, i++)
DirContext.DirEntry.Fat.Ext[j] = aName[i];
}
if (DirContext.DirEntry.Fat.Filename[0] == 0xe5)
DirContext.DirEntry.Fat.Filename[0] = 0x05;//已删除标记 改为 空闲标记
if (needLong)//if 需要长名(也即需要分配slot),就构造好长名,后面部分用0xff填充
{
RtlCopyMemory(LongNameBuffer, DirContext.LongNameU.Buffer, DirContext.LongNameU.Length);
DirContext.LongNameU.Buffer = LongNameBuffer;
DirContext.LongNameU.MaximumLength = sizeof(LongNameBuffer);
DirContext.LongNameU.Buffer[DirContext.LongNameU.Length / sizeof(WCHAR)] = 0;
memset(DirContext.LongNameU.Buffer + DirContext.LongNameU.Length / sizeof(WCHAR) + 1, 0xff,DirContext.LongNameU.MaximumLength - DirContext.LongNameU.Length - sizeof(WCHAR));
}
else
{
nbSlots = 1;//只需要一个目录项
if (lCaseBase)
DirContext.DirEntry.Fat.lCase |= VFAT_CASE_LOWER_BASE;
if (lCaseExt)
DirContext.DirEntry.Fat.lCase |= VFAT_CASE_LOWER_EXT;
}
DirContext.DirEntry.Fat.Attrib = ReqAttr;
if (RequestedOptions & FILE_DIRECTORY_FILE)
DirContext.DirEntry.Fat.Attrib |= FILE_ATTRIBUTE_DIRECTORY;
KeQuerySystemTime(&SystemTime);
FsdSystemTimeToDosDateTime(DeviceExt, &SystemTime, &DirContext.DirEntry.Fat.CreationDate,
&DirContext.DirEntry.Fat.CreationTime);
DirContext.DirEntry.Fat.UpdateDate = DirContext.DirEntry.Fat.CreationDate;
DirContext.DirEntry.Fat.UpdateTime = DirContext.DirEntry.Fat.CreationTime;
DirContext.DirEntry.Fat.AccessDate = DirContext.DirEntry.Fat.CreationDate;
if (needLong)
{
for (pSlots[0].alias_checksum = 0, i = 0; i < 11; i++)
{
pSlots[0].alias_checksum = (((pSlots[0].alias_checksum & 1) << 7
| ((pSlots[0].alias_checksum & 0xfe) >> 1))
+ DirContext.DirEntry.Fat.ShortName[i]);
}
//构造好所有的slot
for (i = nbSlots - 2; i >= 0; i--)
{
pSlots[i].attr = 0xf;//标记本结构是一个slot而不是目录项
if (i)
pSlots[i].id = (unsigned char)(nbSlots - i - 1);
else
pSlots[i].id = (unsigned char)(nbSlots - i - 1 + 0x40);//标记为第一个slot
pSlots[i].alias_checksum = pSlots[0].alias_checksum;
RtlCopyMemory(pSlots[i].name0_4, DirContext.LongNameU.Buffer + (nbSlots - i - 2) * 13, 10);
RtlCopyMemory(pSlots[i].name5_10, DirContext.LongNameU.Buffer + (nbSlots - i - 2) * 13 + 5, 12);
RtlCopyMemory(pSlots[i].name11_12, DirContext.LongNameU.Buffer + (nbSlots - i - 2) * 13 + 11, 4);
}
}
//至此,已经构造好slot和目录项了,现在只需要写回磁盘中即可
----------------------------------------------------------------------------------------
//在父目录内部的目录项数组中寻找nbSlots块连续的空闲表项,来安插该文件的所有slot和目录项
//StartIndex返回找到的起始表项位置,注意,若找不到的话,目录项数组会自动增大,从而导致目//录的文件体积变大
if (!vfatFindDirSpace(DeviceExt, ParentFcb, nbSlots, &DirContext.StartIndex))
{
ExFreePoolWithTag(Buffer, TAG_VFAT);
return STATUS_DISK_FULL;
}
// DirIndex为目录项的位置
DirContext.DirIndex = DirContext.StartIndex + nbSlots - 1;
//如果新创建的文件是个子目录,那么由于每个目录中即使没有文件,都至少会有两个表项,一个是‘.’, 一个是‘..’,分别指代目录本身和其父目录。这就是为什么可以使用‘.\路径’ 和 ‘..\路径’方式来指定文件路径。既然每个目录刚一创建都至少有两个表项,那么就得马上为它分配簇来存放那两个表项。
if (RequestedOptions & FILE_DIRECTORY_FILE)
{
CurrentCluster = 0;
//为该子目录分配第一个簇
Status = NextCluster(DeviceExt, 0, &CurrentCluster, TRUE);
if (CurrentCluster == 0xffffffff || !NT_SUCCESS(Status))
return STATUS_DISK_FULL;
if (DeviceExt->FatInfo.FatType == FAT32)
DirContext.DirEntry.Fat.FirstClusterHigh = (unsigned short)(CurrentCluster >> 16);
DirContext.DirEntry.Fat.FirstCluster = (unsigned short)CurrentCluster;
}
i = DeviceExt->FatInfo.BytesPerCluster / sizeof(FAT_DIR_ENTRY);
FileOffset.u.HighPart = 0;
FileOffset.u.LowPart = DirContext.StartIndex * sizeof(FAT_DIR_ENTRY);
//if 所有slot和目录项都在同一个簇中
if (DirContext.StartIndex / i == DirContext.DirIndex / i)
{
//将那块连续的表项区域读入内存进行修改
CcPinRead(ParentFcb->FileObject, &FileOffset, nbSlots * sizeof(FAT_DIR_ENTRY),
TRUE, &Context, (PVOID*)&pFatEntry);
if (nbSlots > 1)//拷贝slot
RtlCopyMemory(pFatEntry, Buffer, (nbSlots - 1) * sizeof(FAT_DIR_ENTRY));
RtlCopyMemory(pFatEntry + (nbSlots - 1), &DirContext.DirEntry.Fat, sizeof(FAT_DIR_ENTRY));
}
Else //若那块连续表项区域跨占两个簇(不会超过3个的)
{
size = DeviceExt->FatInfo.BytesPerCluster -
(DirContext.StartIndex * sizeof(FAT_DIR_ENTRY)) % DeviceExt->FatInfo.BytesPerCluster;
i = size / sizeof(FAT_DIR_ENTRY);
CcPinRead(ParentFcb->FileObject, &FileOffset, size, TRUE, &Context, &pFatEntry);
RtlCopyMemory(pFatEntry, Buffer, size);//复制落在第一个簇中的那些slot
CcSetDirtyPinnedData(Context, NULL);
CcUnpinData(Context);//回写磁盘
FileOffset.u.LowPart += size;
CcPinRead(ParentFcb->FileObject, &FileOffset,
nbSlots * sizeof(FAT_DIR_ENTRY) - size,TRUE, &Context, &pFatEntry);
if (nbSlots - 1 > i) //复制落在后一个簇中的所有slot
RtlCopyMemory(pFatEntry, (Buffer + size), (nbSlots - 1 - i) * sizeof(FAT_DIR_ENTRY));
//最后复制目录项
RtlCopyMemory(pFatEntry + nbSlots - 1 - i, &DirContext.DirEntry.Fat, sizeof(FAT_DIR_ENTRY));
}
CcSetDirtyPinnedData(Context, NULL);
CcUnpinData(Context);
//关键。为新建的文件创建一个FCB
Status = vfatMakeFCBFromDirEntry(DeviceExt, ParentFcb, &DirContext, Fcb);
if (RequestedOptions & FILE_DIRECTORY_FILE)
{
FileOffset.QuadPart = 0;
CcPinRead((*Fcb)->FileObject, &FileOffset, DeviceExt->FatInfo.BytesPerCluster, TRUE,
&Context, (PVOID*)&pFatEntry);
RtlZeroMemory(pFatEntry, DeviceExt->FatInfo.BytesPerCluster);
//看到没, .表示目录本身, ..表示其父目录
RtlCopyMemory(&pFatEntry[0].Attrib, &DirContext.DirEntry.Fat.Attrib, sizeof(FAT_DIR_ENTRY) - 11);
RtlCopyMemory(pFatEntry[0].ShortName, ". ", 11);
RtlCopyMemory(&pFatEntry[1].Attrib, &DirContext.DirEntry.Fat.Attrib, sizeof(FAT_DIR_ENTRY) - 11);
RtlCopyMemory(pFatEntry[1].ShortName, ".. ", 11);
pFatEntry[1].FirstCluster = ParentFcb->entry.Fat.FirstCluster;
pFatEntry[1].FirstClusterHigh = ParentFcb->entry.Fat.FirstClusterHigh;
if (vfatFCBIsRoot(ParentFcb))//磁盘卷根目录,也是一种文件
{
pFatEntry[1].FirstCluster = 0;//根目录的起始簇号自然为0
pFatEntry[1].FirstClusterHigh = 0;
}
CcSetDirtyPinnedData(Context, NULL);
CcUnpinData(Context);
}
ExFreePoolWithTag(Buffer, TAG_VFAT);
return STATUS_SUCCESS;
}
上面这个函数代码一大堆,其实最核心的工作就是为新创建的文件分配目录项,创建FCB而已。
我们说,每当创建一个文件时都需要提供一个父目录的FCB,而FCB只能在打开文件(或目录)后取得,如果我们想要在根目录中创建一个文件,那怎么取得父目录的FCB呢?实际上根目录FCB在文件卷挂载后就就创建了。下面的函数用于为指定卷的根目录创建一个FCB
PVFATFCB
vfatMakeRootFCB(PDEVICE_EXTENSION pVCB)//文件卷
{
PVFATFCB FCB;
ULONG FirstCluster, CurrentCluster, Size = 0;
NTSTATUS Status = STATUS_SUCCESS;
UNICODE_STRING NameU = RTL_CONSTANT_STRING(L"\\");//根目录的名字为‘\’
FCB = vfatNewFCB(pVCB, &NameU);//创建一个FCB
if (FCB->Flags & FCB_IS_FATX_ENTRY) // FATX文件系统,略
else
{
memset(FCB->entry.Fat.ShortName, ' ', 11);
//根目录的文件大小(记录在引导块中)
FCB->entry.Fat.FileSize = pVCB->FatInfo.rootDirectorySectors * pVCB->FatInfo.BytesPerSector;
FCB->entry.Fat.Attrib = FILE_ATTRIBUTE_DIRECTORY;//根目录也是一种目录
if (pVCB->FatInfo.FatType == FAT32)
{
//根目录的起始簇号(记录在引导块中)
CurrentCluster = FirstCluster = pVCB->FatInfo.RootCluster;
FCB->entry.Fat.FirstCluster = (unsigned short)(FirstCluster & 0xffff);
FCB->entry.Fat.FirstClusterHigh = (unsigned short)(FirstCluster >> 16);
//遍历根目录的簇链,获得根目录的分配大小
while (CurrentCluster != 0xffffffff && NT_SUCCESS(Status))
{
Size += pVCB->FatInfo.BytesPerCluster;
Status = NextCluster (pVCB, FirstCluster, &CurrentCluster, FALSE);
}
}
Else 。。。
}
FCB->ShortHash.Hash = FCB->Hash.Hash;//根目录的长短名hash值一样
FCB->RefCount = 2;
FCB->dirIndex = 0;
FCB->RFCB.FileSize.QuadPart = Size;
FCB->RFCB.ValidDataLength.QuadPart = Size;
FCB->RFCB.AllocationSize.QuadPart = Size;
FCB->RFCB.IsFastIoPossible = FastIoIsNotPossible;
vfatFCBInitializeCacheFromVolume(pVCB, FCB);
//文件卷的FCB链表记录了该卷中当前打开的所有文件
vfatAddFCBToTable(pVCB, FCB);//将根目录的FCB挂入文件卷的FCB链表中
return(FCB);
}
每个文件卷的设备扩展都维护着一个简单的打开文件列表(即FCB链表)。除此之外,还维护着一个hash
FCB表,用来方便根据文件名查找FCB。下面的函数用于将FCB挂入表中。
VOID //将指定FCB挂入指定文件卷的FCB表中
vfatAddFCBToTable(PDEVICE_EXTENSION pVCB, PVFATFCB pFCB)
{
ULONG Index;
InsertTailList (&pVCB->FcbListHead, &pFCB->FcbListEntry);//挂入简单FCB链表
Index = pFCB->Hash.Hash % pVCB->HashTableSize;//长名hash值
pFCB->Hash.next = pVCB->FcbHashTable[Index];
pVCB->FcbHashTable[Index] = &pFCB->Hash;//挂入链表开头
if (pFCB->Hash.Hash != pFCB->ShortHash.Hash)
{
ULONG ShortIndex = pFCB->ShortHash.Hash % pVCB->HashTableSize;//短名hash值
pFCB->ShortHash.next = pVCB->FcbHashTable[ShortIndex];
pVCB->FcbHashTable[ShortIndex] = &pFCB->ShortHash;//挂入链表开头
}
if (pFCB->parentFcb)
pFCB->parentFcb->RefCount++;
}
利用hash的方式是为了提高查找效率。挂入hash表后,以后就可以根据文件名直接找到先前打开的FCB了
下面的函数就是这个用途
PVFATFCB
vfatGrabFCBFromTable(PDEVICE_EXTENSION pVCB, PUNICODE_STRING PathNameU)//绝对/相对路径
{
PVFATFCB rcFCB;
ULONG Hash;
UNICODE_STRING DirNameU;
UNICODE_STRING FileNameU;
PUNICODE_STRING FcbNameU;
HASHENTRY* entry;
Hash = vfatNameHash(0, PathNameU);//先计算hash值
entry = pVCB->FcbHashTable[Hash % pVCB->HashTableSize];
if (entry)
vfatSplitPathName(PathNameU, &DirNameU, &FileNameU);//分割成 目录名+文件名 形式
while (entry)
{
if (entry->Hash == Hash)
{
rcFCB = entry->self;
//比较目录名部分
if (RtlEqualUnicodeString(&DirNameU, &rcFCB->DirNameU, TRUE))
{
if (rcFCB->Hash.Hash == Hash)
FcbNameU = &rcFCB->LongNameU;
else
FcbNameU = &rcFCB->ShortNameU;
//再比较文件名部分
if (RtlEqualUnicodeString(&FileNameU, FcbNameU, TRUE))
{
rcFCB->RefCount++;
return rcFCB;
}
}
}
entry = entry->next;
}
return NULL;
}
看看FAT32文件系统驱动(FastFat.sys)的DriverEntry
NTSTATUS
DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)
{
PDEVICE_OBJECT DeviceObject;
UNICODE_STRING DeviceName = RTL_CONSTANT_STRING(L"\\Fat");
NTSTATUS Status;
//创建一个cdo,代表FAT32文件系统驱动本身
Status = IoCreateDevice(DriverObject,
sizeof(VFAT_GLOBAL_DATA),//cdo的设备扩展
&DeviceName,// cdo的名字"\\Fat"
FILE_DEVICE_DISK_FILE_SYSTEM,//文件系统本身也是一种设备
0,FALSE,&DeviceObject);
VfatGlobalData = DeviceObject->DeviceExtension;
RtlZeroMemory (VfatGlobalData, sizeof(VFAT_GLOBAL_DATA));
VfatGlobalData->DriverObject = DriverObject;
VfatGlobalData->DeviceObject = DeviceObject;
DeviceObject->Flags |= DO_DIRECT_IO;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = VfatBuildRequest;
DriverObject->MajorFunction[IRP_MJ_CREATE] = VfatBuildRequest;
DriverObject->MajorFunction[IRP_MJ_READ] = VfatBuildRequest;
DriverObject->MajorFunction[IRP_MJ_WRITE] = VfatBuildRequest;
DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = VfatBuildRequest;
DriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION] = VfatBuildRequest;
DriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = VfatBuildRequest;
DriverObject->MajorFunction[IRP_MJ_DIRECTORY_CONTROL] = VfatBuildRequest;
DriverObject->MajorFunction[IRP_MJ_QUERY_VOLUME_INFORMATION] = VfatBuildRequest;
DriverObject->MajorFunction[IRP_MJ_SET_VOLUME_INFORMATION] = VfatBuildRequest;
DriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = VfatShutdown;//特殊
DriverObject->MajorFunction[IRP_MJ_LOCK_CONTROL] = VfatBuildRequest;
DriverObject->MajorFunction[IRP_MJ_CLEANUP] = VfatBuildRequest;
DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS] = VfatBuildRequest;
DriverObject->MajorFunction[IRP_MJ_PNP] = VfatBuildRequest;
DriverObject->DriverUnload = NULL;//文件系统不卸载
/缓冲管理函数/
VfatGlobalData->CacheMgrCallbacks.AcquireForLazyWrite = VfatAcquireForLazyWrite;
VfatGlobalData->CacheMgrCallbacks.ReleaseFromLazyWrite = VfatReleaseFromLazyWrite;
VfatGlobalData->CacheMgrCallbacks.AcquireForReadAhead = VfatAcquireForReadAhead;
VfatGlobalData->CacheMgrCallbacks.ReleaseFromReadAhead = VfatReleaseFromReadAhead;
/* Fast I/O */
VfatInitFastIoRoutines(&VfatGlobalData->FastIoDispatch);
DriverObject->FastIoDispatch = &VfatGlobalData->FastIoDispatch;
/* Private lists */
ExInitializeNPagedLookasideList(&VfatGlobalData->FcbLookasideList,
NULL, NULL, 0, sizeof(VFATFCB), TAG_FCB, 0);
ExInitializeNPagedLookasideList(&VfatGlobalData->CcbLookasideList,
NULL, NULL, 0, sizeof(VFATCCB), TAG_CCB, 0);
ExInitializeNPagedLookasideList(&VfatGlobalData->IrpContextLookasideList,
NULL, NULL, 0, sizeof(VFAT_IRP_CONTEXT), TAG_IRP, 0);
ExInitializeResourceLite(&VfatGlobalData->VolumeListLock);
InitializeListHead(&VfatGlobalData->VolumeListHead);//文件内部的文件卷列表
IoRegisterFileSystem(DeviceObject);//关键。注册为一个文件系统同时通知所有文件系统过滤驱动
return(STATUS_SUCCESS);
}
如上,FAT32文件系统的初始化最主要就是创建一个代表文件系统本身的cdo,然后向系统注册。
注意在创建cdo的时候传入的设备类型是FILE_DEVICE_DISK_FILE_SYSTEM,我们回顾一下IoCreateDevice函数。
NTSTATUS
IoCreateDevice(IN PDRIVER_OBJECT DriverObject,//指定驱动(可以是其它第三方驱动)
IN ULONG DeviceExtensionSize,//自定义设备扩展的大小
IN PUNICODE_STRING DeviceName,//设备对象的名字
IN DEVICE_TYPE DeviceType,//设备类型
IN ULONG DeviceCharacteristics,//设备特征
IN BOOLEAN Exclusive,//同一时刻是否只能被一个进程独占打开
OUT PDEVICE_OBJECT *DeviceObject)//返回
{
。。。
//创建分配设备对象(在非分页池中)
Status = ObCreateObject(KernelMode,IoDeviceObjectType,&ObjectAttributes,KernelMode,NULL,
TotalSize,0,0, (PVOID*)&CreatedDeviceObject);
。。。
//物理卷(磁盘卷、光盘卷、磁带卷、网络磁盘卷)设备在创建时都会自动分配一个vpb(后面我们会看到,文件卷设备在绑定物理卷设备后,也会记录对应的vpb)
if ((CreatedDeviceObject->DeviceType == FILE_DEVICE_DISK) ||
(CreatedDeviceObject->DeviceType == FILE_DEVICE_VIRTUAL_DISK) ||
(CreatedDeviceObject->DeviceType == FILE_DEVICE_CD_ROM) ||
(CreatedDeviceObject->DeviceType == FILE_DEVICE_TAPE))
{
Status = IopCreateVpb(CreatedDeviceObject);//分配一个vpb
KeInitializeEvent(&CreatedDeviceObject->DeviceLock,SynchronizationEvent,TRUE);
}
//文件系统中cdo和文件卷都需要挂入相应的全局的链表中
if ((CreatedDeviceObject->DeviceType == FILE_DEVICE_DISK_FILE_SYSTEM) || //cdo
(CreatedDeviceObject->DeviceType == FILE_DEVICE_FILE_SYSTEM) || //文件卷
(CreatedDeviceObject->DeviceType == FILE_DEVICE_CD_ROM_FILE_SYSTEM) || //cdo
(CreatedDeviceObject->DeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM) || //cdo
(CreatedDeviceObject->DeviceType == FILE_DEVICE_TAPE_FILE_SYSTEM)) //cdo
{
InitializeListHead(&CreatedDeviceObject->Queue.ListEntry);
}
Else//其他的一般设备都有一个DeviceQueue表示irp队列。
{
KeInitializeDeviceQueue(&CreatedDeviceObject->DeviceQueue);
}
。。。
return STATUS_SUCCESS;
}
如上。各种物理卷设备内部都有一个vpb,用来加载该物理卷挂载的文件卷信息。
NTSTATUS IopCreateVpb(IN PDEVICE_OBJECT DeviceObject)//为指定物理卷创建一个vpb
{
PVPB Vpb;
Vpb = ExAllocatePoolWithTag(NonPagedPool,sizeof(VPB),TAG_VPB);
DeviceObject->Vpb = Vpb;//关键
RtlZeroMemory(Vpb, sizeof(VPB));
Vpb->Type = IO_TYPE_VPB;
Vpb->Size = sizeof(VPB);
Vpb->RealDevice = DeviceObject;//实际的物理卷设备
return STATUS_SUCCESS;
}
typedef struct _VPB { //卷参数块
CSHORT Type;
CSHORT Size;
USHORT Flags;
USHORT VolumeLabelLength;
struct _DEVICE_OBJECT *DeviceObject;//文件卷
struct _DEVICE_OBJECT *RealDevice;//实际的物理卷
ULONG SerialNumber;//序列号
ULONG ReferenceCount;
WCHAR VolumeLabel[MAXIMUM_VOLUME_LABEL_LENGTH / sizeof(WCHAR)];//卷标
} VPB, *PVPB;
物理卷与文件卷之间的挂载关系就是通过一个vpb结构来描述的。
下面的函数用于将一个驱动注册为文件系统驱动,并通知当前安装的所有文件系统过滤驱动
VOID IoRegisterFileSystem(IN PDEVICE_OBJECT DeviceObject) //DeviceObject为cdo
{
PLIST_ENTRY FsList = NULL;
KeEnterCriticalRegion();
ExAcquireResourceExclusiveLite(&FileSystemListLock, TRUE);
if (DeviceObject->DeviceType == FILE_DEVICE_DISK_FILE_SYSTEM)
FsList = &IopDiskFsListHead; //磁盘文件系统
else if (DeviceObject->DeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM)
FsList = &IopNetworkFsListHead; //网络文件系统(指网络驱动器)
else if (DeviceObject->DeviceType == FILE_DEVICE_CD_ROM_FILE_SYSTEM)
FsList = &IopCdRomFsListHead;//光盘文件系统
else if (DeviceObject->DeviceType == FILE_DEVICE_TAPE_FILE_SYSTEM)
FsList = &IopTapeFsListHead;//磁带文件系统
if (FsList)
{
//根据文件系统的优先级,挂入队列开头/结尾
//识别器文件系统、Raw文件系统都挂在文件系统列表的末尾
if (DeviceObject->Flags & DO_LOW_PRIORITY_FILESYSTEM)
InsertTailList(FsList->Blink, &DeviceObject->Queue.ListEntry);
else
InsertHeadList(FsList, &DeviceObject->Queue.ListEntry);
}
DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
ExReleaseResourceLite(&FileSystemListLock);
KeLeaveCriticalRegion();
IopNotifyFileSystemChange(DeviceObject, TRUE);//关键。通知所有文件系统过滤驱动
}
VOID IopNotifyFileSystemChange(IN PDEVICE_OBJECT DeviceObject,IN BOOLEAN DriverActive)
{
PFS_CHANGE_NOTIFY_ENTRY ChangeEntry;
PLIST_ENTRY ListEntry;
KeAcquireGuardedMutex(&FsChangeNotifyListLock);
ListEntry = FsChangeNotifyListHead.Flink;
while (ListEntry != &FsChangeNotifyListHead)//遍历所有注册的文件系统过滤驱动
{
ChangeEntry = CONTAINING_RECORD(ListEntry,FS_CHANGE_NOTIFY_ENTRY,FsChangeNotifyList);
ChangeEntry->FSDNotificationProc(DeviceObject, DriverActive);//回调通知
ListEntry = ListEntry->Flink;
}
KeReleaseGuardedMutex(&FsChangeNotifyListLock);
}
当文件系统加载初始化完成后,此时文件系统内部只有一个cdo设备,名叫‘\Fat’。文件系统内部还会为每个物理卷创建文件卷,挂载在上面(间接绑定)。这样,应用程序打开物理卷时,irp将发给其上挂载的文件卷,此时,文件系统就得到了控制权,起到了作用(物理设备只是一个无组织、无结构、线性存储的原始设备,为物理卷挂载了一个文件卷后,就能将数据组织为文件的形式进行存放和访问)。那么文件卷是在什么时候挂载到物理卷上的呢,要挂载哪种文件系统卷呢?以及是如何挂上去的呢?回忆前面的CreateFile函数内部,当首次调用CreateFile打开一个磁盘卷时,就会调用IopMountVolumn为磁盘卷挂载一个文件卷。我们看:
NTSTATUS
IopMountVolume(IN PDEVICE_OBJECT DeviceObject,//目标物理卷
IN BOOLEAN AllowRawMount,//是否允许挂载原始文件系统
IN BOOLEAN DeviceIsLocked,
IN BOOLEAN Alertable,
OUT PVPB *Vpb)//OUT
{
KEVENT Event;
NTSTATUS Status;
IO_STATUS_BLOCK IoStatusBlock;
PIRP Irp;
PIO_STACK_LOCATION StackPtr;
PLIST_ENTRY FsList, ListEntry;
LIST_ENTRY LocalList;
PDEVICE_OBJECT AttachedDeviceObject = DeviceObject;
PDEVICE_OBJECT FileSystemDeviceObject, ParentFsDeviceObject;
ULONG FsStackOverhead;
if (!DeviceIsLocked)
{
Status = KeWaitForSingleObject(&DeviceObject->DeviceLock,Executive,
KeGetPreviousMode(),Alertable,NULL);
if ((Status == STATUS_ALERTED) || (Status == STATUS_USER_APC))
return Status;
}
KeEnterCriticalRegion();
ExAcquireResourceSharedLite(&FileSystemListLock, TRUE);
if (!(DeviceObject->Vpb->Flags & (VPB_MOUNTED | VPB_REMOVE_PENDING)))
{
KeInitializeEvent(&Event, NotificationEvent, FALSE);
DeviceObject->Flags &= ~DO_VERIFY_VOLUME;
//AttachedDeviceObject最终为栈顶的物理卷设备
while (AttachedDeviceObject->AttachedDevice)
AttachedDeviceObject = AttachedDeviceObject->AttachedDevice;
ObReferenceObject(AttachedDeviceObject);
if ((DeviceObject->DeviceType == FILE_DEVICE_DISK) ||
(DeviceObject->DeviceType == FILE_DEVICE_VIRTUAL_DISK))
{
FsList = &IopDiskFsListHead; //磁盘文件系统链表
}
else if (DeviceObject->DeviceType == FILE_DEVICE_CD_ROM)
FsList = &IopCdRomFsListHead; //光盘文件系统链表
else
FsList = &IopTapeFsListHead; //磁带文件系统链表
Status = STATUS_UNSUCCESSFUL;
ListEntry = FsList->Flink;
//关键。请求所有文件系统 识别、挂载一个文件卷
while ((ListEntry != FsList) && !(NT_SUCCESS(Status)))
{
//如果不许挂载原始文件系统 && 这是链表中的最后一个文件系统
if (!(AllowRawMount) &&
(ListEntry->Flink == FsList) && (ListEntry != FsList->Flink))
{
break;//找不到一个符合的文件系统,失败返回
}
//如果挂载了一个原始文件系统,但还没有穷尽列表,就继续尝试挂载下一个文件系统
if ((DeviceObject->Vpb->Flags & VPB_RAW_MOUNT) && (ListEntry->Flink != FsList))
continue;
FileSystemDeviceObject = CONTAINING_RECORD(ListEntry,DEVICE_OBJECT,
Queue.ListEntry);
ParentFsDeviceObject = FileSystemDeviceObject; //文件系统cdo
FsStackOverhead = 1; //即StackSize
//下面的循环用来取得了栈顶的文件系统cdo,因为可能有文件系统过滤驱动绑定了cdo
while (FileSystemDeviceObject->AttachedDevice)
{
FileSystemDeviceObject = FileSystemDeviceObject->AttachedDevice;
FsStackOverhead++;
}
KeClearEvent(&Event);
//构造一个irp发给文件系统,请求识别、挂载文件卷。注意StackSize为//AttachedDeviceObject->StackSize + FsStackOverhead,,说明文件系统cdo也实质堆栈在//物理卷设备上面
Irp = IoAllocateIrp(AttachedDeviceObject->StackSize + FsStackOverhead, TRUE);
Irp->UserIosb = &IoStatusBlock;
Irp->UserEvent = &Event;
Irp->Tail.Overlay.Thread = PsGetCurrentThread();
Irp->Flags = IRP_MOUNT_COMPLETION | IRP_SYNCHRONOUS_PAGING_IO;
Irp->RequestorMode = KernelMode;
StackPtr = IoGetNextIrpStackLocation(Irp);
StackPtr->MajorFunction = IRP_MJ_FILE_SYSTEM_CONTROL;
StackPtr->MinorFunction = IRP_MN_MOUNT_VOLUME;//请求识别和挂载
StackPtr->Flags = AllowRawMount;
//挂载信息返回到物理卷vpb中
StackPtr->Parameters.MountVolume.Vpb = DeviceObject->Vpb;
StackPtr->Parameters.MountVolume.DeviceObject = AttachedDeviceObject;//栈顶物理卷
Status = IoCallDriver(FileSystemDeviceObject, Irp);//将irp发给文件系统cdo
if (Status == STATUS_PENDING)
{
KeWaitForSingleObject(&Event,Executive,KernelMode,FALSE,NULL);
Status = IoStatusBlock.Status;
}
if (NT_SUCCESS(Status))//if 那个文件系统识别、挂载成功
{
//修改文件卷的StackSize,使其实质堆栈在栈顶物理卷上面
*Vpb = IopInitializeVpbForMount(DeviceObject,AttachedDeviceObject,
(DeviceObject->Vpb->Flags & VPB_RAW_MOUNT));
}
Else //若挂载失败(但有可能识别成功)
{
if ((IoIsErrorUserInduced(Status)) && (IoStatusBlock.Information == 1))
break;
//重要。如果那个文件系统是个识别器(也即存根文件系统),识别成功了,就请求加//载真正的文件系统,然后回到链表开头重新开始循环。
if (Status == STATUS_FS_DRIVER_REQUIRED)
{
ExReleaseResourceLite(&FileSystemListLock);
if (!DeviceIsLocked)
KeSetEvent(&DeviceObject->DeviceLock, 0, FALSE);
//重要。请求识别器加载真正的文件系统
IopLoadFileSystem(ParentFsDeviceObject);
if (!DeviceIsLocked)
{
Status = KeWaitForSingleObject(&DeviceObject->DeviceLock,Executive,
KeGetPreviousMode(),Alertable,NULL);
if ((Status == STATUS_ALERTED) || (Status == STATUS_USER_APC))
{
ObDereferenceObject(AttachedDeviceObject);
return Status;
}
}
ExAcquireResourceSharedLite(&FileSystemListLock, TRUE);
//if 在这期间,其它用户为这个物理卷挂载文件卷了
if (DeviceObject->Vpb->Flags & VPB_MOUNTED)
{
Status = STATUS_SUCCESS;
break;
}
Status = STATUS_UNRECOGNIZED_VOLUME;
LocalList.Flink = FsList->Flink;
ListEntry = &LocalList;
}
if (!(AllowRawMount) && (Status != STATUS_UNRECOGNIZED_VOLUME) &&
FsRtlIsTotalDeviceFailure(Status))
{
break;
}
}
ListEntry = ListEntry->Flink;//请求下一个文件系统进行识别和挂载
}
if (!NT_SUCCESS(Status)) ObDereferenceObject(AttachedDeviceObject);
}
else if (DeviceObject->Vpb->Flags & VPB_REMOVE_PENDING)
Status = STATUS_DEVICE_DOES_NOT_EXIST;
else
Status = STATUS_SUCCESS;
ExReleaseResourceLite(&FileSystemListLock);
KeLeaveCriticalRegion();
if (!DeviceIsLocked) KeSetEvent(&DeviceObject->DeviceLock, 0, FALSE);
if ((!NT_SUCCESS(Status)) && (DeviceObject->Flags & DO_SYSTEM_BOOT_PARTITION))
{
KeBugCheckEx(INACCESSIBLE_BOOT_DEVICE,DeviceObject,Status,0,0);
}
return Status;
}
如上,挂载的过实质就是给系统中的所有同类文件系统cdo发irp,一一请求进行识别和挂载。
简单一句话:【请求识别和挂载】
我们看看下面FAT32文件系统内部处理这种irp请求的函数,看看它是如何进行识别和挂载的
NTSTATUS VfatMount (PVFAT_IRP_CONTEXT IrpContext)
{
PDEVICE_OBJECT DeviceObject = NULL;
PDEVICE_EXTENSION DeviceExt = NULL;
BOOLEAN RecognizedFS;
NTSTATUS Status;
PVFATFCB Fcb = NULL;
PVFATFCB VolumeFcb = NULL;
PVFATCCB Ccb = NULL;
PDEVICE_OBJECT DeviceToMount;
PVPB Vpb;
UNICODE_STRING NameU = RTL_CONSTANT_STRING(L"\\
FatFat
");
UNICODE_STRING VolumeNameU = RTL_CONSTANT_STRING(L"\\
VolumeVolume
");
ULONG HashTableSize;
ULONG eocMark;
FATINFO FatInfo;
//只处理发给cdo的
if (IrpContext->DeviceObject != VfatGlobalData->DeviceObject)
{
Status = STATUS_INVALID_DEVICE_REQUEST;
goto ByeBye;
}
//DeviceToMount为栈顶的物理卷
DeviceToMount = IrpContext->Stack->Parameters.MountVolume.DeviceObject;
Vpb = IrpContext->Stack->Parameters.MountVolume.Vpb;//vpb为物理卷的vpb(非栈顶物理卷)
//关键。识别那个物理卷(也即判断是不是格式化成了FAT文件系统类型)
Status = VfatHasFileSystem (DeviceToMount, &RecognizedFS, &FatInfo);
if (!NT_SUCCESS(Status)) goto ByeBye;
if (RecognizedFS == FALSE)
{
Status = STATUS_UNRECOGNIZED_VOLUME;//不能识别,也即不能匹配
goto ByeBye;
}
HashTableSize = FCB_HASH_TABLE_SIZE;//65536
//关键。创建一个匿名的文件卷设备
Status = IoCreateDevice(VfatGlobalData->DriverObject,
ROUND_UP(sizeof (DEVICE_EXTENSION), sizeof(ULONG)) + sizeof(HASHENTRY*) * HashTableSize,
NULL,
FILE_DEVICE_FILE_SYSTEM,//文件卷类型
DeviceToMount->Characteristics,
FALSE,
&DeviceObject);
DeviceObject->Flags = DeviceObject->Flags | DO_DIRECT_IO;
DeviceExt = (PVOID) DeviceObject->DeviceExtension;
RtlZeroMemory(DeviceExt, ROUND_UP(sizeof(DEVICE_EXTENSION), sizeof(ULONG)) + sizeof(HASHENTRY*) * HashTableSize);
//文件卷设备都有一个hash FCB表
DeviceExt->FcbHashTable = (HASHENTRY**)((ULONG_PTR)DeviceExt + ROUND_UP(sizeof(DEVICE_EXTENSION), sizeof(ULONG)));
DeviceExt->HashTableSize = HashTableSize;
DeviceObject->Vpb = Vpb; //vpb此时为物理卷的vpb(非栈顶物理卷)
DeviceToMount->Vpb = DeviceObject->Vpb; //关键。文件卷的vpb与物理卷的vpb指向同一个结构
//正题。将创建的文件卷挂载到栈顶物理卷上面
Status = VfatMountDevice(DeviceExt, DeviceToMount);
switch (DeviceExt->FatInfo.FatType)
{
case FAT12:
DeviceExt->GetNextCluster = FAT12GetNextCluster;
DeviceExt->FindAndMarkAvailableCluster = FAT12FindAndMarkAvailableCluster;
DeviceExt->WriteCluster = FAT12WriteCluster;
DeviceExt->CleanShutBitMask = 0;
break;
case FAT16:
case FATX16:
DeviceExt->GetNextCluster = FAT16GetNextCluster;
DeviceExt->FindAndMarkAvailableCluster = FAT16FindAndMarkAvailableCluster;
DeviceExt->WriteCluster = FAT16WriteCluster;
DeviceExt->CleanShutBitMask = 0x8000;
break;
case FAT32:
case FATX32:
DeviceExt->GetNextCluster = FAT32GetNextCluster;
DeviceExt->FindAndMarkAvailableCluster = FAT32FindAndMarkAvailableCluster;
DeviceExt->WriteCluster = FAT32WriteCluster;
DeviceExt->CleanShutBitMask = 0x80000000;
break;
}
if (DeviceExt->FatInfo.FatType == FATX16 || DeviceExt->FatInfo.FatType == FATX32) 。。。
Else
{
DeviceExt->GetNextDirEntry = FATGetNextDirEntry;
DeviceExt->BaseDateYear = 1980;
}
//将绑定的物理卷 记录 在文件卷的设备扩展中
DeviceExt->StorageDevice = DeviceToMount;//栈顶物理卷
//同时也将挂载信息记录到物理卷的vpb中(以后拿到一个物理卷,就自然知道它上面挂载的文件卷了)
DeviceToMount ->Vpb->DeviceObject = DeviceObject;//文件卷
DeviceToMount ->Vpb->RealDevice = DeviceToMount;//物理卷
DeviceToMount ->Vpb->Flags |= VPB_MOUNTED;
//修改文件卷的StackSize,使其实质堆栈在物理卷的上面
DeviceObject->StackSize = DeviceToMount ->StackSize + 1;
DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
ExInitializeResourceLite(&DeviceExt->DirResource);
//为FAT表文件创建一个流文件对象(FAT表本身也当做一种特殊的文件)
DeviceExt->FATFileObject = IoCreateStreamFileObject(NULL, DeviceExt->StorageDevice);
//为FAT表文件创建一个文件名为‘\\
’的fcb
Fcb = vfatNewFCB(DeviceExt, &NameU);
DeviceExt->FATFileObject->FsContext = Fcb;
//为FAT表文件创建一个Ccb上下文控制块
Ccb = ExAllocateFromNPagedLookasideList(&VfatGlobalData->CcbLookasideList);
RtlZeroMemory(Ccb, sizeof (VFATCCB));
DeviceExt->FATFileObject->FsContext2 = Ccb;
//注意:各个文件对象的section对象指针均指向其所属的FCB内部的Section指针,FAT表文件也不例外
DeviceExt->FATFileObject->SectionObjectPointer = &Fcb->SectionObjectPointers;
DeviceExt->FATFileObject->PrivateCacheMap = NULL;
DeviceExt->FATFileObject->Vpb = DeviceObject->Vpb;
Fcb->FileObject = DeviceExt->FATFileObject;//表示最初打开本文件的那个文件对象
Fcb->Flags |= FCB_IS_FAT;
//FAT表的大小
Fcb->RFCB.FileSize.QuadPart = DeviceExt->FatInfo.FATSectors * DeviceExt->FatInfo.BytesPerSector;
Fcb->RFCB.ValidDataLength = Fcb->RFCB.FileSize;
Fcb->RFCB.AllocationSize = Fcb->RFCB.FileSize;
//初始化每个文件对象的私有缓冲控制块,指向FCB的公共缓冲控制块。这样,所有文件对象都使用同//一份文件缓冲。这就是为什么文件缓冲是所有进程共享的。
CcInitializeCacheMap(DeviceExt->FATFileObject, (&Fcb->RFCB.AllocationSize),
TRUE,&VfatGlobalData->CacheMgrCallbacks,Fcb);
DeviceExt->LastAvailableCluster = 2;//2号簇开始是数据区
ExInitializeResourceLite(&DeviceExt->FatResource);
InitializeListHead(&DeviceExt->FcbListHead);//初始时卷中未打开任何文件,因此FCB列表为空
//整个物理卷也当做一个特殊的文件,为其创建一个FCB
VolumeFcb = vfatNewFCB(DeviceExt, &VolumeNameU);
VolumeFcb->Flags = FCB_IS_VOLUME;
VolumeFcb->RFCB.FileSize.QuadPart = DeviceExt->FatInfo.Sectors * DeviceExt->FatInfo.BytesPerSector;
VolumeFcb->RFCB.ValidDataLength = VolumeFcb->RFCB.FileSize;
VolumeFcb->RFCB.AllocationSize = VolumeFcb->RFCB.FileSize;
DeviceExt->VolumeFcb = VolumeFcb;
ExAcquireResourceExclusiveLite(&VfatGlobalData->VolumeListLock, TRUE);
//将新创建的文件卷挂入FAT32内部的全局链表
InsertHeadList(&VfatGlobalData->VolumeListHead, &DeviceExt->VolumeListEntry);
ExReleaseResourceLite(&VfatGlobalData->VolumeListLock);
DeviceObject->Vpb->SerialNumber = DeviceExt->FatInfo.VolumeID; //卷ID即是序列号
ReadVolumeLabel(DeviceExt, DeviceObject->Vpb);//记录卷标到vpb中
//第一个簇表项的内容有点特殊,存放的不是下一个簇号,而是一个脏标记
Status = GetNextCluster(DeviceExt, 1, &eocMark);
if (NT_SUCCESS(Status))
{
if (eocMark & DeviceExt->CleanShutBitMask)
{
eocMark &= ~DeviceExt->CleanShutBitMask;//清除脏标记
WriteCluster(DeviceExt, 1, eocMark);
VolumeFcb->Flags |= VCB_CLEAR_DIRTY;
}
}
VolumeFcb->Flags |= VCB_IS_DIRTY;
FsRtlNotifyVolumeEvent(DeviceExt->FATFileObject, FSRTL_VOLUME_MOUNT);
Status = STATUS_SUCCESS;
ByeBye:
if (!NT_SUCCESS(Status)) 。。。
return Status;
}
如上,这个函数的核心功能便是先识别,识别成功后再创建一个匿名的文件卷,挂载在物理卷上面(即堆栈、绑定在上面),然后将挂载信息记录在物理卷的vpb中。
我们看下是如何进行识别的。
NTSTATUS
VfatHasFileSystem(PDEVICE_OBJECT DeviceToMount,//栈顶物理卷
PBOOLEAN RecognizedFS,//OUT 识别结果
PFATINFO pFatInfo)//OUT 引导信息(即格式化信息)
{
NTSTATUS Status;
PARTITION_INFORMATION PartitionInfo;//该物理卷的分区信息
DISK_GEOMETRY DiskGeometry;//该物理卷的几何信息
FATINFO FatInfo;//引导信息
ULONG Size;
ULONG Sectors;
LARGE_INTEGER Offset;
struct _BootSector* Boot;//引导扇区(刚好是一个这样的结构)
struct _BootSectorFatX* BootFatX;
BOOLEAN PartitionInfoIsValid = FALSE;
*RecognizedFS = FALSE;
Size = sizeof(DISK_GEOMETRY);
//向下层的硬件驱动查询几何信息
Status = VfatBlockDeviceIoControl(DeviceToMount,IOCTL_DISK_GET_DRIVE_GEOMETRY,
NULL,0,&DiskGeometry,&Size,FALSE);
FatInfo.FixedMedia = DiskGeometry.MediaType == FixedMedia ? TRUE : FALSE;
//if 那个物理卷是个磁盘(不是光盘)
if (DiskGeometry.MediaType == FixedMedia || DiskGeometry.MediaType == RemovableMedia)
{
Size = sizeof(PARTITION_INFORMATION);
////向下层的硬件驱动查询该磁盘卷的分区信息
Status = VfatBlockDeviceIoControl(DeviceToMount,IOCTL_DISK_GET_PARTITION_INFO,
NULL,0,&PartitionInfo,&Size,FALSE);
PartitionInfoIsValid = TRUE;
if (PartitionInfo.PartitionType)
{
if (PartitionInfo.PartitionType == PARTITION_FAT_12 ||
PartitionInfo.PartitionType == PARTITION_FAT_16 ||
PartitionInfo.PartitionType == PARTITION_HUGE ||
PartitionInfo.PartitionType == PARTITION_FAT32 ||
PartitionInfo.PartitionType == PARTITION_FAT32_XINT13 ||
PartitionInfo.PartitionType == PARTITION_XINT13)
{
*RecognizedFS = TRUE;
}
}
else if (DiskGeometry.MediaType == RemovableMedia && //可移动磁盘
PartitionInfo.PartitionNumber > 0 &&
PartitionInfo.StartingOffset.QuadPart == 0 &&
PartitionInfo.PartitionLength.QuadPart > 0)
{
*RecognizedFS = TRUE;
}
}
else if (DiskGeometry.MediaType == Unknown)
{
*RecognizedFS = TRUE;
DiskGeometry.BytesPerSector = 512;
}
Else *RecognizedFS = TRUE;
if (*RecognizedFS)
{
Boot = ExAllocatePoolWithTag(NonPagedPool, DiskGeometry.BytesPerSector, TAG_VFAT);
Offset.QuadPart = 0;
//读取磁盘的引导扇区
Status = VfatReadDisk(DeviceToMount, &Offset, DiskGeometry.BytesPerSector, Boot, FALSE);
if (NT_SUCCESS(Status))
{
if (Boot->Signatur1 != 0xaa55)//验证签名
*RecognizedFS = FALSE;
if (*RecognizedFS && Boot->BytesPerSector != 512 && Boot->BytesPerSector != 1024 &&
Boot->BytesPerSector != 2048 && Boot->BytesPerSector != 4096)
{
*RecognizedFS = FALSE;
}
if (*RecognizedFS && Boot->FATCount != 1 && Boot->FATCount != 2)
{
*RecognizedFS = FALSE;
}
if (*RecognizedFS &&Boot->Media != 0xf0 &&Boot->Media != 0xf8 &&Boot->Media != 0xf9 &&
Boot->Media != 0xfa &&Boot->Media != 0xfb &&Boot->Media != 0xfc &&
Boot->Media != 0xfd &&Boot->Media != 0xfe &&Boot->Media != 0xff)
{
*RecognizedFS = FALSE;
}
if (*RecognizedFS &&Boot->SectorsPerCluster != 1 &&Boot->SectorsPerCluster != 2 &&
Boot->SectorsPerCluster != 4 &&Boot->SectorsPerCluster != 8 &&
Boot->SectorsPerCluster != 16 &&Boot->SectorsPerCluster != 32 &&
Boot->SectorsPerCluster != 64 &&Boot->SectorsPerCluster != 128)
{
*RecognizedFS = FALSE;
}
if (*RecognizedFS && Boot->BytesPerSector * Boot->SectorsPerCluster > 32 * 1024)
*RecognizedFS = FALSE;
if (*RecognizedFS) //if 验证正确
{
FatInfo.VolumeID = Boot->VolumeID;//即序列号
FatInfo.FATStart = Boot->ReservedSectors;//FAT表的起始扇区号(即FAT表的位置)
FatInfo.FATCount = Boot->FATCount;//FAT表的个数(可最多支持两个FAT表),即长度
//Fat表的扇区数
FatInfo.FATSectors = Boot->FATSectors ? Boot->FATSectors : ((struct _BootSector32*) Boot)->FATSectors32;
//每个扇区的字节数(即扇区大小)
FatInfo.BytesPerSector = Boot->BytesPerSector;
//每个簇的扇区数(即簇的大小)
FatInfo.SectorsPerCluster = Boot->SectorsPerCluster;
FatInfo.BytesPerCluster = FatInfo.BytesPerSector * FatInfo.SectorsPerCluster;
//根目录占用的扇区数(即根目录的大小)
FatInfo.rootDirectorySectors = ((Boot->RootEntries * 32) + Boot->BytesPerSector - 1) / Boot->BytesPerSector;
//根目录的起始扇区号(即根目录的位置)
FatInfo.rootStart = FatInfo.FATStart + FatInfo.FATCount * FatInfo.FATSectors;
//数据区的起始扇区号,即位置
FatInfo.dataStart = FatInfo.rootStart + FatInfo.rootDirectorySectors;
//该磁盘卷总的扇区数,即该磁盘卷的总长度
FatInfo.Sectors = Sectors = Boot->Sectors ? Boot->Sectors : Boot->SectorsHuge;
//数据区的扇区数,即数据区的大小
Sectors -= Boot->ReservedSectors + FatInfo.FATCount * FatInfo.FATSectors + FatInfo.rootDirectorySectors;
//该磁盘卷总的簇数,即该磁盘卷的总长度
FatInfo.NumberOfClusters = Sectors / Boot->SectorsPerCluster;
if (FatInfo.NumberOfClusters < 4085)
{
FatInfo.FatType = FAT12;
FatInfo.RootCluster = (FatInfo.rootStart - 1) / FatInfo.SectorsPerCluster;
}
else if (FatInfo.NumberOfClusters >= 65525)
{
FatInfo.FatType = FAT32;
FatInfo.RootCluster = ((struct _BootSector32*) Boot)->RootCluster;
FatInfo.rootStart = FatInfo.dataStart + ((FatInfo.RootCluster - 2) * FatInfo.SectorsPerCluster);
FatInfo.VolumeID = ((struct _BootSector32*) Boot)->VolumeID;
}
Else 。。。
if (PartitionInfoIsValid &&
FatInfo.Sectors > PartitionInfo.PartitionLength.QuadPart / FatInfo.BytesPerSector)
{
*RecognizedFS = FALSE;
}
if (pFatInfo && *RecognizedFS)
*pFatInfo = FatInfo;//将引导信息返回给用户
}
}
ExFreePool(Boot);
}
if (!*RecognizedFS && PartitionInfoIsValid) 。。。 //再检查是不是FATX文件系统
return Status;
}
上面的函数读取磁盘卷的引导信息,以此判断该卷是不是格式化成了本文件系统要求的数据格式。
磁盘卷分为基本区和数据区。数据区用来存放普通文件和目录的数据,基本区则占用了两个簇,存放着该卷的引导信息、FAT表、和根目录(一个目录项数组)。顺次为:【引导、FAT、根目录】
卷中的第一个扇区即是引导扇区,记录了该卷的一些格式化信息。引导扇区内容的格式就是如下的结构定义(其整个结构大小刚好为512B)
struct _BootSector
{
unsigned char magic0, res0, magic1;//实际为一条jmp指令
unsigned char OEMName[8];
unsigned short BytesPerSector;//该卷中每个扇区的大小
unsigned char SectorsPerCluster;//该卷中每个簇的扇区数(也即间接指定了每个簇的大小)
unsigned short ReservedSectors;//FAT表的起始扇区号(也即FAT表的偏移位置)
unsigned char FATCount;//FAT表的个数
unsigned short RootEntries;//根目录中的条目个数
unsigned short Sectors;//该卷中的总扇区数,也即该卷的总长
unsigned char Media;//该卷的介质类型
unsigned short FATSectors;//每个FAT表包含的扇区数
unsigned short SectorsPerTrack, Heads;
unsigned long HiddenSectors, SectorsHuge;//隐藏扇区数
unsigned char Drive, Res1, Sig;
unsigned long VolumeID;//即序列号
unsigned char VolumeLabel[11], SysType[8];//卷标
unsigned char Res2[448];//保留的填充字节
unsigned short Signatur1;//签名,固定为0xAA55
};
从上可以看出,引导扇区中记录了该卷的格式化信息。
识别过程需要读取磁盘的引导扇区,下面的函数就是这个作用
NTSTATUS
VfatReadDisk (IN PDEVICE_OBJECT pDeviceObject,
IN PLARGE_INTEGER ReadOffset,
IN ULONG ReadLength,
IN OUT PUCHAR Buffer,
IN BOOLEAN Override)
{
PIO_STACK_LOCATION Stack;
PIRP Irp;
IO_STATUS_BLOCK IoStatus;
KEVENT event;
NTSTATUS Status;
KeInitializeEvent (&event, NotificationEvent, FALSE);
Irp = IoBuildSynchronousFsdRequest (IRP_MJ_READ,pDeviceObject,Buffer,ReadLength,
ReadOffset,&event,&IoStatus);
if (Override)
{
Stack = IoGetNextIrpStackLocation(Irp);
Stack->Flags |= SL_OVERRIDE_VERIFY_VOLUME;
}
Status = IoCallDriver (pDeviceObject, Irp);//发给下层的磁盘驱动
if (Status == STATUS_PENDING)
{
KeWaitForSingleObject (&event, Suspended, KernelMode, FALSE, NULL);
Status = IoStatus.Status;
}
if (!NT_SUCCESS (Status)) 。。。
return (STATUS_SUCCESS);
}
当识别成功后,FAT32系统就会创建一个匿名的卷设备,然后调用下面的函数挂载在物理卷上面
NTSTATUS
VfatMountDevice(PDEVICE_EXTENSION DeviceExt,//文件卷设备扩展
PDEVICE_OBJECT DeviceToMount)//栈顶物理卷
{
NTSTATUS Status;
BOOLEAN RecognizedFS;
//再次调用这个函数,不过,这次的目的不是为了识别,而是读取引导信息返回到FatInfo中
//为什么要这样绕弯再次读取引导信息,我也不明白
Status = VfatHasFileSystem(DeviceToMount, &RecognizedFS, &DeviceExt->FatInfo);
if (!NT_SUCCESS(Status))
return(Status);
return(STATUS_SUCCESS);
}
挂载完成后,会把挂载信息记录到物理卷的vpb中,并且文件卷的vpb与物理卷的vpb指向同一结构,这样,就能根据物理卷找到它上面的文件卷,也能根据文件卷找到其下的物理卷。因此,物理卷与文件卷之间看似没有直接绑定,实质上是绑定在一起的,二者之间通过vpb相互联系。
当刚刚挂载后,还会创建一个代表FAT表的流文件对象和FCB。
PFILE_OBJECT
IoCreateStreamFileObject(IN PFILE_OBJECT FileObject,IN PDEVICE_OBJECT DeviceObject)
{
return IoCreateStreamFileObjectEx(FileObject, DeviceObject, NULL);
}
PFILE_OBJECT
IoCreateStreamFileObjectEx(IN PFILE_OBJECT FileObject OPTIONAL,
IN PDEVICE_OBJECT DeviceObject OPTIONAL,
OUT PHANDLE FileObjectHandle OPTIONAL)
{
PFILE_OBJECT CreatedFileObject;
NTSTATUS Status;
HANDLE FileHandle;
OBJECT_ATTRIBUTES ObjectAttributes;
if (FileObject) DeviceObject = FileObject->DeviceObject;
InterlockedIncrement(&DeviceObject->ReferenceCount);
InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);
Status = ObCreateObject(KernelMode,IoFileObjectType,&ObjectAttributes,KernelMode,
NULL,sizeof(FILE_OBJECT),sizeof(FILE_OBJECT),
0, (PVOID*)&CreatedFileObject);
RtlZeroMemory(CreatedFileObject, sizeof(FILE_OBJECT));
CreatedFileObject->DeviceObject = DeviceObject;
CreatedFileObject->Type = IO_TYPE_FILE;
CreatedFileObject->Size = sizeof(FILE_OBJECT);
CreatedFileObject->Flags = FO_STREAM_FILE;//标志
KeInitializeEvent(&CreatedFileObject->Event, SynchronizationEvent, FALSE);
Status = ObInsertObject(CreatedFileObject,NULL,FILE_READ_DATA,1,
(PVOID*)&CreatedFileObject,&FileHandle);
if (!NT_SUCCESS(Status)) ExRaiseStatus(Status);
CreatedFileObject->Flags |= FO_HANDLE_CREATED;
if (DeviceObject->Vpb)
InterlockedIncrement((PLONG)&DeviceObject->Vpb->ReferenceCount);
if (FileObjectHandle)
{
*FileObjectHandle = FileHandle;
ObDereferenceObject(CreatedFileObject);
}
else
ObCloseHandle(FileHandle, KernelMode);
return CreatedFileObject;
}
下面的函数用于为指定文件创建一个FCB
PVFATFCB vfatNewFCB(PDEVICE_EXTENSION pVCB, PUNICODE_STRING pFileNameU)
{
PVFATFCB rcFCB;
rcFCB = ExAllocateFromNPagedLookasideList(&VfatGlobalData->FcbLookasideList);
RtlZeroMemory(rcFCB, sizeof(VFATFCB));
vfatInitFcb(rcFCB, pFileNameU);//将文件名填写到fcb中
if (pVCB->Flags & VCB_IS_FATX) 。。。
else
rcFCB->Attributes = &rcFCB->entry.Fat.Attrib;
rcFCB->Hash.Hash = vfatNameHash(0, &rcFCB->PathNameU);//计算全路径的hash值
rcFCB->Hash.self = rcFCB;
rcFCB->ShortHash.self = rcFCB;
ExInitializeResourceLite(&rcFCB->PagingIoResource);
ExInitializeResourceLite(&rcFCB->MainResource);
FsRtlInitializeFileLock(&rcFCB->FileLock, NULL, NULL);
ExInitializeFastMutex(&rcFCB->LastMutex);
rcFCB->RFCB.PagingIoResource = &rcFCB->PagingIoResource;
rcFCB->RFCB.Resource = &rcFCB->MainResource;
rcFCB->RFCB.IsFastIoPossible = FastIoIsNotPossible;
return rcFCB;
}
每个文件FCB都有一个公共的文件缓冲,供所有相应的文件对象使用,也即N个进程在同一时刻打开着同一文件时,他们使用相同的文件缓冲。
下面的函数用来初始化每个文件对象关联的文件缓冲,使其指向公共的文件缓冲(如果尚未创建公共缓冲,就创建一个)
VOID
CcInitializeCacheMap (
IN PFILE_OBJECT FileObject,
IN PCC_FILE_SIZES FileSizes,//ReactOS没用
IN BOOLEAN PinAccess,//ReactOS没用
IN PCACHE_MANAGER_CALLBACKS CallBacks,
IN PVOID LazyWriterContext
)
{
//ReactOS的实现没使用FileSizes和PinAccess参数
CcRosInitializeFileCache(FileObject,VACB_MAPPING_GRANULARITY, CallBacks,
LazyWriterContext);
}
NTSTATUS
CcRosInitializeFileCache(PFILE_OBJECT FileObject,//指定文件对象
ULONG CacheSegmentSize,//缓冲段的大小
PCACHE_MANAGER_CALLBACKS CallBacks,
PVOID LazyWriterContext)//传的是fcb
{
PBCB Bcb;
//FileObject->SectionObjectPointer实际上等于fcb->SectionObjectPointers
Bcb = FileObject->SectionObjectPointer->SharedCacheMap;
KeAcquireGuardedMutex(&ViewLock);
if (Bcb == NULL)//if 该文件对象所属的fcb尚未分配bcb缓冲控制块
{
Bcb = ExAllocateFromNPagedLookasideList(&BcbLookasideList);//分配一个缓冲控制块
memset(Bcb, 0, sizeof(BCB));
ObReferenceObjectByPointer(FileObject,FILE_ALL_ACCESS,NULL,KernelMode);
Bcb->FileObject = FileObject;
Bcb->CacheSegmentSize = CacheSegmentSize;
Bcb->Callbacks = CallBacks;
Bcb->LazyWriteContext = LazyWriterContext;//传的是fcb
if (FileObject->FsContext)
{
Bcb->AllocationSize =
((PFSRTL_COMMON_FCB_HEADER)FileObject->FsContext)->AllocationSize;
Bcb->FileSize =
((PFSRTL_COMMON_FCB_HEADER)FileObject->FsContext)->FileSize;
}
KeInitializeSpinLock(&Bcb->BcbLock);
InitializeListHead(&Bcb->BcbSegmentListHead);//一个bcb内部有N个缓冲段
//记录分配的bcb到fcb中
FileObject->SectionObjectPointer->SharedCacheMap = Bcb;
}
if (FileObject->PrivateCacheMap == NULL)
{
//看到没,每个文件对象使用的私有文件缓冲都指向fcb中的公共文件缓冲
FileObject->PrivateCacheMap = Bcb;
Bcb->RefCount++;
}
if (Bcb->BcbRemoveListEntry.Flink != NULL)
{
RemoveEntryList(&Bcb->BcbRemoveListEntry);
Bcb->BcbRemoveListEntry.Flink = NULL;
}
KeReleaseGuardedMutex(&ViewLock);
return(STATUS_SUCCESS);
}
明白了文件卷的挂载时机、挂载过程后,再次回到CreateFile中,看看文件系统是如何处理CreateFile irp的。代码就不再贴出来了,回忆一下,CreateFile内部会创建一个IRP_MJ_CREATE发给磁盘卷上面的文件卷(是通过vpb找到其上挂载的文件卷的),我们看下FAT32文件系统是如何处理这种irp的。
NTSTATUS VfatCreateFile ( PDEVICE_OBJECT DeviceObject, PIRP Irp )
{
PIO_STACK_LOCATION Stack;
PFILE_OBJECT FileObject;
NTSTATUS Status = STATUS_SUCCESS;
PDEVICE_EXTENSION DeviceExt;
ULONG RequestedDisposition, RequestedOptions;
PVFATCCB pCcb;
PVFATFCB pFcb = NULL;
PVFATFCB ParentFcb = NULL;
PWCHAR c, last;
BOOLEAN PagingFileCreate = FALSE;
BOOLEAN Dots;
UNICODE_STRING FileNameU;
UNICODE_STRING PathNameU;
Stack = IoGetCurrentIrpStackLocation (Irp);
RequestedDisposition = ((Stack->Parameters.Create.Options >> 24) & 0xff);
RequestedOptions = Stack->Parameters.Create.Options & FILE_VALID_OPTION_FLAGS;
//要打开的是不是页文件
PagingFileCreate = (Stack->Flags & SL_OPEN_PAGING_FILE) ? TRUE : FALSE;
FileObject = Stack->FileObject;//IopParseDevice中创建的文件对象
DeviceExt = DeviceObject->DeviceExtension;
if (RequestedOptions & FILE_DIRECTORY_FILE && RequestedDisposition == FILE_SUPERSEDE)
return(STATUS_INVALID_PARAMETER);
if (RequestedOptions & FILE_DIRECTORY_FILE &&
RequestedOptions & FILE_NON_DIRECTORY_FILE)
{
return(STATUS_INVALID_PARAMETER);
}
//if 要打开的就是文件卷本身,而非文件卷中的文件,特殊处理
if (FileObject->FileName.Length == 0 &&
(FileObject->RelatedFileObject == NULL || FileObject->RelatedFileObject->FsContext2 ))
{
if (RequestedDisposition == FILE_CREATE || RequestedDisposition == FILE_OVERWRITE_IF || RequestedDisposition == FILE_SUPERSEDE)
{
return(STATUS_ACCESS_DENIED);
}
pFcb = DeviceExt->VolumeFcb;//当初挂载时创建的文件卷fcb
pCcb = ExAllocateFromNPagedLookasideList(&VfatGlobalData->CcbLookasideList);
RtlZeroMemory(pCcb, sizeof(VFATCCB));
FileObject->SectionObjectPointer = &pFcb->SectionObjectPointers;
FileObject->FsContext = pFcb;
FileObject->FsContext2 = pCcb;
pFcb->RefCount++;
Irp->IoStatus.Information = FILE_OPENED;
return(STATUS_SUCCESS);
}
PathNameU = FileObject->FileName;//相对于盘符的路径 或 相对于起点目录的路径
c = PathNameU.Buffer + PathNameU.Length / sizeof(WCHAR);
last = c - 1;
Dots = TRUE;
//下面while循环用来检测路径是否符合正确的点号规则
while (c-- > PathNameU.Buffer)
{
if (*c == L'\\' || c == PathNameU.Buffer)
{
if (Dots && last > c)
return(STATUS_OBJECT_NAME_INVALID);
last = c - 1;
Dots = TRUE;
}
else if (*c != L'.')
Dots = FALSE;
if (*c != '\\' && vfatIsLongIllegal(*c))
return(STATUS_OBJECT_NAME_INVALID);
}
//如果用户给定了起点目录,那么相对路径不能以\开头
if (FileObject->RelatedFileObject && PathNameU.Length >= sizeof(WCHAR) && PathNameU.Buffer[0] == L'\\')
{
return(STATUS_OBJECT_NAME_INVALID);
}
//去掉路径末尾的\
if (PathNameU.Length > sizeof(WCHAR) && PathNameU.Buffer[PathNameU.Length/sizeof(WCHAR)-1] == L'\\')
{
PathNameU.Length -= sizeof(WCHAR);
}
//根据路径尝试打开磁盘文件,查找/创建FCB(注意这个函数不会创建文件)
//这个函数会逐级临时打开路径中的每个目录,然后关闭。但最后的父目录不会关闭。
Status = VfatOpenFile (DeviceExt, &PathNameU, FileObject, &ParentFcb);
//STATUS_OBJECT_PATH_NOT_FOUND指路径的某个中间目录在磁盘上不存在
if (Status == STATUS_OBJECT_PATH_NOT_FOUND || Status == STATUS_INVALID_PARAMETER ||
Status == STATUS_DELETE_PENDING)
{
if (ParentFcb)
vfatReleaseFCB (DeviceExt, ParentFcb);//关闭临时打开的最后一个目录
return(Status);
}
if (!NT_SUCCESS(Status) && ParentFcb == NULL)
return Status;
//if一直找到了父目录,但是文件不存在。
if (!NT_SUCCESS (Status))
{
//if用户指定的创建选项是‘如果文件不存在就创建’
if (RequestedDisposition == FILE_CREATE ||
RequestedDisposition == FILE_OPEN_IF ||
RequestedDisposition == FILE_OVERWRITE_IF ||
RequestedDisposition == FILE_SUPERSEDE)
{
ULONG Attributes;
Attributes = Stack->Parameters.Create.FileAttributes;
vfatSplitPathName(&PathNameU, NULL, &FileNameU);//提取出路径中的文件名部分
//在父目录中创建文件(包括目录项和FCB)
Status = VfatAddEntry (DeviceExt, &FileNameU, &pFcb, ParentFcb, RequestedOptions,
(UCHAR)(Attributes & FILE_ATTRIBUTE_VALID_FLAGS));
vfatReleaseFCB (DeviceExt, ParentFcb);//不再需要父目录fcb了,关闭它
if (NT_SUCCESS (Status))
{
//关联文件对象和FCB在一起
Status = vfatAttachFCBToFileObject (DeviceExt, pFcb, FileObject);
if ( !NT_SUCCESS(Status) ) 。。。
Irp->IoStatus.Information = FILE_CREATED;
//每个文件在创建时可以指定初始的文件长度
VfatSetAllocationSizeInformation(FileObject,pFcb,DeviceExt,
&Irp->Overlay.AllocationSize);
//设置EA附加属性
VfatSetExtendedAttributes(FileObject,Irp->AssociatedIrp.SystemBuffer,
Stack->Parameters.Create.EaLength);
if (PagingFileCreate) //要打开的是一个页文件
pFcb->Flags |= FCB_IS_PAGE_FILE;
}
Else return(Status);
}
else
{
if (ParentFcb) vfatReleaseFCB (DeviceExt, ParentFcb);
return(Status);
}
}
Else //如果磁盘上有这个文件
{
if (ParentFcb) vfatReleaseFCB (DeviceExt, ParentFcb);
if (RequestedDisposition == FILE_CREATE)//if 要求只能创建
{
Irp->IoStatus.Information = FILE_EXISTS;
VfatCloseFile (DeviceExt, FileObject);
return(STATUS_OBJECT_NAME_COLLISION);//看到没,这个错误码
}
pFcb = FileObject->FsContext;
if (pFcb->OpenHandleCount != 0) //如果不是首次打开该文件,检查共享权限
{
Status = IoCheckShareAccess(Stack->Parameters.Create.SecurityContext->DesiredAccess,
Stack->Parameters.Create.ShareAccess,
FileObject,
&pFcb->FCBShareAccess,
FALSE);
if (!NT_SUCCESS(Status))//后来者共享访问权限检查失败
{
VfatCloseFile (DeviceExt, FileObject);
return(Status);
}
}
if (RequestedOptions & FILE_NON_DIRECTORY_FILE &&
*pFcb->Attributes & FILE_ATTRIBUTE_DIRECTORY)
{
VfatCloseFile (DeviceExt, FileObject);
return(STATUS_FILE_IS_A_DIRECTORY);
}
if (RequestedOptions & FILE_DIRECTORY_FILE &&
!(*pFcb->Attributes & FILE_ATTRIBUTE_DIRECTORY))
{
VfatCloseFile (DeviceExt, FileObject);
return(STATUS_NOT_A_DIRECTORY);
}
if (PagingFileCreate)
{
if (pFcb->RefCount > 1)
{
if(!(pFcb->Flags & FCB_IS_PAGE_FILE))
{
VfatCloseFile(DeviceExt, FileObject);
return(STATUS_INVALID_PARAMETER);
}
}
else
{
pFcb->Flags |= FCB_IS_PAGE_FILE;
}
}
else
{
if (pFcb->Flags & FCB_IS_PAGE_FILE)
{
VfatCloseFile(DeviceExt, FileObject);
return(STATUS_INVALID_PARAMETER);
}
}
//若用户要求如果存在就重写
if (RequestedDisposition == FILE_OVERWRITE ||
RequestedDisposition == FILE_OVERWRITE_IF ||
RequestedDisposition == FILE_SUPERSEDE)
{
ExAcquireResourceExclusiveLite(&(pFcb->MainResource), TRUE);
Status = VfatSetAllocationSizeInformation (FileObject,pFcb,DeviceExt,
&Irp->Overlay.AllocationSize);
ExReleaseResourceLite(&(pFcb->MainResource));
if (!NT_SUCCESS (Status)) 。。。
}
if (RequestedDisposition == FILE_SUPERSEDE)
Irp->IoStatus.Information = FILE_SUPERSEDED;
else if (RequestedDisposition == FILE_OVERWRITE ||
RequestedDisposition == FILE_OVERWRITE_IF)
{
Irp->IoStatus.Information = FILE_OVERWRITTEN;
}
else
Irp->IoStatus.Information = FILE_OPENED;
}
//if 首次打开,设置共享权限
if (pFcb->OpenHandleCount == 0)
{
IoSetShareAccess(Stack->Parameters.Create.SecurityContext->DesiredAccess,
Stack->Parameters.Create.ShareAccess,
FileObject,
&pFcb->FCBShareAccess);
}
Else //修改共享权限为已有共享权限的交集
IoUpdateShareAccess(FileObject,&pFcb->FCBShareAccess);
pFcb->OpenHandleCount++;//递增FCB的文件对象个数
return(Status);
}
上面最关键的工作便是VfatOpenFile。我们看
NTSTATUS
VfatOpenFile (
PDEVICE_EXTENSION DeviceExt,//文件卷设备扩展
PUNICODE_STRING PathNameU,//相对路径
PFILE_OBJECT FileObject,//IopParseDevice中创建的文件对象
PVFATFCB* ParentFcb )//OUT,返回临时打开的父目录fcb
{
PVFATFCB Fcb;
NTSTATUS Status;
if (FileObject->RelatedFileObject)//如果用户给定了起点目录
{
*ParentFcb = FileObject->RelatedFileObject->FsContext;
(*ParentFcb)->RefCount++;
}
Else *ParentFcb = NULL;
if (!DeviceExt->FatInfo.FixedMedia)//可移动磁盘需要校验是否仍安在机器上
{
Status = VfatBlockDeviceIoControl (DeviceExt->StorageDevice,
IOCTL_DISK_CHECK_VERIFY,NULL,0,NULL,0,FALSE);
if (Status == STATUS_VERIFY_REQUIRED)
{
PDEVICE_OBJECT DeviceToVerify;
DeviceToVerify = IoGetDeviceToVerify (PsGetCurrentThread ());
IoSetDeviceToVerify (PsGetCurrentThread (), NULL);
Status = IoVerifyVolume(DeviceToVerify,FALSE);
}
if (!NT_SUCCESS(Status))
{
*ParentFcb = NULL;
return Status;
}
}
if (*ParentFcb) (*ParentFcb)->RefCount++;
//检查这个文件是否已经被其他进程打开了,若是,就返回其FCB。若没有,就为其创建一个FCB
Status = vfatGetFCBForFile (DeviceExt, ParentFcb, &Fcb, PathNameU);
if (!NT_SUCCESS (Status))
return Status;
if (Fcb->Flags & FCB_DELETE_PENDING)
{
vfatReleaseFCB (DeviceExt, Fcb);
return STATUS_DELETE_PENDING;
}
//关联文件对象与fcb
Status = vfatAttachFCBToFileObject (DeviceExt, Fcb, FileObject);
if (!NT_SUCCESS(Status))
vfatReleaseFCB (DeviceExt, Fcb);
return Status;
}
继续看:
NTSTATUS
vfatGetFCBForFile (
PDEVICE_EXTENSION pVCB,//文件卷设备扩展
PVFATFCB *pParentFCB,//起点目录
PVFATFCB *pFCB,//OUT 返回查找/创建的FCB
PUNICODE_STRING pFileNameU)//相对于起点目录的路径
{
NTSTATUS status;
PVFATFCB FCB = NULL;
PVFATFCB parentFCB;
UNICODE_STRING NameU;
UNICODE_STRING RootNameU = RTL_CONSTANT_STRING(L"\\");
UNICODE_STRING FileNameU;
WCHAR NameBuffer[260];
PWCHAR curr, prev, last;
ULONG Length;
FileNameU.Buffer = NameBuffer;
FileNameU.MaximumLength = sizeof(NameBuffer);
RtlCopyUnicodeString(&FileNameU, pFileNameU);
parentFCB = *pParentFCB;
if (parentFCB == NULL)//if 用户没给定起点目录,给的是绝对路径
{
//如果给定的相对路径就是‘\’,也即用户给定了‘C:\’形式路径
if (RtlEqualUnicodeString(&FileNameU, &RootNameU, FALSE))
{
FCB = vfatOpenRootFCB (pVCB);//打开根目录,返回根目录的FCB
*pFCB = FCB;
*pParentFCB = NULL;
return (FCB != NULL) ? STATUS_SUCCESS : STATUS_OBJECT_PATH_NOT_FOUND;
}
//检查这个文件是否已经被其他进程打开了
FCB = vfatGrabFCBFromTable (pVCB, &FileNameU);
if (FCB)
{
*pFCB = FCB;
*pParentFCB = FCB->parentFcb;//返回其父目录的FCB
(*pParentFCB)->RefCount++;
return STATUS_SUCCESS;
}
//目标文件尚未被任何进程打开
last = curr = FileNameU.Buffer + FileNameU.Length / sizeof(WCHAR) - 1;
while (*curr != L'\\' && curr > FileNameU.Buffer)
curr--;
//if 路径中有一个父目录,如C:\Windows\Explorer.exe 而不是 C:\Explorer.exe 形式,
//就先看看它的父目录是否被打开了,以直接从父目录开始搜索,提高查找效率
if (curr > FileNameU.Buffer)
{
NameU.Buffer = FileNameU.Buffer;
NameU.MaximumLength = NameU.Length = (curr - FileNameU.Buffer) * sizeof(WCHAR);
//先看父目录是否被打开了
FCB = vfatGrabFCBFromTable(pVCB, &NameU);
if (FCB)//if 父目录打开了
{
Length = (curr - FileNameU.Buffer) * sizeof(WCHAR);//父目录绝对路径的长度
//如果全路径长度不相符(一个是带~省略的全路径,一个是真正的全路径)
if (Length != FCB->PathNameU.Length)
{
if (FileNameU.Length + FCB->PathNameU.Length - Length > FileNameU.MaximumLength)
{
vfatReleaseFCB (pVCB, FCB);
return STATUS_OBJECT_NAME_INVALID;
}
//拷贝文件名部分
RtlMoveMemory(FileNameU.Buffer + FCB->PathNameU.Length / sizeof(WCHAR),
curr, FileNameU.Length - Length);
FileNameU.Length += (USHORT)(FCB->PathNameU.Length - Length);
curr = FileNameU.Buffer + FCB->PathNameU.Length / sizeof(WCHAR);
last = FileNameU.Buffer + FileNameU.Length / sizeof(WCHAR) - 1;
}
//拷贝父目录的全路径部分
RtlCopyMemory(FileNameU.Buffer, FCB->PathNameU.Buffer, FCB->PathNameU.Length);
}
}
else
FCB = NULL;
if (FCB == NULL)
{
FCB = vfatOpenRootFCB(pVCB);
curr = FileNameU.Buffer;
}
parentFCB = NULL;
prev = curr;
}
else
{
FCB = parentFCB;
parentFCB = NULL;
prev = curr = FileNameU.Buffer - 1;
last = FileNameU.Buffer + FileNameU.Length / sizeof(WCHAR) - 1;
}
//上面的逻辑用来决定FCB、curr、last、prev变量的值,分别表示搜索的起点目录FCB,相对路径开头位置,相对路径最后一个字符位置,prev变量。这分三种情况:
1、 用户给定了起点目录,那么fcb就是起点目录的fcb,相应的curr就是相对路径
2、 用户给定的是绝对路径,且父目录被打开,那么fcb就是父目录的fcb,curr就是文件名
3、 用户给定的是绝对路径,但父目录没打开,那么fcb就是根目录的fcb,curr是绝对路径
此处所谓绝对路径指相对于盘符的路径如‘\Windows\Explorer.exe’
最终构成 起点fcb+相对路径 形式
//下面的while循环,用来逐级打开相对路径中的中间目录,一一查找,直至找到最后的文件
while (curr <= last)
{
if (parentFCB)//临时关闭上次的父目录fcb
{
vfatReleaseFCB (pVCB, parentFCB);
parentFCB = 0;
}
if (!vfatFCBIsDirectory (FCB))//if 路径中间的节点不是目录
{
vfatReleaseFCB (pVCB, FCB);
FCB = NULL;
*pParentFCB = NULL;
*pFCB = NULL;
return STATUS_OBJECT_PATH_NOT_FOUND;
}
parentFCB = FCB;// parentFCB变量表示当前父目录
if (prev < curr)
{
Length = (curr - prev) * sizeof(WCHAR);
//if该中间节点是~开头的缩写短名,转换成长名
if (Length != parentFCB->LongNameU.Length)
{
if (FileNameU.Length + parentFCB->LongNameU.Length - Length > FileNameU.MaximumLength)
{
vfatReleaseFCB (pVCB, parentFCB);
return STATUS_OBJECT_NAME_INVALID;
}
RtlMoveMemory(prev + parentFCB->LongNameU.Length / sizeof(WCHAR), curr,
FileNameU.Length - (curr - FileNameU.Buffer) * sizeof(WCHAR));
FileNameU.Length += (USHORT)(parentFCB->LongNameU.Length - Length);
curr = prev + parentFCB->LongNameU.Length / sizeof(WCHAR);
last = FileNameU.Buffer + FileNameU.Length / sizeof(WCHAR) - 1;
}
RtlCopyMemory(prev, parentFCB->LongNameU.Buffer, parentFCB->LongNameU.Length);
}
curr++;
prev = curr;//prev指向中间目录名的第一个字符
while (*curr != L'\\' && curr <= last)
curr++;
//此时的NameU可能是个相对路径,也可能是个绝对路径
NameU.Buffer = FileNameU.Buffer;
NameU.Length = (curr - NameU.Buffer) * sizeof(WCHAR);
NameU.MaximumLength = FileNameU.MaximumLength;
//检查该中间目录是否被打开着
FCB = vfatGrabFCBFromTable(pVCB, &NameU);
if (FCB == NULL)//if 没打开
{
NameU.Buffer = prev;
NameU.MaximumLength = NameU.Length = (curr - prev) * sizeof(WCHAR);
//检查这个中间目录在磁盘上是否存在,若存在,临时打开它,返回它的fcb
status = vfatDirFindFile(pVCB, parentFCB, &NameU, &FCB);
if (status == STATUS_OBJECT_NAME_NOT_FOUND)//if 不存在
{
*pFCB = NULL;
if (curr > last)
{
*pParentFCB = parentFCB;
return STATUS_OBJECT_NAME_NOT_FOUND;//文件名不存在
}
else
{
vfatReleaseFCB (pVCB, parentFCB);
*pParentFCB = NULL;
return STATUS_OBJECT_PATH_NOT_FOUND;//中间目录不存在
}
}
else if (!NT_SUCCESS (status)) //文件存在,但创建fcb失败
{
vfatReleaseFCB (pVCB, parentFCB);
*pParentFCB = NULL;
*pFCB = NULL;
return status;
}
}
}
*pParentFCB = parentFCB;//返回目标文件的父目录fcb
*pFCB = FCB;//返回目标文件的fcb
return STATUS_SUCCESS;
}
上面这个函数内部就会调用vfatDirFindFile,如果文件确实在磁盘上存在,但没被打开,就会创建一个fcb。首次打开文件时,必定会创建fcb,以后再打开时,就不用再创建了,直接查得它的fcb。
vfatDirFindFile函数我们之前看过,内部会调用vfatMakeFCBFromDirEntry创建fcb,我们看
NTSTATUS
vfatMakeFCBFromDirEntry(
PVCB vcb,//文件卷fcb
PVFATFCB directoryFCB,//目录
PVFAT_DIRENTRY_CONTEXT DirContext,
PVFATFCB* fileFCB)
{
PVFATFCB rcFCB;
PWCHAR PathNameBuffer;
USHORT PathNameLength;
ULONG Size;
ULONG hash;
UNICODE_STRING NameU;
//目标文件的全路径
PathNameLength = directoryFCB->PathNameU.Length + max(DirContext->LongNameU.Length, DirContext->ShortNameU.Length);
if (!vfatFCBIsRoot(directoryFCB))
PathNameLength += sizeof(WCHAR);
if (PathNameLength > LONGNAME_MAX_LENGTH * sizeof(WCHAR))
return STATUS_OBJECT_NAME_INVALID;
PathNameBuffer = ExAllocatePoolWithTag(NonPagedPool, PathNameLength + sizeof(WCHAR));
NameU.Buffer = PathNameBuffer;//全路径
NameU.Length = 0;
NameU.MaximumLength = PathNameLength;
RtlCopyUnicodeString(&NameU, &directoryFCB->PathNameU);
if (!vfatFCBIsRoot (directoryFCB))
RtlAppendUnicodeToString(&NameU, L"\\");
hash = vfatNameHash(0, &NameU);
if (DirContext->LongNameU.Length > 0)
RtlAppendUnicodeStringToString(&NameU, &DirContext->LongNameU);
else
RtlAppendUnicodeStringToString(&NameU, &DirContext->ShortNameU);
NameU.Buffer[NameU.Length / sizeof(WCHAR)] = 0;
//此时NameU为目标文件的全路径
rcFCB = vfatNewFCB (vcb, &NameU);//创建一个fcb(内部会计算、保存全路径hash值)
RtlCopyMemory (&rcFCB->entry, &DirContext->DirEntry, sizeof (DIR_ENTRY));//目录项
RtlCopyUnicodeString(&rcFCB->ShortNameU, &DirContext->ShortNameU);
if (vcb->Flags & VCB_IS_FATX)
rcFCB->ShortHash.Hash = rcFCB->Hash.Hash;
else
rcFCB->ShortHash.Hash = vfatNameHash(hash, &rcFCB->ShortNameU);
if (vfatFCBIsDirectory(rcFCB))
{
ULONG FirstCluster, CurrentCluster;
NTSTATUS Status = STATUS_SUCCESS;
Size = 0;
FirstCluster = vfatDirEntryGetFirstCluster (vcb, &rcFCB->entry);
if (FirstCluster == 1)//说明是个根目录
Size = vcb->FatInfo.rootDirectorySectors * vcb->FatInfo.BytesPerSector;
else if (FirstCluster != 0)//if不是空目录
{
CurrentCluster = FirstCluster;
while (CurrentCluster != 0xffffffff && NT_SUCCESS(Status))
{
Size += vcb->FatInfo.BytesPerCluster;
Status = NextCluster (vcb, FirstCluster, &CurrentCluster, FALSE);
}
}
}
else if (rcFCB->Flags & FCB_IS_FATX_ENTRY)
Size = rcFCB->entry.FatX.FileSize;
else
Size = rcFCB->entry.Fat.FileSize;
rcFCB->dirIndex = DirContext->DirIndex;//目录项索引
rcFCB->startIndex = DirContext->StartIndex;//slot或目录项的索引
if ((rcFCB->Flags & FCB_IS_FATX_ENTRY) && !vfatFCBIsRoot (directoryFCB))
{
ASSERT(DirContext->DirIndex >= 2 && DirContext->StartIndex >= 2);
rcFCB->dirIndex = DirContext->DirIndex-2;
rcFCB->startIndex = DirContext->StartIndex-2;
}
rcFCB->RFCB.FileSize.QuadPart = Size;
rcFCB->RFCB.ValidDataLength.QuadPart = Size;
rcFCB->RFCB.AllocationSize.QuadPart = ROUND_UP(Size, vcb->FatInfo.BytesPerCluster);
rcFCB->RefCount++;
if (vfatFCBIsDirectory(rcFCB))
vfatFCBInitializeCacheFromVolume(vcb, rcFCB);
rcFCB->parentFcb = directoryFCB;//记录它的父目录fcb
vfatAddFCBToTable (vcb, rcFCB);
*fileFCB = rcFCB;//返回目标文件的fcb
ExFreePool(PathNameBuffer);
return STATUS_SUCCESS;
}
一个磁盘文件一旦打开,在内存中就有一个FCB。因此,一个FCB就代表一个磁盘文件。一个FCB对应N个文件对象。
下面是FCB结构的定义:
typedef struct _VFATFCB
{
FSRTL_COMMON_FCB_HEADER RFCB;
SECTION_OBJECT_POINTERS SectionObjectPointers;
ERESOURCE MainResource;
ERESOURCE PagingIoResource;
DIR_ENTRY entry;//该文件的目录项,来源于磁盘父目录
PUCHAR Attributes;
UNICODE_STRING LongNameU;//长名
UNICODE_STRING ShortNameU;//短名
UNICODE_STRING DirNameU;
UNICODE_STRING PathNameU;//该文件的全路径
PWCHAR PathNameBuffer;
WCHAR ShortNameBuffer[13];
LONG RefCount;
LIST_ENTRY FcbListEntry;
struct _VFATFCB* parentFcb;//该文件的父目录fcb
ULONG Flags;
PFILE_OBJECT FileObject;//第一个打开本文件的文件对象
ULONG dirIndex;//该文件在父目录中的目录项索引,也即最后位置
ULONG startIndex; //该文件在父目录中的slot或目录项索引,也即开始位置
SHARE_ACCESS FCBShareAccess;//该文件当前对后来者提供的共享权限
ULONG OpenHandleCount;//文件对象计数
HASHENTRY Hash;//全路径的hash值,用来提高查找速度
HASHENTRY ShortHash;
FILE_LOCK FileLock;
FAST_MUTEX LastMutex;
ULONG LastCluster;
ULONG LastOffset;
} VFATFCB, *PVFATFCB;
一个FCB关联N个文件对象,下面的函数用于将FCB关联给文件对象
NTSTATUS
vfatAttachFCBToFileObject (
PDEVICE_EXTENSION vcb,
PVFATFCB fcb,
PFILE_OBJECT fileObject)
{
PVFATCCB newCCB;
//一个动态的CCB上下文控制块
newCCB = ExAllocateFromNPagedLookasideList(&VfatGlobalData->CcbLookasideList);
RtlZeroMemory (newCCB, sizeof (VFATCCB));
//指向所属FCB的Section
fileObject->SectionObjectPointer = &fcb->SectionObjectPointers;
fileObject->FsContext = fcb;//关键。关联在一起。
fileObject->FsContext2 = newCCB;
return STATUS_SUCCESS;
}
vfatCreateFile内部对后来者(也即非第一次)打开文件时,都要进行共享权限的检查,检查不通过,就失败返回。我们看是如何检查的
NTSTATUS
IoCheckShareAccess(IN ACCESS_MASK DesiredAccess,//本次打开文件要求的权限
IN ULONG DesiredShareAccess,//本次打开对外提供的共享权限
IN PFILE_OBJECT FileObject,//本次打开者
IN PSHARE_ACCESS ShareAccess,//目标文件已有的对外权限集
IN BOOLEAN Update)//是否更新fcb的权限集
{
BOOLEAN ReadAccess;
BOOLEAN WriteAccess;
BOOLEAN DeleteAccess;
BOOLEAN SharedRead;
BOOLEAN SharedWrite;
BOOLEAN SharedDelete;
ReadAccess = (DesiredAccess & (FILE_READ_DATA | FILE_EXECUTE)) != 0;
WriteAccess = (DesiredAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA)) != 0;
DeleteAccess = (DesiredAccess & DELETE) != 0;
//将声明的要求权限记录在本次文件对象中
FileObject->ReadAccess = ReadAccess;
FileObject->WriteAccess = WriteAccess;
FileObject->DeleteAccess = DeleteAccess;
if (FileObject->Flags & FO_FILE_OBJECT_HAS_EXTENSION)
return STATUS_SUCCESS;
if ((ReadAccess) || (WriteAccess) || (DeleteAccess))
{
SharedRead = (DesiredShareAccess & FILE_SHARE_READ) != 0;
SharedWrite = (DesiredShareAccess & FILE_SHARE_WRITE) != 0;
SharedDelete = (DesiredShareAccess & FILE_SHARE_DELETE) != 0;
//将本次声明的对外提供的共享权也记录在文件对象中
FileObject->SharedRead = SharedRead;
FileObject->SharedWrite = SharedWrite;
FileObject->SharedDelete = SharedDelete;
//关键。检查本次打开声明要求的权限 和 那个文件已有的共享权是否冲突
//若本次要求读,但只要以前的打开者中有一个不准许共享读,失败
//若本次要求写,但只要以前的打开者中有一个不准许共享写,失败
//若本次要求删除,但只要以前的打开者中有一个不准许共享删除,失败
if ((ReadAccess && (ShareAccess->SharedRead < ShareAccess->OpenCount)) ||
(WriteAccess && (ShareAccess->SharedWrite < ShareAccess->OpenCount)) ||
(DeleteAccess && (ShareAccess->SharedDelete < ShareAccess->OpenCount)) ||
((ShareAccess->Readers != 0) && !SharedRead) ||
((ShareAccess->Writers != 0) && !SharedWrite) ||
((ShareAccess->Deleters != 0) && !SharedDelete))
{
return STATUS_SHARING_VIOLATION;//共享冲突
}
if (Update)
{
ShareAccess->OpenCount++;//递增打开计数
ShareAccess->Readers += ReadAccess;//递增要求读的打开者计数
ShareAccess->Writers += WriteAccess; //递增要求写的打开者计数
ShareAccess->Deleters += DeleteAccess;//递增要求删除的打开者计数
ShareAccess->SharedRead += SharedRead;//递增对外允许共享读的打开者计数
ShareAccess->SharedWrite += SharedWrite; //递增对外允许共享写的打开者计数
ShareAccess->SharedDelete += SharedDelete; //递增对外允许共享删除的打开者计数
}
}
return STATUS_SUCCESS;
}
如上,共享权限的叠加实际上是交集。只要历代打开者有一个不准许共享读,那么整个文件就不允许共享读了。共享写、共享删除 权限处理也一样。
内核中的文件缓冲机制:
注意文件缓冲机制是Windows内核自身提供的,而并不是某个文件系统提供的,文件系统只是借用内核提供的文件缓冲机制而已(调用内核提供的文件缓冲管理函数),不管谁编写的文件系统,都得遵循内核的文件缓冲机制,这一点一定要注意。(文件缓冲是内核的,而不是某个文件系统创建的)
比如FAT32文件系统内部的函数vfatFindDirSpace,用于在父目录的目录项数组中查找一块连续的区域,这个函数需要访问父目录的目录项数组内容,将其读入内存,因此,它内部调用了CcPinRead函数来达到目的。而CcPinRead是内核提供的文件缓冲管理函数之一,用来获取指定文件块对应的文件缓冲地址(如果尚未为那个文件块创建文件缓冲,就创建)。
内核的文件缓冲机制,将文件内容逻辑划分成一个个连续的文件块,一个文件块对应一个文件缓冲。内核以文件块为单位对文件进行缓冲。把文件块缓冲在内存的好处便是可以提高访问效率,应用程序可以直接从内存缓冲读,而不必从磁盘中读,因为内存的速度比磁盘速度快得多。这种技术叫预读,也即事先把应用程序可能要访问的一块连续区域预读到文件缓冲中。
BOOLEAN //获取指定文件块在内核中的文件缓冲,并锁定在内存(这个函数的名字取得不好)
CcPinRead (
IN PFILE_OBJECT FileObject,//目标文件
IN PLARGE_INTEGER FileOffset,//指定文件块的位置
IN ULONG Length,//指定文件块的长度(注意长度不能跨越两个文件块)
IN ULONG Flags,
OUT PVOID * Bcb,//返回ibcb
OUT PVOID * Buffer //返回该文件块的缓冲地址
)
{
//调用实质函数CcMapData,来获得文件缓冲
if (CcMapData(FileObject, FileOffset, Length, Flags, Bcb, Buffer))
{
if (CcPinMappedData(FileObject, FileOffset, Length, Flags, Bcb))//锁定在内存,防止置换
return TRUE;
else
CcUnpinData(Bcb);//解除锁定
}
return FALSE;
}
BOOLEAN //获取指定文件块在内核中的缓冲地址(这个函数的名字取得不好)
CcMapData (IN PFILE_OBJECT FileObject,
IN PLARGE_INTEGER FileOffset,//指定文件块
IN ULONG Length,
IN ULONG Flags,
OUT PVOID *pBcb,//返回ibcb
OUT PVOID *pBuffer)//返回缓冲地址
{
ULONG ReadOffset;
BOOLEAN Valid;
PBCB Bcb;
PCACHE_SEGMENT CacheSeg;
NTSTATUS Status;
PINTERNAL_BCB iBcb;
ULONG ROffset;
ReadOffset = (ULONG)FileOffset->QuadPart;
Bcb = FileObject->SectionObjectPointer->SharedCacheMap;//fcb的bcb缓冲控制块
//给定的文件区域不能跨越两个文件块
if (ReadOffset % Bcb->CacheSegmentSize + Length > Bcb->CacheSegmentSize)
return(FALSE);
//对齐文件块边界
ROffset = ROUND_DOWN (ReadOffset, Bcb->CacheSegmentSize);
//找到/创建 该文件块对应的缓冲段
Status = CcRosRequestCacheSegment(Bcb,
ROffset,//指定文件块
pBuffer,//返回缓冲段的基地址
&Valid,//返回值表示该缓冲段是否有效(即不比文件块旧)
&CacheSeg);//返回找到或创建的缓冲段
if (!NT_SUCCESS(Status)) return(FALSE);
if (!Valid)//if 缓冲段比磁盘中的文件块旧
{
if (!(Flags & MAP_WAIT))//if 用户要求不能获取旧的缓冲段
{
CcRosReleaseCacheSegment(Bcb, CacheSeg, FALSE, FALSE, FALSE);
return(FALSE);
}
//将文件块读入到缓冲段中(相当于更新缓冲段使其保持最新)
if (!NT_SUCCESS(ReadCacheSegment(CacheSeg)))
{
CcRosReleaseCacheSegment(Bcb, CacheSeg, FALSE, FALSE, FALSE);
return(FALSE);
}
}
//关键。返回找到的缓冲地址
*pBuffer = (PVOID)((ULONG_PTR)(*pBuffer) + (ReadOffset % Bcb->CacheSegmentSize));
iBcb = ExAllocateFromNPagedLookasideList(&iBcbLookasideList);//内部的ibcb
memset(iBcb, 0, sizeof(INTERNAL_BCB));
iBcb->PFCB.NodeTypeCode = 0xDE45;
iBcb->PFCB.NodeByteSize = sizeof(PUBLIC_BCB);
iBcb->PFCB.MappedLength = Length;
iBcb->PFCB.MappedFileOffset = *FileOffset;//记录
iBcb->CacheSegment = CacheSeg;//记录缓冲段
iBcb->Dirty = FALSE;
iBcb->RefCount = 1;
*pBcb = (PVOID)iBcb;//返回
return(TRUE);
}
下面是相关的结构定义
typedef struct _BCB //缓冲控制块
{
LIST_ENTRY BcbSegmentListHead;//关键。本bcb中的缓冲段链表
LIST_ENTRY BcbRemoveListEntry;
BOOLEAN RemoveOnClose;
ULONG TimeStamp;
PFILE_OBJECT FileObject;//首次打开的文件对象
ULONG CacheSegmentSize;//每个缓冲段的大小(一般就是一个簇的大小)
LARGE_INTEGER AllocationSize;//所属文件的AllocationSize
LARGE_INTEGER FileSize; //所属文件的FileSize
PCACHE_MANAGER_CALLBACKS Callbacks;
PVOID LazyWriteContext;//一般为所属FCB
KSPIN_LOCK BcbLock;
ULONG RefCount;
} BCB, *PBCB;
文件对象内部的SectionobjectPointer是个SECTION_OBJECT_POINTERS结构指针
typedef struct _SECTION_OBJECT_POINTERS {
PVOID DataSectionObject;
PVOID SharedCacheMap;//fcb的公共bcb,各文件对象均指向这个bcb
PVOID ImageSectionObject;
} SECTION_OBJECT_POINTERS, *PSECTION_OBJECT_POINTERS;
typedef struct _CACHE_SEGMENT //缓冲段,即缓冲描述符
{
PVOID BaseAddress;//基地址
struct _MEMORY_AREA* MemoryArea;//所属区段
BOOLEAN Valid;//False就表示比磁盘中对应的文件块内容旧
BOOLEAN Dirty;//True就表示比磁盘中对应的文件块内容新
BOOLEAN PageOut;//该缓冲段是否正在进行Pageout的一个标记
ULONG MappedCount;
LIST_ENTRY BcbSegmentListEntry;//用来挂入所属bcb的内部缓冲段链表
LIST_ENTRY DirtySegmentListEntry;//用来挂入内核全局的脏缓冲段链表
LIST_ENTRY CacheSegmentListEntry;//用来挂入内核全局的缓冲段链表
LIST_ENTRY CacheSegmentLRUListEntry;//用来挂入内核全局的LRU算法缓冲段链表
ULONG FileOffset;//本缓冲段对应文件块在文件中的偏移位置
EX_PUSH_LOCK Lock;
ULONG ReferenceCount;
PBCB Bcb;//本缓冲段的所属bcb
struct _CACHE_SEGMENT* NextInChain;
} CACHE_SEGMENT, *PCACHE_SEGMENT;
如上,CcMapData内部会调用下面的函数来查找/创建指定文件块的缓冲段。不过这是ReactOS中的实现,Windows不太相同。
NTSTATUS
CcRosRequestCacheSegment(PBCB Bcb,
ULONG FileOffset,//必须对其文件块的开头位置
PVOID* BaseAddress,//返回缓冲地址
PBOOLEAN UptoDate,//返回该缓冲段是否有效(即与文件块同步)
PCACHE_SEGMENT* CacheSeg)//返回找到/创建的缓冲段
{
ULONG BaseOffset;
if ((FileOffset % Bcb->CacheSegmentSize) != 0)
KeBugCheck(CACHE_MANAGER);
return(CcRosGetCacheSegment(Bcb,FileOffset,&BaseOffset,
BaseAddress,UptoDate,CacheSeg));
}
NTSTATUS
CcRosGetCacheSegment(PBCB Bcb,
ULONG FileOffset,
PULONG BaseOffset,
PVOID* BaseAddress,
PBOOLEAN UptoDate,
PCACHE_SEGMENT* CacheSeg)
{
PCACHE_SEGMENT current;
NTSTATUS Status;
//先在该bcb(即该文件)的缓冲段链表中查找
current = CcRosLookupCacheSegment(Bcb, FileOffset);
if (current == NULL)//找不到就创建
Status = CcRosCreateCacheSegment(Bcb, FileOffset, ¤t);
*UptoDate = current->Valid;
*BaseAddress = current->BaseAddress;
*CacheSeg = current;
*BaseOffset = current->FileOffset;
return(STATUS_SUCCESS);
}
PCACHE_SEGMENT //查找指定文件块的缓冲段
CcRosLookupCacheSegment(PBCB Bcb, ULONG FileOffset)
{
PLIST_ENTRY current_entry;
PCACHE_SEGMENT current;
KIRQL oldIrql;
KeAcquireSpinLock(&Bcb->BcbLock, &oldIrql);
current_entry = Bcb->BcbSegmentListHead.Flink;
//可以看出,bcb中的缓冲段链表是一个按文件偏移有序的链表
while (current_entry != &Bcb->BcbSegmentListHead)
{
current = CONTAINING_RECORD(current_entry, CACHE_SEGMENT,
BcbSegmentListEntry);
if (current->FileOffset <= FileOffset &&
(current->FileOffset + Bcb->CacheSegmentSize) > FileOffset)
{
CcRosCacheSegmentIncRefCount(current);
KeReleaseSpinLock(&Bcb->BcbLock, oldIrql);
ExAcquirePushLockExclusive(¤t->Lock);
return(current);
}
current_entry = current_entry->Flink;
}
KeReleaseSpinLock(&Bcb->BcbLock, oldIrql);
return(NULL);
}
NTSTATUS //为指定文件块创建一个缓冲段
CcRosCreateCacheSegment(PBCB Bcb,
ULONG FileOffset,
PCACHE_SEGMENT* CacheSeg)
{
PCACHE_SEGMENT current;
PCACHE_SEGMENT previous;
PLIST_ENTRY current_entry;
NTSTATUS Status;
KIRQL oldIrql;
PHYSICAL_ADDRESS BoundaryAddressMultiple;
BoundaryAddressMultiple.QuadPart = 0;
if (FileOffset >= Bcb->FileSize.u.LowPart)
{
CacheSeg = NULL;
return STATUS_INVALID_PARAMETER;
}
//分配一个缓冲段
current = ExAllocateFromNPagedLookasideList(&CacheSegLookasideList);
current->Valid = FALSE;
current->Dirty = FALSE;
current->PageOut = FALSE;
current->FileOffset = ROUND_DOWN(FileOffset, Bcb->CacheSegmentSize);
current->Bcb = Bcb;//所属bcb
current->MappedCount = 0;
current->DirtySegmentListEntry.Flink = NULL;
current->DirtySegmentListEntry.Blink = NULL;
current->ReferenceCount = 1;
ExInitializePushLock((PULONG_PTR)¤t->Lock);
ExAcquirePushLockExclusive(¤t->Lock);
KeAcquireGuardedMutex(&ViewLock);
*CacheSeg = current;//
KeAcquireSpinLock(&Bcb->BcbLock, &oldIrql);
current_entry = Bcb->BcbSegmentListHead.Flink;
previous = NULL;
//创建好了缓冲段后,再次检查这期间,是否其他进程为这个文件块创建了缓冲段
while (current_entry != &Bcb->BcbSegmentListHead)
{
current = CONTAINING_RECORD(current_entry, CACHE_SEGMENT,
BcbSegmentListEntry);
if (current->FileOffset <= FileOffset &&
(current->FileOffset + Bcb->CacheSegmentSize) > FileOffset)
{
CcRosCacheSegmentIncRefCount(current);
KeReleaseSpinLock(&Bcb->BcbLock, oldIrql);
ExReleasePushLock(&(*CacheSeg)->Lock);
KeReleaseGuardedMutex(&ViewLock);
ExFreeToNPagedLookasideList(&CacheSegLookasideList, *CacheSeg);
*CacheSeg = current;//返回
ExAcquirePushLockExclusive(¤t->Lock);
return STATUS_SUCCESS;
}
if (current->FileOffset < FileOffset)
{
if (previous == NULL)
previous = current;
else if (previous->FileOffset < current->FileOffset)
previous = current;
}
current_entry = current_entry->Flink;
}//end while
current = *CacheSeg;
//通过插入方式,可以看出,Bcb->BcbSegmentListHead是个有序链表
if (previous)//挂在previous后面
InsertHeadList(&previous->BcbSegmentListEntry, ¤t->BcbSegmentListEntry);
Else //挂在最前面
InsertHeadList(&Bcb->BcbSegmentListHead, ¤t->BcbSegmentListEntry);
KeReleaseSpinLock(&Bcb->BcbLock, oldIrql);
InsertTailList(&CacheSegmentListHead, ¤t->CacheSegmentListEntry);//挂入全局链表
InsertTailList(&CacheSegmentLRUListHead, ¤t->CacheSegmentLRUListEntry);//挂入LRU链表
KeReleaseGuardedMutex(&ViewLock);
MmLockAddressSpace(MmGetKernelAddressSpace());
current->BaseAddress = NULL;
Status = MmCreateMemoryArea(MmGetKernelAddressSpace(),
MEMORY_AREA_CACHE_SEGMENT,//专门的文件缓冲区段类型
¤t->BaseAddress, Bcb->CacheSegmentSize,
PAGE_READWRITE, ¤t->MemoryArea,
FALSE,0,BoundaryAddressMultiple);
MmUnlockAddressSpace(MmGetKernelAddressSpace());
if (!NT_SUCCESS(Status))
KeBugCheck(CACHE_MANAGER);
//分配物理内存,建立映射
MmMapMemoryArea(current->BaseAddress, Bcb->CacheSegmentSize,MC_CACHE, PAGE_READWRITE);
return(STATUS_SUCCESS);
}
新创建的缓冲段是无效的,比文件块旧,需要更新缓冲段,将文件块中的数据读入缓冲段,下面的函数就是用来将文件块读入到缓冲段,同步更新缓冲段的。
NTSTATUS ReadCacheSegment(PCACHE_SEGMENT CacheSeg)
{
ULONG Size;
PMDL Mdl;
NTSTATUS Status;
LARGE_INTEGER SegOffset;
IO_STATUS_BLOCK IoStatus;
KEVENT Event;
SegOffset.QuadPart = CacheSeg->FileOffset;
Size = (ULONG)(CacheSeg->Bcb->AllocationSize.QuadPart - CacheSeg->FileOffset);
if (Size > CacheSeg->Bcb->CacheSegmentSize)
Size = CacheSeg->Bcb->CacheSegmentSize;
Mdl = _alloca(MmSizeOfMdl(CacheSeg->BaseAddress, Size));//mdl缓冲描述符
MmInitializeMdl(Mdl, CacheSeg->BaseAddress, Size);
MmBuildMdlForNonPagedPool(Mdl);
Mdl->MdlFlags |= MDL_IO_PAGE_READ;
KeInitializeEvent(&Event, NotificationEvent, FALSE);
//构造一个分页读irp,请求文件系统将数据从磁盘读入到内存缓冲
Status = IoPageRead(CacheSeg->Bcb->FileObject, Mdl, &SegOffset, & Event, &IoStatus);
if (Status == STATUS_PENDING)
{
KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
Status = IoStatus.Status;
}
if (!NT_SUCCESS(Status) && Status != STATUS_END_OF_FILE)
return Status;
if (CacheSeg->Bcb->CacheSegmentSize > Size)//末尾部分清0
memset(CacheSeg->BaseAddress + Size, 0,CacheSeg->Bcb->CacheSegmentSize - Size);
return STATUS_SUCCESS;
}
NTSTATUS
IoPageRead(IN PFILE_OBJECT FileObject,
IN PMDL Mdl,
IN PLARGE_INTEGER Offset,
IN PKEVENT Event,
IN PIO_STATUS_BLOCK StatusBlock)
{
PIRP Irp;
PIO_STACK_LOCATION StackPtr;
PDEVICE_OBJECT DeviceObject;
DeviceObject = IoGetRelatedDeviceObject(FileObject);//栈顶的文件卷
Irp = IoAllocateIrp(DeviceObject->StackSize, FALSE);//分配irp
StackPtr = IoGetNextIrpStackLocation(Irp);
Irp->MdlAddress = Mdl;
Irp->UserBuffer = MmGetMdlVirtualAddress(Mdl);
Irp->UserIosb = StatusBlock;
Irp->UserEvent = Event;
Irp->RequestorMode = KernelMode;
Irp->Flags = IRP_PAGING_IO |
IRP_NOCACHE | //这个表示告诉文件系统不要从文件缓冲读,而要直接从磁盘读
IRP_SYNCHRONOUS_PAGING_IO |
IRP_INPUT_OPERATION;
Irp->Tail.Overlay.OriginalFileObject = FileObject;
Irp->Tail.Overlay.Thread = PsGetCurrentThread();
StackPtr->Parameters.Read.Length = MmGetMdlByteCount(Mdl);
StackPtr->Parameters.Read.ByteOffset = *Offset;
StackPtr->MajorFunction = IRP_MJ_READ;//这种irp
StackPtr->FileObject = FileObject;
return IoCallDriver(DeviceObject, Irp);//将irp发给文件卷
}
如上,这个函数内部构造一个分页读irp,发给文件系统,请求直接从磁盘读取数据。以后我们将看FAT32文件系统是如何处理这种分页读irp,现在暂时不看。
CcPinRead、CcMapData都是底层的函数,用来获取一个文件块的文件缓冲地址(若没缓冲,就创建缓冲)
内核提供了高层的封装函数CcCopyRead,这个函数用来从文件读取任意长的内容到用户指定的缓冲中(非文件缓冲),这个函数内部其实是先尝试从文件缓冲读,若没有,再才从磁盘上读,并建立缓冲以方便以后读。因此,这是一个更为一般的、方便的函数。我们看
BOOLEAN CcCopyRead (
IN PFILE_OBJECT FileObject,
IN PLARGE_INTEGER FileOffset,//指定偏移
IN ULONG Length,//用户缓冲的长度
IN BOOLEAN Wait,//是否允许有无效缓冲段存在
OUT PVOID Buffer,//用户提供的缓冲
OUT PIO_STATUS_BLOCK IoStatus) //返回IO完成结果
{
ULONG ReadOffset;
ULONG TempLength;
NTSTATUS Status = STATUS_SUCCESS;
PVOID BaseAddress;
PCACHE_SEGMENT CacheSeg;
BOOLEAN Valid;
ULONG ReadLength = 0;
PBCB Bcb;
KIRQL oldirql;
PLIST_ENTRY current_entry;
PCACHE_SEGMENT current;
Bcb = FileObject->SectionObjectPointer->SharedCacheMap;//fcb的bcb
ReadOffset = (ULONG)FileOffset->QuadPart;
if (!Wait)//如果不许有无效缓冲段存在,就检查要读取的那块文件区域是否存在无效缓冲段
{
KeAcquireSpinLock(&Bcb->BcbLock, &oldirql);
current_entry = Bcb->BcbSegmentListHead.Flink;
while (current_entry != &Bcb->BcbSegmentListHead)
{
current = CONTAINING_RECORD(current_entry, CACHE_SEGMENT,BcbSegmentListEntry);
//if 从FileOffset到FileOffset+Length这块文件区域 对应有无效的缓冲段,失败返回
if (current->FileOffset < ReadOffset + Length && current->FileOffset + Bcb->CacheSegmentSize > ReadOffset && !current->Valid)
{
KeReleaseSpinLock(&Bcb->BcbLock, oldirql);
IoStatus->Status = STATUS_UNSUCCESSFUL;
IoStatus->Information = 0;
return FALSE;
}
current_entry = current_entry->Flink;
}
KeReleaseSpinLock(&Bcb->BcbLock, oldirql);
}
TempLength = ReadOffset % Bcb->CacheSegmentSize;
if (TempLength != 0)//先读取左跨部分
{
TempLength = min (Length, Bcb->CacheSegmentSize - TempLength);
Status = CcRosRequestCacheSegment(Bcb, ROUND_DOWN(ReadOffset,Bcb->CacheSegmentSize),
&BaseAddress, &Valid, &CacheSeg);
if (!NT_SUCCESS(Status)) 。。。
if (!Valid)//如果该缓冲段无效或者是新创建的
{
Status = ReadCacheSegment(CacheSeg);//更新读入内存
if (!NT_SUCCESS(Status)) 。。。
}
//关键。再从文件缓冲拷贝到用户缓冲
memcpy (Buffer, (char*)BaseAddress + ReadOffset % Bcb->CacheSegmentSize,TempLength);
CcRosReleaseCacheSegment(Bcb, CacheSeg, TRUE, FALSE, FALSE);
ReadLength += TempLength;
Length -= TempLength;
ReadOffset += TempLength;
Buffer = (PVOID)((char*)Buffer + TempLength);
}
//下面的循环再读取剩余的缓冲段
while (Length > 0)
{
TempLength = min(max(Bcb->CacheSegmentSize, MAX_RW_LENGTH), Length);
//使用这个函数可以读出物理上连续的缓冲段 到用户缓冲中,以提高效率
Status = ReadCacheSegmentChain(Bcb, ReadOffset, TempLength, Buffer);
if (!NT_SUCCESS(Status)) 。。。
ReadLength += TempLength;
Length -= TempLength;
ReadOffset += TempLength;
Buffer = (PVOID)((ULONG_PTR)Buffer + TempLength);
}
IoStatus->Status = STATUS_SUCCESS;
IoStatus->Information = ReadLength;
return TRUE;
}
对应的,内核还提供了个一般的CcCopyWrite函数,用来将用户缓冲数据写入到文件缓冲(称为延迟写技术)
BOOLEAN
CcCopyWrite (IN PFILE_OBJECT FileObject,
IN PLARGE_INTEGER FileOffset,//写入指定偏移位置
IN ULONG Length, //用户缓冲长度
IN BOOLEAN Wait,//是否不许有无效缓冲段存在
IN PVOID Buffer)//用户缓冲地址
{
NTSTATUS Status;
ULONG WriteOffset;
KIRQL oldirql;
PBCB Bcb;
PLIST_ENTRY current_entry;
PCACHE_SEGMENT CacheSeg;
ULONG TempLength;
PVOID BaseAddress;
BOOLEAN Valid;
Bcb = FileObject->SectionObjectPointer->SharedCacheMap;
WriteOffset = (ULONG)FileOffset->QuadPart;
if (!Wait)
{
KeAcquireSpinLock(&Bcb->BcbLock, &oldirql);
current_entry = Bcb->BcbSegmentListHead.Flink;
while (current_entry != &Bcb->BcbSegmentListHead) //检查是否存在无效缓冲段
{
CacheSeg = CONTAINING_RECORD(current_entry, CACHE_SEGMENT,BcbSegmentListEntry);
if (!CacheSeg->Valid)
{
if ((WriteOffset >= CacheSeg->FileOffset &&
WriteOffset < CacheSeg->FileOffset + Bcb->CacheSegmentSize)
|| (WriteOffset + Length > CacheSeg->FileOffset &&
WriteOffset + Length <= CacheSeg->FileOffset +
Bcb->CacheSegmentSize))
{
return(FALSE);
}
}
current_entry = current_entry->Flink;
}
KeReleaseSpinLock(&Bcb->BcbLock, oldirql);
}
TempLength = WriteOffset % Bcb->CacheSegmentSize;
if (TempLength != 0)//先写入左跨部分
{
ULONG ROffset;
ROffset = ROUND_DOWN(WriteOffset, Bcb->CacheSegmentSize);
TempLength = min (Length, Bcb->CacheSegmentSize - TempLength);
Status = CcRosRequestCacheSegment(Bcb, ROffset,
&BaseAddress, &Valid, &CacheSeg);
if (!NT_SUCCESS(Status))
return(FALSE);
if (!Valid)
if (!NT_SUCCESS(ReadCacheSegment(CacheSeg)))
return(FALSE);
//关键。写入缓冲段中
memcpy ((char*)BaseAddress + WriteOffset % Bcb->CacheSegmentSize,Buffer, TempLength);
CcRosReleaseCacheSegment(Bcb, CacheSeg, TRUE, TRUE, FALSE);
Length -= TempLength;
WriteOffset += TempLength;
Buffer = (PVOID)((ULONG_PTR)Buffer + TempLength);
}
while (Length > 0) //剩余的缓冲段
{
TempLength = min (Bcb->CacheSegmentSize, Length);
Status = CcRosRequestCacheSegment(Bcb, WriteOffset,&BaseAddress, &Valid, &CacheSeg);
if (!NT_SUCCESS(Status))
return(FALSE);
if (!Valid && TempLength < Bcb->CacheSegmentSize)
{
if (!NT_SUCCESS(ReadCacheSegment(CacheSeg)))
{
CcRosReleaseCacheSegment(Bcb, CacheSeg, FALSE, FALSE, FALSE);
return FALSE;
}
}
memcpy (BaseAddress, Buffer, TempLength);//关键。写入对应的缓冲段中
CcRosReleaseCacheSegment(Bcb, CacheSeg, TRUE, TRUE, FALSE);//释放缓冲段引用计数
Length -= TempLength;
WriteOffset += TempLength;
Buffer = (PVOID)((ULONG_PTR)Buffer + TempLength);
}
return(TRUE);
}
如上,上面的函数不直接把用户数据写入文件,而是写入文件缓冲。然后,对缓冲做一下脏标记处理,将缓冲段转入全局的脏缓冲段链表中(内核中有一个守护线程,会周期性的扫描那个链表,将缓冲段中的数据回写到磁盘中对应的文件块)。
NTSTATUS
CcRosReleaseCacheSegment(PBCB Bcb,
PCACHE_SEGMENT CacheSeg,
BOOLEAN Valid,//标记该缓冲段是否有效(即是否最新)
BOOLEAN Dirty,//该缓冲是否已脏
BOOLEAN Mapped)
{
BOOLEAN WasDirty = CacheSeg->Dirty;//之前的脏标记
KIRQL oldIrql;
CacheSeg->Valid = Valid;
CacheSeg->Dirty = CacheSeg->Dirty || Dirty;
KeAcquireGuardedMutex(&ViewLock);
//如果该缓冲第一次变脏,就挂入全局的脏链表
if (!WasDirty && CacheSeg->Dirty)
{
InsertTailList(&DirtySegmentListHead, &CacheSeg->DirtySegmentListEntry);
DirtyPageCount += Bcb->CacheSegmentSize / PAGE_SIZE;
}
RemoveEntryList(&CacheSeg->CacheSegmentLRUListEntry);
InsertTailList(&CacheSegmentLRUListHead, &CacheSeg->CacheSegmentLRUListEntry);//挂会尾部
if (Mapped)
CacheSeg->MappedCount++;
KeAcquireSpinLock(&Bcb->BcbLock, &oldIrql);
CcRosCacheSegmentDecRefCount(CacheSeg);//递减引用计数
if (Mapped && CacheSeg->MappedCount == 1)
CcRosCacheSegmentIncRefCount(CacheSeg);
if (!WasDirty && CacheSeg->Dirty)
CcRosCacheSegmentIncRefCount(CacheSeg);//首次变脏,挂入脏链表是递增引用计数
KeReleaseSpinLock(&Bcb->BcbLock, oldIrql);
KeReleaseGuardedMutex(&ViewLock);
ExReleasePushLock(&CacheSeg->Lock);
return(STATUS_SUCCESS);
}
内核中有一个守护线程,会周期性调用下面的函数,扫描那个脏链表,将脏缓冲段中的数据回写到磁盘中对应的文件块。我们看
NTSTATUS
NTAPI
CcRosFlushDirtyPages(ULONG Target, PULONG Count)
{
PLIST_ENTRY current_entry;
PCACHE_SEGMENT current;
ULONG PagesPerSegment;
BOOLEAN Locked;
NTSTATUS Status;
static ULONG WriteCount[4] = {0, 0, 0, 0};
ULONG NewTarget;
(*Count) = 0;
KeAcquireGuardedMutex(&ViewLock);
WriteCount[0] = WriteCount[1];
WriteCount[1] = WriteCount[2];
WriteCount[2] = WriteCount[3];
WriteCount[3] = 0;
NewTarget = WriteCount[0] + WriteCount[1] + WriteCount[2];
if (NewTarget < DirtyPageCount)
{
NewTarget = (DirtyPageCount - NewTarget + 3) / 4;
WriteCount[0] += NewTarget;
WriteCount[1] += NewTarget;
WriteCount[2] += NewTarget;
WriteCount[3] += NewTarget;
}
NewTarget = WriteCount[0];
Target = max(NewTarget, Target);
current_entry = DirtySegmentListHead.Flink;
while (current_entry != &DirtySegmentListHead && Target > 0)
{
current = CONTAINING_RECORD(current_entry, CACHE_SEGMENT,DirtySegmentListEntry);
current_entry = current_entry->Flink;
Locked = current->Bcb->Callbacks->AcquireForLazyWrite(
current->Bcb->LazyWriteContext, FALSE);
if (!Locked)
continue;
Locked = ExTryToAcquirePushLockExclusive(¤t->Lock);
if (!Locked)
{
current->Bcb->Callbacks->ReleaseFromLazyWrite(current->Bcb->LazyWriteContext);
continue;
}
if (current->ReferenceCount > 1)//这种缓冲段不回写到磁盘
{
ExReleasePushLock(¤t->Lock);
current->Bcb->Callbacks->ReleaseFromLazyWrite(current->Bcb->LazyWriteContext);
continue;
}
PagesPerSegment = current->Bcb->CacheSegmentSize / PAGE_SIZE;
KeReleaseGuardedMutex(&ViewLock);
Status = CcRosFlushCacheSegment(current);//关键函数。回写到磁盘
ExReleasePushLock(¤t->Lock);
current->Bcb->Callbacks->ReleaseFromLazyWrite(current->Bcb->LazyWriteContext);
(*Count) += PagesPerSegment;
Target -= PagesPerSegment;
KeAcquireGuardedMutex(&ViewLock);
current_entry = DirtySegmentListHead.Flink;
}
if (*Count < NewTarget)
WriteCount[1] += (NewTarget - *Count);
KeReleaseGuardedMutex(&ViewLock);
return(STATUS_SUCCESS);
}
//将指定缓冲段刷回到磁盘
NTSTATUS CcRosFlushCacheSegment(PCACHE_SEGMENT CacheSegment)
{
NTSTATUS Status;
KIRQL oldIrql;
Status = WriteCacheSegment(CacheSegment);//关键函数
if (NT_SUCCESS(Status))
{
KeAcquireGuardedMutex(&ViewLock);
KeAcquireSpinLock(&CacheSegment->Bcb->BcbLock, &oldIrql);
CacheSegment->Dirty = FALSE;
RemoveEntryList(&CacheSegment->DirtySegmentListEntry);//移出队列
DirtyPageCount -= CacheSegment->Bcb->CacheSegmentSize / PAGE_SIZE;
CcRosCacheSegmentDecRefCount ( CacheSegment );
KeReleaseSpinLock(&CacheSegment->Bcb->BcbLock, oldIrql);
KeReleaseGuardedMutex(&ViewLock);
}
return(Status);
}
//将指定缓冲段回写到磁盘
NTSTATUS WriteCacheSegment(PCACHE_SEGMENT CacheSeg)
{
ULONG Size;
PMDL Mdl;
NTSTATUS Status;
IO_STATUS_BLOCK IoStatus;
LARGE_INTEGER SegOffset;
KEVENT Event;
CacheSeg->Dirty = FALSE;
SegOffset.QuadPart = CacheSeg->FileOffset;
Size = (ULONG)(CacheSeg->Bcb->AllocationSize.QuadPart - CacheSeg->FileOffset);
if (Size > CacheSeg->Bcb->CacheSegmentSize)
Size = CacheSeg->Bcb->CacheSegmentSize;
int i = 0;
do
{
MmGetPfnForProcess(NULL, ((ULONG_PTR)CacheSeg->BaseAddress + (i << PAGE_SHIFT)));
} while (++i < (Size >> PAGE_SHIFT));
Mdl = _alloca(MmSizeOfMdl(CacheSeg->BaseAddress, Size));
MmInitializeMdl(Mdl, CacheSeg->BaseAddress, Size);
MmBuildMdlForNonPagedPool(Mdl);
Mdl->MdlFlags |= MDL_IO_PAGE_READ;
KeInitializeEvent(&Event, NotificationEvent, FALSE);
//关键函数
Status = IoSynchronousPageWrite(CacheSeg->Bcb->FileObject, Mdl, &SegOffset, &Event, &IoStatus);
if (Status == STATUS_PENDING)
{
KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
Status = IoStatus.Status;
}
if (!NT_SUCCESS(Status) && (Status != STATUS_END_OF_FILE))
{
CacheSeg->Dirty = TRUE;
return(Status);
}
return(STATUS_SUCCESS);
}
NTSTATUS
IoSynchronousPageWrite(IN PFILE_OBJECT FileObject,
IN PMDL Mdl,//缓冲描述符
IN PLARGE_INTEGER Offset,//目标文件偏移位置
IN PKEVENT Event,
IN PIO_STATUS_BLOCK StatusBlock)
{
PIRP Irp;
PIO_STACK_LOCATION StackPtr;
PDEVICE_OBJECT DeviceObject;
DeviceObject = IoGetRelatedDeviceObject(FileObject);//栈顶的文件卷对象
Irp = IoAllocateIrp(DeviceObject->StackSize, FALSE);
StackPtr = IoGetNextIrpStackLocation(Irp);
Irp->MdlAddress = Mdl;
Irp->UserBuffer = MmGetMdlVirtualAddress(Mdl);
Irp->UserIosb = StatusBlock;
Irp->UserEvent = Event;
Irp->RequestorMode = KernelMode;
// IRP_NOCACHE标志表示让文件系统直接把mdl中的数据写回磁盘,不要再写到文件缓冲中
//因为mdl本身就是文件缓冲
Irp->Flags = IRP_PAGING_IO | IRP_NOCACHE | IRP_SYNCHRONOUS_PAGING_IO;
Irp->Tail.Overlay.OriginalFileObject = FileObject;
Irp->Tail.Overlay.Thread = PsGetCurrentThread();
StackPtr->Parameters.Write.Length = MmGetMdlByteCount(Mdl);
StackPtr->Parameters.Write.ByteOffset = *Offset;
StackPtr->MajorFunction = IRP_MJ_WRITE;//这种irp
StackPtr->FileObject = FileObject;
return IoCallDriver(DeviceObject, Irp);//发给文件卷,进入文件系统的派遣函数
}
好了,一切就绪,下面我么看看文件系统是如何处理ReadFile、WriteFile 这两种irp的。以FAT32文件系统为例,看看它是如何处理文件读写请求的。
NTSTATUS VfatRead(PVFAT_IRP_CONTEXT IrpContext) //处理读请求的派遣函数
{
NTSTATUS Status;
PVFATFCB Fcb;
ULONG Length = 0;
ULONG ReturnedLength = 0;
PERESOURCE Resource = NULL;
LARGE_INTEGER ByteOffset;
PVOID Buffer;
PDEVICE_OBJECT DeviceToVerify;
ULONG BytesPerSector;
//不能发给cdo,而只能发给文件卷设备
if (IrpContext->DeviceObject == VfatGlobalData->DeviceObject)
{
Status = STATUS_INVALID_DEVICE_REQUEST;
goto ByeBye;
}
Fcb = IrpContext->FileObject->FsContext;//关键、第一时间拿到文件对象对应的fcb
if (Fcb->Flags & FCB_IS_PAGE_FILE)//如果目标文件是个页文件,特殊处理
{
PFATINFO FatInfo = &IrpContext->DeviceExt->FatInfo;
//将文件偏移转换为卷偏移(页文件刚好位于数据区的开头?)
IrpContext->Stack->Parameters.Read.ByteOffset.QuadPart += FatInfo->dataStart * FatInfo->BytesPerSector;
IoSkipCurrentIrpStackLocation(IrpContext->Irp);
//将irp发给下层的驱动(磁盘驱动、光盘驱动等)
Status = IoCallDriver(IrpContext->DeviceExt->StorageDevice, IrpContext->Irp);
VfatFreeIrpContext(IrpContext);
return Status;
}
ByteOffset = IrpContext->Stack->Parameters.Read.ByteOffset;//文件偏移
Length = IrpContext->Stack->Parameters.Read.Length;
BytesPerSector = IrpContext->DeviceExt->FatInfo.BytesPerSector;
//目录不支持分页IO
if (*Fcb->Attributes & FILE_ATTRIBUTE_DIRECTORY && !(IrpContext->Irp->Flags & IRP_PAGING_IO))
{
Status = STATUS_INVALID_PARAMETER;
goto ByeBye;
}
//只支持32位文件偏移
if (ByteOffset.u.HighPart && !(Fcb->Flags & FCB_IS_VOLUME))
{
Status = STATUS_INVALID_PARAMETER;
goto ByeBye;
}
//文件偏移越过文件末尾了
if (ByteOffset.QuadPart >= Fcb->RFCB.FileSize.QuadPart)
{
IrpContext->Irp->IoStatus.Information = 0;
Status = STATUS_END_OF_FILE;
goto ByeBye;
}
//分页读 和 非缓冲读 的文件偏移和长度必须对齐扇区
if (IrpContext->Irp->Flags & (IRP_PAGING_IO | IRP_NOCACHE) || (Fcb->Flags & FCB_IS_VOLUME))
{
if (ByteOffset.u.LowPart % BytesPerSector != 0 || Length % BytesPerSector != 0)
{
Status = STATUS_INVALID_PARAMETER;
goto ByeBye;
}
}
if (Fcb->Flags & FCB_IS_VOLUME)
Resource = &IrpContext->DeviceExt->DirResource;
else if (IrpContext->Irp->Flags & IRP_PAGING_IO)
Resource = &Fcb->PagingIoResource;
else
Resource = &Fcb->MainResource;
if (!ExAcquireResourceSharedLite(Resource,
IrpContext->Flags & IRPCONTEXT_CANWAIT ? TRUE : FALSE))
{
Resource = NULL;
Status = STATUS_PENDING;
goto ByeBye;
}
if (!(IrpContext->Irp->Flags & IRP_PAGING_IO) &&
FsRtlAreThereCurrentFileLocks(&Fcb->FileLock))
{
if (!FsRtlCheckLockForReadAccess(&Fcb->FileLock, IrpContext->Irp))
{
Status = STATUS_FILE_LOCK_CONFLICT;
goto ByeBye;
}
}
Buffer = VfatGetUserBuffer(IrpContext->Irp);
if (!Buffer)
{
Status = STATUS_INVALID_USER_BUFFER;
goto ByeBye;
}
//if没有这两个标志,就先从缓冲读,再从磁盘读(最典型)
if (!(IrpContext->Irp->Flags & (IRP_NOCACHE|IRP_PAGING_IO)) &&
!(Fcb->Flags & (FCB_IS_PAGE_FILE|FCB_IS_VOLUME)))
{
Status = STATUS_SUCCESS;
if (ByteOffset.u.LowPart + Length > Fcb->RFCB.FileSize.u.LowPart)
{
Length = Fcb->RFCB.FileSize.u.LowPart - ByteOffset.u.LowPart;
Status = STATUS_SUCCESS;
}
//各文件对象的私有缓冲都指向fcb的公共缓冲
if (IrpContext->FileObject->PrivateCacheMap == NULL)
{
CcInitializeCacheMap(IrpContext->FileObject,
(PCC_FILE_SIZES)(&Fcb->RFCB.AllocationSize),FALSE,
&(VfatGlobalData->CacheMgrCallbacks), Fcb);
}
//关键。调用CcCopyRead这个内核函数,先从缓冲读,若没有缓冲再从磁盘读,并建立缓冲
if (!CcCopyRead(IrpContext->FileObject, &ByteOffset, Length,
(BOOLEAN)(IrpContext->Flags & IRPCONTEXT_CANWAIT), Buffer,
&IrpContext->Irp->IoStatus))
{
Status = STATUS_PENDING;
goto ByeBye;
}
if (!NT_SUCCESS(IrpContext->Irp->IoStatus.Status))
Status = IrpContext->Irp->IoStatus.Status;
}
Else //irp标志含有IRP_NOCACHE,IRP_PAGING_IO之一,就直接从磁盘读
{
if (ByteOffset.QuadPart + Length > ROUND_UP(Fcb->RFCB.FileSize.QuadPart, BytesPerSector))
{
Length = (ROUND_UP(Fcb->RFCB.FileSize.QuadPart, BytesPerSector) - ByteOffset.QuadPart);
}
Status = VfatLockUserBuffer(IrpContext->Irp, Length, IoWriteAccess);
if (!NT_SUCCESS(Status)) goto ByeBye;
//调用这个函数给下面的磁盘驱动发送irp,请求读出数据。注意并不是直接把irp下发给下层,而是构造一个新的irp发给下层。
Status = VfatReadFileData(IrpContext, Length, ByteOffset, &ReturnedLength);
if (Status == STATUS_VERIFY_REQUIRED) 。。。
if (NT_SUCCESS(Status))
IrpContext->Irp->IoStatus.Information = ReturnedLength;
}
ByeBye:
if (Resource) ExReleaseResourceLite(Resource);
if (Status == STATUS_PENDING)//if下层磁盘驱动尚未完成请求
{
Status = VfatLockUserBuffer(IrpContext->Irp, Length, IoWriteAccess);
if (NT_SUCCESS(Status))
Status = VfatQueueRequest(IrpContext);//将irp挂入队列
else
{
IrpContext->Irp->IoStatus.Status = Status;
IoCompleteRequest(IrpContext->Irp, IO_NO_INCREMENT);
VfatFreeIrpContext(IrpContext);
}
}
Else //if下层磁盘驱动完成了请求
{
IrpContext->Irp->IoStatus.Status = Status;
if (IrpContext->FileObject->Flags & FO_SYNCHRONOUS_IO && //同步文件对象自动维护文件指针
!(IrpContext->Irp->Flags & IRP_PAGING_IO) &&
(NT_SUCCESS(Status) || Status==STATUS_END_OF_FILE))
{
IrpContext->FileObject->CurrentByteOffset.QuadPart =
ByteOffset.QuadPart + IrpContext->Irp->IoStatus.Information;//自动维护文件指针
}
IoCompleteRequest(IrpContext->Irp, (NT_SUCCESS(Status) ? IO_DISK_INCREMENT : IO_NO_INCREMENT));
VfatFreeIrpContext(IrpContext);
}
return Status;
}
如上,FAT32文件系统处理读irp的方法是:
若不是分页读请求和非缓冲读请求,就调用CcCopyRead尝试从缓冲读。默认情况下,应用程序打开文件时,都没有使用NO_IMMEDIATE_BUFFER标志,而且也不是采用内存映射文件的方式进行IO,所以,通常情况下,ReadFile函数内部生成的irp都是缓冲读irp。相反,若在CreateFile打开文件时显式指定了不使用缓冲标志或者采用内存映射方式的话,生成的irp是非缓冲读irp(即含有IRP_NOCACHE标志)、分页读irp(即含有IRP_PAGING_IO标志),这两种irp都不从缓冲读,而是直接从磁盘读取数据到用户缓冲中。
下面的函数就是用于直接从磁盘读取数据到用户指定的缓冲中
NTSTATUS //处理分页读irp、非缓冲读irp请求的函数
VfatReadFileData (PVFAT_IRP_CONTEXT IrpContext,//发给文件卷的irp
ULONG Length,//请求要读取的长度
LARGE_INTEGER ReadOffset,//文件偏移
PULONG LengthRead)//实际读取的长度
{
DeviceExt = IrpContext->DeviceExt;
*LengthRead = 0;
Fcb = IrpContext->FileObject->FsContext;//首先拿到fcb
Ccb = (PVFATCCB)IrpContext->FileObject->FsContext2;//上下文控制块
BytesPerSector = DeviceExt->FatInfo.BytesPerSector;
BytesPerCluster = DeviceExt->FatInfo.BytesPerCluster;
if (Fcb->Flags & FCB_IS_FAT)//if 目标文件是FAT表(FAT表本身也是一种特殊的文件)
{
ReadOffset.QuadPart += DeviceExt->FatInfo.FATStart * BytesPerSector;//转为卷偏移
//文件偏移转为卷偏移后向下层的物理卷设备发送irp
Status = VfatReadDiskPartial(IrpContext, &ReadOffset, Length, 0, TRUE);
if (NT_SUCCESS(Status))
*LengthRead = Length;
return Status;
}
if (Fcb->Flags & FCB_IS_VOLUME) //if 目标文件是整个卷(整个卷也是一种特殊的文件)
{
//文件偏移刚好就是卷偏移
Status = VfatReadDiskPartial(IrpContext, &ReadOffset, Length, 0, TRUE);
if (NT_SUCCESS(Status))
*LengthRead = Length;
return Status;
}
//下面是最为常见的情形。目标文件为一个普通的文件或目录
//关键。获取该文件的第一个簇的簇号
FirstCluster = CurrentCluster = vfatDirEntryGetFirstCluster (DeviceExt, &Fcb->entry);
if (FirstCluster == 1) 。。。只有FAT12、FAT16才可能满足这个条件
ExAcquireFastMutex(&Fcb->LastMutex);
LastCluster = Fcb->LastCluster;//上次访问的那个簇的簇号
LastOffset = Fcb->LastOffset;// 上次访问的那个簇的文件偏移
ExReleaseFastMutex(&Fcb->LastMutex);
//如果目标文件曾经访问过,且这次访问的文件位置在上次后面,就从上次访问的簇开始向后搜索,这、、//样提高搜索效率
if (LastCluster > 0 && ReadOffset.u.LowPart >= LastOffset)
{
Status = OffsetToCluster(DeviceExt, LastCluster,//上次的簇
ROUND_DOWN(ReadOffset.u.LowPart, BytesPerCluster) -LastOffset,
&CurrentCluster, FALSE);
}
Else //从第一个簇开始向后搜索
{
Status = OffsetToCluster(DeviceExt, FirstCluster,//第一个簇
ROUND_DOWN(ReadOffset.u.LowPart, BytesPerCluster),
&CurrentCluster, FALSE);
}
//上面的逻辑获得了指定文件偏移所在的簇保存在CurrentCluster中,非常重要。
ExAcquireFastMutex(&Fcb->LastMutex);
Fcb->LastCluster = CurrentCluster;
Fcb->LastOffset = ROUND_DOWN (ReadOffset.u.LowPart, BytesPerCluster);
ExReleaseFastMutex(&Fcb->LastMutex);
KeInitializeEvent(&IrpContext->Event, NotificationEvent, FALSE);
IrpContext->RefCount = 1;
//下面的循环用来将要读取的区域分割成N个物理连续的簇,分批读。这样能减少向下层发送irp请求的次数,提高效率
while (Length > 0 && CurrentCluster != 0xffffffff)
{
StartCluster = CurrentCluster;//StartCluster代表当前连续簇区的第一个簇号
// StartOffset表示当前连续簇区的卷偏移
StartOffset.QuadPart = ClusterToSector(DeviceExt, StartCluster) * BytesPerSector;
BytesDone = 0;//表示该连续区的长度
ClusterCount = 0;//表示该连续区含有的簇个数
//内层的do循环用来构造一片连续簇区
do
{
ClusterCount++;
if (First)//第一次处理左跨部分
{
BytesDone = min (Length, BytesPerCluster - (ReadOffset.u.LowPart % BytesPerCluster));
StartOffset.QuadPart += ReadOffset.u.LowPart % BytesPerCluster;
First = FALSE;
}
else
{
if (Length - BytesDone > BytesPerCluster)
BytesDone += BytesPerCluster;
else
BytesDone = Length;
}
Status = NextCluster(DeviceExt, FirstCluster, &CurrentCluster, FALSE);
}
while (StartCluster + ClusterCount == CurrentCluster && NT_SUCCESS(Status) && Length > BytesDone);
ExAcquireFastMutex(&Fcb->LastMutex);
Fcb->LastCluster = StartCluster + (ClusterCount - 1);//记录
Fcb->LastOffset = ROUND_DOWN(ReadOffset.u.LowPart, BytesPerCluster) + (ClusterCount - 1) * BytesPerCluster;
ExReleaseFastMutex(&Fcb->LastMutex);
//向下层驱动发出一个irp,请求读取磁盘卷中的一块连续区域
Status = VfatReadDiskPartial (IrpContext, &StartOffset, BytesDone, *LengthRead, FALSE);
if (!NT_SUCCESS(Status) && Status != STATUS_PENDING)
break;
*LengthRead += BytesDone;
Length -= BytesDone;
ReadOffset.u.LowPart += BytesDone;
}
if (0 != InterlockedDecrement((PLONG)&IrpContext->RefCount))
KeWaitForSingleObject(&IrpContext->Event, Executive, KernelMode, FALSE, NULL);
if (NT_SUCCESS(Status) || Status == STATUS_PENDING)
{
if (Length > 0)
Status = STATUS_UNSUCCESSFUL;
else
Status = IrpContext->Irp->IoStatus.Status;
}
return Status;
}
下面的函数用于将文件偏移转换成物理卷中的簇号,也即根据文件偏移获得物理位置(从某种程度上说,这是文件系统最核心、最本质的功能)。那么,FAT32文件系统又是怎么转换的呢?答案是:靠的就是FAT表。
NTSTATUS
OffsetToCluster(PDEVICE_EXTENSION DeviceExt,
ULONG FirstCluster,//起始簇号,不一定硬要传入文件的第一个簇
ULONG FileOffset,//相对FirstCluster的偏移
PULONG Cluster,//返回目标偏移所在簇号
BOOLEAN Extend)//表示是否分配新簇
ULONG CurrentCluster;
ULONG i;
NTSTATUS Status;
if (FirstCluster == 1) /* FAT16 or FAT12 才可能 */
else //FAT32
{
CurrentCluster = FirstCluster;
if (Extend)
{
for (i = 0; i < FileOffset / DeviceExt->FatInfo.BytesPerCluster; i++)
{
Status = GetNextClusterExtend (DeviceExt, CurrentCluster, &CurrentCluster);
if (!NT_SUCCESS(Status))
return(Status);
}
*Cluster = CurrentCluster;
}
else
{
for (i = 0; i < FileOffset / DeviceExt->FatInfo.BytesPerCluster; i++)
{
Status = GetNextCluster (DeviceExt, CurrentCluster, &CurrentCluster);
if (!NT_SUCCESS(Status))
return(Status);
}
*Cluster = CurrentCluster;
}
return(STATUS_SUCCESS);
}
}
如上,上面的函数遍历文件的簇链表,找到指定文件偏移所在的簇。
文件系统最终使用下面的函数从磁盘读取数据
NTSTATUS
VfatReadDiskPartial (IN PVFAT_IRP_CONTEXT IrpContext,//发给文件卷的irp
IN PLARGE_INTEGER ReadOffset,//此时为卷偏移
IN ULONG ReadLength,
ULONG BufferOffset,
IN BOOLEAN Wait)//是否同步返回
PIRP Irp;
PIO_STACK_LOCATION StackPtr;
NTSTATUS Status;
PVOID Buffer = MmGetMdlVirtualAddress(IrpContext->Irp->MdlAddress) + BufferOffset;
//看到没,上层发给文件卷的irp并没有直接下发给下层的设备,而是重新构造了另一个irp发下去
Irp = IoAllocateIrp(IrpContext->DeviceExt->StorageDevice->StackSize, TRUE);
Irp->UserIosb = NULL;
Irp->Tail.Overlay.Thread = PsGetCurrentThread();
StackPtr = IoGetNextIrpStackLocation(Irp);
StackPtr->MajorFunction = IRP_MJ_READ;
StackPtr->MinorFunction = 0;
StackPtr->Flags = 0;
StackPtr->Control = 0;
StackPtr->DeviceObject = IrpContext->DeviceExt->StorageDevice;//物理卷
StackPtr->FileObject = NULL;
StackPtr->CompletionRoutine = NULL;
StackPtr->Parameters.Read.Length = ReadLength;
StackPtr->Parameters.Read.ByteOffset = *ReadOffset;
IoAllocateMdl(Buffer, ReadLength, FALSE, FALSE, Irp);
IoBuildPartialMdl(IrpContext->Irp->MdlAddress, Irp->MdlAddress, Buffer, ReadLength);
IoSetCompletionRoutine(Irp,VfatReadWritePartialCompletion,IrpContext,TRUE,TRUE,TRUE);
if (Wait)
{
KeInitializeEvent(&IrpContext->Event, NotificationEvent, FALSE);
IrpContext->RefCount = 1;
}
else
InterlockedIncrement((PLONG)&IrpContext->RefCount);
Status = IoCallDriver (IrpContext->DeviceExt->StorageDevice, Irp);//发给下层的物理卷设备
if (Wait && Status == STATUS_PENDING)
{
KeWaitForSingleObject(&IrpContext->Event, Executive, KernelMode, FALSE, NULL);
Status = IrpContext->Irp->IoStatus.Status;
}
return Status;
}
再看下FAT32系统是如何处理写irp的
NTSTATUS VfatWrite (PVFAT_IRP_CONTEXT IrpContext)
{
PVFATFCB Fcb;
PERESOURCE Resource = NULL;
LARGE_INTEGER ByteOffset;
LARGE_INTEGER OldFileSize;
NTSTATUS Status = STATUS_SUCCESS;
ULONG Length = 0;
ULONG OldAllocationSize;
PVOID Buffer;
ULONG BytesPerSector;
if (IrpContext->DeviceObject == VfatGlobalData->DeviceObject)
{
Status = STATUS_INVALID_DEVICE_REQUEST;
goto ByeBye;
}
Fcb = IrpContext->FileObject->FsContext;
if (Fcb->Flags & FCB_IS_PAGE_FILE)
{
PFATINFO FatInfo = &IrpContext->DeviceExt->FatInfo;
IrpContext->Stack->Parameters.Write.ByteOffset.QuadPart += FatInfo->dataStart * FatInfo->BytesPerSector;
IoSkipCurrentIrpStackLocation(IrpContext->Irp);
Status = IoCallDriver(IrpContext->DeviceExt->StorageDevice, IrpContext->Irp);
VfatFreeIrpContext(IrpContext);
return Status;
}
if (*Fcb->Attributes & FILE_ATTRIBUTE_DIRECTORY && !(IrpContext->Irp->Flags & IRP_PAGING_IO))
{
Status = STATUS_INVALID_PARAMETER;
goto ByeBye;
}
ByteOffset = IrpContext->Stack->Parameters.Write.ByteOffset;
if (ByteOffset.u.LowPart == FILE_WRITE_TO_END_OF_FILE &&
ByteOffset.u.HighPart == -1)
{
ByteOffset.QuadPart = Fcb->RFCB.FileSize.QuadPart;
}
Length = IrpContext->Stack->Parameters.Write.Length;
BytesPerSector = IrpContext->DeviceExt->FatInfo.BytesPerSector;
if (ByteOffset.u.HighPart && !(Fcb->Flags & FCB_IS_VOLUME))
{
Status = STATUS_INVALID_PARAMETER;
goto ByeBye;
}
if (Fcb->Flags & (FCB_IS_FAT | FCB_IS_VOLUME) ||
1 == vfatDirEntryGetFirstCluster (IrpContext->DeviceExt, &Fcb->entry))
{
if (ByteOffset.QuadPart + Length > Fcb->RFCB.FileSize.QuadPart)
{
Status = STATUS_END_OF_FILE;
goto ByeBye;
}
}
if (IrpContext->Irp->Flags & (IRP_PAGING_IO|IRP_NOCACHE) || (Fcb->Flags & FCB_IS_VOLUME))
{
if (ByteOffset.u.LowPart % BytesPerSector != 0 || Length % BytesPerSector != 0)
{
Status = STATUS_INVALID_PARAMETER;
goto ByeBye;
}
}
if (IrpContext->Irp->Flags & IRP_PAGING_IO)
{
if (ByteOffset.u.LowPart + Length > Fcb->RFCB.AllocationSize.u.LowPart)
{
Status = STATUS_INVALID_PARAMETER;
goto ByeBye;
}
if (ByteOffset.u.LowPart + Length > ROUND_UP(Fcb->RFCB.AllocationSize.u.LowPart, BytesPerSector))
{
Length = ROUND_UP(Fcb->RFCB.FileSize.u.LowPart, BytesPerSector) - ByteOffset.u.LowPart;
}
}
if (Fcb->Flags & FCB_IS_VOLUME)
Resource = &IrpContext->DeviceExt->DirResource;
else if (IrpContext->Irp->Flags & IRP_PAGING_IO)
Resource = &Fcb->PagingIoResource;
else
Resource = &Fcb->MainResource;
if (Fcb->Flags & FCB_IS_PAGE_FILE)
{
if (!ExAcquireResourceSharedLite(Resource, (IrpContext->Flags & IRPCONTEXT_CANWAIT)))
{
Resource = NULL;
Status = STATUS_PENDING;
goto ByeBye;
}
}
else
{
if (!ExAcquireResourceExclusiveLite(Resource, (IrpContext->Flags & IRPCONTEXT_CANWAIT)))
{
Resource = NULL;
Status = STATUS_PENDING;
goto ByeBye;
}
}
if (!(IrpContext->Irp->Flags & IRP_PAGING_IO) &&
FsRtlAreThereCurrentFileLocks(&Fcb->FileLock))
{
if (!FsRtlCheckLockForWriteAccess(&Fcb->FileLock, IrpContext->Irp))
{
Status = STATUS_FILE_LOCK_CONFLICT;
goto ByeBye;
}
}
if (!(IrpContext->Flags & IRPCONTEXT_CANWAIT) && !(Fcb->Flags & FCB_IS_VOLUME))
{
if (ByteOffset.u.LowPart + Length > Fcb->RFCB.AllocationSize.u.LowPart)
{
Status = STATUS_PENDING;
goto ByeBye;
}
}
OldFileSize = Fcb->RFCB.FileSize;
OldAllocationSize = Fcb->RFCB.AllocationSize.u.LowPart;
Buffer = VfatGetUserBuffer(IrpContext->Irp);
if (!Buffer)
{
Status = STATUS_INVALID_USER_BUFFER;
goto ByeBye;
}
if (!(Fcb->Flags & (FCB_IS_FAT|FCB_IS_VOLUME)) &&
!(IrpContext->Irp->Flags & IRP_PAGING_IO) &&
ByteOffset.u.LowPart + Length > Fcb->RFCB.FileSize.u.LowPart)
{
LARGE_INTEGER AllocationSize;
AllocationSize.QuadPart = ByteOffset.u.LowPart + Length;
Status = VfatSetAllocationSizeInformation(IrpContext->FileObject, Fcb,
IrpContext->DeviceExt, &AllocationSize);
if (!NT_SUCCESS (Status))
{
goto ByeBye;
}
}
//缓冲写
if (!(IrpContext->Irp->Flags & (IRP_NOCACHE|IRP_PAGING_IO)) &&
!(Fcb->Flags & (FCB_IS_PAGE_FILE|FCB_IS_VOLUME)))
{
if (IrpContext->FileObject->PrivateCacheMap == NULL)
{
CcInitializeCacheMap(IrpContext->FileObject,
(PCC_FILE_SIZES)(&Fcb->RFCB.AllocationSize),
FALSE,
&VfatGlobalData->CacheMgrCallbacks,
Fcb);
}
if (ByteOffset.QuadPart > OldFileSize.QuadPart)
{
CcZeroData(IrpContext->FileObject, &OldFileSize, &ByteOffset, TRUE);
}
//看到没,使用CcCopyWrite函数进行缓冲写。
if (CcCopyWrite(IrpContext->FileObject, &ByteOffset, Length,1, Buffer))
{
IrpContext->Irp->IoStatus.Information = Length;
Status = STATUS_SUCCESS;
}
else
{
Status = STATUS_UNSUCCESSFUL;
}
}
Else //直接写入硬盘
{
if (ByteOffset.QuadPart > OldFileSize.QuadPart)
CcZeroData(IrpContext->FileObject, &OldFileSize, &ByteOffset, TRUE);
Status = VfatLockUserBuffer(IrpContext->Irp, Length, IoReadAccess);
if (!NT_SUCCESS(Status)) goto ByeBye;
//这个函数的代码猜都能猜到,不用看了
Status = VfatWriteFileData(IrpContext, Length, ByteOffset);
if (NT_SUCCESS(Status))
IrpContext->Irp->IoStatus.Information = Length;
}
if (!(IrpContext->Irp->Flags & IRP_PAGING_IO) &&
!(Fcb->Flags & (FCB_IS_FAT|FCB_IS_VOLUME)))
{
if(!(*Fcb->Attributes & FILE_ATTRIBUTE_DIRECTORY))
{
LARGE_INTEGER SystemTime;
KeQuerySystemTime (&SystemTime);
if (Fcb->Flags & FCB_IS_FATX_ENTRY) 。。。
else
{
//修改上次访问时间、更新时间等属性
FsdSystemTimeToDosDateTime (IrpContext->DeviceExt,
&SystemTime, &Fcb->entry.Fat.UpdateDate,
&Fcb->entry.Fat.UpdateTime);
Fcb->entry.Fat.AccessDate = Fcb->entry.Fat.UpdateDate;
}
Fcb->Flags |= FCB_IS_DIRTY;
}
}
ByeBye:
if (Resource)
ExReleaseResourceLite(Resource);
if (Status == STATUS_PENDING)
{
Status = VfatLockUserBuffer(IrpContext->Irp, Length, IoReadAccess);
if (NT_SUCCESS(Status))
Status = VfatQueueRequest(IrpContext);
else
{
IrpContext->Irp->IoStatus.Status = Status;
IoCompleteRequest(IrpContext->Irp, IO_NO_INCREMENT);
VfatFreeIrpContext(IrpContext);
}
}
else
{
IrpContext->Irp->IoStatus.Status = Status;
if (IrpContext->FileObject->Flags & FO_SYNCHRONOUS_IO && //同步打开的文件对象
!(IrpContext->Irp->Flags & IRP_PAGING_IO) && NT_SUCCESS(Status))
{
IrpContext->FileObject->CurrentByteOffset.QuadPart =
ByteOffset.QuadPart + IrpContext->Irp->IoStatus.Information;//自动维护文件指针
}
IoCompleteRequest(IrpContext->Irp, (NT_SUCCESS(Status) ? IO_DISK_INCREMENT : IO_NO_INCREMENT));
VfatFreeIrpContext(IrpContext);
}
return Status;
}
每个缓冲段都会挂入LRU链表中,目的是在内存紧张时将缓冲置换到外存。回忆Windows的内存管理,系统中有用户、分页池、非分页池、文件缓冲 四种内存消费者,当内存紧张或者某个消费者占用的页面超过其配额时,系统就会在后台调用那个消费者提供的裁剪函数,将页面置换到外存。现在,我们该看看文件缓冲这种消费者提供的裁剪函数是如何工作的
NTSTATUS
CcRosTrimCache(ULONG Target, ULONG Priority, PULONG NrFreed) //页面裁剪函数
{
PLIST_ENTRY current_entry;
PCACHE_SEGMENT current;
ULONG PagesPerSegment;
ULONG PagesFreed;
KIRQL oldIrql;
LIST_ENTRY FreeList;
*NrFreed = 0;
InitializeListHead(&FreeList);//空闲页面链表
KeAcquireGuardedMutex(&ViewLock);
current_entry = CacheSegmentLRUListHead.Flink;
//遍历系统中的LRU缓冲段链表
while (current_entry != &CacheSegmentLRUListHead && Target > 0)
{
current = CONTAINING_RECORD(current_entry, CACHE_SEGMENT, CacheSegmentLRUListEntry);
current_entry = current_entry->Flink;
KeAcquireSpinLock(¤t->Bcb->BcbLock, &oldIrql);
//对于那些没脏的缓冲段,立即释放
if (current->MappedCount > 0 && !current->Dirty && !current->PageOut)
{
ULONG i;
CcRosCacheSegmentIncRefCount(current);//递增引用计数
current->PageOut = TRUE;//标志
KeReleaseSpinLock(¤t->Bcb->BcbLock, oldIrql);
KeReleaseGuardedMutex(&ViewLock);
for (i = 0; i < current->Bcb->CacheSegmentSize / PAGE_SIZE; i++)
{
PFN_NUMBER Page;
Page = (PFN_NUMBER)(MmGetPhysicalAddress((char*)current->BaseAddress + i * PAGE_SIZE).QuadPart >> PAGE_SHIFT);
MmPageOutPhysicalAddress(Page);
}
KeAcquireGuardedMutex(&ViewLock);
KeAcquireSpinLock(¤t->Bcb->BcbLock, &oldIrql);
CcRosCacheSegmentDecRefCount(current); //递减引用计数
}
//对于那些未锁定的文件缓冲,递增要释放的总目标页数
if (current->ReferenceCount == 0)
{
PagesPerSegment = current->Bcb->CacheSegmentSize / PAGE_SIZE;
PagesFreed = min(PagesPerSegment, Target);
Target -= PagesFreed;
(*NrFreed) += PagesFreed; //要释放的页数
}
KeReleaseSpinLock(¤t->Bcb->BcbLock, oldIrql);
}
current_entry = CacheSegmentLRUListHead.Flink;
//再次遍历系统中的LRU缓冲段链表
while (current_entry != &CacheSegmentLRUListHead)
{
current = CONTAINING_RECORD(current_entry, CACHE_SEGMENT,CacheSegmentLRUListEntry);
current->PageOut = FALSE;
current_entry = current_entry->Flink;
KeAcquireSpinLock(¤t->Bcb->BcbLock, &oldIrql);
if (current->ReferenceCount == 0)//如果该缓冲段的引用计数(也即锁定计数)为0了
{
RemoveEntryList(¤t->BcbSegmentListEntry);//移出
KeReleaseSpinLock(¤t->Bcb->BcbLock, oldIrql);
RemoveEntryList(¤t->CacheSegmentListEntry); //移出
RemoveEntryList(¤t->CacheSegmentLRUListEntry); //移出
InsertHeadList(&FreeList, ¤t->BcbSegmentListEntry);//转入空闲缓冲段链表
}
else
KeReleaseSpinLock(¤t->Bcb->BcbLock, oldIrql);
}
KeReleaseGuardedMutex(&ViewLock);
//现在,再将空闲链表中的缓冲段一一释放,回写到磁盘,腾出内存
while (!IsListEmpty(&FreeList))
{
current_entry = RemoveHeadList(&FreeList);
current = CONTAINING_RECORD(current_entry, CACHE_SEGMENT,BcbSegmentListEntry);
CcRosInternalFreeCacheSegment(current);//实质函数
}
return(STATUS_SUCCESS);
}
如上,可以看到文件缓冲消费者 会将LRU链表中所有锁定计数为0的文件缓冲释放,置换到外存。
并且,还可以看出,文件缓冲不同于其它消费者,它是以缓冲段为单位进行释放的,而不是以页面为单位进行释放。整个缓冲段的引用计数为0了,才将缓冲段中的所有页面予以释放。
NTSTATUS CcRosInternalFreeCacheSegment(PCACHE_SEGMENT CacheSeg)
{
MmLockAddressSpace(MmGetKernelAddressSpace());
MmFreeMemoryArea(MmGetKernelAddressSpace(),CacheSeg->MemoryArea,//这个缓冲段
CcFreeCachePage,NULL);
MmUnlockAddressSpace(MmGetKernelAddressSpace());
ExFreeToNPagedLookasideList(&CacheSegLookasideList, CacheSeg);
return(STATUS_SUCCESS);
}
至此,看完了FAT32系统的重要实现机理后,相信大家对该文件系统有所了解了吧?通过剖析FAT32文件系统的代码,我们可以看到这种文件系统有一些固有的设计缺陷。比如最大只能支持4GB的文件、不支持用户访问权限控制等。相比FAT32,Ntfs文件系统就是一个相对优越的文件系统,但没开源,在此略过。