Processes

The architecture of the BlackBerry 10 OS consists of a small microkernel and some number of cooperating processes. Your applications should be written the same way — as a set of cooperating processes. We'll see how to start processes (also known as creating processes) from code, how to terminate them, and how to detect their termination when it happens.

For another perspective, see Processes and threads and Message passing.

Process creation

The process manager component of procnto is responsible for process creation. If a process wants to create another process, it makes a call to one of the process-creation functions, which then effectively sends a message to the process manager.

Here are the process-creation functions:

When you start a new process, it replaces the existing process if:

  • You specify P_OVERLAY when calling one of the spawn* functions.
  • You call one of the exec* routines.

The existing process may be suspended while the new process executes (control continues at the point following the place where the new process was started) in the following situations:

  • You specify P_WAIT when calling one of the spawn* functions.
  • You call system().

There are several versions of spawn*() and exec*(). The * is one to three letters, where:

  • l or v (one is required) indicates the way the process parameters are passed
  • p (optional) indicates that the PATH environment variable is searched to locate the program for the process
  • e (optional) indicates that the environment variables are being passed

For details on each of these functions, see their entries in the BlackBerry 10 OS C Library Reference. Here we'll mention some of the things common to many of them.

Concurrency

Three possibilities can happen to the creator during process creation:

  1. The child process is created and runs concurrently with the parent. In this case, as soon as process creation is successful, the process manager replies to the parent, and the child is made READY. If it's the parent's turn to run, then the first thing it does is return from the process-creation function. This may not be the case if the child process was created at a higher priority than the parent (in which case the child will run before the parent gets to run again).

    This is how fork(), forkpty(), popen(), and spawn() work. This is also how the spawn*() family of functions work when the mode is passed as P_NOWAIT or P_NOWAITO.

  2. The child replaces the parent. They're not really parent and child, because the image of the given process simply replaces that of the caller. Many things will change, but those things that uniquely identify a process (such as the process ID) will remain the same. This is typically referred to as execing, since usually the exec*() functions are used.

    Many things will remain the same (including the process ID, parent process ID, and file descriptors) with the exception of file descriptors that had the FD_CLOEXEC flag set using fcntl(). See the exec*() functions for more on what will and will not be the same across the exec.

    The login command serves as a good example of execing. Once the login is successful, the login command execs into a shell.

    Functions you can use for this type of process creation are the exec*() and spawn*() families of functions, with mode passed as P_OVERLAY.

  3. The parent waits until the child terminates. This can be done by passing the mode as P_WAIT for the spawn*() family of functions.

    Note that what is going on underneath the covers in this case is that spawn() is called as in the first possibility above. Then, after it returns, waitpid() is called in order to wait for the child to terminate. This means that you can use any of the functions mentioned in our first possibility above to achieve the same thing if you follow them by a call to one of the wait*() functions (that is, wait() or waitpid()).

Many programmers coming from the Unix world are familiar with the technique of using a call to fork() followed by a call to one of the exec*() functions to create a process that's different from the caller. In BlackBerry 10 OS, you can usually achieve the same thing in a single call to one of the posix_spawn*() or spawn*() functions.

Inheriting file descriptors

The documentation in the BlackBerry 10 OS C Library Reference for each function describes in detail what the child inherits from the parent. One thing that we should talk about here, however, is file-descriptor inheritance. With many of the process-creation functions, the child inherits the file descriptors of the parent. For example, if the parent had file descriptor 5 in use for a particular file when the parent creates the child, the child will also have file descriptor 5 in use for that same file. The child's file descriptor will have been duplicated from the parent's. This means that at the file system manager level, the parent and child have the same open control block (OCB) for the file, so if the child seeks to some position in the file, then that changes the parent's seek position as well. It also means that the child can do a write(5, buf, nbytes) without having previously called open().

If you don't want the child to inherit a particular file descriptor, then you can use fcntl() to prevent it. Note that this won't prevent inheritance of a file descriptor during a fork(). The call to fcntl() would be:

fcntl(fd, F_SETFD, FD_CLOEXEC);

If you want the parent to set up exactly which files will be open for the child, then you can use the fd_count and fd_map parameters with spawn(). Note that in this case, only the file descriptors you specify will be inherited. This is especially useful for redirecting the child's standard input (file descriptor 0), standard output (file descriptor 1), and standard error (file descriptor 2) to places where the parent wants them to go.

Alternatively this file descriptor inheritance can also be done through use of fork(), one or more calls to dup(), dup2(), and close(), and then exec*(). The call to fork() creates a child that inherits all the of the parent's file descriptors. dup(), dup2() and close() are then used by the child to rearrange its file descriptors. Lastly, exec*() is called to replace the child with the process to be created. Though more complicated, this method of setting up file descriptors is portable whereas the spawn() method is not.

Inheriting file descriptors can be a security problem for setuid or setgid processes. For example, a malicious programmer might close stdin, stdout, or stderr before spawning the process. If the process opens a file, it can receive file descriptor 0, 1, or 2. If the process then uses stdin, stdout, or stderr, it might unintentionally corrupt the file. To help prevent such a situation, you can use set_lowest_fd() to make sure that your process never gets a file descriptor lower than what you expect.

Last modified: 2014-12-11



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

comments powered by Disqus