Sie sind auf Seite 1von 9

Process Management

1 Introduction
Many operating systems rely upon processes either for their own structure or for the support of user tasks (or both). The final part of your previous module on Operating Systems considered a structure for an operating system based upon processes. The following examines the more general use of processes and in particular looks towards Unix1 for its examples. In the early stages, though, an attempt is made to generalise the principles involved in process organisation and operation with specific reference to Unix being made only when the fundamentals are clear. Systems exist that utilise either static processes or dynamic processes or both. Elsewhere in this module consideration of real-time environments makes use of static processes, each taking care of a particular part of the solution. It is more common, though, for systems to employ dynamic processes whereby processes are created and destroyed as necessary. The remainder of this document will concentrate upon dynamic processes.

2 Process Creation and Disposal


The presence of dynamic processes within a system demands that mechanisms exist for the creation and destruction of processes. In most of such systems, processes are created by other processes, resulting in a tree-like structure. Thus each process, apart from the root process, will have a parent and any process will have zero or more child processes. Child processes are created on behalf of the parent process in response to what we shall call a fork operation. In the diagram below process P1 is the parent of processes P2 and P3. Process P2 itself has a single child process, namely P4, whilst process P3 can be seen to have spawned two child processes, P5 and P6. None of processes P4, P5 or P6 themselves have children, although there is nothing inherent in the structure that would prevent any or all of them from spawning further processes subsequent to this snapshot being taken.
P1

P2

P3

P4

P5

P6

Once a process has been created it will, in the general case, run concurrently with its parent. System functions exist to allow a parent process to wait for a child process to complete, denoted by the child executing an exit operation, before it continues. This is, in effect, the opposite of the fork operation and produces a logical join. The execution of related parent and child processes following the aforementioned pattern may be viewed as follows:
1

Throughout the notes for this module, Unix should be taken to also refer to any flavour of Linux. Process Management 1 Colin H C Machin, January 2012

parent process child process fork(...) (creation)

wait(...) (join) (disposal) exit(...)

The fork operation requests that a child process be created and the two processes then execute concurrently. In the above example, the parent process is shown waiting for the child process to complete before the join is effected. The simultaneous existence of both the wait operation on the parents side and the exit operation on the childs marks the join. Once the child process has executed an exit operation, the system is free to dispose of the process, provided the parent has already reached the corresponding wait. If the child process executes an exit operation before the parents wait, the child process must not be disposed of; Unix uses the term zombie to refer to such processes that are no longer subject to scheduling but will be consuming resources such as memory. Note that wait and exit are system operations whereas the join is only a logical viewpoint, highlighted purely for the purpose of this description. When the child process is spawned it will inherit at least some of its parent's resources. In most systems, at least those that utilise segmentation, processes share with their parents the program memory area and any open files and channels. Of course, the child process will require its own data memory, but even this starts as a copy of the parent's data space. A table is provided in section 2.6 towards the end of this document detailing the manner in which resources and other attributes are inherited by processes under Unix. In addition to normal process termination, many systems allow premature termination of child processes. This abort operation is normally only available to the parent of the process in question. Should the parent of a process terminate, usually inadvertently, some systems allow the child process to continue whilst others will automatically terminate all of the child processes of the terminated parent. In the former case, the system inherits any orphans thus created. This is the preferred option as significant difficulties, such as those arising from files being left in an unexpected state, could ensue.

2.1 The Parent/Child Relationship


The manner in which a child process behaves depends upon the reason for the parent spawning the child in the first place. Given that the child process is an exact copy of its parent, a straightforward method is created for organising some form of parallel processing. Both parent and identical child processes are contributing to the solution of the same problem and are both subject to the normal scheduling scheme, competing for processor time like any other process. The scheduler will attempt to divide time between these processes on a fair basis and although, if we assume a single processor, the two processes can never actually run simultaneously, the appearance is that they do so. Even with multiple processors, there is no guarantee that the two processes will run simultaneously, but still the illusion is maintained. Returning to the reasons why a
Process Management 2 Colin H C Machin, January 2012

process might spawn a new process, this style of operation might be used when processing a tree structure of some sort. The parent process might fire off identical processes to deal with the left and right branches at a particular node, for example. Of course, without the benefits of multiple processors or, at least, multiple cores, the overhead of arranging to run two processes rather than one will outweigh any benefit. Much more commonly, a process spawns another so that the new process can perform some task that is totally unconnected with the processing being performed by the parent process. For example, in Unix all processes that run on behalf of a given user in order to process commands are spawned by that user's shell. Its sole purpose is to receive commands from the user and select the appropriate executable program to satisfy each command. At present, the only way that a process can be spawned is for the parent process - the shell in this case - to spawn a new process that is an exact copy of the shell process. This is insufficient in this situation; the requirement here is for the new process to change its identity immediately and take on the appearance of the program that will satisfy the user's command. Assume that the user wishes to receive a directory listing and therefore types the command ls. The shell must somehow organise that the ls program, held in one of the system directories (traditionally /bin), is loaded and run. The shell performs a fork operation in order to create a new process that will ultimately support the ls program's execution. The parent process continues to be the shell program, but the child process must change its identity to ls. It must request that the operating system throw away its current program and data areas and replace them with those specified for the ls program. In general terminology, this operation is known as an exec operation and at the 'C' programming level may be assumed to take a single parameter that specifies the location of the executable program's file. In the example chosen, the parent process would normally be required to wait for the child process to terminate before displaying the user prompt and awaiting a further command. Should, though, the user type an ampersand after the command name, this denotes that the program (process) is to run in background mode. In this case the shell would not issue a wait operation but would instead immediately display a new prompt to the user. In this case, it would be up to the user to handle the synchronisation of the completion of the new process with any other activity, as and when required. The user would in essence be testing for completion of the new process - in effect doing the same as the operating system's wait operation.

2.2 Process Creation - The fork Operation


2.2.1 The Program's View
In order to establish a scheme for the fork operation, it will be assumed that the child process will inherit the parent's program space and that fork will have created for it a copy of the parent's data space. Thus, upon return from the fork system call, both processes will resume with the same relative address in their program counter (PC). Further, it will be assumed that the return code from the fork operation is slightly different for the child than it is for the parent, allowing each to identify their position in the hierarchy. In fact, we shall specify that the system returns zero to the child process whilst returning the process id of the child to the parent process. This means that the statement that the child receives a copy of the parent's data area is not entirely true. The data area received by the child process is an exact copy of the parent's data area except that the return code from the fork system call is different. Considering this
Process Management 3 Colin H C Machin, January 2012

in terms of 'C' code, where it is likely that the top of stack carries the return value, this is seen to be the only difference between the parent's data memory and that of the child. This gives a clue as to the likely context of the fork operation. In terms of a 'C' program, the fork operation would typically appear as shown below.
if ((id = fork()) > 0) { | (parent's code) | } else { | (child's code) | }

It must be remembered that at the machine-code level, there will be a number of instructions that follow the one that actually marks the fork SVC, which will be executed by both the parent and the child. These are the instructions that contribute to returning from the function call, unloading the return code and the commencement of the 'C' if statement. It will only be as the if statement is processed that the parent and child process executions will diverge. However, viewing the operation from the 'C' code level is sufficiently detailed and equally instructive.

2.2.2 The Operating System's View


A number of steps have to be taken by the operating system in order to accommodate a new process and a representative list is given below. Before reading it, please note that steps 7 and 8 are unique to the implementation from which this sequence is taken, namely Andrew Tanenbaum's Minix operating system [Operating Systems Design and Implementation (1987)]. As a result they should be considered as not strictly necessary to the understanding of the implementation of fork operation. 1. Check if the process table is full. The operating system will not have an infinite capacity for creating processes and this check ensures that the predefined capacity is not exceeded. As this step could cause the fork operation to fail, it is performed first so that the whole operation can be aborted before any resources are allocated. 2. Attempt to allocate memory for the child. This will involve finding or creating space only for the data area, assuming that the memory management capability of the system allows shareable code segments. Whilst in theory it might prove impossible to allocate sufficient memory, it is highly unlikely with a virtual memory system in operation. In that event, though, the fork operation will abort here before any resource allocations are made. Once this stage is complete, fork is guaranteed to complete. 3. Copy the parent's image to the newly allocated memory. This should involve copying only the data part, memory management facilities permitting. 4. Locate a free slot in the process table and copy the parent's process data into it. This ensures that the child process is an exact copy of the process as well as the program as all of its resource allocations will be the same as the parent's.

Process Management

Colin H C Machin, January 2012

Depending upon the precise manner in which the data defining a process is stored, this step may involve updating more than one physical table. 5. Enter the child's memory map into the process table. The only part of the process data relating to the child process that differs from that of the parent is the section that describes where the (data part of the) process is stored. 6. Choose a process id for the child. This must be unique and is usually the next available process number in order. 7. Inform the kernel and the filing system about the child process. This is so that they may both update their tables resulting in the scheduler being able to consider this process. 8. Report the child's memory map to the kernel. This step is necessary so that the kernel becomes aware of the revised memory allocations. 9. Drop the respective return codes into the parents and childs data memory. This allows the two processes to identify themselves and thus to distinguish the child from the parent.

2.3 Replacing the Code - The exec Operation


2.3.1 The Program's View
When a process wishes to run another program, it executes an exec system operation. Most systems offer a range of exec-style operations (BSD has six versions, none of which is called simply exec) with each one providing a different way of passing command arguments and/or environments to the new program. This system call requests the operating system to replace the present memory image with a new one as defined by the executable program name passed as a parameter. Assume that the shell has detected that the user has typed the ls -l command at the prompt. It first creates a new process by means of a fork operation and then the newly created child process executes an exec call. In reality, the shell would build the name of the program into a string variable, but for clarity it may be assumed that the child process executes the following call:
exec("/bin/ls");

This causes the operating system to replace the current code area for the child process with the code for the ls program, the detail of which is described in section 2.3.2. Note that the command argument -l does not form part of the string passed in the exec call. Instead, it is passed, as previously mentioned, in an argument to one of the specific versions of exec. The source of the shell program (parent process), then, will appear something like the following:

Process Management

Colin H C Machin, January 2012

if ((id = fork()) > 0) { | (parent's code) | } else { exec("/bin/ls"); }

2.3.2 The Operating System's View


The provision of the exec system call is perhaps one of the most demanding tasks of the operating system. The following steps are required to satisfy an exec call. 1. Mark process as non-runnable. As the loading procedure is likely to include disk access, the scheduler must not be allowed select this process during the autonomous disk transfers that will inevitably occur. 2. Check that the selected file is executable its inode possesses an X attribute for the current user and the file has the appropriate format and magic number. 3. Establish the memory requirements by examining the program's file header. Ensure that sufficient memory exists before proceeding to the allocation procedure. 4. Collect any arguments or other parameters from the exec call as appropriate to the particular flavour of exec in use. 5. Reclaim (but do not overwrite) the previously allocated memory and allocate memory according to the new memory requirement. In many cases the new memory requirement will exceed the previous allocation and so this step may involve locating further memory areas over and above those reclaimed. 6. Copy the process stack to the new memory allocation. 7. Copy the program and data segments from the executable file to the newly allocated memory areas. 8. Make the necessary changes to the process table data. This is done so that the data held about the process now relates to its new size and position in memory and reflects any changes in priority or privileges that might have taken place. 9. Inform the kernel that the process is now runnable. When the new program executes within the existing process, it retains a number of resources and other items that were inherited from the parent process. The process retains its process id and its link back to its parent. The process owner's id is retained unless it has been explicitly changed (by, for example, the set uid bit in Unix, see the section on Domain Switching in the notes on Protection). The working directory is maintained, as is the root directory of the parent, along with the connection to the control terminal. Any resource limitations or accumulated usage statistics associated with the parent's accounting mechanisms are also carried to the. There are system calls in various operating systems that combine exec and fork. Windows, for example, has its spawn function.

Process Management

Colin H C Machin, January 2012

2.4 Inter-Process Cooperation - The wait Operation


In order to enable inter-process cooperation, the wait operation allows the calling process to wait for a child process to terminate and then to see its status. If a given parent has a number of child processes, it cannot be specific about which process it is waiting for, instead, successive calls to wait will yield different child processes. The example below shows the wait operation in the context of a 'C' program executing on a machine called hpc.
#include <stdio.h> #include <sys/wait.h> main() { int pid1, pid2, pid; union wait status; printf("First fork ...\n"); if ((pid1 = fork()) == 0) /* First child */ { printf("First child pid = %d\n", getpid()); exit(0); } printf("Another fork ...\n"); if ((pid2 = fork()) == 0) /* Second child */ { printf("Second child pid = %d\n", getpid()); exit(0); } printf("First wait ...\n"); pid = wait(&status); printf("pid = %d, status = %d\n", pid, status.w_status); printf("Second wait ...\n"); pid = wait(&status); printf("pid = %d, status = %d\n", pid, status.w_status); }

The output from two successive runs of this program is shown below. Note that the order of the output lines is different in the two cases. This demonstrates a certain level of randomness in process scheduling within Unix.
hpc% fork First fork ... Another fork ... Second child pid = 13820 First child pid = 13819 First wait ... pid = 13819, status = 0 Second wait ... pid = 13820, status = 0 hpc% fork First fork ... First child pid = 13878 Another fork ... First wait ... Second child pid = 13879 pid = 13879, status = 0 Second wait ... pid = 13878, status = 0 hpc%

The program is sufficiently short that the reader should, with minimal effort, be able to key it in and try it on any Unix system.
Process Management 7 Colin H C Machin, January 2012

2.5 Inter-Process Communication - The pipe Operation


Inter-process communication takes place in any given system using one or more of a number of available schemes. In Unix, the pipe operation is a commonly used means by which processes communicate data. A pipe is a one-way communication structure set up between two processes. Various implementations of pipes exist in other operating systems, MS-DOS, for example, with some using memory for the communication and others using the filing system. Unix implements pipes in memory whilst MS-DOS uses files, although either could be referred to as a shared memory solution. Both manifest themselves to programs in the same way as files. Given that parent and child processes share open files, in Unix, the parent process sets up the pipe before the fork operation is executed to create the child that will communicate along the pipe. This works because the parent and child processes share open files, and so the file descriptor is carried form the parent to the child. The pipe function carries a single parameter, which is a pointer to a two-element array. The function places identifiers for the read and write ports of the pipe into the two elements of the array, with pipe_id[0] pointing to the read end and pipe_id[1] pointing to the write end. The following example 'C' program illustrates how pipes are utilised.
#include <stdio.h> #include <strings.h> #include <sys/wait.h> main() { int i, pid, pipe_id[2]; union wait status; char buffer[20]; pipe(pipe_id); if ((pid = fork()) == 0) /* Child process */ { close(pipe_id[1]); if ((i = read(pipe_id[0], buffer, 20)) == -1) { perror("Child failed to read pipe\n"); exit(-1); } printf("Received by child ...\n%s\n", buffer); exit(0); } close(pipe_id[0]); /* Parent process */ write(pipe_id[1], "Hello there", 11); wait(&status); if (status.w_status == 0) printf("Child process complete\n"); else printf("Child process failed\n"); }

In this example the pipe is used to transfer data only from the parent to the child as, since the pipe is only a one-way channel, the child process closes its write port and the parent process closes its read port. Executing the above program on hpc results in the output shown.
Process Management 8 Colin H C Machin, January 2012

hpc% pipe Received by child ... Hello there Child process complete hpc%

As before, the reader is encouraged to key in and try the above program.

2.6 Resource and Attribute Inheritance - fork() and exec()


The following table gives details of how various process resources and other attributes are inherited (or not) by processes as they are subject to fork() and exec() operations. Resource/Attribute Process ID Real User ID Effective User ID Static Data Stack Heap Code Open File Descriptors Environment Current (working) Directory Root Directory Signal-handling Behaviour Inherited by child? No, a new ID is allocated Yes Yes Copied to new data memory area Copied to new data memory area Copied to new data memory area Shared Copied; file descriptors are shared Yes Yes Yes Copied Retained on exec? Yes Yes Usually, but not if the setuid bit of the executable file is set No, the data memory is filled with the requisite data for the new program No, the stack area of the data memory is undefined until the child process runs No, the heap area of the data memory is undefined until the child process runs No, the code memory is filled with the requisite code for the new program Usually, but can be explicitly prevented using an fcntl() call on the descriptor Depends on which version of exec() is used Yes Yes Partially

Process Management

Colin H C Machin, January 2012

Das könnte Ihnen auch gefallen