1
votes

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?

2

2 Answers

5
votes

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.

1
votes

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.