world leader in high performance signal processing
Trace: » living_without_forks

We have no Fork

In the early days ( late 1990's ) uClinux was scorned by a few because , without a MMU, it could not do a true fork.

The fork system call is the mechanism by which every single UNIX process normally gets started. Every process is, in fact, a clone of the first process started by the booting kernel.

A fork produces a parent and child. Each have individual task control structures and each can be scheduled independently by the kernel. When we have an MMU the parent and child eventually occupy different physical memory spaces. The MMU maps different physical memory addresses to identical virtual memory addresses and copies the data from parent to child. Under Linux this happens when either process attepmts to write to a shared physical memory area.

On a typical system this cloneing can work because immediately after the fork the child replaces the parent's program with a new executable. Sometimes the parent and child are supposed to be identical at first but operating on different data.

uClinux does not have the functionality of a true fork. It does have a close cousin the vfork system call.

When a vfork system call is made the parent task is halted (put in a wait queue and suspend it) and a new task control block created using the same text memory, stack, and data memory as the parent. The new child then has to replace its program and data areas with an execve call or it has to exit before the parent is allowed to continue.

The execve system call is one of a series of exec system calls that all have different arrangements of arguments and envronment variables.

 Try this
 man 2 execl 

The purpose of this call is to use the same kernel space task control block but run a different program under that control block. The text and data are read from an executable file specified in one of the arguments and the new program starts from the defined start location specified in the new executable.

Once the new program memory has been established the parent task is released.

Some Examples

The uClinux Distribution contains many good examples of using vfork.

The simpleinit program ( which is the first user code executed by the kernel after boot) is given the job of running all the initial programs required by the system.

Here is an extract where it is booting to single user mode and trying to run a shell program.

    // part of the "boot to single user "code 
 
    av[0] = _PATH_BSHELL;        // /bin/sh
    av[1] = NULL;
    if((pid = vfork()) == 0) {  // pid = 0 in the child
    extern char **environ;
        /* the child */
        execve(_PATH_BSHELL, av, environ);  // if this works parent is released
        err("exec of single user shell failed\n");
        _exit(0);                           // if execve failed exit releases parent
    } else if(pid > 0) {
    int i;                                   // this is the parent
        while(wait(&i) != pid) /* nothing */;
    } else if(pid < 0) {
        err("fork of single user shell failed\n");
    }
 
   //parent continues, the child has execve'd a new program.
 

Another example

static int do_command(const char *path, const char *filename, int dowait)
{
        pid_t pid, wpid;
        int stat, st;
 
        if((pid = vfork()) == 0) {
                /* the child */
                char *argv[3];
                char *env[3];
 
                close(0);
                argv[0] = (char *)path;
                argv[1] = (char *)filename;
                argv[2] = NULL;
 
                env[0] = "PATH=/bin:/usr/bin:/etc:/sbin:/usr/sbin";
                env[1] = NULL;
                execve(path, argv, env);   
                err("exec rc failed\n");
                _exit(2);
 
        } else if(pid > 0) {  // parent
                if (!dowait) {
                        stat = 0;
                } else {
                        /* parent, wait till rc process dies before spawning */
                        while ((wpid = wait(&stat)) != pid)
                                if (wpid == -1 && errno == ECHILD) { /* see wait
(2) manpage */
                                        stat = 0;
                                        break;
                                }
                }
        } else if(pid < 0) {    // error in vfork caught here
                err("fork of rc shell failed\n");
                stat = -1;
        }
        st = WEXITSTATUS(stat);
        return st;
}

The inetd internet daemon is given the task of monitoring a number of internet sockets and waiting for a connection on one of them. When a connection is established the daemon has to fork a new process to handle the connection. The /etc/inetd.conf file contains details of the ports to be monitored and the programs used to handle incoming connections.

Example /etc/inetd.conf file



Having established a connection attempt on a designated port the daemon is required to start executing the handler task for that connection. The complication here is that the child has to have the incoming socket as its stdin and stdout devices.

The incoming port (fd in this case ) is transferred to stdin and stdout of the new task after the vfork.

Here is a code example showing this.

// here fd is the incoming socket
static pid_t
start_child(struct stService *p, int fd)
{
        pid_t   pid;
 
        pid = vfork();
 
        if (pid == 0) {                // if we are the child
                if (fd != 0)
                        dup2(fd, 0);  // fd becomes stdin
                if (fd != 1)
                        dup2(fd, 1);  // fd becomes stdout
                if (fd != 2)
                        dup2(fd, 2);  // fd becomes stderr
                if (fd > 2)
                        close(fd);    // now we can close it (the dups stay open)
                close_all_fds(2);
                execlp(p->args[0],    // run the new program with fd as stdin etc
                                p->args[0],
                                p->args[1],
                                p->args[2],
                                p->args[3],
                                p->args[4],
                                p->args[5],
                                NULL
                                );
                _exit(0);   
        }
        return(pid);  // back to the parent here
}

Webservers

A webserver is another example of a process needing to use forks. Each incoming socket is normally passed to a forked clone of the parent.

Consider this code from cgi.c ( in directory user/boa/src ). Here the switch to MMUless is quite painless.

// either fork or vfork will do here 
 
#if HAVE_FORK
    child_pid = fork();
#else
    child_pid = vfork();
#endif

Consider this code from thttpd.c ( in directory user/thttpd/ ) which shows the modification made to the MMUfull version of this code to make it run as a foreground process , skipping the daemonize fork

 
       */
#ifndef EMBED
        (void) fclose( stdin );
        (void) fclose( stdout );
        (void) fclose( stderr );
 
        /* Daemonize - make ourselves a subprocess. */
        switch ( fork() )
            {
            case 0:
            break;
            case -1:
            syslog( LOG_CRIT, "fork - %m" );
            exit( 1 );
            default:
            exit( 0 );
            }
#endif
 

Stubborn Cases

There are a few. You have to fork and you originally ( in MMUfull world) wanted an exact copy of yourself.

These are the problem cases. Normally you can save important data in environment variables and then restart yourself in a call to execve using a command line argument to select the child mode of operation.

The new child task will detect that it is functioning in child mode and restore the needed data from environment variables and continue.

Complete Table of Contents/Topics