Process management

The first responsibility of procnto is to dynamically create new processes. These processes then depend on procnto's other responsibilities of memory management and pathname management. Process management consists of both process creation and destruction as well as the management of process attributes such as process IDs, process groups, user IDs, and so on.

Process primitives

The process primitives include:

posix_spawn()
POSIX
spawn()
BlackBerry 10 OS
fork()
POSIX
vfork()
UNIX BSD extension
exec*()
POSIX

posix_spawn()

The posix_spawn() function creates a child process by directly specifying an executable to load. To those familiar with UNIX systems, the posix_spawn() call is modeled after a fork() followed by an exec*(). However, it operates much more efficiently in that there's no need to duplicate address spaces as in a fork(), only to destroy and replace it when the exec*() is called.

In a UNIX system, one of the main advantages of using the fork()-then-exec*() method of creating a child process is the flexibility in changing the default environment inherited by the new child process. This is done in the forked child just before the exec*(). For example, the following simple shell command would close and reopen the standard output before exec*()'ing:

ls > file

You can do the same with posix_spawn(); it gives you control over the following classes of environment inheritance, which are often adjusted when creating a new child process:

  • File descriptors
  • Process user and group IDs
  • Signal mask
  • Ignored signals

There's also a companion function, posix_spawnp(), that doesn't require the absolute path to the program to spawn, but instead searches for the executable using the caller's PATH.

Using the posix_spawn() functions is the preferred way to create a new child process.

spawn()

The BlackBerry 10 OS spawn() function is similar to posix_spawn(). The spawn() function gives you control over the following:

  • File descriptors
  • Process group ID
  • Signal mask
  • Ignored signals
  • The node to create the process on
  • Scheduling policy
  • Scheduling parameters (priority)
  • Maximum stack size
  • Runmask (for SMP systems)

The basic forms of the spawn() function are:

spawn()
Spawn with the explicitly specified path.
spawnp()
Search the current PATH and invoke spawn() with the first matching executable.

There's also a set of convenience functions that are built on top of spawn() and spawnp() as follows:

spawnl()
Spawn with the command line provided as inline arguments.
spawnle()
spawnl() with explicitly passed environment variables.
spawnlp()
spawnp() that follows the command search path.
spawnlpe()
spawnlp() with explicitly passed environment variables.
spawnv()
Spawn with the command line pointed to by an array of pointers.
spawnve()
spawnv() with explicitly passed environment variables.
spawnvp()
spawnv() that follows the command search path.
spawnvpe()
spawnvp() with explicitly passed environment variables.

When a process is spawn()'ed, the child process inherits the following attributes of its parent:

  • Process group ID (unless SPAWN_SETGROUP is set in inherit.flags)
  • Session membership
  • Real user ID and real group ID
  • Supplementary group IDs
  • Priority and scheduling policy
  • Current working directory and root directory
  • File creation mask
  • Signal mask (unless SPAWN_SETSIGMASK is set in inherit.flags)
  • Signal actions specified as SIG_DFL
  • Signal actions specified as SIG_IGN (except the ones modified by inherit.sigdefault when SPAWN_SETSIGDEF is set in inherit.flags)

The child process has several differences from the parent process:

  • Signals set to be caught by the parent process are set to the default action (SIG_DFL).
  • The child process's tms_utime, tms_stime, tms_cutime, and tms_cstime are tracked separately from the parent's.
  • The number of seconds left until a SIGALRM signal would be generated is set to zero for the child process.
  • The set of pending signals for the child process is empty.
  • File locks set by the parent aren't inherited.
  • Per-process timers created by the parent aren't inherited.
  • Memory locks and mappings set by the parent aren't inherited.

If the child process is spawned on a remote node, the process group ID and the session membership aren't set; the child process is put into a new session and a new process group.

The child process can access the parent process's environment by using the environ global variable (found in <unistd.h>).

For more information, see the spawn() function.

vfork()

The vfork() function (which should be called only from a single-threaded process) is useful when the purpose of fork() would have been to create a new system context for a call to one of the exec*() functions. The vfork() function differs from fork() in that the child doesn't get a copy of the calling process's data. Instead, it borrows the calling process's memory and thread of control until a call to one of the exec*() functions is made. The calling process is suspended while the child is using its resources.

The vfork() child can't return from the procedure that called vfork(), since the eventual return from the parent vfork() would then return to a stack frame that no longer existed.

fork()

The fork() function creates a new child process by sharing the same code as the calling process and duplicating the calling process's data to give the child process an exact copy. Most process resources are inherited. The following resources are explicitly not inherited:

  • Process ID
  • Parent process ID
  • File locks
  • Pending signals and alarms
  • Timers

The fork() function is typically used for one of two reasons:

  • To create a new instance of the current execution environment
  • To create a new process running a different program

When creating a new thread, common data is placed in an explicitly created shared memory region. Prior to the POSIX thread standard, this was the only way to accomplish this. With POSIX threads, this use of fork() is better accomplished by creating threads within a single process using pthread_create().

When creating a new process running a different program, the call to fork() is soon followed by a call to one of the exec*() functions. This too is better accomplished by a single call to the posix_spawn() function or the BlackBerry 10 OS spawn() function, which combine both operations with far greater efficiency.

Since BlackBerry 10 OS provides better POSIX solutions than using fork(), its use is probably best suited for porting existing code and for writing portable code that must run on a UNIX system that doesn't support the POSIX pthread_create() or posix_spawn() API.

exec*()

The exec*() family of functions replaces the current process with a new process, loaded from an executable file. Since the calling process is replaced, there can be no successful return. The following exec*() functions are defined:

execl()
Exec with the command line provided as inline arguments.
execle()
execl() with explicitly passed environment variables.
execlp()
execl() that follows the command search path.
execlpe()
execlp()with explicitly passed environment variables.
execv()
execl() with the command line pointed to by an array of pointers.
execve()
execv() with explicitly passed environment variables.
execvp()
execv() that follows the command search path.
execvpe()
execvp() with explicitly passed environment variables.

The exec*() functions usually follow a fork() or vfork() to load a new child process. This is better achieved by using the posix_spawn() call.

Process loading

Processes loaded from a file system using the exec*(), posix_spawn() or spawn() calls are in ELF format. If the file system is on a block-oriented device, the code and data are loaded into main memory. By default, the memory pages containing the binaries are demand-loaded, but you can use the procnto -m option to change this; for more information, see Locking memory.

If the file system is memory mapped (for example, ROM/flash image), the code needn't be loaded into RAM, but may be executed in place. This approach makes all RAM available for data and stack, leaving the code in ROM or flash. In all cases, if the same process is loaded more than once, its code is shared.

Last modified: 2015-05-07



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus