Understanding Device Mapper and Filter Driver

Pankaj Saraf Apr 28 - 4 min read

Audio : Listen to This Blog.

Device Mapper is a virtual block device driver framework provided by Linux kernel which provides an infrastructure to filter I/O for block devices. It provides a platform for filter drivers also known as targets to map a BIO to multiple block devices, or to modify the BIO while it is in transit in kernel.

Figure 1.1 shows the position of the device mapper layer in the kernel I/O architecture. It is positioned between the generic block layer and the I/O scheduler.

Figure 1.1
Since Device Mapper itself is a block device driver, it registers the functions to handle block I/O with the generic block layer. These functions transform the received BIOs and pass them to corresponding functions from the target device drivers for further processing. The Device Mapper block device driver exports a set of ioctl methods which are used by a userspace program dmsetup. dmsetup creates a mapping between the sectors of the virtual block device and the sectors of the real block device. When this mapping is created, a data structure (mapping device) is generated, which stores all the information about the target and the underlying block drivers. The information regarding the underlying block drivers is stored in a configuration table in the kernel memory.

When the generic block layer receives a BIO for an I/O, the BIO is plugged into the request queue of the Device Mapper block driver. The Device Mapper driver now processes the BIO as follows.

  • The BIO is cloned and the end-of-I/O completion handler for the cloned BIO is set to that of Device Mapper’s end-of-I/O handler.
  • The targets for the BIO are looked up in the list of targets, and the cloned BIO is handed over to the appropriate target implementation.
  • The target implementation processes the cloned BIO and modifies the data contained by the BIO.
  • The target driver directs the BIO towards the underlying block device that was mapped by the Device Mapper layer earlier, sets an appropriate end of I/O handler for the BIO and invokes the entry method generic_make_request() for the device driver.
  • Upon completion of the I/O request by the device driver, the cloned BIO’send-of-I/O handler invokes Device Mapper block driver’s end-of-I/O handler, which then notifies the upper layers about the completion of I/O. The above process is depicted in figure 1.2.

Linux Filter Driver

This Filter Driver intends to use a design that makes inserting and removing the filter hook into the Block I/O stack. Hook can be dynamically placed and removed into the io stack of the intended block device. The hook can be placed and removed with minimal and almost zero downtime. The IOs will be suspended for a very short time while the hook is placed. The upper layers/applications are not knowledgeable of the hook and will continue performing IO as before i.e the hook is transparent to the upper layers/ applications.

The hook is placed for device corresponding to the major number. This means that IO to any minor device which is a child to the hooked major number will be filtered. However IO to only the intended minor device will be recorded.

Filter driver is called on every I/O operation. Such drivers are used by backup and snapshot software. The interface to the kernel module will be a character device which will facilitate various IOCTLs through which a user can control and command the kernel module. A filter driver should not affect the normal working of the existing driver stack in any major way.
Following is the code snippet for the filter driver hook:

void filter_hook(struct request_queue *q, struct bio *bio)
{
unsigned long sector;
unsigned int nr_sectors;
int ret = 0, first_minor = 0;
if (bio_data_dir(bio) == WRITE) {
nr_sectors = bio_sectors(bio);
sector = bio->bi_sector;
printk(KERN_INFO "Capture %llu:%llu, start %ld, len %x.", MAJOR(bio->bi_bdev->bd_dev),
MINOR(bio->bi_bdev->bd_dev), sector, nr_sectors);
orig_request_fn(q, bio);
}
//    return ret;
}
long filter_handle_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret = 0;
struct gendisk *gd = NULL;
struct block_device *bdev = NULL;
orig_request_fn = NULL;
bdev = bdget(MKDEV(disk_major, disk_minor));
gd = bdev->bd_disk;
if (gd->queue->queue_lock)
spin_lock(gd->queue->queue_lock);
orig_request_fn = gd->queue->make_request_fn;
//Replace the make_request_fn with custom make_request
gd->queue->make_request_fn = filter_hook;
if (gd->queue->queue_lock)
spin_unlock(gd->queue->queue_lock);
return ret;
}

Leave a Reply

MSys has become a renowned player in outsourced product development industry with years of experience developing multiple products for several ISVs in storage, cloud, embedded systems, DevOps, and mobile spaces. Download our brochure on Product Engineering to learn more.