views:

206

answers:

2

In Unix Network Programming there is an example of a Pre-forked server which uses message passing on a Unix Domain Pipe to instruct child processes to handle an incoming connection:

for ( ; ; ) {
    rset = masterset;
    if (navail <= 0)
        FD_CLR(listenfd, &rset);    /* turn off if no available children */
    nsel = Select(maxfd + 1, &rset, NULL, NULL, NULL);

        /* 4check for new connections */
    if (FD_ISSET(listenfd, &rset)) {
        clilen = addrlen;
        connfd = Accept(listenfd, cliaddr, &clilen);

        for (i = 0; i < nchildren; i++)
            if (cptr[i].child_status == 0)
                break;              /* available */

        if (i == nchildren)
            err_quit("no available children");
        cptr[i].child_status = 1;   /* mark child as busy */
        cptr[i].child_count++;
        navail--;

        n = Write_fd(cptr[i].child_pipefd, "", 1, connfd);
        Close(connfd);
        if (--nsel == 0)
            continue;   /* all done with select() results */
}

As you can see, the parent writes the file descriptor number for the socket to the pipe, and then calls close on the file descriptor. When the preforked children finish with the socket they also call close on the descriptor. The thing which is throwing me for a loop is that because these children are preforked I would assume that only file descriptors which existed at the time the children were forked would be shared. However, if that was true, then this example would fail spectacularly, yet it works.

Can someone shed some light on how it is that file descriptors created by the parent after the fork end up being shared with the children process?

+4  A: 

Take a look at the Write_fd implementation. It uses something like

union {
  struct cmsghdr        cm;
  char                          control[CMSG_SPACE(sizeof(int))];
} control_un;
struct cmsghdr  *cmptr;

msg.msg_control = control_un.control;
msg.msg_controllen = sizeof(control_un.control);

cmptr = CMSG_FIRSTHDR(&msg);
cmptr->cmsg_len = CMSG_LEN(sizeof(int));
cmptr->cmsg_level = SOL_SOCKET;
cmptr->cmsg_type = SCM_RIGHTS;
*((int *) CMSG_DATA(cmptr)) = sendfd;

That is, sending a control message with type SCM_RIGHTS is a way unixes can share a file descriptor with an unreleated process.

nos
+1  A: 

You can send (most) arbitrary file descriptors to a potentially unrelated process using the FD passing mechanism in Unix sockets.

This is typically a little-used mechanism and rather tricky to get right - both processes need to cooperate.

Most prefork servers do NOT do this, rather, they have the child process call accept() on a shared listen socket, and create its own connected socket this way. Other processes cannot see this connected socket, and there is only one copy of it, so when the child closes it, it's gone.

One disadvantage is that the process cannot tell what the client is going to request BEFORE calling accept, so you cannot handle different types of requests in different children etc. Once one child has accept()ed it, another child cannot.

MarkR
Yeah, I can't actually see many reasons to use a pre-forked server at all these days; I would pretty much always choose a pre-threaded server. The only case I can think of where I would run a pre-forked server would be maybe for security reasons where you want each clients memory space isolated from any other clients so that hacking or generating an error in one client doesn't take down the whole system or expose sensitive user data.
Robert S. Barnes
Preforked servers are good because they prevent memory leaks from breaking stuff. The child processes can quit and be recycled without interrupting service, reclaiming memory.
MarkR
forked servers also shield you from 3. party stuff that's not thread safe, and assumes there's only one execution thread. There's lots of such libraries around.
nos