This article would be useful for Windows developers, as it explains how to create a virtual disk for the Windows system.
Windows virtual disks are developed using kernel mode drivers. The Windows and disks section will provide you necessary information about Windows disk drivers.
The widely known open-source project named FileDisk will also provide you the information about ways for Windows virtual disk implementation.
As Apriorit frequently works on the advanced driver development projects, virtual disk technologies are an important part of our profile. The main differentiator of the Virtual Disk Development Kit, provided in this article, is the fact that it serves requests to the virtual disk in the user mode in contrast to FileDisk, which processes them in the kernel mode.
Processing requests in the user mode is rather useful if we've already developed user mode code that gives us access to the data source (like disk image in memory, remote disk, cash register), and it’s difficult to port this code to the kernel mode; or we have no source code, for example during network access or if we use a specific encryption library.
We used SparseFile (file-container with data; in the case of data accumulation, it'll size up to the maximum size) as an example of such user mode library.
- C++. The whole project is developed in C++ with the utilizing of the exceptions (both kernel mode and user mode parts). There are conflicting opinions concerning the use of C++ for driver development. You can learn more details on this subject here
- CppLib, provides the possibility to develop drivers in C++
- STLPort, which is a standard library
There are several projects in the solution:
- CoreMntTest (user mode, executable) – makes and mounts a disk image using the CoreMnt_user code.
- CoreMnt_user (user mode, library) – gets requests to virtual disks from the CoreMnt driver and processes them.
- UsrUtils (user mode, library) – uses DeviceIoControl to contain auxiliary code of interaction with drivers.
- CoreMnt (kernel mode, executable) – implements OS requirements for the disks; transforms requests; sends them to CoreMnt_user.
- drvUtils (kernel mode, headers only library) – auxiliary kernel mode code, for example, synchronization tools.
The following scheme shows the projects’ relations.
Structure of the project directory
.\bin - folder with binary files
.\lib - folder with library files
.\obj - folder with object files
.\src - folder with source files
|-> .\CoreMnt - Kernel mode driver.
|-> .\CoreMnt_user - User mode mount library.
|-> .\CoreMntTest - User mode mount test.
|-> .\drvCppLib - Kernel Library to develop driver in C++.
|-> .\drvUtils - Kernel Library with utils for kernel mode projects.
|-> .\mnt_cmn - Files that are shared between projects.
|-> .\STLPort - Directory with STLPort 4.6 ported for
utilizing in windows drivers.
|-> .\usrUtils - Win32 Library with utils for user mode projects.
You can simply skip this section if you have any experience in Windows driver development. We're happy to appease the rest – this process is very simple. Windows sends write or read request to the disk. The disk returns read data or error code. That's all.
Of course, there are a few nuances.
We'll discuss a simple scheme of processing the requests to the disk. So, what happens after an application calls, for example, the ReadFile function?
First, a file system driver (for example ntfs.sys) receives the read file request. The scheme below illustrates this process:
File system driver defines the exact location of the requested file (the offset) and creates the read disk request. The file can be divided into several parts, located at different places on the disk. Thus, a few requests will be created. Our virtual disk driver will receive requests like that from the file system driver. Virtual disk can also be implemented at the file system level. You can learn more about this process from this article.
Let’s consider the technical terms we use:
- IRP (I/O Request Packet) is a Windows kernel structure that stores parameters of the requests. In order to read data from a device, we should indicate the request type, the buffer to read data to, size and offset. We can assume that IRP is a request to some device. Talking about IRP in this article, we always mean request. You can learn more about it in Microsoft documentation.
- STATUS_PENDING is a special return code that alerts the request initiator that IRP can't be served now and will be served later on. There's a termination event for this case, the device will set it when it completes the request serving. Below we'll describe the usage of this return code.
- Device is a Windows kernel object that represents any device. It contains information about this device, for example, its name. It also contains DeviceExtension.
- DeviceExtension is a Device structure field that can be used by the device creator in his own way. We'll consider the usage of DeviceExtension below.
The solution consists of a driver (CoreMnt.sys) and an application (CoreMntTest.exe). The general scheme is shown below:
Driver helps to mount disks. Application creates a data source and mounts it as a disk via the driver service. After receiving IRP, driver processes them in the user mode and returns the result. Below you can find the general scheme of driver's work:
CoreMntTest.exe processes requests from the operating system to the virtual disks. Its structural scheme is shown below:
Now let’s consider the source code stage by stage.
Now we're going to start the CoreMnt driver using the following command:
DriverEntry, we should create the management device as an access point for CoreMntTest from the user mode:
Then we should register the driver request handler. We're going to use one handler for all types of requests:
Finally, we're going to create
2. Virtual disk mounting
This is the stage where we run the CoreMntTest.exe application. It sends the
CORE_MNT_MOUNT_IOCTL management message to the driver:
DispatchMount function deserializes request parameters and calls
MountManager::Mount, we make and save the
LogicIrpDispatcher, whose constructor creates a disk device. The operating system will send requests to this device:
After creating a device, we should initialize DeviceExtension. We'll use it for storing device identifier. Therefore, after getting IRP, we'll simply find the corresponding
MountManager has created the
MountedDisk instance and saved it to the container. Then weєre finishing the initialization stage in the user mode. For each disk, we create a thread where all disk requests will be served. A thread sends
IOCTL RequestExchange to the driver and proceeds to the request awaiting mode:
Performance note: surely, processing requests in one thread is the «bottle neck». A thread pool should certainly be there in the real project.
3. Requests processing
Now our virtual disk is able to serve requests. We'll describe the complete chain of request processing. Everything starts from the
IrpHandler function registered by our driver as the IRP processing procedure. Now we receive the device identifier from
DeviceExtension (we saved it there during the initialization stage) and transmit IRP to
MountManager receives IRP, finds the corresponding
MountedDisk by the device identifier, and redirects IRP to it. The code below makes decision whether it's possible to process this request at once or it should be processed in the user mode:
In the case of
IRP_MJ_WRITE, it should be processed in the user mode. All other requests can be processed by the driver itself. For example, in the case of
IOCTL_DISK_GET_LENGTH_INFO, driver knows the disk size as well as that this size can't be changed. In
LogicIrpDispatcher::dispatchIoctl, you can find the complete list of requests, which Windows can send to the disk.
The thread, which serves this disk, chooses requests from the list:
IRP_MJ_WRITE, we should copy data to write to the buffer. After that, the solution passes this buffer to the user mode code:
After exiting the
RequestExchange function, we get to the
DispatchImage (cycle of requests processing) again:
The type, size, and offset variables now contain the new request for processing. The
DispatchImageOperation function will manage it:
After serving the request, the
RequestExchange function will be called again and the thread will proceed to the mode of waiting for a new request.
4. Virtual disk unmounting
It starts in the user mode from the
UnmountImage function calling. Below you can find the code that checks whether the disk is being used now or not:
Then we eliminate the connection between the mounting point and our device:
After that, we send a message to all components that store the list of disks in the system (for example, explorer.exe or any other file manager):
Finally, we notify our driver that it's time to delete the device:
MountManager::Unmount simply removes the corresponding
MountedDisk from the container. It causes the calling of its destructor:
We set the stop event for the thread of requests processing for this disk:
Then we terminate all IRPs that were not processed and now are in a queue:
The thread of requests serving, which has been awaiting in
MountedDisk::RequestExchange, reacts on
stopEvent_ set and throws an exception:
We are catching the thrown exception in the
DispatchException function catch block, and returning
STATUS_UNSUCCESSFUL to the user mode:
The returned error state is processed in the
DriverControl::RequestExchange function by the user mode code and throws an exception:
This exception is caught in the
SyncMountmanager::mountDispatchThread catch block:
Therefore, it'll lead to requests processing thread termination and the Image destructor calling.
1. Windows virtual disk building
- Install Windows Driver Developer Kit 2003
- Set global environment variable "BASEDIR" to the path to the installed DDK.
- Download and install boost (tested with 1.40 version)
- Set global environment variable "BOOST" to the path to the installed boost. Restart your computer after this.
- Build the solution by means of Visual Studio 2008.
Computer -> Properties -> Advanced -> Environment variables ->System Variables -> New
Like this: BASEDIR -> c:\winddk\3790
2. Steps to test the Solution
- Build the solution by means of described instructions.
- Copy CoreMnt.sys to %windir%\system32\drivers.
- Register the driver in the system:
- Start the driver using the command:
- Start CoreMntTest.exe.
sc create CoreMnt type= kernel binPath= system32\drivers\CoreMnt.sys
sc start CoreMnt
If everything was OK, CoreMntTest.exe displays the message:
Image was mounted. Press any key for unmount.
Disk Z appears in the system.
Now we're able to format it.
The tst_img file appears in the directory along with CoreMntTest.exe.
3. Supported Windows versions
- Windows XP SP2
In this article, we described just one of the approaches for creating a Windows virtual disk. We can call it logical virtual disk. You can also consider the following approaches:
- File system virtual disk. Driver receives the
WriteFilerequests and so on.
- Physical virtual disk. The approach of physical virtual disk is semantically similar to the logical one but it has some differences on the driver level: there's another format of requests to the virtual disk as well as PNP disk device.