This document offers a brief conceptual overview of the component framework along with links to more detailed documents on specific topics.
Note: The component framework is under active development. This document only covers the new architecture implemented by component_manager. The old architecture implemented by appmgr is still in use but will be removed once the transition to the new architecture is complete.
A component is a program that runs in its own sandbox on Fuchsia and that interacts with other components using inter-process communication channels.
The component framework is a framework for developing component-based software for Fuchsia.
The component framework is responsible for running nearly all software on Fuchsia so it is important for developers to learn how it works and how to use it effectively.
The component framework emphasizes separation of concerns by helping developers to write simpler programs as components that work together to support more complex systems through composition.
Each component typically has a small number of responsibilities. For example, an ethernet driver component exposes a hardware interface service that the network stack component uses to send and receive ethernet frames. These components can work together smoothly because they agree on a common set of protocols even though they may have been authored by different parties or distributed separately.
Software composition offers numerous advantages:
Fuchsia takes software composition to its logical conclusion by building almost the entire system from components (including device drivers). The component framework makes it easier to update and improve the system incrementally as new software becomes available.
Components are ubiquitous. They are governed by the same mechanisms and they all work together seamlessly.
Almost all programs run as components on Fuchsia, including:
There are only a few exceptions, notably:
A component is a program.
A component is an isolated program.
A component is a composable isolated program.
A component is a hermetic composable isolated program.
The component manager is the heart of the component framework. It manages the lifecycle of all components, provides them with the capabilities they require, and keeps them isolated from one another.
The system starts the component manager very early in the boot process. The component manager first starts the root component. The root component then asks the component manager to start other components including the device manager, filesystems, network stack, and other essential services.
As more and more components are started, the system springs to life. Eventually, the session framework starts the user interface components and the user takes control.
A component instance is a distinct copy of a component running in its own sandbox with its own state that is separate from that of any other component instance.
The terms component and component instance are often used interchangeably when the context is clear. For example, it would be more precise to talk about “starting a component instance” rather than “starting a component” but you can easily infer that “starting a component” requires an instance of that component to be created first so that the instance can be started.
Component instances progress through four major lifecycle events: create, start, stop, and destroy.
Unlike processes, component instances continue to exist and can retain state even when they are not running thereby allowing them to be stopped and restarted repeatedly while preserving the illusion of continuity.
Refer to lifecycle for more details.
When a component instance is created, the component frameworks assigns a unique identity to the instance, adds it to the component topology, and makes its capabilities available for other components to use.
Once created, a component instance can then be started or destroyed.
Starting a component instance loads and runs the component's program and provides it access to the capabilities that it requires.
Every component runs for a reason. The component framework only starts a component instance when it has work to do, such as when another component requests to use its the instance's capabilities.
Once started, a component instance continues to run until it is stopped.
Stopping a component instance terminates the component's program but preserves its persistent state so that it can continue where it left off when subsequently restarted.
The component framework may stop a component instance for a variety of reasons, such as:
A component can implement a lifecycle handler to be notified of its impending termination and other events on a best effort basis. Note that a component can be terminated involuntarily and without notice in circumstances such as resource exhaustion, crashes, or power failure.
Once stopped, a component instance can then be restarted or destroyed.
Destroying a component instance permanently deletes all of its associated state and releases the system resources it consumed.
Once destroyed, a component instance ceases to exist and cannot be restarted. New instances of the same component can still be created but they will each have their own identity and state distinct from all prior instances.
A component declaration is a machine-readable description of what the component can do and how to run it. It contains metadata that the component framework requires to instantiate the component and to compose the component with others.
Every component has a declaration. For components that are distributed in packages, the declaration typically takes the form of a component manifest file.
Components can also be distributed in other forms such as web applications with the help of a suitable resolver and runner which provide the necessary component declaration and take care of running the component.
For example, the declaration for a calculator component might specify the following information:
A component URL specifies the location from which a component's declaration, program, and assets are retrieved.
Components can be retrieved from many different sources as indicated by the URL scheme. These are some common URL schemes you may encounter:
fuchsia-boot: The component is resolved from the system boot image. This scheme is used for retrieving components that are essential to the system's operation during early boot before the package system is available.fuchsia-pkg: The component is resolved by the Fuchsia package resolver. This scheme is used for components that are distributed in the form of packages which can be downloaded on demand and kept up-to-date.http and https: The component is resolved as a web application by a web resolver. This scheme is used to integrate web-based content with the component framework.Note: The set of URL schemes available in each realm is configured with capability routing in accordance with the realm's need to access components from various sources. The examples presented above are not universal.
Note: Use monikers to identify specific instances of components instead of their source.
The component topology is an abstract data structure that describes the relationships among component instances. It is made of three parts:
TODO: Add a picture or a thousand words.
The structure of the component topology greatly influences component lifecycle and use of capabilities.
Any number of components can be combined together to make more complex components through hierarchical composition.
In hierarchical composition, a parent component creates instances of other components which are known as its children. The newly created children belong to the parent and are dependent upon the parent to provide them with the capabilities that they need to run. Meanwhile, the parent gains access to the capabilities exposed by its children through capability routing.
Children can be created in two ways:
Children remain forever dependent upon their parent; they cannot be reparented and they cannot outlive their parent. When a parent is destroyed so are all of its children.
The component topology represents the structure of these parent-child relationships as a component instance tree.
TODO: Add a diagram of a component instance tree.
The capabilities of child components cannot be directly accessed outside of the scope of their parent; they are encapsulated.
This model resembles composition in object-oriented programming languages.
A realm is a subtree of component instances formed by hierarchical composition. Each realm is rooted by a component instance and includes all of that instance's children and their descendants.
Realms are important encapsulation boundaries in the component topology. The root of each realm receives certain privileges to influence the behavior of components, such as:
See the realms documentation for more information.
A moniker identifies a specific component instance in the component tree using a topological path. Monikers are collected in system logs and for persistence.
See the monikers documentation for details.
Components gain access to use capabilities exposed by other components through capability routing.
TODO: Refactor existing manifests and capabilities to explain the basic concepts here. Draw parallels with constructor dependency injection. Include links to capability types.
A compartment is an isolation boundary for component instances. It is an essential mechanism for preserving the confidentiality, integrity, and availability of components.
Physical hardware can act as a compartment. Components running on the same physical hardware share CPU, memory, persistent storage, and peripherals. They may be vulnerable to side-channels, privilege elevation, physical attacks, and other threats that are different from those faced by components running on different physical hardware. System security relies on making effective decisions about what capabilities to entrust to components.
A job can act as a compartment. Running a component in its own job ensures that the component‘s processes cannot access the memory or capabilities of processes belonging to other components in other jobs. The component framework can also kill the job to kill all of the component’s processes (assuming the component could not create processes in other jobs). The kernel strongly enforces this isolation boundary.
A runner provides a compartment for each component that it runs. The runner is responsible for protecting itself and its runnees from each other, particularly if they share a runtime environment (such as a process) that limits the kernel's ability to enforce isolation.
Compartments nest: runner provided compartments reside in job compartments which themselves reside in hardware compartments. This encapsulation clarifies the responsibilities of each compartment: the kernel is responsible for enforcing job isolation guarantees so a runner doesn't have to.
Some compartments offer weaker isolation guarantees than others. A job offers stronger guarantees than a runner so sometimes it makes sense to run multiple instances of the same runner in different job compartments to obtain those stronger guarantees on behalf of runnees. Similarly, running each component on separate hardware might offer the strongest guarantees but would be impractical. There are trade-offs.
TODO: Fill in more details when component framework APIs for assigning components to compartments have been formalized.
Components use framework capabilities to interact with their environment:
Components use framework extensions to integrate the component framework with software ecosystems:
TODO: Link to docs about how to build components, diagnostic tools, and debugging features.
System resources are finite. There's only so much memory, disk, or CPU time available on a computing device. The component framework keeps track of how resources are used by components to ensure they are being used efficiently and that they can be reclaimed when no longer required or when they are more urgently needed for other purposes if the system is oversubscribed.
Resources must be used for a reason.
For example, every running process must belong to at least one component instance whose capabilities are currently in use, were recently of use, or will soon be of use; any outliers are considered to be running for no reason and are promptly stopped.
Similarly, the system may terminate processes if they exceed the resource constraints of the components that are responsible for them.
Here are some more examples of accountability:
As a general rule, every resource in the system must be accounted for in some way so the system can ensure they are being used effectively.
The component framework offers mechanisms to preserve the illusion of continuity: the user should generally not be concerned about restarting their software because it will automatically resume right where they left off, even when they reboot or replace their devices.
The fidelity of the illusion depends on how well the following properties are preserved across restarts:
In practice, the illusion is imperfect. The system cannot guarantee faithful reproduction in the presence of software upgrades, non-determinism, bugs, faults, and external dependencies on network services.
While it might seem simpler to keep components running forever, eventually the system will run out of resources so it needs a way to balance its working set size by stopping less essential components at a moment's notice.