Allocating and sharing memory with user space from an I/O Kit driver

Q: My I/O Kit kernel driver maps memory to user space using IOMemoryDescriptor::createMappingInTask. But createMappingInTask fails and returns NULL. How should a driver share memory with a user process?

A: IOMemoryDescriptor::createMappingInTask returns NULL because the buffer could not be mapped as requested. There are a couple of reasons why this can happen.

I/O Kit uses the kernel zalloc pool as its small malloc resource. This gives driver writers fast allocations for local use. This is very important for new operators and short-lived buffers.

The problem arises when a driver writer tries to share memory with user space, forgetting that the zone allocator is designed for temporary and very private allocations. Under the covers, I/O Kit allocators such as IOMalloc, IOMallocAligned, and IOMallocContiguous may use this zone allocator to improve the performance of frequent, small allocations.

IMPORTANT: The kernel zalloc pool is of a limited fixed size and is shared by all code running in the kernel. If your code makes heavy use ofIOMalloc, it is possible to exhaust this pool and panic the kernel.

Also, since mapping is a virtual memory concept, maps are always sized to an integral number of pages.

Note: IOMemoryDescriptor::createMappingInTask was added in Mac OS X 10.5 Leopard. The equivalent function on older versions of Mac OS X was IOMemoryDescriptor::map which has been deprecated.

The preferred way to allocate kernel memory for sharing is to allocate the buffer and memory descriptor together using IOBufferMemoryDescriptor as shown in Listing 1.

Listing 1: Use of IOBufferMemoryDescriptor.

IOBufferMemoryDescriptor* memDesc;

memDesc = IOBufferMemoryDescriptor::withOptions(
              kIODirectionOutIn | kIOMemoryKernelUserShared,
              alloc_bytes, page_size );

Note: The size of a page should not be hard-coded; use the kernel global variable page_size instead.

The kIOMemoryKernelUserShared option ensures that the buffer is mapped to a page boundary.

A less-preferable option is to request an integral number of pages from IOMallocAligned or IOMallocContiguous. In that case, either of these interfaces will return a buffer capable of being shared.

IMPORTANT: Do not use IOMalloc to acquire memory you ultimately intend to share.

Once you've allocated your buffer using either of these techniques, it can be shared with user space using IOMemoryDescriptor::createMappingInTask.

It is strongly recommended that you create buffers shared between the kernel and user space from the kernel. However, there are cases where that isn't practical, such as a driver that reads from or writes to application-created buffers. In that situation, createMappingInTask can fail for another reason. createMappingInTask can only create mappings for memory that is described by a single VM object. However, a memory range allocated in a typical fashion using malloc-based functions might be described by more than one VM object.

To allocate memory in user space that is guaranteed to be described by only a single VM object, use anonymous mmap as shown in Listing 2.

Listing 2: Use of anonymous mmap.

#include <sys/mman.h>

size_t		bigBufferLen;
uint8_t		*bigBuffer;

bigBufferLen = 54321;

// Use anonymous mmap to ensure we get a single VM object.
bigBuffer = (uint8_t *) mmap(NULL, bigBufferLen, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0);
if (bigBuffer == MAP_FAILED) {
    perror("mmap() call error:");
}
else {
    // Success.
    printf("buffer is created @ %p\n", bigBuffer);
}

Document Revision History

Date Notes
2008-10-13 Modernized content and and made editorial changes.
2002-09-13 Describes the preferred technique for allocating and sharing buffers in an I/O Kit kernel driver.

Posted: 2008-10-13


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.