HPlogo HP-UX Process Management: White Paper > Chapter 1 Process Management

Process Execution

» 

Technical documentation

Complete book in PDF

 » Table of Contents

Once a process is created with fork() and vfork(), the process calls exec() (found in kern_exec.c) to begin executing program code. For example, a user might run the command /usr/bin/ll from the shell and to execute the command, a call is made to exec().

exec(), in all its forms, loads a program from an ordinary, executable file onto the current process, replacing the existing process's text with a new copy of an executable file.

An executable object file consists of a header (see a.out(4)), text segment, and data segment. The data segment contains an initialized portion and an uninitialized portion (bss). The path or file argument refers to either an executable object file or a script file of data for an interpreter. The entire user context (text, data, bss, heap, and user stack) is replaced. Only the arguments passed to exec() are passed from the old address space to the new address space. A successful call to exec() does not return because the new program overwrites the calling program.

The Routines of exec

The exec() system call consists of numerous routines and subroutines that prepare the environment to execute the command in an orderly fashion. The table that follows describes them.

Table 1-6 Major routines and subroutines of exec()

RoutinePurpose
exec

Called from user space with arguments of filename, argv array, and environment array.

Calls execv(), which calls execve()

execve

Determines characteristics of the executable:

  • Gets the complete file path name, its vnode and attributes.

  • Makes calls to the vnode-specific routines to extract information about the uid and gid of the executable.

  • Ascertains whether the executable is a script, and if so, gets its "interpreter" name, arguments, vnode pointer, object file, and relevant kernel information; then sets up the arguments to enable the shell script to run.

  • Sets up the structure to enable copying in from user space to kernel space.

  • Copies the filename, argument, and environment pointers.

  • Gets the new executable by calling the subroutine getxfile().

getxfile

Sets up structures:

  • Sets up memory and reads in the executed file according to the executable's magic number.

  • Sets up kernel stack by moving stack pointers from user stack to make room for argument, environment pointers and strings.

  • Copies the buffer containing the name of the executable and arguments into a per-process record (pstat_cmd).

  • Saves argc and argv values in the save_state structure.

exec_cleanupCopies arguments onto the user stack.If cannot load a.out, clean up memory allocations.Release any buffers held and file vnode.

 

A Closer Look at getxfile

The execve() function calls getxfile() to map out the memory that will be used by executed file. When called, getxfile() is passed in:

vp

Pointer to a vnode representing the file to be executed

argc

A count of argments

uid, gid

User and group IDs

ap

Header file information

vattr

vnode attributes

Figure 1-13 kernel view of getxfile

[kernel view of getxfile]

getxfile() performs the following:

  1. Verifies that no other program is currently writing to the file.

  2. Sets up a.out-dependent values needed to load the file into memory.

  3. Checks page alignment.

  4. Switches with the magic number appropriate to the executable's a.out. Three types are defined for the case statement:

    EXEC_MAGIC407Creates only a data object
    SHARE_MAGIC410Creates text and data, but does not assume file alignment
    DEMAND_MAGIC413Assumes both memory and file are aligned

    Calls the function create_execmagic() for the case of EXEC_MAGIC to place the entire executable (text and data) into one region.

    Checks the sizes and alignments of the a.out.

  5. Determines whether the process will execself(). If execself(), calls dispreg() to dispose of old pregions. From this point on, the process is committed to the new image. Releases virtual memory resources of the old process and initializes the virtual memory of the new process.

  6. Creates new uarea for the child and restore the parent's uarea. At this point, if the process was vfork'd, call vfork_createU().

  7. Having destroyed the old address space of the process, load the executable into the new address space. Determine whether executable is using static branch prediction and whether text should be locked down using superpages. Build set of pregions and regions for each magic number.

  8. Depending on the file size and offset and given the implementation of memory-mapped files, determine how much of the file should be mapped. Call mapvnode to provide the flexibility. Call add_text() and add_data().

  9. For programs with large marked executables, execute code to reduce ITLB misses. Call add_text().

  10. After the switch has completed, set up the bss, by calling add_bss(), and the stack, by calling add_stack().

If getxfile is Called by a vfork'd Process

The child process runs using the parent's stack until the process does an exec() or exit(). In the case of exec(), the routine vfork_createU() is called to create a new vas and Uarea for the child (it copies the current ones into these). We then call vfork_switchU() to activate the newly created uarea and to set up the space and pid registers for the child. The state is then set to VFORK_CHILDEXIT. On an exec(), we call vfork_transfer() directly from vfork_createU() to restore the parent's stack. The parent is then awakened.

Figure 1-14 Runnable vfork'd child calls exec()

[Runnable vfork'd child calls exec()]

Table 1-7 vfork subroutines called by getxfile

SubroutinePurpose
vfork_createU

Called when child does a vfork() followed by an exec().

Sets up a new vas and dups the stack/uarea from the parent (which it has been using until now).

Switches the child to use the created stack/uarea.

vfork_switchUSwitches the current process to a new uarea/stack.
vfork_transferThe code that implements vfork. When a process does a vfork, a vforkinfo struct is allocated, shared, and pointed to by the vfork'd parent & child process. The vfork_state is set to VFORK_INIT until the child is made runnable in procdup(), when the state is set to VFORK_PARENT and the parent is put to sleep. At this point the child runs in the parent's vas using the parent's uarea and stack. The vfork'd process calls vfork_transfer from within the VFORK_PARENT state. The schedlock is held to prevent any process from running during the save(). The sizes of the stack and uarea are calculated and copied into the vforkinfou_and_stack_buf area to enable the parent to be restored when the child does an exec or exit. Then the state of the process is set to VFORK_CHILDRUN and returned.If the child exits, it changes its state to VFORK_CHILDEXIT, calls swtch() and awakens the parent, which calls resume() to restore its stack. The parent cleans up the vforkinfo structure.

 

vfork in a Multiprocessor Environment

In a multiprocessor environment, if vfork() is called, the child must not be picked up by another processor before the parent is fully switched out. To prevent this from occurring the TSRUNPROC bit is left on. The code that picks up a process to run (find_process_my_spu()) ignores TSRUNPROC processes. When the parent has switched out completely, it will clear the TSRUNPROC bit for the child.

The sleep*() R outines

Unless a thread is running with real-time priority, it will exhaust its time slice and be put to sleep. sleep() causes the calling thread (not the process) to suspend execution for the required time period. A sleeping thread gives up the processor until a wakeup() occurs on the channel on which the thread is placed. During sleep() the thread enters the scheduling queue at priority (pri).

  • When pri <= PZERO, a signal cannot disturb the sleep

  • If pri > PZERO the signal request will be processed.

  • In the case of RTPRIO scheduling, a signal can be disturbed only if SSIGABL is set. Setting SSIGABL is dependent on the value of pri.

NOTE: The sleep.h header file has parameter and sleep hash queue definitions for use by the sleep routines. The ksleep.h header file has structure definitions for the channel queues to which the kernel thread is linked when asleep.

Figure 1-15 sleep() routine

[sleep() routine]
  • sleep() is passed the following parameters:

    • Address of the channel on which to sleep.

    • Priority at which to sleep and sleep flags.

    • Address of thread that called sleep().

  • The priority of the sleeping thread is determined.

    • If the thread is scheduled real-time, sleep() makes its priority the stronger of the requested value and kt_pri.

    • Otherwise, sleep() uses the requested priority.

  • The thread is placed on the appropriate sleep queue and the sleep-queue lock is unlocked.

    • If sleeping at an interruptable priority, the thread is marked SSIGABL and handle any signals received.

    • If sleeping at an uninterruptable priority, the thread is marked !TSSIGABL and will not handle any signals.

  • The thread's voluntary context switches are increased and swtch() is called to block the thread.

  • Once time passes and the thread awakens, it checks to determine if a signal was received, and if so, handles it.

  • Semaphores previously set aside are now called again.

wakeup()

The wakeup() routine is the counterpart to the sleep() routine. If a thread is put to sleep with a call to sleep(), it must be awakened by calling wakeup().

When wakeup() is called, all threads sleeping on the wakeup channel are awakened. The actual work of awakening a thread is accomplished by the real_wakeup() routine, called by wakeup() with the type set to ST_WAKEUP_ALL. When real_wakeup() is passed the channel being aroused, it takes the following actions:

  • Determines appropriate sleep queue (slpque) data structure, based on the type of wakeup passed in.

  • Acquires the sleep queue lock if needed in the multiprocessing (MP) case; goes to spl6 in the uniprocessing (UP) case.

  • Acquires the thread lock for all threads on the appropriate sleep queue.

    • If the kt_wchan matches the argument chan, removes them from the sleep queue and updates the sleep tail array, if needed.

    • Clears kt_wchan and its sleeping time.

    • If threads were TSSLEEP and not for a beta semaphore, real_wakeup() assumes they were not on a run queue and calls force_run() to force the thread into a TSRUN state.

    • Otherwise, if threads were swapped out (TSRUN && !SLOAD), real_wakeup() takes steps to get them swapped in.

    • If the thread is on the ICS, attributes this time to the thread being awakened. Starts a new timing interval attributing the previous one to the thread being awakened.

  • Restores the spl level, in the UP case; releases the sleep queue lock as needed in the MP case.

force_run()

The force_run subroutine marks a thread TSRUN, asserts that the thread is in memory (SLOAD), and puts the thread on a run queue with setrq(). If its priority is stronger than the one running, force a context switch. Set the processor's wakeup flag and notify the thread's processor (kt_spu) with the mpsched_set() routine. Otherwise, force_run() improves the the swapper's priority if needed, sets wantin, and wakes up the swapper.