Section 9.5. Using Mach IPC


9.5. Using Mach IPC

We will now look at some examples of using Mach IPC. Besides serving as programming examples, these will also help illustrate the working of several interesting aspects of Mach IPC, for example, out-of-line transfers, interposition of port rights, port sets, and dead names.

9.5.1. A Simple Client-Server Example

In this client-server example, the client will send an integer value as inline data in a Mach message to the server, which will compute the factorial of the integer and send the result in a reply message to the client. We will use this example to demonstrate how to send and receive Mach messages. The next few examples assume familiarity with this example.

Figure 919 shows the common header file shared between the client and the server. Note the data type for send and receive buffers: We account for a message trailer on the receive side.

Figure 919. Common header file for the simple IPC client-server example

// simple_ipc_common.h #ifndef _SIMPLE_IPC_COMMON_H_ #define _SIMPLE_IPC_COMMON_H_ #include <mach/mach.h> #include <servers/bootstrap.h> #define SERVICE_NAME   "com.osxbook.FactorialServer" #define DEFAULT_MSG_ID 400 #define EXIT_ON_MACH_ERROR(msg, retval, success_retval) \     if (kr != success_retval) { mach_error(msg ":" , kr); exit((retval)); } typedef struct {     mach_msg_header_t header;     int               data; } msg_format_send_t; typedef struct {     mach_msg_header_theader;     int                data;     mach_msg_trailer_t trailer; } msg_format_recv_t; #endif // _SIMPLE_IPC_COMMON_H_

Figure 920 shows the code for the simple IPC server. To become a Mach server that provides a named service, our program creates and checks in that service. After that, it goes into the idiomatic server loop consisting of the receive message, process message, and send reply operations.

Figure 920. Source for the simple IPC server

// simple_ipc_server.c #include <stdio.h> #include <stdlib.h> #include "simple_ipc_common.h" int factorial(int n) {     if (n < 1)         return 1;     else return n * factorial(n - 1); } int main(int argc, char **argv) {     kern_return_t      kr;     msg_format_recv_t  recv_msg;     msg_format_send_t  send_msg;     mach_msg_header_t *recv_hdr, *send_hdr;     mach_port_t        server_port;     kr = bootstrap_create_service(bootstrap_port, SERVICE_NAME, &server_port);     EXIT_ON_MACH_ERROR("bootstrap_create_service", kr, BOOTSTRAP_SUCCESS);     kr = bootstrap_check_in(bootstrap_port, SERVICE_NAME, &server_port);     EXIT_ON_MACH_ERROR("bootstrap_check_in", kr, BOOTSTRAP_SUCCESS);     printf("server_port = %d\n", server_port);     for (;;) { // server loop         // receive message         recv_hdr                  = &(recv_msg.header);         recv_hdr->msgh_local_port = server_port;         recv_hdr->msgh_size       = sizeof(recv_msg);         kr = mach_msg(recv_hdr,              // message buffer                       MACH_RCV_MSG,          // option indicating receive                       0,                     // send size                       recv_hdr->msgh_size,   // size of header + body                       server_port,           // receive name                       MACH_MSG_TIMEOUT_NONE, // no timeout, wait forever                       MACH_PORT_NULL);       // no notification port         EXIT_ON_MACH_ERROR("mach_msg(recv)", kr, MACH_MSG_SUCCESS);         printf("recv data = %d, id = %d, local_port = %d, remote_port = %d\n",                recv_msg.data, recv_hdr->msgh_id,                recv_hdr->msgh_local_port, recv_hdr->msgh_remote_port);         // process message and prepare reply         send_hdr                   = &(send_msg.header);         send_hdr->msgh_bits        = MACH_MSGH_BITS_LOCAL(recv_hdr->msgh_bits);         send_hdr->msgh_size        = sizeof(send_msg);         send_hdr->msgh_local_port  = MACH_PORT_NULL;         send_hdr->msgh_remote_port = recv_hdr->msgh_remote_port;         send_hdr->msgh_id          = recv_hdr->msgh_id;         send_msg.data              = factorial(recv_msg.data);         // send message         kr = mach_msg(send_hdr,              // message buffer                       MACH_SEND_MSG,         // option indicating send                       send_hdr->msgh_size,   // size of header + body                       0,                     // receive limit                       MACH_PORT_NULL,        // receive name                       MACH_MSG_TIMEOUT_NONE, // no timeout, wait forever                       MACH_PORT_NULL);       // no notification port         EXIT_ON_MACH_ERROR("mach_msg(send)", kr, MACH_MSG_SUCCESS);         printf("reply sent\n");     }     exit(0); }

Figure 921 shows the source for the simple IPC client. Note that we use the MACH_MSGH_BITS() macro to set the value of the msgh_bits field in the request message. The value of the remote bits is MACH_MSG_TYPE_COPY_SEND, which means the message carries a caller-provided send right (server_port). The value of the local bits is MACH_MSG_TYPE_MAKE_SEND, which means that a send right is created from the caller-supplied receive right (client_port) and carried in the message.

Figure 921. Source for the simple IPC client

// simple_ipc_client.c #include <stdio.h> #include <stdlib.h> #include "simple_ipc_common.h" int main(int argc, char **argv) {     kern_return_t      kr;     msg_format_recv_t  recv_msg;     msg_format_send_t  send_msg;     mach_msg_header_t *recv_hdr, *send_hdr;     mach_port_t        client_port, server_port;     kr = bootstrap_look_up(bootstrap_port, SERVICE_NAME, &server_port);     EXIT_ON_MACH_ERROR("bootstrap_look_up", kr, BOOTSTRAP_SUCCESS);     kr = mach_port_allocate(mach_task_self(),        // our task is acquiring                             MACH_PORT_RIGHT_RECEIVE, // a new receive right                             &client_port);           // with this name     EXIT_ON_MACH_ERROR("mach_port_allocate", kr, KERN_SUCCESS);     printf("client_port = %d, server_port = %d\n", client_port, server_port);     // prepare request     send_hdr                   = &(send_msg.header);     send_hdr->msgh_bits        = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, \                                                 MACH_MSG_TYPE_MAKE_SEND);     send_hdr->msgh_size        = sizeof(send_msg);     send_hdr->msgh_remote_port = server_port;     send_hdr->msgh_local_port  = client_port;     send_hdr->msgh_reserved    = 0;     send_hdr->msgh_id          = DEFAULT_MSG_ID;     send_msg.data              = 0;     if (argc == 2)         send_msg.data = atoi(argv[1]);     if ((send_msg.data < 1) || (send_msg.data > 20))         send_msg.data = 1; // some sane default value     // send request     kr = mach_msg(send_hdr,              // message buffer                   MACH_SEND_MSG,         // option indicating send                   send_hdr->msgh_size,   // size of header + body                   0,                     // receive limit                   MACH_PORT_NULL,        // receive name                   MACH_MSG_TIMEOUT_NONE, // no timeout, wait forever                   MACH_PORT_NULL);       // no notification port     EXIT_ON_MACH_ERROR("mach_msg(send)", kr, MACH_MSG_SUCCESS);     do { // receive reply         recv_hdr                   = &(recv_msg.header);         recv_hdr->msgh_remote_port = server_port;         recv_hdr->msgh_local_port  = client_port;         recv_hdr->msgh_size        = sizeof(recv_msg);         kr = mach_msg(recv_hdr,              // message buffer                       MACH_RCV_MSG,          // option indicating receive                       0,                     // send size                       recv_hdr->msgh_size,   // size of header + body                       client_port,           // receive name                       MACH_MSG_TIMEOUT_NONE, // no timeout, wait forever                       MACH_PORT_NULL);       // no notification port         EXIT_ON_MACH_ERROR("mach_msg(recv)", kr, MACH_MSG_SUCCESS);         printf("%d\n", recv_msg.data);     } while (recv_hdr->msgh_id != DEFAULT_MSG_ID);     exit(0); }

Let us now test the simple IPC client-server example.

$ gcc -Wall -o simple_ipc_server simple_ipc_server.c $ gcc -Wall -o simple_ipc_client simple_ipc_client.c $ ./simple_ipc_server server_port = 3079                     # another shell                     $ ./simple_ipc_client 10 recv data = 10, id = 400, local_port = 3079, remote_port = 3843 reply sent                     client_port = 3843, server_port = 3079                     3628800


9.5.2. Dead Names

We came across the concept of dead names earlier in this chapter. When a port is destroyed, its receive right is deallocated,[9] causing the port's send rights to become invalid and turn into dead names. Dead names inherit references from the erstwhile send rights. Only when a dead name loses all its references does the port name become available for reuse. The mach_msg routines return an error when sending to a dead name. This way, a program can realize that a right it holds is dead and can then deallocate the dead name.

[9] When a port dies, all messages in its queue are destroyed.

Moreover, if the server wishes to be notified of a client's death earlier, it can use mach_port_request_notification() to request the kernel to send it a dead-name notification for the client's send-once right reply port. Conversely, if a server dies during an RPC, any send-once rights held by the server will be deallocated. Recall that send-once rights always result in a message. In this case, the kernel will use the send-once rights to send a notification message to the client.

kern_return_t mach_port_request_notification(     // task holding the right in question     ipc_space_t            task,     // name for the right in the task's IPC space     mach_port_name_t       name,     // type of notification desired     mach_msg_id_t          variant,     // used for avoiding race conditions for some notification types     mach_port_mscount_t    sync,     // send-once right to which notification will be sent     mach_port_send_once_t  notify,     // MACH_MSG_TYPE_MAKE_SEND_ONCE or MACH_MSG_TYPE_MOVE_SEND_ONCE     mach_msg_type_name_t   notify_right_type,     // previously registered send-once right     mach_port_send_once_t *previousp);


Table 91 shows the values that can be passed as the variant argument to mach_port_request_notification().

Table 91. Notifications That Can Be Requested from the Kernel

Notification Type

Description

MACH_NOTIFY_PORT_DELETED

A send or send-once right was deleted.

MACH_NOTIFY_PORT_DESTROYED

A receive right was (would have been) destroyed; instead of actually being destroyed, the right is sent in the notification.

MACH_NOTIFY_NO_SENDERS

A receive right has no existing send rights; this can be used for garbage collection of receive rights.

MACH_NOTIFY_SEND_ONCE

An existing send-once right died.

MACH_NOTIFY_DEAD_NAME

A send or send-once right died and became a dead name.


9.5.3. Port Sets

Mach allows ports to be grouped into port sets. A port set represents a queue that is a combination of the queues of its constituent ports. Listening on a port set is equivalent to listening on all members of the set concurrentlyanalogous to the select() system call. Specifically, a port set can be used by a thread to receive messages sent to any of the member ports. The receiving task knows which port the message was sent to because that information is specified in the message.

A given port may belong to at most one port set at a time. Moreover, while a port is a member of a port set, that port cannot be used to receive messages other than through the port set. The name of a port set is on par with the name of a portboth reside in the same namespace. However, unlike a port's name, a port set's name cannot be transferred in a message.

Figure 922 shows the skeleton for a server (based on our example from Section 9.5.1) that provides two services. It places the server ports for both services into a single port set, which it then uses to receive request messages.

Figure 922. Using a port set to receive request messages destined for multiple services

// port_set_ipc_server.c ... int main(int argc, char **argv) {     ...     mach_port_t        server_portset, server1_port, server2_port;     // allocate a port set     kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_PORT_SET,                             &server_portset);     // first service     kr = bootstrap_create_service(bootstrap_port, SERVICE1_NAME, &server1_port);     ...     kr = bootstrap_check_in(bootstrap_port, SERVICE1_NAME, &server1_port);     ...     // second service     kr = bootstrap_create_service(bootstrap_port, SERVICE2_NAME, &server2_port);     ...     kr = bootstrap_check_in(bootstrap_port, SERVICE2_NAME, &server2_port);     ...     // move right to the port set     kr = mach_port_move_member(mach_task_self(), server1_port, server_portset);     ...     // move right to the port set     kr = mach_port_move_member(mach_task_self(), server2_port, server_portset);     ...     for (;;) {         // receive message on the port set         kr = mach_msg(recv_hdr, ..., server_portset, ...);         ...         // determine target service and process         if (recv_hdr->msgh_local_port == server1_port) {             // processing for the first service         } else if (recv_hdr->msgh_local_port == server2_port) {             // processing for the second service         } else {             // unexpected!         }         // send reply     }     ... }

9.5.4. Interposition

Port rights can be added to or removed from a target task's IPC spaceeven without involving the target task. In particular, port rights can be interposed, which allows a task to transparently intercept messages sent to or sent by another task. This is how the netmsgserver program provides network transparency.

Another example is that of a debugger that can intercept all messages sent through a send right. Specifically, the debugger extracts a task's send right and inserts a send right for another port that the debugger owns. The mach_port_extract_right() and mach_port_insert_right() routines are used for this purpose. Thereafter, the debugger will receive messages sent by the taskit can examine and forward these messages to the extracted send right.

Figure 923 shows a program that demonstrates the use of mach_port_insert_right(). The program forks, after which the child task suspends itself. The parent acquires send rights to the host privileged port and calls mach_port_insert_right() to insert these rights into the child, giving the rights a specific name that is unused in the child's IPC spacesay, 0x1234. It then resumes the child, which uses the send rights named by 0x1234 to retrieve the number of processors on the system. Note that the parent process requires superuser privileges in this case, but the child process, which actually uses the host privileged port, does not.

Figure 923. Inserting port rights into an IPC space

// interpose.c #include <stdio.h> #include <unistd.h> #include <mach/mach.h> #define OUT_ON_MACH_ERROR(msg, retval) \     if (kr != KERN_SUCCESS) { mach_error(msg ":" , kr); goto out; } void print_processor_count(host_priv_t host_priv) {     kern_return_t          kr;     natural_t              processor_count = 0;     processor_port_array_t processor_list;     kr = host_processors(host_priv, &processor_list, &processor_count);     if (kr == KERN_SUCCESS)         printf("%d processors\n", processor_count);     else         mach_error("host_processors:", kr); } void childproc() {     printf("child suspending...\n");     (void)task_suspend(mach_task_self());     printf("child attempting to retrieve processor count...\n");     print_processor_count(0x1234); } void parentproc(pid_t child) {     kern_return_t kr;     task_t        child_task;     host_priv_t   host_priv;     // kludge: give child some time to run and suspend itself     sleep(1);     kr = task_for_pid(mach_task_self(), child, &child_task);     OUT_ON_MACH_ERROR("task_for_pid", kr);     kr = host_get_host_priv_port(mach_host_self(), &host_priv);     OUT_ON_MACH_ERROR("host_get_host_priv_port", kr);     kr = mach_port_insert_right(child_task, 0x1234, host_priv,                                 MACH_MSG_TYPE_MOVE_SEND);     if (kr != KERN_SUCCESS)         mach_error("mach_port_insert_right:", kr); out:     printf("resuming child...\n");     (void)task_resume(child_task); } int main(void) {     pid_t pid = fork();     if (pid == 0)         childproc();     else if (pid > 0)         parentproc(pid);     else         return 1;     return 0; } $ gcc -Wall -o interpose interpose.c $ sudo ./interpose child suspending... resuming child... child attempting to retrieve processor count... 2 processors

9.5.5. Transferring Out-of-Line Memory and Port Rights

When transferring large amounts of data, a sender can include the address of a memory region (rather than the entire data inline) in its address space as part of the message. This is called an out-of-line (OOL) transfer. On the receive side, the out-of-line region is mapped by the kernel to a hitherto unused portion of the receiver's address space. In particular, the sender and the receiver share the region thus transferred as copy-on-write. Moreover, the sender can set the deallocate bit in the out-of-line data's type descriptor to have the kernel automatically deallocate the region from the sender's address space. In this case, copy-on-write will not be usedthe kernel will instead move the region.

Figure 924 shows partial source for a simple OOL memory server. When the server receives a message from a client, it sends a character string as OOL memory back to the client. The program is based on the simple IPC client-server example from Section 9.5.1, with the following differences.

  • The request message from the client has an empty body in this caseit is used only as a trigger.

  • The response message from the server contains an OOL descriptor for memory. The server initializes various fields of this descriptor before sending it.

  • The server must mark the response message as a complex message by setting MACH_MSGH_BITS_COMPLEX in the msgh_bits field of the outgoing message.

Figure 924. Sending out-of-line memory in an IPC message

// ool_memory_ipc_common.h #ifndef _OOL_MEMORY_IPC_COMMON_H_ #define _OOL_MEMORY_IPC_COMMON_H_ ... #define SERVICE_NAME   "com.osxbook.OOLStringServer" ... typedef struct {     mach_msg_header_t  header; } msg_format_request_t; typedef struct {     mach_msg_header_t  header;     mach_msg_trailer_t trailer; } msg_format_request_r_t; typedef struct {     mach_msg_header_t          header;     mach_msg_body_t            body; // start of kernel-processed data     mach_msg_ool_descriptor_t  data; // end of kernel-processed data     mach_msg_type_number_t     count; } msg_format_response_t; typedef struct {     mach_msg_header_t          header;     mach_msg_body_t            body; // start of kernel-processed data     mach_msg_ool_descriptor_t  data; // end of kernel-processed data     mach_msg_type_number_t     count;     mach_msg_trailer_t         trailer; } msg_format_response_r_t; #endif // _OOL_MEMORY_IPC_COMMON_H_ // ool_memory_ipc_server.c ... #include "ool_memory_ipc_common.h" // string we will send as OOL memory const char *string = "abcdefghijklmnopqrstuvwxyz"; int main(int argc, char **argv) {     ...     msg_format_request_r_t recv_msg;     msg_format_response_tsend_msg;     ...     for (;;) { // server loop         // receive request         ...         // prepare response         send_hdr                   = &(send_msg.header);         send_hdr->msgh_bits        = MACH_MSGH_BITS_LOCAL(recv_hdr->msgh_bits);         send_hdr->msgh_bits       |= MACH_MSGH_BITS_COMPLEX;         send_hdr->msgh_size        = sizeof(send_msg);         send_hdr->msgh_local_port  = MACH_PORT_NULL;         send_hdr->msgh_remote_port = recv_hdr->msgh_remote_port;         send_hdr->msgh_id          = recv_hdr->msgh_id;         send_msg.body.msgh_descriptor_count = 1;         send_msg.data.address               = (void *)string;         send_msg.data.size                  = strlen(string) + 1;         send_msg.data.deallocate            = FALSE;         send_msg.data.copy                  = MACH_MSG_VIRTUAL_COPY;         send_msg.data.type                  = MACH_MSG_OOL_DESCRIPTOR;         send_msg.count                      = send_msg.data.size;         // send response         ...     }     exit(0); }

The client code remains almost the same, except that the client uses the updated request and response message buffer structures. When the client receives the server's response, the address field of the out-of-line descriptor will contain the address of the string in the client's virtual address space.

We could modify the example in Figure 924 to send port rights instead of out-of-line memory in an IPC message. The kernel provides both inline and out-of-line descriptors for sending port rights, although a message carrying a port right is always complex. Figure 925 shows another adaptation of our client-server example that acquires send rights to the host privileged port and sends them to a client. The client can then use these rights, which appear in the name field of the received port descriptor, just as if it had acquired these rights normally (say, by calling host_get_host_priv_port()).

Figure 925. Sending port rights in an IPC message

// ool_port_ipc_common.h #ifndef _OOL_PORT_IPC_COMMON_H_ #define _OOL_PORT_IPC_COMMON_H_ ... #define SERVICE_NAME   "com.osxbook.ProcessorInfoServer" ... typedef struct {     mach_msg_header_t          header;     mach_msg_body_t            body; // start of kernel-processed data     mach_msg_port_descriptor_t data; // end of kernel-processed data } msg_format_response_t; typedef struct {     mach_msg_header_t          header;     mach_msg_body_t            body; // start of kernel-processed data     mach_msg_port_descriptor_t data; // end of kernel-processed data     mach_msg_trailer_t         trailer; } msg_format_response_r_t; #endif // _OOL_PORT_IPC_COMMON_H_ // ool_port_ipc_server.c int main(int argc, char **argv) {     ...     host_priv_t             host_priv;     ...     // acquire send rights to the host privileged port in host_priv     ...     for (;;) { // server loop         // receive request         ...         // prepare response         send_hdr                   = &(send_msg.header);         send_hdr->msgh_bits        = MACH_MSGH_BITS_LOCAL(recv_hdr->msgh_bits);         send_hdr->msgh_bits       |= MACH_MSGH_BITS_COMPLEX;         send_hdr->msgh_size        = sizeof(send_msg);         send_hdr->msgh_local_port  = MACH_PORT_NULL;         send_hdr->msgh_remote_port = recv_hdr->msgh_remote_port;         send_hdr->msgh_id          = recv_hdr->msgh_id;         send_msg.body.msgh_descriptor_count = 1;         send_msg.data.name                  = host_priv;         send_msg.data.disposition           = MACH_MSG_TYPE_COPY_SEND;         send_msg.data.type                  = MACH_MSG_PORT_DESCRIPTOR;         // send response         ...     }     exit(0); }




Mac OS X Internals. A Systems Approach
Mac OS X Internals: A Systems Approach
ISBN: 0321278542
EAN: 2147483647
Year: 2006
Pages: 161
Authors: Amit Singh

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net