One could argue that you can't really understand what the Binder is about until you understand how it deals with processes. Conceptually, the Binder deals with processes in a very different way than you are probably used to thinking about them. This approach to processes is, in a way, its purest expression of the system-level component model it provides.
The process model described here is made possible by the underlying Binder IPC Mechanism features that allow operations remote objects to behave like local functional calls.
This IPC model is important to the Binder because it provides a high degree of process transparency. That is, developers for the most part don't have to worry about which process a particular Binder Object exists in, but can simply treat any instance as if it is in the local process. This allows for a lot of flexibility and scalability of systems built on top of the Binder, in that we can decided dynamically at run-time how they are to be distributed across processes (if any).
One immediately obvious benefit of this flexibility is that the entire Binder system is able to run without the process model being available at all. If the kernel driver implementing the Binder IPC Mechanism is not available, it will simply run everything in a single process. Likewise, the BINDER_SINGLE_PROCESS environment variable can be set to have everything run in one process, for ease of debugging.
This flexibility in process distribution leads, as described here, to some fundamental advances in the ways that high-level programmers can think about processes.
Processes are used in an operating system to protect one piece of code from disruption due to misbehavior of another piece of code. What this typically means is that each running application is given its own process, so a problem in one application won't cause trouble for other running applications.
Thus, the act of starting an application means to the operating system, "create a new process for the application, load the application code into that process, and start a thread running in that process at the beginning of the application."
This model makes sense because applications are generally fairly large, independent entities: when one application is running, it very rarely needs to know or wants to care at all about what other applications are doing at the same time. The process it is running in gives it its own isolated world, through which it can be protected from others and others can be protected from it.
Note that the Binder process model is designed to sit on top of standard kernel and runtime process support, such as fork()+exec() on Unix. It does not supplant or modify those in any way, but simply uses them in a different way than is done by traditional shells.
The goal, then, is to define a process model for the Binder that is independent of an application. Clearly, we can't just treat every single component someone creates as a potential application and give it its own process, so some other approach must be taken.
In fact, a process in the Binder can best be thought of as just another kind of component. There is a special API you call to create it, but what you get back is just another Binder interface (called, not surprisingly, IProcess) that then allows you to interact with your new process.
Here is some example Binder code that will make a new process, and then create a new component inside of that process:
sptr<IProcess> process = Context().NewProcess( SString("my empty process")); sptr<IBinder> obj = Context().RemoteNew( SValue::String("org.openbinder.services.WindowManager"), process);
Or the same thing from the Binder Shell:
/$ p=$[new_process "my empty process"] Result: IBinder::shnd(0x2) /$ c=$[new -r $p org.openbinder.services.WindowManager] Result: IBinder::shnd(0x3)
After running this code, you now have a Window Manager service running in its own process. Note that if the service being instantiated is an application, the end result would be the same as with a traditional process model. For example:
sptr<IProcess> process = Context().NewProcess( SString("AddressBook")); sptr<IBinder> obj = Context().RemoteNew( SValue::String("com.palmsource.apps.AddressBook"), process);
/$ p=$[new_process "AddressBook"] Result: IBinder::shnd(0x2) /$ app=$[new -r $p com.palmsource.apps.AddressBook] Result: IBinder::shnd(0x3)
With this code we have started a process running the Address Book application/component.
With the Binder, however, we have a lot more flexibility in how we use processes. For example, we can run a number of related components in the same process in order to reduce the overhead needed for them:
sptr<IProcess> process = Context().NewProcess( SString("my empty process")); sptr<IBinder> surface = Context().RemoteNew( SValue::String("org.openbinder.services.Surface"), process); sptr<IBinder> wm = Context().RemoteNew( SValue::String("org.openbinder.services.WindowManager"), process);
/$ p=$[new_process "my empty process"] Result: IBinder::shnd(0x2) /$ surface=$[new -r $p org.openbinder.services.Surface] Result: IBinder::shnd(0x3) /$ wm=$[new -r $p org.openbinder.services.WindowManager] Result: IBinder::shnd(0x4)
The division between processes can be done completely at run-time, depending on factors such as the capabilities of the hardware, the permissions needed by components, and third party components installed on the device.
The default rule for the Binder is that a process lasts as long as there are any strong references on its objects that are held by other processes. In the most trivial case, this means that after creating an empty process you can make it go away by releasing your reference on that object:
/$ p=$[new_process "my temporary process"] Result: IBinder::shnd(0x4) /$ p= Result: "" [SIGCHLD handler] child process 23356 exited normally with exit value 0
More interesting, if you create any components in that process, then it will continue to stay around as long as other processes are using those components:
/$ p=$[new_process "my temporary process"] Result: IBinder::shnd(0x2) /$ c=$[new -r $p org.openbinder.tools.commands.BPerf] Result: IBinder::shnd(0x4) /$ p= Result: "" /$ c= Result: "" [SIGCHLD handler] child process 23359 exited normally with exit value 0
This is a very powerful feature, as it allows you to easily create temporary processes as sandboxes, which will automatically stay around for only as long as they are needed. An example of how you could use this feature would be in a web browser: if you have some code that you don't want running in the main browser process (such as a third party extension for displaying a special kind of media), you can create a temporary process in which it will run, and as long as others are using that code the process will continue to exist.
Even better, your web browser can hold a weak reference on that temporary process. Then, whenever it has new content to display, it can try to promote that reference and if the promotion succeeds then it can continue using the process for creating additional instances of the component. Otherwise, the current process has exited and it will need to make a new process.
Sometimes, however, you want to have more control over the lifetime of a process. This can be accomplished with the SLooper::StopProcess() method or
stop_process shell command. These ask that the process stop as soon as all strong references on its process object go away, allowing you to let the process go away even if others have references to some of its components. In addition, you can specify an option to force the process to exit immediately, even ignoring anyone holding references on the process object.
See Process Lifetime in the Binder Shell Tutorial for more examples of this feature.
sptr<IBinder> obj = Context().New( SValue::String("com.vender.MyApp.Something"));
Unlike the RemoteNew() call, New() does not specify the process the new component should be created in. What this usually means is just that the component will always be instantiated in the local process. However, because the Binder hides the details of which processes components live in, it is not required to use the same process.
A simple form of this dynamic process binding can be seen with the BINDER_SINGLE_PROCESS option. This is a fairly brute-force approach, which simply forces SContext::NewProcess() to not create a new process unless the caller specifies that one is absolutely necessary. In other words, "run as much of the system as possible in a single process."
The current OpenBinder distribution also includes an early implementation of more extensive process management. This is provided through the IProcessManager interface, and a simple implementation of that API which currently lives within smooved itself.
This implementation allows a component to say that it would like a dedicated process for its package, and have itself instantiated there instead of the local process of the caller. The example code in
samples/ServiceProcess demonstrates this functionality, as can be seen in the Dynamic Processes section of the Binder Shell Tutorial.
As mentioned, though, the current implementation is quite primitive and with the underlying architecture there is are many more interesting things that could be done. For example, the Process Manager could look at the signature of the component's executable and permissions needed by the component itself and, if they are in conflict with the current process, behind the scenes create the component in a more appropriate process. By doing so, we can enable a number of useful scenarios for dealing with security and other protection issues: