New in the Apache space: the Perchild MPM, which specifies User and Group IDs for clusters of child process. Ryan Bloom explains how this will make your life simpler.
One of the biggest problems with administering a major server housing multiple sites is restricting access to the sites to only those people responsible for maintaininga specific site. The reason for this is that all of the Apache childprocesses run with the same user and group Id.Therefore, all of the files need to be readable, writable, andexecutable by the user and group that the server is running as. Thisbecomes a much bigger issue when you add CGI and PHP scripts to thesite. If those scripts must access private information, then thatinformation must be stored with relatively insecure user and groupIds.
Apache 1.3 solved this problem by introducing suexec, whichintroduces other problems and PHP and mod_cgi can not take advantageof it. Apache 2.0 has introduced a new MPM to solve this problem ina more elegant way that all scripts can take advantage of.
The newMPM is called Perchild, and it is based on the Dexter MPM. Thismeans that a set number of child processes are created and eachprocess has a dynamic number of threads. In this MPM it is possibleto specify User and Group IDs for clusters of child process. Then,each virtual host is assigned to run in a specific cluster of childprocesses. If no cluster of child processes is specified, then thevirtual host is run with the default User and Group Ids.
Therewere many designs considered for this MPM, but in the end only onemade sense. The first consideration was which MPM to base off of.The options were the prefork, mpmt_pthread, and dexter. Prefork andmpmt_pthread had one major drawback, they create new child processeswhich are completely separated from each other whenever the servergets busy. This means that the parent process would need todetermine what User and Group Ids the new process should have when itis created. While this seems easy at first glance, it requires loadbalancing techniques that begin to get very complicated. If theprefork or mpmt_pthread MPMs are desired, it makes more sense to puta load balancer or proxy in front of the web servers, and runmultiple instances of Apache on different ports. To the client, thiswould look very similar to the Perchild MPM.
Aftereliminating prefork and mpmt_pthread, the only option left wasDexter. Now, the question was how to associate virtual hosts withchild processes. Do we base the number of child processes on thenumber of virtual hosts, or do we allow the web admin to specify howthe setup should look. Assuming that the more flexible we make thePerchild MPM, the more likely it was to be used, we allow the webadmin to determine how their site looks. This is done through thecombination of two directives:
ChildPerUserIDNumChildren UserID GroupID
Thefirst directive allow the administrator to assign a number of childprocesses to use the same User and Group Ids. This is to provide forsome level of robustness. Because Perchild creates new threads inthe same child process to handle new requests, it is not the mostrobust server, although it is very scalable. If one ofthe threads seg faults, then that entire process will die, takingwith it all of the requests currently being server by that childprocess. By specifying more than one child per user/group pair, weallow the server to balance the number of requests between multiplechild processes. The second directive is specified inside aVirtualHost stanza, and assigns that Virtual Host to a specific Userand Group Id. The server is smart enough to combine all of theVirtualHosts with the same User and Group Ids to the same childprocesses.
How Does it Work?
Theobvious question now, is how does this work internally. The PerchildMPM has a special global table which it uses to start children andallow those children to change to the correct user Ids. Italso uses the per-server configuration to pass requests between childprocesses. When the MPM encounters a
directive itbegins to fill out the global child table. Each child process getsone place in the table, which stores the User and Group Id that thechild should run as. The table also stores a socket descriptor, butit isn't filled out until later.
Whileparsing the configuration for each VirtualHost, if the serverencounters an
AssignUserId directive, it fills out a perchildper-server configuration structure, which contains the two socketdescriptors. In order to do this, the server creates a set ofanonymous Unix Domain Sockets which are used to pass the requestbetween processes. After the sockets are created, the serversearches the child table to find the child processes that have thesame User and Group Ids. Once found, one of the socket descriptorsis 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 allVirtualHosts. Once all VirtualHosts have been configured, the serverensures that each host has been assigned a socket. If not, theserver creates a set of default sockets and stores those in anyserver that doesn't already have a socket.
The nextstep is to create the child processes. When each process is started,it checks the global child table, and switches to the appropriateUser and Group Ids. If no User and Group Id are specified for thischild process, then the User and Group specified in the main serverare used. Each child also adds the socket in the socket table to thelist of sockets it will poll on. From here, child startup proceedsas normal with each child process polling on all of the ports openedin the parent process. This leaves the server looking like Figure 1.
When arequest comes in, the Perchild MPM is the first module called in the
post_read_request phase. During this phase, the Perchild MPM ensuresthat the request is for the current child process. If so, processingcontinues as normal. If not, the child process uses the VirtualHostthat is attached to the request to find the correct Unix DomainSocket to use. The child process begins by finding the socket thatis currently being used to communicate with the client in theconnection structure. Once this socket is found, it is passed to thecorrect child process through the Unix Domain socket (S1 or S2 in thediagram). Finally, the part of the request that has already beenread from the client is sent to the new child over the Unix Domainsocket. The original child process then closes its connection to theclient, and longjmps out of the
post_read_request phase to the end ofprocessing a request. This thread then goes back to listening foranother new request.
Therequest processing then moves to the correct child processing. Oncea socket is passed over the Unix Domain socket, the new child processis woken up out of poll with data its end of the Unix Domain socket.Each child has a table over sockets to use for this occasion, thereis one socket in the table for each thread in the process. Usually,the sockets are set to -1, but when the passed socket descriptor isdetected, we set this thread's spot in the table to -2. Later, thefact that the socket is -2 is used to determine that we must receivethe socket descriptor from the Unix Domain socket. The receivedsocket is then placed in the thread's position in the socket table.
Processingthen continues as normal, reading from the Unix Domain socket, untilthe post_read_request phase. At this point, we know that the requesthas come from another child process in our server and we know thatthis request is meant for this child processes User and Group Id.The only thing left to do is replace the Unix Domain socket that iscurrently in the connection structure with the socket that was passedfrom the first child process. This child then continues serving theoriginal request.
Thiswill never be the fastest MPM, because it relies on passing socketdescriptors between processes, which is inherently a slow process.It would be much faster to give the server multiple IP addresses, andhave different Apache installations listen to port 80 on differentIPs. However, that can get very difficult to administer.
ThisMPM was finished the day before the fifth alpha was released, so itis not well tested at all. Over the next few weeks and months, thisMPM will become more stable and more portable. Currently, this MPMhas only ever been tested on Linux, but with minor modifications, itshould work on almost all Unices. There has been talk ofmodifying the Windows MPM to allow the threads to change theiridentities for each request, but that has not happened yet.
This article was originally published on Friday Aug 18th 2000