文件系统

一台机器上可以安装很多物理介质来存放资料(如磁盘、光盘、软盘、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.exeIopParseDevice函数内部则会创建一个文件对象和一个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.exeIopParseDevice中是如何解析的。

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派遣处理函数。文件系统分很多种,如FAT32Ntfs等,本文只关注FAT32文件系统。

    Status
= IoCallDriver(DeviceObject,
Irp);

    if
(Status == STATUS_PENDING)//if
irp未完成

{

   
//可看出,CreateFile函数本身总是同步返回的,只有ReadFileWriteFile等函数才有异步一说。

        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;//附加FCBFAT32文件系统中是一个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中。这个驱动内部集成了FAT12FAT16FAT32三种文件系统。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);

  //调用具体文件系统的函数,FAT12FAT16FAT32各不相同,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+213个宽字符,如果一个长文件名超过了24个字符,就需要使用多个slot。每添加一个slot便能多描述13个字符。需要多少个slot由文件名的长度决定。

这个slot结构与目录项结构的大小刚好完全一样,而且与目录项结构位于同一数组中,那么,如何区分数组元素是一个目录项还是一个slot呢? 那就是靠attr字段的作用来区分。另外:如果一个文件有slot,那么在数组中,先排放的是各个slot,然后才是目录项,也即每个文件的slot在目录项前面。

注意:一个目录也是一种文件,它的内容就是一个目录项(混合着slot)的数组。

 

明白了slot的作用,我们看下文件的查找过程.当用户调用CreateFile要创建或打开一个文件时,比如C:\Windows\Explorer.exeFAT32文件系统会检测磁盘上是否有该文件。下面的函数就是用来在指定目录中根据文件名查找文件的,并为其创建一个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个目录项  INOUT

                   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;

}

 

 

如上,挂载的过实质就是给系统中的所有同类文件系统cdoirp,一一请求进行识别和挂载。

简单一句话:【请求识别和挂载】

我们看看下面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”\\$$Fat$$”);

   UNICODE_STRING
VolumeNameU = RTL_CONSTANT_STRING(L”\\$$Volume$$”);

   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表文件创建一个文件名为‘\\$$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的实现没使用FileSizesPinAccess参数

    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个缓冲段

//记录分配的bcbfcb

       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;

     }

     //上面的逻辑用来决定FCBcurrlastprev变量的值,分别表示搜索的起点目录FCB,相对路径开头位置,相对路径最后一个字符位置,prev变量。这分三种情况:

1、  用户给定了起点目录,那么fcb就是起点目录的fcb,相应的curr就是相对路径

2、  用户给定的是绝对路径,且父目录被打开,那么fcb就是父目录的fcbcurr就是文件名

3、  用户给定的是绝对路径,但父目录没打开,那么fcb就是根目录的fcbcurr是绝对路径

此处所谓绝对路径指相对于盘符的路径如‘\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));

     //指向所属FCBSection

     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,
&current);

   *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(&current->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)&current->Lock);

  ExAcquirePushLockExclusive(&current->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(&current->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,
&current->BcbSegmentListEntry);

  Else //挂在最前面

     InsertHeadList(&Bcb->BcbSegmentListHead,
&current->BcbSegmentListEntry);

  KeReleaseSpinLock(&Bcb->BcbLock, oldIrql);

  InsertTailList(&CacheSegmentListHead, &current->CacheSegmentListEntry);//挂入全局链表

  InsertTailList(&CacheSegmentLRUListHead, &current->CacheSegmentLRUListEntry);//挂入LRU链表

  KeReleaseGuardedMutex(&ViewLock);

  MmLockAddressSpace(MmGetKernelAddressSpace());

  current->BaseAddress = NULL;

  Status
= MmCreateMemoryArea(MmGetKernelAddressSpace(),

                    MEMORY_AREA_CACHE_SEGMENT,//专门的文件缓冲区段类型

                    &current->BaseAddress,  Bcb->CacheSegmentSize,

                    PAGE_READWRITE,
&current->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 从FileOffsetFileOffset+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(&current->Lock);

         if
(!Locked)

         {

              current->Bcb->Callbacks->ReleaseFromLazyWrite(current->Bcb->LazyWriteContext);

              continue;

         }

         if
(current->ReferenceCount
> 1)//这种缓冲段不回写到磁盘

         {

              ExReleasePushLock(&current->Lock);

              current->Bcb->Callbacks->ReleaseFromLazyWrite(current->Bcb->LazyWriteContext);

              continue;

         }

         PagesPerSegment
= current->Bcb->CacheSegmentSize / PAGE_SIZE;

         KeReleaseGuardedMutex(&ViewLock);

         Status
= CcRosFlushCacheSegment(current);//关键函数。回写到磁盘

         ExReleasePushLock(&current->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);//发给文件卷,进入文件系统的派遣函数

}

 

 

好了,一切就绪,下面我么看看文件系统是如何处理ReadFileWriteFile 这两种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_NOCACHEIRP_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) 。。。只有FAT12FAT16才可能满足这个条件

  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(&current->Bcb->BcbLock, &oldIrql);

          //对于那些没脏的缓冲段,立即释放

         if
(current->MappedCount
> 0 && !current->Dirty && !current->PageOut)

         {

              ULONG i;

              CcRosCacheSegmentIncRefCount(current);//递增引用计数

              current->PageOut = TRUE;//标志

              KeReleaseSpinLock(&current->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(&current->Bcb->BcbLock,
&oldIrql);

              CcRosCacheSegmentDecRefCount(current);
//递减引用计数

         }

          //对于那些未锁定的文件缓冲,递增要释放的总目标页数

         if
(current->ReferenceCount
== 0)

         {

              PagesPerSegment = current->Bcb->CacheSegmentSize
/ PAGE_SIZE;

              PagesFreed
= min(PagesPerSegment,
Target);

              Target -= PagesFreed;

              (*NrFreed) += PagesFreed;
//要释放的页数          

         }

         KeReleaseSpinLock(&current->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(&current->Bcb->BcbLock, &oldIrql);

         if
(current->ReferenceCount
== 0)//如果该缓冲段的引用计数(也即锁定计数)为0

         {

              RemoveEntryList(&current->BcbSegmentListEntry);//移出

              KeReleaseSpinLock(&current->Bcb->BcbLock, oldIrql);

              RemoveEntryList(&current->CacheSegmentListEntry); //移出

              RemoveEntryList(&current->CacheSegmentLRUListEntry); //移出

              InsertHeadList(&FreeList,
&current->BcbSegmentListEntry);//转入空闲缓冲段链表

         }

         else

              KeReleaseSpinLock(&current->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的文件、不支持用户访问权限控制等。相比FAT32Ntfs文件系统就是一个相对优越的文件系统,但没开源,在此略过。