Process creation and execution
Process creation occurs by calling the osl_executeProcess(...) function, which loads a program image into a new process. The function definition is:
SAL_DLLPUBLIC oslProcessError SAL_CALL osl_executeProcess(
rtl_uString* ustrImageName,
rtl_uString* ustrArguments[],
sal_uInt32 nArguments,
oslProcessOption Options,
oslSecurity Security,
rtl_uString* ustrDirectory,
rtl_uString* ustrEnvironments[],
sal_uInt32 nEnvironmentVars,
oslProcess* pProcess
);The parameters are:
ustrImageName- the file URL of the executable to be started. This can be NULL, in which case the file URL of the executable must be the first element inustrArguments.ustrArguments- an array of argument strings. Can be NULL ifstrImageNameis not NULL. If, however,strImageNameis NULL the function expects the first element ofustrArgumentswill contain the file URL of the executable to start.nArguments- the number of arguments provided. If this number is 0 strArguments will be ignored.Options- a combination of int-constants to describe the mode of execution.Security- the user and the user rights under which the process is started. This may be NULL, in which case the process will be started in the context of the current user.ustrDirectory- the file URL of the working directory of the new process. If the specified directory does not exist or is inaccessible the working directory of the newly created process is undefined. If this parameter is NULL or the caller provides an empty string the new process will have the same current working directory as the calling process.ustrEnvironments- an array of strings describing environment variables that should be merged into the environment of the new process. Each string has to be in the form "variable=value". This parameter can be NULL in which case the new process gets the same environment as the parent process.nEnvironmentVarsthe number of environment variables to set.pProcess- an output parameter, this variable is a pointer to an oslProcess variable, which receives the handle of the newly created process. This parameter must not be NULL.
On both Windows and Unix platforms, this is a wrapper to osl_executeProcess_WithRedirectedIO().
Example
The following example can be found in my private examples branch.
.../sal/workben/osl/process/executeprocess.cxx
Unix platform implementation details
osl_executeProcess(...) calls on osl_executeProcess_WithRedirectedIO(...), which on Unix platforms works as follows:
Step 1: gets the executable image name, checks that the directory exists if the first argument is NULL.
Step 2: search for the image via the $PATH variable.
Step 3: get the directory the executable resides in.
Step 4: process the arguments.
Step 5: process the environment variables.
Step 6: Load the image and execute it in a new process.
Step 7: clean up the process by freeing allocated memory structures.
Loading and execution the image
The process is actually loaded and executed in step 6 above, with a call to osl_psz_executeProcess(...), which works as follows:
Step 1: Zero-initialize the process data structure.
Step 2: initialize the process' data structure's anonymous pipes.
Step 3: setup the process data structure's executable image name, arguments and working directory.
Step 4: setup the environment variables.
Step 5: sets up the security of the process - sets the Unix user ID (uid), group ID (gid) and the name of the process owner.
Step 6: initializes the process ID (PID) as 0, sets up a condition variable (for more details on this, see the threads chapter), and sets the next process in the linked list to NULL.
Initializes the process ID (PID) as 0, sets up a condition variable (for more details on this, see the threads chapter), and sets the next process in the linked list to NULL.
Note the line:
This sets up a condition variable that is set if the thread is unexpected terminated.
Step 7: ChildListMutex ensures that the pointer to the global pointer to the mutex that protects access to the global linked list of child processes is set to a new mutex.
Step 8: The process is actually executed in this thread, when it is done it sets the condition variable to allow the function to shutdown the process cleanly.
Note that it calls on osl_createThread(ChildStatusProc, &Data) - we fork and execute the process in ChildStatusProc(...) which I will detail later.
Step 9: Free up all resources
The process is actually executed in this thread, when it is done it sets the condition variable to allow the function to shutdown the process cleanly.
Step 10: If the process has been flagged to wait, then this waits for the child process to finish (via osl_joinProcess(*pProcess), which blocks the current process until the specified process finishes).
Step 11: If the process was terminated abnormally the application cleans up by destroying the termination condition variable, and frees the process structure.
ChildStatusProc(...) function
ChildStatusProc(...) functionChildStatusProc(...) forks and executes the process, sets up a Unix domain socket between the child and parent processes and redirects IO pipes. It works as follows:
Step 1: setup the function
We need to declare the function with C linking.
It gives the current thread a name and declares the variables needed.
Handles operating systems that have no processes.
Step 2: create a Unix domain socket so that the parent and child processes can communicate. A Unix domain socket is part of the Unix address family (what the "AF" in "AF_UNIX" stands for), and uses a byte-oriented bi-directional stream. The socketpair(...) function returns a pair of file descriptors that define both communication endpoints. These file descriptors are set to close on execution termination with fcntl(...) by setting FD_CLOEXEC.
Step 3: create pipes for standard input, standard ouput and standard error
Step 4: fork the process. What this means is that the process is cloned with a new process ID, and the cloned process is made the child of the process that forked it.
Child process: Step 1: if fork(...) returns 0 then the process is the child process.
A copy of the file descriptors of the parent process is provided to the child process, which means that the child process needs to close the file descriptor used for the parent process of the Unix domain socket used for IPC between the child and parent processes. This needs to be done because until all file descriptors referencing this socket are closed the resource will not be freed.
Child process: Step 2: if there is a valid process user owner (data.m_uid is not -1) and the process owner or the process group of the child process are not the same as the parent's, then the process's uid and guid is changed to the one passed to the function. It also clears the $HOME environment variable.
Child process: Step 3: change the working director of the process
Child process: Step 4: if allowed, then checks for invalid environment variables, closes the write end of the standard input descriptor and the read end of standard output and standard error (as these do not get used in the child process and redirects the pipes created earlier to their corresponding pipe ends
Child process: Step 5: program now executes the program and passes on the arguments via execv(...).
Child process: unreachable code: we can only get into this section if execv fails. If this occurs, then we send the error to the parent process to advise it of the problem.
Parent process: Step 1: fork() returns a non-zero positive value that holds the child process' ID when in the parent process that called on fork(). If the value is -1 then this indicates an error and errno is set.
As with the child process closing the Unix domain socket's parent file descriptor, the parent process must close the child process' Unix domain socket file descriptor so it can later be reclaimed by the operating system.
The unused pipe ends of the parent process must also be closed for the same reason.
Parent process: Step 2: If the PID is less than 0, then it indicates an error. The parent process waits for the child process to send its error to the parent process, which it reads from the socket. If the read fails, then errno returns EINTR so it breaks out of the loop.
Parent process: Step 3: Once the child process has terminated, then close the IPC socket on the parent.
Parent process: Step 4a: If the process finished cleanly, then lock the child list, record the process ID, add the process to the linked list of children, and store the pipe ends in ProcessData structure.
Notify threads that the process has finished starting.
Now for final cleanup we need to run waitpid(...) on the child process.
Walk the child process list until it finds the child process that exited and check to see and store the process status - if the process exited normally (WIFEXITED(status)) then record this, otherwise if the process terminated abnormally (WIFSIGNALED(status)), in which case store the termination process status, or if it terminated for any other reason then store -1.
After the status has been recorded set the termination condition variable.
Parent process: Step 4b: If the process terminated abnormally the close the pipe ends, and if for some reason the child process was actually created, to prevent the process from being a "defunct" process wait on the process, then once this is done set the condition variable to notify the parent thread.
Windows implementation
osl_executeProcess(...) calls on osl_executeProcess_WithRedirectedIO(...), which on the Windows platforms works as follows:
Step 1: get the executable image name
Step 2: check if the executable is a batch file, if so add the "batch processor" (normally cmd.exe, which requires a /c switch) to the processor
Step 2: process the arguments from the command line
Step 3: process the environment variables
Step 4: get the directory the executable resides in.
Step 5: setup the process.
When the process is created from the command processor, the command processor creates a new console process, which is a character mode application that has an input buffer and one or more screen buffers. When the command processor creates a new process, this new process inherits the command processors's console, unless the CreateProcess(...) function is passed a CREATE_NEW_CONSOLE flag (in which case, the new process creates a new process with a new console), or DETACHED_PROCESS (which creates a new process, that doesn't have a console process attached to it). These two flags are obviously incompatible, hence the check to see if CREATE_NEW_CONSOLE is set as a flag option.
Allocate the initial STARTUPINFO instance (STARTUPINFO "specifies the window station, desktop, standard handles, and appearance of the main window for a process at creation time.")
Size of STARTUPINFO in bytes.
Flags that wShowWindow holds information.
Indicates that the process should connect to the interactive window station of the current user.
Redirect IO pipes.
Specify the type of window - hidden, minimized, maximized or set to full screen mode.
Setup the command line for the process.
Step 6: Creates the process, either as an impersonated user or as the current user. CreateProcess(...) takes the command line, startup info, process creation flags, environment and current working directory. It the returns the process information in a PROCESS_INFORMATION structure.
Step 7: Once the process has been created, then the handles need to be closed.
Last updated
Was this helpful?