Sharing memory using Mach part of OS X

This passage continues the previous post about low level microkernel interface provided by Mac OS X, and also all tests are on a late-2011 Mac Book Pro with a Mac OS X 10.7.4 system. This time is mainly about sharing memory.

For sharing memory, proper synchronization method should be applied. Here I chose the semaphore provided by Mach (declared in <mach/semaphore.h>). The interface is rather straight forward: semaphore_create, semaphore_signal, semaphore_wait. The only thing may need to explain is the sync_policy parameter in semaphore_create, as far as I can see, the only one supported is SYNC_POLICY_FIFO, which I think is OK for most synchronization and fast.

The first way is to use vm_inherit. This call sets the specified pages’ inheritance attribute: NONE, COPY, SHARE. If it is set to NONE, the pages involved will not be inherited by the child task. If it is set to SHARE, the pages will be shared by children. COPY is the default, and is aligned with Unix fork. OpenBSD also has minherit syscall, which is the same in semantic meaning and OS X also provides it for compability to do the same work as vm_inherit.

Another shared memory facility involves a memory_entry which I had not seen elsewhere before. They are not mentioned in old Mach documents. In these documents, vm_inherit is the only way to share memory without using a pager. Users can ask the pager to provide a ‘memory object’ and map (vm_map) to the task vm space. However when I was consulting <mach/vm_map.h> I found the type used in vm_map is a mem_entry_name_port_t. After several confirmation it may be a creation by Apple. For usage, I found an example usage: the ‘commpage’ machanism used by OS X.

Also in the system, two set of the API co-exist. The one set is only prefixed with vm_, the other set is prefixed with mach_vm_, mach_vm_ version is always 64bit(uint64_t), vm_ version is 32bit(int in Mach-O i386) or 64bit(long in Mach-O x86_64). For this topic, I have prepared a better sample code (compared to the code provided by previous post). You can access it at this gist.

Firstly I prepare memory using mach_vm_allocate and then call mach_make_memory_entry_64 to create a memory entry with allocated area and create a semaphore. Next, name the memory entry and semaphore via bootstrap server (deprecated, for sample purpose). Fork the child and use mach_vm_map to map the memory entry in the child process. Now compare the memory inherited via fork (COW!) and the memory shared explictly. Then I try to verify the writing. Finally I verify whether the parent process can see the modification.

[UPDATE] Initializing of the address parameter is required, e.g., in the sample code:

mach_vm_address_t address = (mach_vm_address_t)main;

mach_vm_address_t map_address = (mach_vm_address_t)main;

The problem is that the VM space is much smaller than the 64-bit unsigned integer space (about 50 bits in a 64-bit process). If a large integer is found in address, the kernel will fail with no space since the kernel tries to find the first available address which is larger or equal to address.

ACKNOWLEDGEMENT

Sorry for long code :). Feel free to comment. Names or trademarks are owned by coresponding author(s)/orgnazition(s). Use of these names with best regards and wishes.

Yuuko PrZhu 26 August 2012
blog comments powered by Disqus