This article would be useful for Windows developers, as it explains how to create virtual disk for Windows system.
Development Leader of Driver Team
Windows virtual disks are used to be developed using kernel mode drivers. “Windows and disks” section will provide you necessary information about Windows disk drivers.
The widely known open source project named FileDisk (http://www.acc.umu.se/~bosse/) will also provide you 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 user mode in contrast to FileDisk, which processes them in kernel mode.
Pros of processing requests in user mode
Processing requests in user mode is rather useful if we have already developed user mode code that gives 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 specific encryption library.
I have used SparseFile (file-container with data; in case of data accumulation, it will size up to the maximal size) as an example of such user mode library.
In the «Windows and disks» section, we’ll consider the way Windows interacts with the disks; Windows virtual disk creation variants; useful tips for disk driver development.
In the «How to implement Windows virtual disk» section, we’ll consider the solution architecture, main implementation points and key stages of our disk’s lifecycle.
The «How to build Windows virtual disk Solution» section contains tips for building and testing the virtual disk project.
- 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 using C++ for driver development. Learn more details on this subject http://www.osronline.com/article.cfm?article=490.
- CppLib (http://www.acc.umu.se/~bosse/) provides possibility to develop drivers in C++.
- STLPort (http://www.stlport.org/) is a standard library.
- BOOST (http://www.boost.org/).
There are several projects in the solution:
- CoreMntTest (user mode, executable) – makes and mounts 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.
Windows and disks
You can simply skip this section, if you have any experience of Windows driver development. I’m 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 of requests to the disk. So, what happens after application calls, for example, the ReadFile function?
First, file system driver (for example ntfs.sys) receives the read file request. Scheme below illustrates this process:
File system driver defines the exact location of the requested file (the offset) and creates the read disk request. 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 be also implemented at the file system level. You can learn more from the http://www.codeproject.com/KB/system/fs-filter-driver-tutorial.aspx article.
Let’s consider 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 device, we should indicate request type, 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 at http://www.microsoft.com/whdc/driver/kernel/IRPs.mspx.
- STATUS_PENDING is a special return code that alerts the request initiator about that IRP cannot be served now and will be served later on. There is a termination event for this case, device will set it when complete the request serving. Below we will describe the usage of this return code.
- Device is a Windows kernel object that represents any device. It contains info 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 will consider the usage of DeviceExtension below.
How to implement Windows virtual disk
The solution consists of driver (CoreMnt.sys) and application (CoreMntTest.exe). The general scheme is shown below:
Driver helps to mount disks. Application creates data source and mounts it as a disk via the driver service. After receiving IRP, driver processes them in user mode and returns the result. Below you can find the general scheme of driver work:
CoreMntTest.exe processes requests from OS to the virtual disks. Below there is its structural scheme:
Now let’s stage by stage consider the source code.
Now we are going to start the CoreMnt driver using the following command:
c:\>sc start CoreMnt
DriverEntry, we should create the management device as an access point for CoreMntTest from user mode:
Then we should register the driver request handler. We are going to use one handler for all types of requests:
Finally, we are 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, which constructor creates disk device. OS will send requests to this device:
After creating device, we should initialize DeviceExtension. We will use it for storing device identifier. Therefore, after getting IRP, we will simply find the corresponding
MountManager has created the
MountedDisk instance and saved it to the container. Then we are 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». Thread pool should certainly be there in the real project.
3. Requests processing
Now our virtual disk is able to serve requests. I'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 device identifier, and redirects IRP to it. The code below makes decision whether it is possible to process this request at once or it should be processed in the user mode:
In case of
IRP_MJ_WRITE, it should be processed in user mode. All other requests can be processed by the driver itself. For example, in case of
IOCTL_DISK_GET_LENGTH_INFO, driver knows the disk size as well as that this size cannot 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:
If it is
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 new request.
4. Virtual disk unmounting
It starts in 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 mounting point and our device:
After that, we send message to all components that store list of disks in the system (for example, explorer.exe or any other file manager):
Finally, we notify our driver that it is 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, which 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 will lead to requests processing thread termination and the Image destructor calling.
Steps to build Windows virtual disk Solution
1. Windows virtual disk building
- Install Windows Driver Developer Kit 2003 (http://www.microsoft.com/whdc/devtools/ddk/default.mspx)
- Set global environment variable "BASEDIR" to the path to installed DDK.
- Download and install boost (tested with 1.40 version) (http://www.boost.org/users/download/ )
- Set global environment variable "BOOST" to the path to 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 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 are 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, I have described just one of the approaches for Windows virtual disk creation. 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 is another format of requests to the virtual disk as well as PNP disk device.
Read next: SaaS Application Development Team