The Kernel Library: libkern

The runtime support and base classes on which the I/O Kit is built are implemented in a library known as libkern. The libkern library provides support that makes up for much of the functionality that is excluded in the embedded C++ language. The libkern library defines a class known as OSObject, which provides the base class that is used by all I/O Kit classes. Since the base driver class IOService is a subclass of OSObject, the main class of a driver will also be derived from OSObject. Any class that is derived from OSObject gets the following functionality:

  • Runtime Type Information, which is implemented through custom macros provided by libkern. These macros provide functionality that includes
    • Type introspection, which is the ability at runtime to determine the type of an object or whether it is derived from a given base class
    • Dynamic casting, which is the ability to cast an object to the type of one of its derived classes (for example, to cast the provider class from an object of type IOService to IOUSBDevice)
  • Object creation, including the ability to instantiate an object based on a string representation of its class name
  • Object reference counting based on retain/release semantics
  • Object tracking, that is, the ability to determine how many instances of a certain class have been instantiated but not yet released

OSObject

Some of the features provided by libkern should be very familiar if you have written user applications with Apple's Cocoa framework. In particular, the OSObject class can be thought of as the kernel equivalent of the NSObject class in Cocoa, and the dynamic type introspection capabilities are almost identical to their counterparts provided by the Objective-C runtime.

There is no requirement to use OSObject as the superclass for classes that are private to your driver, although you may find that the reference counting and object tracking that OSObject provides (without any extra work from you) is reason enough to adopt it.

Adopting OSObject as the base class involves the following steps:

  1. Using the standard C++ syntax, declare your class to be a subclass of OSObject, or a class that is derived from OSObject (such as IOService). If you are subclassing from OSObject, you may need to include the header file <libkern/c++/OSObject.h>.
  2. As the first line of your class's declaration (in your class's header file), include the macro OSDeclareDefaultDestructors(), passing the name of your class as its argument. This macro, among other things, declares the standard C++ constructor and destructor for your class, and so you should not add either to your class declaration. Instead, add a method named init() to your class to act as the constructor, and a method named free() to act as the destructor. You are free to add any arguments that your class requires to the init() method, as is shown in the following example.
    class com_osxkernel_driver_MyObject : public OSObject
    {
       OSDeclareDefaultStructors(com_osxkernel_driver_MyObject)
    public:
       virtual bool     init (const char* name);
       virtual void     free ();
       …
    };
  3. In the file that implements your class, place the macro OSDefineMetaClassAndStructors(), which takes two arguments, the name of the class, and the name of its direct superclass. The first few lines of an implementation file typically follow the pattern
    #include "MyObject.h"

    // Define super as a convenience macro to refer to the superclass
    #define super OSObject

    OSDefineMetaClassAndStructors(com_osxkernel_driver_MyObject, OSObject)
  4. Provide an implementation of the methods init() and free(). These two methods play the role of the constructor and destructor, respectively, as shown in the following example.
    bool com_osxkernel_driver_MyObject::init (const char* name)
    {
       if (! super::init())
               return false;
       
       // Additional initialization
       return true;
    }
    void com_osxkernel_driver_MyObject::free ()
    {
       // Release resources allocated in init()
       super::free();
    }

Having defined an object that subclasses from OSObject, it can be instantiated in your code by calling the C++ new operator followed by the init() method. As a convenience, many classes provide a static method that performs both of these steps, and return a non-NULL object on success, as shown in Listing 4-4.

Listing 4-4. The Definition of a Static Helper Method to Construct a New Instance of a Custom Class

com_osxkernel_driver_MyObject*
        com_osxkernel_driver_MyObject::withName (const char* name)
{
        com_osxkernel_driver_MyObject* me = new com_osxkernel_driver_MyObject;
        
        if (me && !me->init(name))
        {
                me->release();
                return NULL;
        }
        
        return me;
}

The lifetime of any object that is based on OSObject is determined by reference counting. When an object is first created, its reference count is initialized to 1. To free an object, rather than using the C++ operator delete (which the OSDeclareDefaultStructors macros declare as a protected method), your code should instead call the release() method. This method is implemented by the OSObject class and decrements the reference count of the object by 1. When the object's final reference is released, and the object's reference count becomes 0, the object is released and the free() method is called. If your code takes a pointer to an object that it needs to hold on to, it will need to extend the lifetime of that object to ensure that the object is not released while your code is holding a reference to it. This is done by calling the retain() method, which increments the reference count of the target object by 1. To prevent memory leaks, it is important that each call to retain() is matched with a call to release().

Any object that is derived from OSObject allows type introspection. To cast an object into another type, libkern provides a macro named OSDynamicCast(type, object), which performs the equivalent of the C++ operator dynamic_cast<type>(object). The macro verifies whether the object is derived from the requested class, and if so, a pointer to the object is returned; otherwise, the macro returns NULL. The most common use of dynamic casting is to safely convert an object from a base class to a more specialized class. For example, the driver's start() method is passed a pointer to its provider class as an IOService object. However, the provider is actually a more specialized class such as IOUSBDevice or IOPCIDevice, and a dynamic cast allows this conversion to be made safely. For example, a driver that controls a USB device will contain the following code in its start() method to convert the provider from an IOService to an IOUSBDevice:

IOUSBDevice* usbDevice = OSDynamicCast(IOUSBDevice, provider);
if (usbDevice == NULL)
{
        IOLog("Unknown provider class ");
        return false;
}

The OSObject base class can also track the number of instances of each of its derived classes that have been instantiated but not yet released. This information is not only useful for the I/O Kit; it also provides an invaluable mechanism for tracking memory leaks. Internally, the I/O Kit uses the instance count of each class to ensure that it does not unload a kernel extension that has outstanding objects, which would lead to a kernel panic. When a kernel extension no longer has any outstanding instances for all classes that it defines, the kernel will unload that kernel extension. The number of instances of each OSObject-derived class can be examined through the command line tool “ioclasscount.”

images Tip If you open Terminal and run the command ioclasscount after loading the IOKitTest tutorial, you will see a single instance of the class com_osxkernel_driver_IOKitTest.

Container Classes

As well as defining the base class and providing a runtime environment for the kernel, libkern also defines a number of container classes to manage a collection of objects. The container classes provided by libkern include arrays, dictionaries, and both ordered and unordered sets. While all these containers can contain objects of varying types and can even contain objects of differing types within the same container, a container can contain only objects that are derived from the OSObject class.

images NoteIf you are familiar with user space programming on Mac OS X, the libkern container classes are equivalent to NSMutableArray, NSMutableDictionary, and NSMutableSet, or the Core Foundation types CFMutableDictionary, CFMutableArray, and CFMutableSet.

To allow non-object scalar types such as Booleans, integers, and strings to be included in the container types, libkern provides the corresponding classes OSBoolean, OSNumber, and OSString for wrapping a bool, an integer value of up to 64 bits in length, and a C-string, respectively.

The handling of strings in libkern deserves special mention. The libkern library provides two classes for representing a string, OSString and OSSymbol (which is a subclass of OSString). The purpose of OSSymbol is not to provide a general wrapper for a string, but rather to hold string values that represent “symbols” in the kernel, such as commonly used keys in a matching dictionary. When a new instance of OSSymbol is created, the constructor checks for an existing OSSymbol object that contains the same string value, and if one is found, returns an instance of the existing object rather than creating a new instance. This means that for a given string value, there is at most one OSSymbol object representing that value. As a consequence, a dictionary that is keyed on OSSymbol values needs to compare the address of only two OSSymbol values rather than performing a more expensive string comparison.

All the container classes follow the same behavior with regard to object ownership. Any object added to a container is retained by that container class, and objects are released by the container class once they are removed from the container or the final reference of the container itself is released and so the container is deallocated. After inserting an object into a container, if the caller no longer requires its own reference to that object, it is free to release the inserted object since the container class will maintain a reference to the object.

After querying a container for an object that it contains, the caller should retain that object if there is any chance that the container could be released while the caller is still using the returned object. The libkern container classes do not increment the reference of their content objects before returning it to the caller (for example, the OSArray method getLastObject() will not increment the reference count of its last object before returning it to the caller).

It is important to note that the container classes do not provide any synchronization for use in a multi-threaded environment. That is not to say that they cannot be used in a driver that contains multi-threaded code, but rather that it is the caller's responsibility to add its own locking to ensure that calls to the container classes are serialized.

The container classes provided by libkern include the following:

  • OSArray, which provides storage and retrieval of objects based on the index within the array
  • OSDictionary, which provides storage and retrieval based on a provided string value (which is known as the “key”)
  • OSSet, which provides storage for objects and the ability to test whether an object is in the set
  • OSOrderedSet, which provides storage that is sorted based on a provided comparison function and retrieval based on an index

All libkern container classes can be iterated over using the class OSCollectionIterator, as shown in Listing 4-5. When iterating over an OSDictionary, the objects returned by the iterator represent the keys of the dictionary, and not the values contained in the dictionary itself.

Listing 4-5. A Sample Function to Iterate Over the Objects Contained in an OSArray

void    IterateArray (OSArray* array)
{
        OSCollectionIterator*   iter;

        iter = OSCollectionIterator::withCollection(array);
        if (iter != NULL)
        {
                OSObject*       anObject;
                
                while ((anObject = iter->getNextObject()) != NULL)
                {
                        // Assume the array only contains string values:
                        // OSString* aString = OSDynamicCast(OSString, anObject);
                }
                
                iter->release();
        }
}

A special container object for drivers is their property table. This is a dictionary that contains a number of key/value pairs that are local to a particular driver instance. When a driver is loaded, the I/O Kit fills its property table with the entries of the matching dictionary from the driver's Info.plist file. However, as the driver runs, it is free to add or remove additional values from its property table. A driver's property table is special because it can be accessed by user space applications, including IORegistryExplorer. This makes it a perfect means for passing small amounts of data, such as integer values, between the driver and user space.

Alternatively, a driver can write the values of certain important variables to keys in its property table, which can then be monitored in IORegistryExplorer (or a custom application) to track the state of the driver.

..................Content has been hidden....................

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