Mach ports are capabilities, and are also essentially similar to UNIX pipes. They are unforgeable communication channels, implemented by kernel queues.
Each port has associated with it one receive right and one or more send rights and send-once rights. That is, there is one receiver and one or more senders -- a unidirectional communication channel. Only with the corresponding port right, access to a port is possible; this is enforced by Mach.
The kernel queue can hold a number of messages. Once the queue is full, the send blocks until there is space to enqueue the message (this is interruptible via a timeout mechanism).
A receive right designates a queue and authorizes the holder to dequeue messages from the queue, and to create send and send-once rights.
Send and send-once rights designate a queue and authorize the hold to enqueue messages (in the case of a send-once right, a single message). Enqueuing a message is equivalent to ?invoke a capability.
Ports are automatically destroyed when there is no associated port right to them.
Mach knows what port rights belong to each task, but threads that running in the context of a task refer to ports by means of send and receive rights that are named using local port names. These port names are plain integers, like UNIX file descriptors. Only these local names can be used by threads for invoking operations on ports, threads do not deal with port rights directly.
For that, each task has associated with it a port address space, or port name space. All ports are addressed via this table. Each task thus has its own private naming context for port rights.
So, the picture is that after obtaining a port send right, the client uses a port name to send messages to the port, or exactly one message if it's a send-once right. These messages are (probably) queued and when the server task tries to receive messages by having a thread use its port receive right, it gets the message(s). This is called IPC.
Port rights themselves can be ?delegated in a message, too. When the receiver dequeues the message, the right is made available to it.
The delivery of messages is reliable and strictly ordered. When a thread sends messages 1 and 2, it is guaranteed that the receiving task will catch them in the same order. Of course, there can be intermediate messages that are sent by other threads.
Ports are objects that are implemented by the kernel, and they are kernel-protected resources: they are unforgeable, and there is no way for a task to do anything with a port unless it have corresponding port right.
Due to this, ports are globally unique. This makes them ideal for constituting system-wide object references. (Fruther reading: Wikipedia, object-capability model.) For example, the RPC system as used by the GNU Hurd works by invoking methods on such object references. The available methods are defined in interface files, and are processes by the MIG tool.
Invoking an operation on a port does not transfer the current execution control to the receiver, but instead is an asynchronous operation. For this, and especially in a RPC system, the sender may include a reply port using a send-once right, and synchronize (block) on that one.
Port Set
A thread can only block receiving on a single port. To work around this, the concept of a port set was introduced. A receive right can be added to (at most) one port set. These port sets look like port receive rights, but cannot be passed to other tasks, and there are additional operations for adding and removing port receive rights.
When a server process' thread receives from a port set, it dequeues exactly one message from any of the ports that has a message available in its queue.
This concept of port sets is also the facility that makes convenient
implementation of UNIX's select
system call possible.