9.6. MIGInformally speaking, the phrase remote procedure call (RPC) denotes a mechanism that allows programs to call procedures transparently with respect to the procedures' locations. In other words, using RPC, a program can call a remote procedure, which may reside within another program or even within a program on another computer.
Most RPC systems include tools that ease the programmer's job by taking care of repetitive, tedious, and mechanical aspects of RPC programming. For example, Sun RPC provides the rpcgen program, which compiles an RPC specification file to generate C language code that can be linked with other C code explicitly written by the programmer. The specification filea .x filedefines server procedures, their arguments, and their results. Mach Interface Generator (MIG) is a tool[11] that generates RPC code for client-server-style Mach IPC from specification files. Since typical IPC programs perform similar operations of preparing, sending, receiving, unpacking, and demultiplexing messages, MIG is able to automatically generate code for these operations based on programmer-provided specifications of message passing and procedure call interfaces. Automated code generation also promotes consistency and reduces the likelihood of programming errors. Besides, if the programmer wishes to change the interface, only the appropriate specification file needs to be modified.
9.6.1. MIG Specification FilesA MIG specification file conventionally has the .defs extension. MIG processes a .defs file to generate the following three files:
A MIG specification file contains the following types of sections, not all of which are mandatory:
A MIG "subsystem" is a collective name for a client, the server called by the client, and the set of operations exported by the server. The subsystem keyword names the MIG subsystem specified by the file. MIG use this identifier as a prefix in the names of the code files it generates. subsystem system-name message-base-id ; The subsystem keyword is followed by the ASCII name (e.g., foo) of the subsystem being defined. message-base-id is the integer base value used as the IPC message identifier (the msgh_id field in the message header) of the first operation in the specification file. In other words, this value is the base beginning with which operations are numbered sequentially. message-base-id may be arbitrarily chosen. However, if the same program serves multiple interfaces, then each interface must have a unique identifier so that the server can unambiguously determine the operations invoked.
When MIG creates a reply message corresponding to a request message, the reply identifier is conventionally the sum of the request identifier and the number 100. The serverdemux declaration section can be used to specify an alternative name for the server demultiplexing routine in the server-interface module. The demultiplexing routine examines the request message, calling the appropriate subsystem routine based on the msgh_id value in the message header. If the value is out of bounds for the subsystem, the demultiplexing routine returns an error. The default name for this routine is <system-name>_server, where <system-name> is the name specified through the subsystem statement. serverdemux somethingelse_server ; The type specifications section is used for defining data types corresponding to parameters of the calls exported by the user-interface module. MIG supports declarations for types such as simple, structured, pointer, and polymorphic. /* * Simple Types * type type-name = type-description; */ type int = MACH_MSG_TYPE_INTEGER_32; type kern_return_t = int; type some_string = (MACH_MSG_TYPE_STRING, 8*128); /* * Structured and Pointer Types * type type-name = array [size] of type-description; * type type-name = array [*:maxsize] of type-description; * struct [size] of type-description; * type type-name = ^ type-description; */ type thread_ids = array[16] of MACH_MSG_TYPE_INTEGER_32; type a_structure = struct[16] of array[8] of int; type ool_array = ^ array[] of MACH_MSG_TYPE_INTEGER_32; type intptr = ^ MACH_MSG_TYPE_INTEGER_32; type input_string = array[*:64] of char; A polymorphic type is used to specify an argument whose exact type is not determined until runtimea client must specify the type information as an auxiliary argument at runtime. MIG automatically includes an additional argument to accommodate this. Consider the following simple definition file: /* foo.defs */ subsystem foo 500 #include <mach/std_types.defs> #include <mach/mach_types.defs> type my_poly_t = polymorphic; routine foo_func( server : mach_port_t; arg : my_poly_t); The MIG-generated code for foo_func() has the following prototype: kern_return_t foo_func(mach_port_t server, my_poly_t arg, mach_msg_type_name_t argPoly); A type declaration can optionally contain information specifying procedures for translating or deallocating types. Translation allows a type to be seen differently by the user- and server-interface modules. A deallocation specification allows a destructor function to be specified. In Section 9.6.2 we will see an example involving translation and deallocation specifications. Import declarations are used to include header files in MIG-generated modules. MIG can be directed to include such headers in both the user- and server-interface modules, or in only one of the two. /* * import header-file; * uimport header-file; * simport header-file; */ import "foo.h"; /* imported in both modules */ uimport <stdlib.h>; /* only in user-interface module */ simport <stdio.h>; /* only in server-interface module */ The operations section contains specifications for one or more types of IPC operations. The specification includes a keyword for the kind of operation being described, the name of the operation, and the names and types of its arguments. When MIG compiles the specification file, it generates client and server stubs for each operation. A client stub resides in the user-interface module. Its job is to package and send the message corresponding to a procedure call invocation in the client program. The server stub resides in the server-interface module. It unpacks received messages and calls the programmer's server code that implements the operation. Operation types supported by MIG include Routine, SimpleRoutine, Procedure, SimpleProcedure, and Function. Table 92 shows the characteristics of these types.
The following is an example of an operation specification: routine vm_allocate( target_task : vm_task_entry_t; inout address : vm_address_t; size : vm_size_t; flags : int); A parameter specification contains a name and a type and may optionally be adorned by one of the keywords in, out, or inout, representing that the argument is only sent to the server, is sent by the server on its way out, or both, respectively.
In the operations section, the skip keyword causes MIG to skip assignment of the next operation ID, resulting in a hole in the sequence of operation IDs. This can be useful to preserve compatibility as interfaces evolve. The options declarations section is used for specifying special-purpose or global options that affect the generated code. The following are examples of options:
Numerous examples of MIG specification files exist in /usr/include/mach/ and its subdirectories. 9.6.2. Using MIG to Create a Client-Server SystemLet us use MIG to create a simple client-server system. A MIG server is a Mach task that provides services to its clients using a MIG-generated RPC interface. Our MIG server will serve two routines: one to calculate the length of a string sent by the client and another to calculate the factorial of a number sent by the client. In our example, the client will send the string inline, and the server will send only simple integers. Recall that when an interface call returns out-of-line data, it is the caller's responsibility to deallocate the memory by calling vm_deallocate(). For example, we could add another operation to our interface, say, one that reverses the string sent by the client and returns the reversed string by allocating memory for it in the caller's address space. We call our MIG server the Miscellaneous Server. Its source consists of the following four files:
Figure 926 shows the common header file. We define two new data types: input_string_t, which is a character array 64 elements in size, and xput_number_t, which is another name for an integer. Figure 926. Common header file for the Miscellaneous Server and its client
Figure 927 shows the specification file. Note the type specification of xput_number_t. Each MIG type can have up to three corresponding C types: a type for the user-interface module (specified by the CUserType option), a type for the server module (specified by the CServerType option), and a translated type for internal use by server routines. The CType option can be used in place of CUserType and CServerType if both types are the same. In our case, the CType option specifies the C data type for the MIG type xput_number_t. Figure 927. MIG specification file for the Miscellaneous Server
We use the InTran, OutTran, and Destructor options to specify procedures that we will provide for translation and deallocation. Translation is useful when a type must be seen differently by the server and the client. In our example, we want the type in question to be an xput_number_t for the server and an int for the client. We use InTran to specify misc_translate_int_to_xput_number_t() as the incoming translation routine for the type. Similarly, misc_translate_xput_number_t_to_int() is the outgoing translation routine. Since xput_number_t is actually just another name for an int in our case, our translation functions are trivial: They simply print a message.
Real-life translation functions can be arbitrarily complex. The kernel makes heavy use of translation functions. See Section 9.6.3 for an example. We also use the Destructor option to specify a deallocation function that MIG will call at the appropriate time. Figure 928 shows the source for the server. Figure 928. Programmer-provided source for the Miscellaneous Server
Figure 929 shows the programmer-provided source for the client we will use to call the Miscellaneous Server interface routines. Figure 929. A client for accessing the services provided by the Miscellaneous Server
Next, we must run the mig program on the specification file. As noted earlier, doing so will give us a header file (misc.h), a user-interface module (miscUser.c), and a server-interface module (miscServer.c). As Figure 930 shows, we compile and link together client.c and miscUser.c to yield the client program. Similarly, server.c and miscServer.c yield the server program. Figure 930. Creating a MIG-based client and server system$ ls -m client.c, misc.defs, misc_types.h, server.c $ mig -v misc.defs Subsystem misc: base = 500 Type int8_t = (9, 8) Type uint8_t = (9, 8) ... Type input_string_t = array [64] of (8, 8) Type xput_number_t = (2, 32) CUserType: int CServerType: int InTran: xput_number_t misc_translate_int_to_xput_number_t(int) OutTran: int misc_translate_xput_number_t_to_int(xput_number_t) Destructor: misc_remove_reference(xput_number_t) Import "misc_types.h" Routine (0) string_length( RequestPort server_port: mach_port_t In instring: input_string Out len: xput_number) Routine (4) factorial( RequestPort server_port: mach_port_t In num: xput_number Out fac: xput_number) ServerPrefix Server_ UserPrefix Client_ Writing misc.h ... done. Writing miscUser.c ... done. Writing miscServer.c ... done. $ ls -m client.c, misc.defs, misc.h, miscServer.c, miscUser.c, misc_types.h, server.c $ gcc -Wall -o server server.c miscServer.c $ gcc -Wall -o client client.c miscUser.c $ ./server Once the server is running, we can also use our bootstrap_info program to verify that our service's name (MIG-miscservice, as defined in misc_types.h) is listed. $ bootstrap_info ... 1 MIG-miscservice - $ ./client length of "Hello, MIG!" is 11 factorial of 5 is 120 Figure 931 shows the sequence of actions that occur when the client calls the server's string_length() operation. Figure 931. Invocation of Miscellaneous Server routines by a client9.6.3. MIG in the KernelMIG is used to implement most Mach system calls. Several system calls, such as task-related, IPC-related, and VM-related calls, take a target task as one of their arguments. MIG translates the task argument in each case depending on the kernel-facing data type. For example, a Mach thread is seen as a port name in user space, but inside the kernel, MIG calls convert_port_to_thread() [osfmk/kern/ipc_tt.c] to translate an incoming thread port name to a pointer to the kernel object represented by the porta thread structure. /* osfmk/mach/mach_types.defs */ type thread_t = mach_port_t #if KERNEL_SERVER intran: thread_t convert_port_to_thread(mach_port_t) outtran: mach_port_t convert_thread_to_port(thread_t) destructor: thread_deallocate(thread_t) #endif /* KERNEL_SERVER */ Note the KERNEL_SERVER conditional directive. The Mac OS X kernel uses it, and a related directive KERNEL_USER, in MIG specification files to specify the KernelServer and KernelUser subsystem modifiers. /* osfmk/mach/task.defs */ subsystem #if KERNEL_SERVER KernelServer #endif /* KERNEL_SERVER */ task 3400; The subsystem modifiers instruct MIG to generate alternate code for the user and server modules for special environments. For example, when a MIG server routine resides in the kernel, it is said to be in the KernelServer environment. Although the routine will have the same prototype as it would without the KernelServer modifier, the latter changes how MIG performs type translation. A mach_port_t type is automatically converted to the kernel type ipc_port_t on the KernelServer subsystem's server side. 9.6.3.1. Interfaces to Kernel ObjectsMach not only uses ports to represent several types of kernel objects but also exports interfaces to these objects through Mach IPC. Such interfaces are also implemented using MIG. User programs can use these interfaces either directly via Mach IPC or, as is typically the case, by calling standard library functions. When the system library is compiled, it links in user-interface modules corresponding to several kernel object MIG definition files. The library build process runs mig on the definition files to generate the interface modules.
Examples of kernel object types include thread, task, host, processor, processor set, memory object, semaphore, lock set, and clock. A complete list of defined kernel object types is in osfmk/kern/ipc_kobject.h. 9.6.3.2. MIG Initialization in the KernelThe kernel maintains a mig_subsystem structure [osfmk/mach/mig.h] for the MIG subsystem corresponding to each type of kernel object. As the IPC subsystem is initialized during kernel startup, the MIG initialization functionmig_init() [osfmk/kern/ipc_kobject.c]iterates over each subsystem, populating a global hash table of MIG routines. Figure 932 shows an excerpt from this process. Figure 932. Initialization of MIG subsystems during kernel bootstrap
|