next up previous contents index
Next: A.2 Signals and Alarms Up: A. Operating System Concepts Previous: A. Operating System Concepts   Contents   Index


A.1 Unix Processes

Multitasking means concurrent execution of programs. A task can be a process or a thread, depending on the operating system. As there are usually more tasks than hardware processors in a computer system, the operating system has to multiplex the resources (processor, memory and I/O) to the tasks. There are various strategies for scheduling. In the following, we will focus on preemptive multitasking, which means that a task has no influence on how long it can keep the resources exclusively (and therefore can't block the system).

Figure A.1: Multiprocessing and Multithreading (View PDF)

Imagine a task like a virtual computer -- it offers a CPU, memory and I/O. The state of a task can be found in the processor registers (for example the Program Counter PC) and the stack (the return addresses, the parameters of the procedure calls and local variables). The difference between processes and threads, as shown in figure A.1 is the isolation: While each process has an isolated memory space for its own, all threads (of a process) share one memory space (including the program, of course). Threads are usually bound to processes which define the resource set shared by the threads. In the following, we will focus on UNIX processes.

A.1.1 fork()

In Unix processes are created using the system call fork(). The first process of a program is created upon the start of the program. Using fork() an additional process can be created. Fork simply creates a copy of the current process including all attributes. Like its parent, the new process continues with the code that follows the fork() instruction. To distinguish parent from child, the return value of the fork call can be used. Fork will return 0 to the newly created process while the parent gets the process id of the child process. Therefore the fork system call is usually followed by a decision based on fork's return value.

The example code below is a shortened extract of the make_child procedure of the Apache 2.0 preforking MPM:

if ((pid = fork()) == -1) 

         /* This path is only entered 

          * when fork did not succeed */   
         ap_log_error(APLOG_MARK, APLOG_ERR, errno, s, 

                      "fork: Unable to fork new process");  
         /* fork didn't succeed. Fix the scoreboard 

          * or else it will say SERVER_STARTING 

          * forever and ever */  
        (void) ap_update_child_status_from_indexes(slot, 

                         0, SERVER_DEAD,(request_rec *) NULL);  
        /* In case system resources are maxxed out, 

         * we don't want Apache running away with the

         * CPU trying to fork over and over and 

         * over again. */  

        return -1; 

if (!pid) { 

        /* In this case fork returned 0, which 

         * means this is the new process */   
        apr_signal(SIGHUP, just_die); // It registers a 

        apr_signal(SIGTERM, just_die);// for a few 

                                      // signals   
       child_main(slot); /* And then calls the main 

                           * routine for child worker 

                           * processes, which it will 

                           * never return from. */

/* As the child never returns only the parent process

 * will get to this stage and only  if the fork call 

 * succeeded. The variable PID contains the process 

 * ID of the newly spawned process, which it write to 

 * the scoreboard, a table used to register processes */  
ap_scoreboard_image->parent[slot].pid = pid; 

return 0; 

Figure A.2: Internals of the fork() system call (View PDF)

Look at figure A.2 for a petri net version of the source code above. Looking at the transitions at the bottom we see that the program provides three possible cases depending on the return value of fork(). Some of them are grey because they will never be executed at all, depending on whether the parent or the child process executes the code.

A.1.2 exec()

When a process wants to execute a different program it can use any of the exec() system calls. Exec discards all data of the current process except the file descriptor table -- depending of the variant of exec this in- or excludes the environment -- and loads and starts the execution of a new program. It can be combined with a prior fork() to start the program in a new process running concurrently to the main process.

All variants of exec() serve the same purpose with the exception of the way the command line arguments are submitted. The different exec calls all expect command line arguments using different data types which gives flexibility to the programmer. Additionally there are separate calls that can be used when a new environment is desired for the program that is to be executed. Otherwise the program will simply use the existing environment of the calling process.

The different exec calls are:

  argument list argument vector
keeping environment

Execs demanding an argument list expect multiple arguments each containing a single argument to the program called. Execs demanding a vector demand an array of arguments just like the **argv array that each main method expects.

A.1.3 wait() & waitpid()

The system calls wait() and waitpid() are used to synchronize a parent process with the termination of its child processes. The parent blocks until a child process dies. The difference is that wait() waits for the termination of any child process while waitpid() allows to specify the child process by its PID. The wait system call has some closely related variants, wait3() and wait4() which allow to gather more information about the process and its termination and to supply options for the waiting.

A.1.4 kill()

The kill() system call is used to send signals to processes. Even though the name implies that the kill() system call is solely used to kill processes, more is behind it.However, a signal that is neither caught nor ignored terminates a process which is why this system call is called kill. The syntax is kill(int pid, int signal). Most signals can be caught or ignored, however the signal SIGKILL (#9) cannot and will terminate the process in any case. For more information on signals and the kill() system call look at the next section.

next up previous contents index
Next: A.2 Signals and Alarms Up: A. Operating System Concepts Previous: A. Operating System Concepts   Contents   Index
Apache Modeling Portal Home Apache Modeling Portal