Memory descriptors are implemented by the IOMemoryDescriptor
class and is fundamental to working with memory in I/O Kit. The class
also serves as a super class for other important memory-related classes, which we will discuss later in this chapter. Many parts of the I/O Kit accept an IOMemoryDescriptor
as an argument. For example, the USB family uses the class to describe memory used for USB read and write requests.
The IOMemoryDescriptor
describes the properties of a memory buffer or range of memory, but does not allocate (or free) the described memory. It contains metadata and allows some operations to be performed on the memory. It can describe virtual and physical memory. The class is versatile and can be used for a number of purposes. Consequently, there are also a number of ways to construct an IOMemoryDescriptor
. A common way is to use the withAddressRange()
method, as follows.
static IOMemoryDescriptor* withAddressRange(mach_vm_address_t address,
mach_vm_size_t length, IOOptionBits options,
task_t task);
address
, is the start address of the memory buffer the descriptor should operate on.length
argument is the number of bytes of the buffer pointed to by address
. The task
argument specifies the task, which owns the virtual memory.options
argument specifies the direction of the descriptor in the event that it is used for I/O transfers. It may affect the behaviour of prepare()
and complete(). The following flags are possible:
kernel_task
, which is a global variable pointing to the task_t
structure for the kernel.The options
flags indicate the direction of an I/O transfer and may be used to determine if it is necessary to flush processor caches to ensure cache coherency.
If the descriptor is to be used for an I/O transfer you must first call its prepare()
method, which will do the following:
Caution Calls to prepare()
must be balanced with a call to complete()
. Care must also be taken not to call complete()
unless prepare()
was called first.
The prepare()
method is not thread safe. However, it is valid to call prepare()
multiple times, but you must then call complete()
the same number of times. Calling the descriptors' release()
method will not undo the effects of prepare()
or call complete()
for you, so complete()
must be called before calling release()
. If the descriptor is mapped into an address space, it will be unmapped automatically on release()
. IOMemoryDescriptor
can also be used to describe other types of memory, such as physical addresses. With physical addresses, the prepare()
and complete()
methods do nothing, but return successfully. Moreover, a physical memory descriptor is not associated with a task. The static member method withPhysicalAddress()
can be used to construct an IOMemoryDescriptor
for a physical segment, as in the following.
static IOMemoryDescriptor* withPhysicalAddress(IOPhysicalAddress address,
IOByteCount withLength, IODirection withDirection);
The IOBufferMemoryDescriptor
is a subclass of IOMemoryDescriptor
, but unlike its super class, it also allocates memory. It is currently the preferred way of allocating memory intended to be mapped to user space or for performing device I/O from a kernel-allocated buffer. However, the allocation method used internally depends on the size of the request and the options passed during construction. The IOBufferMemoryDescriptor
is also the preferred way for obtaining physically contiguous memory. IOBufferMemoryDescriptors
can be allocated by the static factory method inTaskWithOptions()
or inTaskWithPhysicalMask()
, as follows.
static IOBufferMemoryDescriptor* inTaskWithOptions(
task_t inTask,
IOOptionBits options,
vm_size_t capacity,
vm_offset_t alignment = 1);
static IOBufferMemoryDescriptor* inTaskWithPhysicalMask(
task_t inTask,
IOOptionBits options,
mach_vm_size_t capacity,
mach_vm_address_t physicalMask);
The inTask
argument specifies which task the memory should be mapped to. For a kernel buffer, this should be set to kernel_task
. If you specify another task identifier, the memory will be allocated and reachable in that task's address space. In addition to the flags and options available to IOMemoryDescriptor
, the following options
can be passed to control the allocation behavior.
kIOMemoryPhysicallyContiguous
allocates memory that is physically contiguous.kIOMemoryPageable
allocates memory that can be paged out. All memory is non-pageable by default.kIOMemoryPurgeable
applies only to pageable memory. If this option is specified, the memory pages can be discarded instead of paged out.kIOMemoryKernelUserShared
should be specified if the memory will be mapped into the kernel and a user space task. It ensures memory will be page-aligned.The second way to construct an IOBufferMemoryDescriptor
is via the inTaskWithPhysicalMask()
, which allows one to specify a bit mask used to restrict the physical address range of the buffer. This is mainly useful when allocating memory for DMA for a device unable to access certain address ranges. For example, some older devices may be unable to access physical memory over 32 bits.
It is generally frowned upon to request physically contiguous memory, particularly after the system has booted, as the memory becomes fragmented quickly. This would make it difficult to find free contiguous buffers, particularly larger ones. Requesting contiguous memory may also result in some memory being paged out to handle the request, which can take a long time. Hardware devices generally support scatter/gather operations, where multiple smaller buffers are chained together in a list and passed to the device, which then reads the list to work out where in physical memory to find its data. Thus, contiguous memory is often unnecessary.
Just like the IOMalloc()
family of functions, IOBufferMemoryDescriptor
may sleep, so it should not be called from interrupt contexts or while holding simple locks. In fact, IOBufferMemoryDescriptor
uses IOMalloc()
and IOMallocAligned()
internally to allocate memory.
IOMemoryDescriptor
has a number of other related subclasses, as follows.
IODeviceMemory
is used to describe a range of memory mapped from a device.IOMultiMemoryDescriptor
can be used to represent a larger contiguous buffer consisting of smaller IOMemoryDescriptor
objects.18.119.213.87