Ports

Declared in: be/kernel/OS.h

Library: libroot.so


Overview

A port is a system-wide message repository into which a thread can copy a buffer of data, and from which some other thread can then retrieve the buffer. This repository is implemented as a first-in/first-out message queue: A port stores its messages in the order in which they're received, and it relinquishes them in the order in which they're stored. Each port has its own message queue.

There are other ways to send data between threads. Most notably, the data-sending and -receiving mechanism provided by the send_data() and receive_data() functions can also transmit data between threads. But note these differences between using a port and using the send_data()/receive_data() functions:

Ports are largely subsumed by the Application Kit's BMessage class (and relatives). The two features of ports that you can't get at the BMessage level are:

For most applications, these are inessential additions.


Creating a Port

A port is represented by a unique, system-wide port_id number (a positive 32-bit integer). The create_port() function creates a new port and assigns it a port_id number. Although ports are accessible to all threads, the port_id numbers aren't disseminated by the operating system; if you create a port and want some other thread to be able to write to or read from it, you have to broadcast the port_id number to that thread. Typically, ports are used within a single team. The easiest way to broadcast a port_id number to the threads in a team is to declare it as a global variable.

A port is owned by the team in which it was created. When a team dies (when all its threads are killed, by whatever hand), the ports that belong to the team are deleted. A team can bestow ownership of its ports to some other team through the set_port_owner() function.

If you want explicitly get rid of a port, you can call delete_port(). You can delete any port, not just those that are owned by the team of the calling thread.


The Message Queue: Reading and Writing Port Messages

The length of a port's message queue--the number of messages that it can hold at a time--is set when the port is created.

The functions write_port() and read_port() manipulate a port's message queue: write_port() places a message at the tail of the port's message queue; read_port() removes the message at the head of the queue and returns it the caller.write_port() blocks if the queue is full; it returns when room is made in the queue by an invocation of read_port(). Similarly, if the queue is empty, read_port() blocks until write_port() is called. When a thread is waiting in a write_port() or read_port() call, its state is B_THREAD_SEM_WAIT (it's waiting to acquire a system-defined, port-specific semaphore).

You can provide a timeout for your port-writing and port-reading operations by using the "full-blown" functions write_port_etc() and read_port_etc(). By supplying a timeout, you can ensure that your port operations won't block forever.

Although each port has its own message queue, all ports share a global "queue slot" pool--there are only so many message queue slots that can be used by all ports taken cumulatively. If too many port queues are allowed to fill up, the slot pool will drain, which will cause write_port() calls on less-than-full ports to block. To avoid this situation, you should make sure that your write_port() and read_port() calls are reasonably balanced.

The write_port() and read_port() functions are the only way to traverse a port's message queue. There's no notion of "peeking" at the queue's unread messages, or of erasing messages that are in the queue.


Port Messages

A port message--the data that's sent through a port--consists of a "message code" and a "message buffer." Either of these elements can be used however you like, but they're intended to fit these purposes:

The message that you pass to write_port() is copied into the port. After write_port() returns, you may free the message data without affecting the copy that the port holds.

When you read a port, you have to supply a buffer into which the port mechanism can copy the message. If the buffer that you supply isn't large enough to accommodate the message, the unread portion will be lost--the next call to read_port() won't finish reading the message.

You typically allocate the buffer that you pass to read_port() by first calling port_buffer_size(), as shown below:

   char *buf = NULL;
   ssize_t size;
   int32 code;
   
   /* We'll assume that my_port is valid. 
    * port_buffer_size() will block until a message shows up.
    */
   if ((size = port_buffer_size(my_port)) < B_OK) 
      /* Handle the error */
   
   if (size > 0)
      buf = (char *)malloc(size);
   
   if (buf) {
      /* Now we can read the buffer. */
      if (read_port(my_port, &code, (void *)buf, size) < B_OK)
      /* Handle the error */

Obviously, there's a race condition (in the example) between port_buffer_size() and the subsequent read_port() call--some other thread could read the port in the interim. If you're going to use port_buffer_size() as shown in the example, you shouldn't have more than one thread reading the port at a time.

As stated in the example, port_buffer_size() blocks until a message shows up. If you don't want to (potentially) block forever, you should use the port_buffer_size_etc() version of the function. As with the other ...etc() functions, port_buffer_size_etc() provides a timeout option.


Port Functions


create_port()

      port_id create_port(int32 queue_length, const char *name)

Creates a new port and returns its port_id number. The port's name is set to name and the length of its message queue is set to queue_length. Neither the name nor the queue length can be changed once they're set. The name shouldn't exceed B_OS_NAME_LENGTH (32) characters.

In setting the length of a port's message queue, you're telling it how many messages it can hold at a time. When the queue is filled--when it's holding queue_length messages--subsequent invocations of write_port() (on that port) block until room is made in the queue (through calls to read_port()) for the additional messages. Once the queue length is set by create_port(), it can't be changed.

This function sets the owner of the port to be the team of the calling thread. Ownership can subsequently be transferred through the set_port_owner() function. When a port's owner dies (when all the threads in the team are dead), the port is automatically deleted. If you want to delete a port prior to its owner's death, use the delete_port() function.

RETURN CODES


delete_port()

      status_t delete_port(port_id port)

Deletes the given port. The port's message queue doesn't have to be empty--you can delete a port that's holding unread messages. Threads that are blocked in read_port() or write_port() calls on the port are automatically unblocked (and return B_BAD_SEM_ID).

The thread that calls delete_port() doesn't have to be a member of the team that owns the port; any thread can delete any port.

RETURN CODES


find_port()

      port_id find_port(const char *port_name)

Returns the port_id of the named port. port_name should be no longer than 32 characters (B_OS_NAME_LENGTH).

RETURN CODES


get_port_info(), get_next_port_info(), port_info

      status_t get_port_info(port_id port, port_info *info)
      status_t get_next_port_info(team_id team, 
         uint32 *cookie, 
         port_info *info)
      struct {} port_info

Copies information about a particular port into the port_info structure designated by info. The first version of the function designates the port directly, by port_id.

The get_next_port_info() version lets you step through the list of a team's ports through iterated calls on the function. The team argument identifies the team you want to look at; a team value of 0 means the team of the calling thread. The cookie argument is a placemark; you set it to 0 on your first call, and let the function do the rest. The function returns B_BAD_VALUE when there are no more ports to visit:

   /* Get the port_info for every port in this team. */
   port_info info;
   int32 cookie = 0;
   
   while (get_next_port_info(0, &cookie, &info) == B_OK)
      ...

The port_info structure is defined as:

      typedef struct port_info {
            port_id port;
            team_id team;
            char name[B_OS_NAME_LENGTH];
            int32 capacity;
            int32 queue_count;
            int32 total_count;
         } port_info

The structure's fields are:

Note that the total_count number doesn't include the messages that are currently in the queue.

The information in the port_info structure is guaranteed to be internally consistent, but the structure as a whole should be consider to be out-of-date as soon as you receive it. It provides a picture of a port as it exists just before the info-retrieving function returns.

RETURN CODES


port_buffer_size(), port_buffer_size_etc()

      ssize_t port_buffer_size(port_id port)
      ssize_t port_buffer_size_etc(port_id port, 
         uint32 flags, 
         bigtime_t timeout)

These functions return the length (in bytes) of the message buffer that's at the head of port's message queue. You call this function in order to allocate a sufficiently large buffer in which to retrieve the message data.

The port_buffer_size() function blocks if the port is currently empty. It unblocks when a write_port() call gives this function a buffer to measure (even if the buffer is 0 bytes long), or when the port is deleted.

The port_buffer_size_etc() function lets you set a limit on the amount of time the function will wait for a message to show up. To set the limit, you pass B_TIMEOUT as the flags argument, and set timeout to the amount of time, in microseconds, that you're willing to wait.

RETURN CODES

See also: read_port()


port_count()

      int32 port_count(port_id port)

Returns the number of messages that are currently in port's message queue. This is the number of messages that have been written to the port through calls to write_port() but that haven't yet been picked up through corresponding read_port() calls.

This function is provided mostly as a convenience and a semi-accurate debugging tool. The value that it returns is inherently undependable: There's no guarantee that additional read_port() or write_port() calls won't change the count as this function is returning.

RETURN CODES

See also: get_port_info()


read_port(), read_port_etc()

      ssize_t read_port(port_id port, 
         int32 *msg_code, 
         void *msg_buffer,
         size_t buffer_size)
      ssize_t read_port_etc(port_id port, 
         int32 *msg_code, 
         void *msg_buffer,
         size_t buffer_size, 
         uint32 flags,
         bigtime_t timeout)

These functions remove the message at the head of port's message queue and copy the messages's contents into the msg_code and msg_buffer arguments. The size of the msg_buffer buffer, in bytes, is given by buffer_size. It's up to the caller to ensure that the message buffer is large enough to accommodate the message that's being read. If you want a hint about the message's size, you should call port_buffer_size() before calling this function.

If port's message queue is empty when you call read_port(), the function will block. It returns when some other thread writes a message to the port through write_port(). A blocked read is also unblocked if the port is deleted.

The read_port_etc() function lets you set a limit on the amount of time the function will wait for a message to show up. To set the limit, you pass B_TIMEOUT as the flags argument, and set timeout to the amount of time, in microseconds, that you're willing to wait.

RETURN CODES

A successful call returns the number of bytes that were written into the msg_buffer argument.

See also: write_port(), port_buffer_size()


set_port_owner()

      status_t set_port_owner(port_id port, team_id team)

Transfers ownership of the designated port to team. A port can only be owned by one team at a time; by setting a port's owner, you remove it from its current owner.

There are no restrictions on who can own a port, or on who can transfer ownership. In other words, the thread that calls set_port_owner() needn't be part of the team that currently owns the port, nor must you only assign ports to the team that owns the calling thread (although these two are the most likely scenarios).

Port ownership is meaningful for one reason: When a team dies (when all its threads are dead), the ports that are owned by that team are freed. Ownership, otherwise, has no significance--it carries no special privileges or obligations.

To discover a port's owner, use the get_port_info() function.

RETURN CODES

See also: get_port_info()


write_port(), write_port_etc()

      status_t write_port(port_id port, 
         int32 msg_code, 
         void *msg_buffer, 
         size_t buffer_size)
      status_t write_port_etc(port_id port, 
         int32 msg_code, 
         void *msg_buffer, 
         size_t buffer_size, 
         uint32 flags, 
         bigtime_t timeout)

These functions place a message at the tail of port's message queue. The message consists of msg_code and msg_buffer:

If the port's queue is full when you call write_port(), the function will block. It returns when a read_port() call frees a slot in the queue for the new message. A blocked write_port() will also return if the target port is deleted.

The write_port_etc() function lets you set a limit on the amount of time the function will wait for a free queue slot. To set the limit, you pass B_TIMEOUT as the flags argument, and set timeout to the amount of time, in microseconds, that you're willing to wait.

RETURN CODES

See also: read_port()






The Be Book, in lovely HTML, for BeOS Release 4.

Copyright © 1998 Be, Inc. All rights reserved.

Last modified December 14, 1998.