This passage is mainly about low level microkernel interface provided by Mac OS X,
and all tests are on a late-2011 Mac Book Pro with a Mac OS X 10.7.4 system.
Apple generally discourages users using these raw interfaces since they may
result mess crashes, security problems, resource leaks, etc and says users should
only use mach messages for IPC. Foundation.framework
and CoreFoundation.framework
have their coresponding wrappers, namely NSMachPort
, NSMessagePort
,
CFMachPort
, CFMessagePort
, which may be preferred.
Since Apple constantly changes the micokernel interface, my discussion may totally
unappliable to you. However some general idea may be applied also. I think the best
way is to dive in XNU kernel source, launchd source, and refer to old Mach Manual Pages
($KERNEL_SOURCE_TREE/osfmk/man/
, outdated but general usages are described), the
Mach 3 Server Writer’s Guide (may be hard to understand if you are not familiar
with operating system design principles), Apple’s Kernel Programming Guide: Mach Overview
(also not refering current, some features promised by the guide are not implemented (at least
not exposed as public C API) even today) and/or CMU’s orginal mach document, GNU Mach document
for principles, ideas.
Now I assume readers reaching here have basic mach port and mach messaging knowledge.
In mach messaging one important problem is how other programmers provide their ports’ send or
send once right to other tasks. Thus the system provides bootstrap server for this purpose (
netname server in Server Writer’s Guide). Bootstrap subsystem is declared in <servers/bootstrap.h>
.
The service is now provided by launchd (pid 1) and the header file includes several deprecated
declaration, which upsets me for some time. Finally I have managed to get the full picture.
kern_return_t bootstrap_check_in(
mach_port_t bp,
const name_t service_name,
mach_port_t *sp);
Use bootstrap_port
external variable as bp, your favorite string as service_name
,
this API directly gives you a port with receive right in sp. The header document
says registering the name using launchd.plist with launchd is required, which is not
true. The full story is, if call this from a normal unix process executed from terminal,
launchd treats this process as a ‘legacy’ task, and dynamically bind the name with the port.
Currently it also does the binding for not ‘legacy’ job (Cocoa app?) but emits a warning
which “deprecated the usage due to performance reasons”.
kern_return_t bootstrap_look_up(
mach_port_t bp,
const name_t service_name,
mach_port_t *sp);
Use this API to receive a port with send right. Mach allows one task hold multiple reference to the same send right, but mach limits total send right reference count and dead port reference count a task can hold, so pay attention to right reference leak.
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_5
kern_return_t
bootstrap_register(mach_port_t bp, name_t service_name, mach_port_t sp);
This API is deprecated since launchd was introduced. It requires the user provides a
send right of sp, which is very convenient for transfering semaphore_t
, lockset_t
,
even task_t
and more. The header documents said please use mach_msg
to send them
directly. Now take this scene into account: process A gives process B a send right,
then B call bootstrap_register
to make the send right available to others (by default
this will be available to any process with the same UID). This is a common problem
need to be deeply considered by server developers. For IPC between co-operative processes,
I think bootstrap_subset
routine is suitable for namespace isolation here.
kern_return_t bootstrap_subset(
mach_port_t bp,
mach_port_t requestor_port,
mach_port_t *subset_port);
The first argument is still the bootstrap port, the second argument is the task port (
send right!), which in most case , is mach_task_self()
, the last argument is the returned
(created) subset port. Names registered with subset port is only available in the subset and
its subset while names registered with original bootstrap port is available to the subset
also. Since only root user is allowed to use bootstrap_parent
to access given bootstrap port’s
parent (documented in the header), a normal task is not able to change the subset by acrossing
the parent. The system uses this technique to isolate users to there own subset.
After creating a subset, set the new subset as the bootstrap_port
:
task_set_bootstrap_port(task, port)
In Unix, the common way to create a new process is fork()
, so I have to know after fork
,
how mach ports are inherited. After examined XNU kernel source, I know the forked process
only gets a task special port (mach_task_self()
) and inherits only task special ports including
bootstrap port and some other ports (Ref. <mach/task_special_ports.h>
, not sure whether they are
all set and set). For the detail, code is listed below.
/*
API Illustrating only.
*/
#include <servers/bootstrap.h>
#include <unistd.h>
#include <mach/mach_interface.h>
/*
extern mach_port_t bootstrap_port;
*/
int main()
{
pid_t pid;
mach_port_t subset_port, service;
/* create a subset_port */
bootstrap_subset(bootstrap_port, mach_task_self(), &subset_port);
/* set the task's bootstrap_port at kernel */
task_set_bootstrap_port(mach_task_self(), subset_port);
/* now the external variable `bootstrap_port' does not refer to the real bootstrap port registered
at kernel, it refers the original bootstrap port. Kernel routines does not know these external
variables.
*/
/*
Ref. fork_mach_init() in /libsyscall/mach/mach_init.c in XNU source
This routine called explicitly by _cthread_fork_child(), which calls mach_init_ports()
For new exectables(exec family), mach_init() (also in that file) is called by static images or by dyld.
*/
pid = fork();
/* At mach side, only task special ports are created(mach_task_self)/copyed(bootstrap, host...) */
if (pid == 0) {
bootstrap_check_in(bootstrap_port, "CHILD", &service);
/* Results in subset */
} else {
bootstrap_check_in(bootstrap_port, "PARENT", &service);
/* Results in the original set */
/* explicitly add one in subset */
bootstrap_check_in(subset_port, "PARENT-1", &service);
/*
explicitly set the external variable,
bootstrap_port = subset_port;
is also sufficient.
*/
task_get_bootstrap_port(mach_task_self(), &bootstrap_port);
bootstrap_check_in(bootstrap_port, "PARENT-2", &service);
/* now also subset */
/*
WANRING: The above code illustrate a port-right user reference leak
*/
}
pause();
}
To check the results, I placed pause()
at the end of the code.
Use launchctl
utility to check the results.
launchctl bslist
which lists all mach ports binded with name available in the system. While
sudo launchctl bstree
prints the Mach bootstrap hierarchy tree. In the system tree, there are
some subsets, noticably, the root set System/
, the
com.apple.launchd.peruser.$UID (Per-user)/
subset. Executes the sample
code and uses the bstree
command to verify what I said in the code.
At last
launchctl bsexec PID command ...
is used for userspace explictly start a command using the same bootstrap subset
as process idetified by PID and it requires privileges for task_for_pid()
call. This is another hard topic and can be found from web and mainly used
by administrators and debuggers. As previously noted, bootstrap subset is
for namespace isolation, this level of security is enough.
In summary, this post roughly discussed bootstrap server, bootstrap subset and how to use bootstrap server to initiate mach communication in Mac OS X. I will post another article soon about my more experience in exploring OS X’s Mach which mainly about sharing memory.
Sorry for another long post. 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 25 August 2012