Introduction to Fuchsia components

<<../_v2_banner.md>>

Overview

This document offers a brief conceptual overview of Components and the Component Framework.

In Fuchsia, [component][glossary.component] is the term for the common abstraction that defines how all software[^1] (regardless of source, programming language, or runtime) is described, sandboxed, and executed on a Fuchsia system.

[^1]: With the exception of early-boot software necessary to run components.

What is sandboxing?

Sandboxing is a security mechanism to isolate programs from each other at runtime. In Fuchsia, all software is sandboxed. When a program is initially created, it does not have the ability to do anything -- not even to allocate memory. The program relies on its creator to provide the capabilities needed for it to execute. This isolation property allows Fuchsia to employ the principle of least privilege: programs are provided only the minimal set of capabilities needed to execute.

Component Framework

The Component Framework (CF) consists of the core concepts, tools, APIs, runtime, and libraries necessary to describe and run components and to coordinate communication and access to resources between components.

The Component Framework includes:

  • CF concepts, including component, component manifest, runner, realm, environment, capabilities, and resolver.
  • The component_manager process, which coordinates the communication and sharing of resources between components.
  • FIDL APIs implemented by component_manager, or implemented by other components and used by component_manager, for the purposes of coordination.
  • Developer tools to build, execute, and test components.
  • Language-specific libraries for components to use to interact with the system. (example)
  • Testing tools and libraries to write unit and integration tests that exercise one or many components. (example)

Capabilities

Since Fuchsia is a capability-based operating system, software on Fuchsia interacts with other software through the use of capabilities. A capability combines both access to a resource and a set of rights, providing both a mechanism for access control and a means by which to interact with the resource. In Fuchsia, capabilities typically have an underlying kernel object and programs hold handles to reference those underlying objects.

A common representation of a capability is a channel that speaks a particular FIDL protocol. The “server end” and the “client end” of the channel each hold a handle allowing them to communicate with each other. The fuchsia.io.Directory protocol allows a client to discover additional capabilities by name. To support the complex composition of software present in today's products, the Component Framework provides more complex capabilities built upon the Zircon objects. For example, storage capabilities are represented as channels speaking the Directory protocol that provide access to a unique persistent directory created for each component.

Fuchsia processes receive both named and numbered handles at launch. The named handles always speak the Directory protocol. The term for the collection of named handles is the namespace.

The Component Framework assembles the namespace for a component by consulting component declarations that describe how the capabilities in the namespace should be delegated at runtime. The process of following a chain of delegation from a consuming component to a providing component is called capability routing. Component topology is the term for the component instance tree and the collective capability routes over that tree.

Note: In the Fuchsia process layer, “having a capability” means the process holds a handle to the kernel object capability in its handle table. In the Component Framework, we often use “having a capability” to mean that the capability is discoverable through the component's namespace at runtime.

Further reading:

Components

A Component is the fundamental unit of executable software on Fuchsia. Components are composable, meaning that components can be selected and assembled in various combinations to create new components. A component and its children are referred to as a realm. The collective parent and child relationships of many individual components are referred to as the component instance tree. A moniker is a topological path that identifies a specific component instance within a component instance tree. You will often see monikers represented as POSIX-like path strings. At its core, a component consists of the following:

  • A Component URL, which uniquely identifies that component.
  • A Component manifest, which describes how to launch the component, as well as any capability routes.

Components are retrieved from a variety of origins, and can run in any runtime (such as a native process, or in a virtual machine). To support origin and runtime variability, the Component Framework can be extended through the use of resolvers and runners. Resolvers and runners are themselves capabilities and interact directly with the framework to extend its functionality.

  • Resolvers take a component URL as an input and produce a component manifest and (optionally) an access mechanism to the bytes of a software package as output.
  • Runners consume parts of the manifest and the package, and provide the component's binary with a way to execute.

Note: To bootstrap the system, component_manager includes a built-in resolver, the boot-resolver, which resolves fuchsia-boot:// URLs to manifests on the boot image, as well as a built-in runner, the ELF runner, which executes ELF binaries stored in signed Fuchsia packages.

Further reading:

Component lifecycle

Components move through the following lifecycle states:

  • Discovered
  • Started
  • Stopped
  • Destroyed

Components are discovered either a) by virtue of being statically declared as a child of another component in a component manifest, or b) by being added to a component collection at runtime. Similarly, components are destroyed implicitly by being removed from the list of static children in a component manifest, or explicitly by being removed from a component collection at runtime.

When a component is started or stopped, component_manager coordinates with the appropriate runner to execute or terminate the component's executable.

Further reading:

Components and capability routing

When started, every component receives its namespace as well as a handle to the server end of a Directory channel. This Directory channel is called the the outgoing directory. Through the outgoing directory, the component‘s executable makes discoverable any capabilities that it serves directly. The Component Framework brokers discovery from one component’s namespace to another's outgoing directory.

A component can interact with the system and other components only via the capabilities discoverable through its namespace and the few numbered handles it receives. The namespace for the component is assembled from declarations in the component's manifest. However, these component manifest declarations alone are not sufficient to gain access to the capabilities at runtime. For these capabilities to be available, there must also be a valid capability route from from the consuming component to a provider. Since capabilities are most often routed through parent components to their children, parent components play an important role in defining the sandboxes for their child components.

Namespaces use directory path semantics, so many components use a POSIX-style interface that treats their namespace as a local file system.

While most capabilities are routed to and from component instances, runner and resolver capabilities are routed to environments. Environments configure the behavior of the framework for the realms to which they are assigned. Capabilities routed to these environments are accessed and used by the framework. Component instances themselves do not have runtime access to the capabilities in their environments.

Further reading: