The
obvious question now, is how does this work internally. The Perchild
MPM has a special global table which it uses to start children and
allow those children to change to the correct user Ids. It
also uses the per-server configuration to pass requests between child
processes. When the MPM encounters a ChildPerUserID
directive it
begins to fill out the global child table. Each child process gets
one place in the table, which stores the User and Group Id that the
child should run as. The table also stores a socket descriptor, but
it isn’t filled out until later.
While
parsing the configuration for each VirtualHost, if the server
encounters an AssignUserId
directive, it fills out a perchild
per-server configuration structure, which contains the two socket
descriptors. In order to do this, the server creates a set of
anonymous Unix Domain Sockets which are used to pass the request
between processes. After the sockets are created, the server
searches the child table to find the child processes that have the
same User and Group Ids. Once found, one of the socket descriptors
is attached to all of the processes with that User Group combination.
Both socket descriptors are attached to the specific VirtualHost
that is being configured. This step is repeated for all
VirtualHosts. Once all VirtualHosts have been configured, the server
ensures that each host has been assigned a socket. If not, the
server creates a set of default sockets and stores those in any
server that doesn’t already have a socket.
The next
step is to create the child processes. When each process is started,
it checks the global child table, and switches to the appropriate
User and Group Ids. If no User and Group Id are specified for this
child process, then the User and Group specified in the main server
are used. Each child also adds the socket in the socket table to the
list of sockets it will poll on. From here, child startup proceeds
as normal with each child process polling on all of the ports opened
in the parent process. This leaves the server looking like Figure 1.
Figure 1.
When a
request comes in, the Perchild MPM is the first module called in the
post_read_request
phase. During this phase, the Perchild MPM ensures
that the request is for the current child process. If so, processing
continues as normal. If not, the child process uses the VirtualHost
that is attached to the request to find the correct Unix Domain
Socket to use. The child process begins by finding the socket that
is currently being used to communicate with the client in the
connection structure. Once this socket is found, it is passed to the
correct child process through the Unix Domain socket (S1 or S2 in the
diagram). Finally, the part of the request that has already been
read from the client is sent to the new child over the Unix Domain
socket. The original child process then closes its connection to the
client, and longjmps out of the post_read_request
phase to the end of
processing a request. This thread then goes back to listening for
another new request.