Building a RAM Disk Device

Having examined the methods that must be implemented to support the IOBlockStorageDevice interface, we can now take a look at how a simple RAM disk device can be implemented in Mac OS X. As with the driver for any disk device in the I/O Kit, we will split our driver into two classes: the transport driver class, which implements the functionality and communicates with the hardware device, and a class that implements the IOBlockStorageDevice interface, which acts as an interface between the transport driver and the device services layer of the storage driver stack.

As we have seen, the I/O Kit does not require the transport driver for a storage device to be written in any particular way or to subclass from any particular superclass. This allows the transport driver to be written in a way that is most natural for the type of hardware that provides access to the disk storage. For our RAM disk, the “hardware” controlled by the transport driver is nothing more than a memory allocation that provides the storage for the RAM disk. As we learned in Chapter 4, an I/O Kit driver that has no hardware device to match against will use the global IOResources class as its provider class. This will be the provider class of our RAM disk's transport driver. Since our transport driver is implemented as a generic driver, we will implement it as a subclass of the generic IOService class.

For simplicity, our RAM disk's transport driver will allocate the storage for the disk when it loads, and will not release it until the driver unloads. The storage is a fixed-size memory allocation. Our transport driver will also be responsible for instantiating the IOBlockStorageDevice object, which will provide the interface, through which the upper layer of the driver stack will communicate, with our transport driver. The header file for a RAM disk's transport driver is given in Listing 14-2.

Listing 14-2. The Header File of the Transport Driver for a RAM Disk Device

#include <IOKit/IOService.h>
#include <IOKit/IOBufferMemoryDescriptor.h>


class com_osxkernel_driver_RAMDisk : public IOService
{
        OSDeclareDefaultStructors(com_osxkernel_driver_RAMDisk)
        
private:
        IOBufferMemoryDescriptor*       m_memoryDesc;
        void*                           m_buffer;
        
protected:
     bool                    createBlockStorageDevice ();
     
public:
     virtual bool            start (IOService* provider);
     virtual void            free (void);
     
     virtual IOByteCount     performRead (IOMemoryDescriptor* dstDesc, UInt64 byteOffset,
                                             UInt64 byteCount);
     virtual IOByteCount     performWrite (IOMemoryDescriptor* srcDesc, UInt64 byteOffset,
                                             UInt64 byteCount);
};

The implementation of a RAM disk's transport driver is given in Listing 14-3.

Listing 14-3. The Implementation of the Transport Driver for a RAM Disk Device

// Define the superclass
#define super IOService

OSDefineMetaClassAndStructors(com_osxkernel_driver_RAMDisk, IOService)

#define kDiskByteSize           (16*1024*1024) // Fix RAM disk size at 16MiB

bool com_osxkernel_driver_RAMDisk::start (IOService *provider)
{
        if (super::start(provider) == false)
                return false;
        
        // Allocate storage for the disk.
        m_memoryDesc = IOBufferMemoryDescriptor::withCapacity(kDiskByteSize,
                                                              kIODirectionOutIn);
        if (m_memoryDesc == NULL)
                return false;
        m_buffer = m_memoryDesc->getBytesNoCopy();
        
        // Allocate an IOBlockStorageDevice nub.
        if (createBlockStorageDevice() == false)
                return false;
        
        return true;
}

void com_osxkernel_driver_RAMDisk::free (void)

{
        if (m_memoryDesc != NULL)
                m_memoryDesc->release();
        
        super::free();
}


IOByteCount com_osxkernel_driver_RAMDisk::performRead (IOMemoryDescriptor* dstDesc,
                                                       UInt64 byteOffset, UInt64 byteCount)
{
        return dstDesc->writeBytes(0, (void*)((uintptr_t)m_buffer + byteOffset), byteCount);
}

IOByteCount com_osxkernel_driver_RAMDisk::performWrite (IOMemoryDescriptor* srcDesc,
                                                        UInt64 byteOffset, UInt64 byteCount)
{
        return srcDesc->readBytes(0, (void*)((uintptr_t)m_buffer + byteOffset), byteCount);
}

The implementation of the RAMDisk class should be fairly straightforward. In its start() method, the transport driver allocates a memory buffer that provides the storage for the disk device. This buffer isn't released until the RAM disk driver is unloaded and its free() method is called. The RAMDisk driver class also defines two methods that provide access to the storage buffer, namely performRead() and performWrite().

As a general rule, the transport driver should be implemented in a way that matches the functionality and protocol of the device that it is controlling. The interface for the RAM disk in Listing 14-2 certainly meets this requirement, with its very simple set of methods. A consequence of this freedom is that the transport driver needs a nub driver, which implements the IOBlockStorageDevice interface, to accept method calls from the upper layers of the storage driver stack and to pass them on to the transport driver. In our sample RAM disk driver, this functionality is provided by a class named com_osxkernel_driver_RAMDiskStorageDevice, which is derived from the IOBlockStorageDevice interface.

A class that implements the IOBlockStorageDevice interface sits between the transport driver and the upper-layer drivers; it implements methods that are called by the upper-layer drivers, and in turn needs to call methods that are implemented in the transport driver. As such, it needs a reference to an instance of the transport driver class. This is usually done by making the transport driver the provider class of the IOBlockStorageDevice nub.

In our RAM disk driver, the transport driver directly instantiates the RAMDiskStorageDevice nub and attaches it to itself. Attaching the RAMDiskStorageDevice to the transport driver sets up the transport driver as the provider class of the RAMDiskStorageDevice. This process is implemented in a private method named createBlockStorageDevice(), which the transport driver calls from its start() method. The implementation of this is given in Listing 14-4.

Listing 14-4. Instantiating the IOBlockStorageDevice Nub from the RAM Disk Transport Driver

bool com_osxkernel_driver_RAMDisk::createBlockStorageDevice ()
{
        com_osxkernel_driver_RAMDiskStorageDevice*      nub = NULL;
        bool            result = false;
        
        // Allocate a new IOBlockStorageDevice nub.

        nub = new com_osxkernel_driver_RAMDiskStorageDevice;
        if (nub == NULL)
                goto bail;
        
        // Call the custom init method (passing the overall disk size).
        if (nub->init(kDiskByteSize) == false)
                goto bail;
        
        // Attach the IOBlockStorageDevice to the this driver.
        // This call increments the reference count of the nub object,
        // so we can release our reference at function exit.
        if (nub->attach(this) == false)
                goto bail;
        
        // Allow the upper level drivers to match against the IOBlockStorageDevice.
        nub->registerService();
        
        result = true;
        
bail:
        // Unconditionally release the nub object.
        if (nub != NULL)
                nub->release();
        
        return result;
}

After instantiating the nub driver and attaching it as a client of the transport driver, it is important to call the method registerService() on the nub. This can either be performed by the implementation of the nub itself (such as in its start() method) or, as in this example, by the transport driver. The purpose of calling registerService() is to publish the IOBlockStorageDevice nub, allowing drivers to match against it, which begins the construction of the rest of the storage driver stack. The header file for the com_osxkernel_driver_RAMDiskStorageDevice nub driver is provided in Listing 14-5.

Listing 14-5. The Header File for the RAMDiskStorageDevice Nub Class

#include <IOKit/storage/IOBlockStorageDevice.h>

class com_osxkernel_driver_RAMDisk;

class com_osxkernel_driver_RAMDiskStorageDevice : public IOBlockStorageDevice
{
        OSDeclareDefaultStructors(com_osxkernel_driver_RAMDiskStorageDevice)
        
private:
        UInt64                          m_blockCount;
        com_osxkernel_driver_RAMDisk*   m_provider;
        
public:
        virtual bool    init(UInt64 diskSize, OSDictionary* properties = 0);

        virtual bool    attach(IOService* provider);

        virtual void            detach(IOService* provider);
        
        virtual IOReturn        doEjectMedia(void);
        virtual IOReturn        doFormatMedia(UInt64 byteCapacity);
        virtual UInt32          doGetFormatCapacities(UInt64 * capacities, UInt32
                                    capacitiesMaxCount) const;
        virtual IOReturn        doLockUnlockMedia(bool doLock);
        virtual IOReturn        doSynchronizeCache(void);
        virtual char*           getVendorString(void);
        virtual char*           getProductString(void);
        virtual char*           getRevisionString(void);
        virtual char*           getAdditionalDeviceInfoString(void);
        virtual IOReturn        reportBlockSize(UInt64 *blockSize);
        virtual IOReturn        reportEjectability(bool *isEjectable);
        virtual IOReturn        reportLockability(bool *isLockable);
        virtual IOReturn        reportMaxValidBlock(UInt64 *maxBlock);
        virtual IOReturn        reportMediaState(bool *mediaPresent,bool *changedState);
        virtual IOReturn        reportPollRequirements(bool *pollRequired,
                                    bool *pollIsExpensive);
        virtual IOReturn        reportRemovability(bool *isRemovable);
        virtual IOReturn        reportWriteProtection(bool *isWriteProtected);
        virtual IOReturn        getWriteCacheState(bool *enabled);
        virtual IOReturn        setWriteCacheState(bool enabled);
        virtual IOReturn        doAsyncReadWrite(IOMemoryDescriptor *buffer, UInt64 block,
                                    UInt64 nblks, IOStorageAttributes *attributes,
                                    IOStorageCompletion *completion);
};

The implementation of the com_osxkernel_driver_RAMDiskStorageDevice class is provided in Listing 14-6. For brevity, methods with an empty implementation have been omitted.

Listing 14-6. The Implementation of an IOBlockStorageDevice Nub Class

#include <IOKit/storage/IOBlockStorageDevice.h>

// Define the superclass
#define super IOBlockStorageDevice

OSDefineMetaClassAndStructors(com_osxkernel_driver_RAMDiskStorageDevice, IOBlockStorageDevice)

#define kDiskBlockSize          512

bool com_osxkernel_driver_RAMDiskStorageDevice::init(UInt64 diskSize, OSDictionary*
   properties)
{
    if (super::init(properties) == false)
      return false;
    m_blockCount = diskSize / kDiskBlockSize;
    return true;
}

bool com_osxkernel_driver_RAMDiskStorageDevice::attach (IOService* provider)

{
    if (super::attach(provider) == false)
      return false;
    m_provider = OSDynamicCast(com_osxkernel_driver_RAMDisk, provider);
    if (m_provider == NULL)
      return false;
    return true;
}

void com_osxkernel_driver_RAMDiskStorageDevice::detach(IOService* provider)
{
    if (m_provider == provider)
      m_provider = NULL;
    super::detach(provider);
}


UInt32 com_osxkernel_driver_RAMDiskStorageDevice::doGetFormatCapacities(UInt64* capacities,
                                                              UInt32 capacitiesMaxCount) const
{
    // Ensure that the array is sufficient to hold all our formats (we require 1 element).
    if ((capacities != NULL) && (capacitiesMaxCount < 1))
      return 0;               // Error, return an array size of 0.
    
    // The caller may provide a NULL array if it wishes to query
    // the number of formats that we support.
    if (capacities != NULL)
      capacities[0] = m_blockCount * kDiskBlockSize;
    return 1;
}

char* com_osxkernel_driver_RAMDiskStorageDevice::getProductString(void)
{
    return (char*)"RAM Disk";
}

IOReturn com_osxkernel_driver_RAMDiskStorageDevice::reportBlockSize(UInt64 *blockSize)
{
    *blockSize = kDiskBlockSize;
    return kIOReturnSuccess;
}

IOReturn com_osxkernel_driver_RAMDiskStorageDevice::reportMaxValidBlock(UInt64 *maxBlock)
{
    *maxBlock = m_blockCount-1;
    return kIOReturnSuccess;
}

IOReturn com_osxkernel_driver_RAMDiskStorageDevice::reportMediaState(bool *mediaPresent, bool *changedState)
{
    *mediaPresent = true;

    *changedState = false;
    return kIOReturnSuccess;
}

IOReturn com_osxkernel_driver_RAMDiskStorageDevice::reportPollRequirements(bool *pollRequired,
   bool *pollIsExpensive)
{
    *pollRequired = false;
    *pollIsExpensive = false;
    return kIOReturnSuccess;
}

IOReturn com_osxkernel_driver_RAMDiskStorageDevice::reportRemovability(bool *isRemovable)
{
    *isRemovable = true;
    return kIOReturnSuccess;
}

IOReturn com_osxkernel_driver_RAMDiskStorageDevice::doAsyncReadWrite(IOMemoryDescriptor
   *buffer, UInt64 block, UInt64 nblks, IOStorageAttributes *attributes, IOStorageCompletion
   *completion)
{
    IODirection               direction;
    IOByteCount               actualByteCount;
    
    // Return errors for incoming I/O if we have been terminated.
    if (isInactive() == true)
      return kIOReturnNotAttached;
    // Ensure the block range being targeted is within the disk's capacity.
    if ((block + nblks) > m_blockCount)
      return kIOReturnBadArgument;
    
    // Get the buffer's direction, which indicates whether the operation is a read or a write.
    direction = buffer->getDirection();
    if ((direction != kIODirectionIn) && (direction != kIODirectionOut))
      return kIOReturnBadArgument;
    
    // Perform the read or write operation through the transport driver.
    if (direction == kIODirectionIn)
      actualByteCount = m_provider->performRead(buffer, (block*kDiskBlockSize),
                                               (nblks*kDiskBlockSize));
    else
      actualByteCount = m_provider->performWrite(buffer, (block*kDiskBlockSize),
                                                (nblks*kDiskBlockSize));
    
    // Call the completion function.
    (completion->action)(completion->target, completion->parameter, kIOReturnSuccess,
                         actualByteCount);
    
    return kIOReturnSuccess;
}

Notice that although the transport driver for the RAM disk has no concept of a block size (since its minimum addressable unit was a byte), the IOBlockStorageDevice interface expresses the disk capacity in blocks, and operates on blocks when performing a read or write operation. For this reason, the nub driver's implementation defines an arbitrary block size of 512 bytes.

Finally, as with every I/O Kit driver, our RAM disk driver requires a property list that describes the requirements of the driver, including its matching dictionary. The IOBlockStorageDevice interface that the RAM disk driver implements is part of the I/O Kit's IOStorageFamily framework, so we need to explicitly include this dependency in the RAM disk driver's property list. This is done by adding an entry to the OSBundleLibraries section of the Info.plist file that references the kernel module com.apple.iokit.IOStorageFamily. In this sample, we import version 1.6 of the IOStorageFamily, which corresponds to the version that was included with Mac OS X 10.6.

images Note Any kernel extension that implements a driver that is a part of the storage driver stack will need to include the IOStorageFamily as a dependency in its property list.

The property list for our sample RAM disk driver, including its matching dictionary and its library dependencies, is shown in Figure 14-3.

images

Figure 14-3. The property list for the sample RAM disk driver

After building the RAM disk driver and loading the resulting kernel extension, you will be presented with a dialog similar to that displayed in Figure 14-4. This does not indicate a problem with the device, but it does indicate that Mac OS X was unable to find a readable file system on the disk. Given that the disk has yet to be written to, this is an expected error.

images

Figure 14-4. The standard Mac OS X dialog that is displayed when a disk is inserted that does not contain a readable file system

Clicking the “Initialize…” button in the dialog displayed in Figure 14-4 will launch the “Disk Utility” application, allowing the storage device to be partitioned and initialized with a file system. Before doing this, it is interesting to examine the state of the driver stack with the IORegisterExplorer utility. In addition to the RAMDisk transport driver and the RAMDiskStorageDevice nub, you will notice that the I/O Kit has constructed three drivers on top of the nub driver. The state of the driver stack is shown in Figure 14-5.

images

Figure 14-5. The driver stack that is created when a non-formatted storage device is loaded

The Disk Utility application allows a disk to be formatted and initialized for a file system. This process involves writing a partition table to the disk, which is required even if the disk contains only a single partition, and then writing a file system to that partition. To format a disk using Disk Utility, select the device from the list of disks on the left, and click the Erase tab. The name of the device that is displayed in Disk Utility is derived from the descriptive strings returned by the IOBlockStorageDevice nub, so in the case of the sample RAM disk, this results in a device with the name “RAM Disk Media.”

By default, Disk Utility will write a GUID partition table to the disk and will use the Mac OS Extended file system, also known as the HFS+ file system. Disk Utility won't perform a low-level format of the volume, so the IOBlockStorageDevice method doFormatMedia does not need to be implemented. The process of initializing a volume is shown in Figure 14-6.

images

Figure 14-6. Initializing a new volume in Disk Utility

After a partition map and a file system has been written to the disk, the storage driver stack for the RAM disk will now contain three more drivers, as shown in Figure 14-7. On top of the IOMedia object that represents the entire disk is an I/O Kit class that represents the partition table that is present on the disk; in this case, it is the GUID partition table. Each partition has an IOMedia object that represents the logical volume of the partition.

images

Figure 14-7. The driver stack for a device that is partitioned with a GUID partition table containing a single partition

When we implemented the driver for our RAM disk, all of the methods that we implemented were specific to accessing data from the device; it didn't need to provide any methods to handle partition schemes or file systems, or to make the device accessible to a user space process. All of this functionality is handled by classes provided by the I/O Kit's IOStorageFamily, in particular the IOBlockStorageDriver, IOMedia, and IOMediaBSDClient classes. The benefit of this design is that the functionality is largely common across all storage devices and can be implemented in shared classes, which removes the need for each storage device to rewrite the same functionality.

The IOBlockStorageDevice and IOBlockStorageDriver classes represent the disk drive hardware, and the IOMedia class represents the disk that is currently present in that drive. In the case of our sample RAM disk driver, or a USB flash drive, there will always be media present when there is a storage device; the two are inseparable. However, this does not always have to be true; a CD drive for example, will create an IOBlockStorageDevice (for which the I/O Kit will create a corresponding IOBlockStorageDriver), but unless there is a CD in the drive, there will be no IOMedia object in the driver stack.

The IOMedia class provides a logical representation of the disk. If the disk has been partitioned, a storage device will have multiple IOMedia objects, one for each partition, and another that represents the overall disk. In the case of a RAID, where a single volume has been created across multiple disks, there will be a single IOMedia object that represents the entire logical RAID volume.

Each IOMedia object in the kernel has an accompanying object known as the IOMediaBSDClient, which is responsible for making the logical disk available to user space processes. In most cases, a process won't need to interact with the disk driver directly. Rather, it will simply use the file system on the mounted volume to read and write files that are contained on that volume. In some cases, a user space process may need to read or write to the disk device directly or to send ioctl calls to the disk driver directly.

Following the convention of BSD, both a block device interface and a character device interface are created for every disk and every disk partition. The block device performs buffered I/O, with each read and write going through a buffer cache. When a process performs a read operation, the disk blocks that contain the accessed data are read from disk, placed into a buffer cache, and then copied into the destination buffer supplied by the user process. If a process reads a disk block that has recently been accessed, the data is likely to be present in the buffer cache, and the read request can be completed without having to access the disk.

The character device provides raw access to the disk storage, and doesn't go through the buffer cache. This means that every read or write to the device will result in a method call of the device's IOBlockStorageDevice interface, which will read or write directly into the process's buffer. As a consequence of this, all read and write operations performed to the character device must start on a disk block boundary, and the number of bytes transferred must be a multiple of the disk block size.

The block device and character device interfaces are created by the IOMediaBSDClient class. The block device interface can be accessed through the path /dev/diskN, and the character device interface can be accessed through the path /dev/rdiskN, where N is an integer to give the device a unique name.

After building and loading the RAM disk driver, a list of the disk devices that are present in the system can be examined by running the terminal command diskutil list. An example of the output from this command for the RAM disk device is shown in Listing 14-7. The interface disk1 corresponds to the entire storage device, and the interface disk1s1 corresponds to the HFS+ partition.

Listing 14-7. The Output from the Command diskutil list for the RAM Disk Device

/dev/disk1
   #:   TYPE                    NAME            SIZE            IDENTIFIER
   0:   GUID_partition_scheme                   *16.8 MB        disk1
   1:   Apple_HFS               VolumeName      16.7 MB         disk1s1
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.164.164