Remote procedure calls are the basis for about everything in the Hurd. They're based on the Mach RPC mechanism (mach msg system call). An RPC is made against a Mach port, which is the gateway to the translator that will serve the RPC. Let's explore the case of opening a file, and advancing (lseek) ten bytes into it. The user program will be something like:

#include <fcntl.h>

int main(void) {
  int fd = open("test.txt", O_RDONLY);
  lseek(fd, 10, SEEK_CUR);

Both open and lseek are functions provided by glibc, which translates these into the appropriate remote procedure calls.

open first has to find its way to the actual translator serving that file, but for a file on the root filesystem, what happens boils down to calling the dir_lookup function against the root filesystem. This is an RPC from the fs interface (see fs.defs). The implementation of this function is thus actually generated during the glibc build in RPC_dir_lookup.c, based on the fs.defs file, using MIG. This generated function essentially encodes the parameters into a data buffer, and makes a mach_msg system call to send the buffer to the root filesystem port, with the dir_lookup RPC ID.

The root filesystem, for instance ext2fs, was sitting in its main service loop (libdiskfs/init-first.c:master_thread_function), which calls ports_manage_port_operations_multithread, which essentially simply keeps making mach_msg system calls to receive messages, and calls the demultiplexer on it, here the diskfs_demuxer. This demultiplexer calls the demultiplexers for the various interfaces supported by ext2fs. These demuxers are generated using MIG during the Hurd build. For instance, the fs interface demultiplexer for diskfs, diskfs_fs_server, is in libdiskfs/fsServer.c. It simply checks whether the RPC ID is an fs interface ID, and if so uses the diskfs_fs_server_routines array for calling the appropriate function corresponding to the RPC ID. Here it's _Xdir_lookup which thus gets called. This one decodes the parameters from the message data buffer, and calls diskfs_S_dir_lookup.

diskfs_S_dir_lookup in the ext2fs translator does stuff to check that the file exists, etc. and eventually creates a new port, which will represent the open file, and a structure to keep information about it. It returns this new port to its caller, _Xdir_lookup, which puts it into the reply message data buffer and returns. ports_manage_port_operations_multithread then calls mach_msg to send that port to the user program.

The mach_msg call in the user program thus returns, returning the port, decoded by dir_lookup. glibc adds a new slot to its file descriptor table, and records the port in it.

lseek is simpler. The glibc implementation simply calls the __io_seek function against the port of the file descriptor. This is an RPC from the ?io interface (see io.defs). As explained above, the implementation is thus in RPC_io_seek.c, it encodes parameters and makes a mach_msg system call to the port of the file descriptor with the io_seek RPC ID.

In the root filesystem, it's now the demultiplexer for the io interface, diskfs_io_server, which will recognize the RPC ID, and call _Xio_seek, which retrieves the data structure for the port, and calls diskfs_S_io_seek. The latter simply modifies ext2fs' internal data structure to account for the file position change, and returns the new position. _Xio_seek encodes the position into the reply message, which is sent back by ports_manage_port_operations_multithread through mach_msg.

The mach_msg call in the user program thus returns the new offset, decoded by __io_seek. lseek can then return it to the user application.

When hacking, one usually does not have to keep all that in mind. All one needs to remember (or look up) is that when the application program calls open, the glibc implementation actually calls dir_lookup, which triggers a call to diskfs_S_dir_lookup in the ext2fs translator. When the application program calls lseek, the glibc implementation calls __io_seek, which triggers a call to diskfs_S_io_seek in the ext2fs translator. And so on...

Questions and Answers

How do I know whether a function is an RPC or not?

Simply grep the function name (without leading underscores) in the /usr/include/hurd/*.defs files.

Why is it a libdiskfs function that get called?

Because the filesystem serving the file, ext2fs, is libdiskfs-based (see HURDLIBS = diskfs in ext2fs/Makefile). Other translators are libnetfs-based or libtrivfs-based. grep for RPC names in those according to what your translator is based on.

How do I know which translator the RPC gets into?

Check the type of file whose port the RPC was made on. Most files are handled by the translator which is mounted where the files are opened. Some special files are handled by particular translators:

See Also