1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
[[!meta copyright="Copyright © 2007, 2008, 2010 Free Software Foundation,
Inc."]]
[[!meta license="""[[!toggle id="license" text="GFDL 1.2+"]][[!toggleable
id="license" text="Permission is granted to copy, distribute and/or modify this
document under the terms of the GNU Free Documentation License, Version 1.2 or
any later version published by the Free Software Foundation; with no Invariant
Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license
is included in the section entitled [[GNU Free Documentation
License|/fdl]]."]]"""]]
RPC stands for remote procedure call.
This is the basis for about everything in the Hurd. It is based on the Mach
RPC mechanism (the mach_msg system call). An RPC is made against a Mach port,
which is the gateway to the translator which will serve the RPC. Let's take for
instance the case of opening a file, and advancing (lseek) 10 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 the libc, which make RPC calls.
Open is a bit complex because it finds its way to the eventual translator, but
for a mere 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 the function is thus actually
generated from the fs.defs file using mig during the glibc build in RPC_dir_lookup.c.
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
loop (libdiskfs/init-first.c, master_thread_function()), which calls
ports_manage_port_operations_multithread(), which essentially simply keeps
making a mach_msg system call to receive a message, and calls the demuxer
on it, here the demuxer parameter, diskfs_demuxer. This demuxer calls the
demuxers for the various interfaces supported by ext2fs. These demuxers are
generated using mig during the hurd build. For instance, the fs interface
demuxer 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 to know which function should be
called according to the RPC id. Here it's _Xdir_lookup which thus gets
called. This 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 symbolize the file
being opened, and a structure to store information about it. It returns the
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 creates a new fd entry in its fd table, and records the
port into it. It returns 0 (success).
Lseek is simpler. The glibc implementation simply calls the __io_seek function
against the port of the fd. 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 fd with the
io_seek RPC id.
In the root filesystem, it's now the demuxer for the io interface,
diskfs_io_server, which will recognize the RPC id, and call _Xio_seek, which
finds the data structure for the port, and calls diskfs_S_io_seek. The latter
simply modifies the 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 does *not* need to keep all that in mind. All one needs
to remember 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...
Q&A
Q: How do I know whether a function is an RPC or not?
A: Simply grep the function name (without leading underscores) in the
/usr/include/hurd/*.defs files.
Q: Why is it libdiskfs functions which get called?
A: Because ext2fs is libdiskfs-based (see HURDLIBS = diskfs in ext2fs/Makefile).
Other translators are libnetfs-based or libtrivfs-based. grep for RPC names into
those according to what your translator is based on.
Q: How do I know which translator the RPC gets into?
A: 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:
* PF_LOCAL/PF_UNIX sockets are served by /hurd/pflocal, see [[hurd/networking]]
* PF_INET sockets are served by /hurd/pfinet, see [[hurd/networking]]
* named sockets (aka fifo) are served by /hurd/fifo
# See Also
* [[Mach RPC|microkernel/mach/rpc]]s
* the [[Hurd's rpctrace|hurd/debugging/rpctrace]]
|