|
» |
|
|
|
| | |
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() Routine | Purpose |
---|
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_cleanup | Copies 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 getxfileThe 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
getxfile() performs the following: Verifies that no other program is currently
writing to the file. Sets up a.out-dependent
values needed to load the file into memory. Checks page alignment. Switches with the magic number appropriate to the
executable's a.out. Three
types are defined for the case statement: 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. 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. Creates new uarea
for the child and restore the parent's uarea. At this
point, if the process was vfork'd,
call vfork_createU(). 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. 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(). For programs with large marked executables, execute
code to reduce ITLB misses. Call add_text(). 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 ProcessThe 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. Table 1-7 vfork
subroutines called by getxfile Subroutine | Purpose |
---|
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_switchU | Switches the current process to a new uarea/stack. | vfork_transfer | The 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 EnvironmentIn 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. | | | | |
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.
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.
|