D-Bus is an interprocess communication standard (IPC). Different processes can communicate with each other using D-Bus, either using remote procedure calls (RPC, one-to-one communication) or through signals (one-to-many communication).
The standard specifies how data is encoded and transferred, allowing programs written in different programming languages to communicate.
Fundamentally D-Bus is user-space technology, i.e. it does not provide any means of crossing the userspace-kernel boundary. Instead, it can be used to arbitrate access to processes which manage resources in the kernel.
Why does embedded care about userspaces?
It’s about understanding the whole system, in order to deliver a better product. Many Embedded Linux systems now make use of Systemd, which in turn makes heavy use of D-Bus.
As Embedded software developers, our primary concern might be at the kernel level or below, writing drivers and interfacing with hardware. But we always have to be mindful that our software lives within a bigger context: Code in userspace will interface with the kernel, drivers, and hardware.
Let’s consider a concrete example: wpa_supplicant, a user space component which acts as a necessary component in a full wifi stack, which is needed in order to establish a connection to a wifi network. wpa_supplicant provides a D-Bus API, which can be used by an application to implement a user interface for controlling wifi.
As an Embedded software developer, the D-Bus API can find use as a interface for testing. It is possible to automate the process of connecting to different wifi networks while simultaneously collecting and evaluate data returned by wpa_supplicant. This provides an easy way of performing holistic tests of the full wifi stack on an Embedded device, as illustrated in the diagram.
Using D-Bus in software architecture
D-Bus isn’t just a tool we need to know to interface with existing systems. In some scenarios, it can be a great tool for a custom software architecture.
As already hinted at, one way to use D-Bus is to arbitrate access to a shared resource. A single userspace process gets the task of talking to the kernel and managing the resource, and exposing it to other processes over D-Bus. Here we can make use of the fact that D-Bus doesn’t re-order messages to make it easier to reason about how userspace programs will behave.
Consider the example shown in the diagram below. Here, three userspace applications all want to interface with some external hardware. Since the access to serial devices by userspace in principle isn’t synchronized, it is necessary that only a single process attempts concurrently accessing the serial port. By using D-Bus, a single service can have control over the serial port, and give access to specific features through that serial port over D-Bus.
This scenario isn’t unique to serial ports. Similar concerns can arise, for example if the hardware has requirements on transaction ordering, where it is important to ensure that application A finishes a multi-part transfer before allowing application B or C to communicate with the hardware. Implementing the logic to enforce these constraints in a userspace service helps debugability, at the same time as it simplifies driver code.
One does however have to be careful: D-Bus is not designed to be a high-performance system. Latency- and throughput-characteristics make it more suited for transfering high-level control data rather than large datasets.
Introspection and tracing
When using D-Bus we also get access to the existing infrastructure built around D-Bus.
The biggest value add here is the ability to trace D-Bus messages, e.g. with dbus-monitor --pcap
, which captures all D-Bus traffic into a file which you can view with WireShark. When using TCP sockets for communication, one has tcpdump
. dbus-monitor
gives some of the same capabilities to D-Bus systems.
It is also possible to view some of the structure of a D-Bus API at runtime, using the busctl tree
and busctl introspect
commands. Be aware that support for these needs to be enabled in the process you are introspecting, so some processes might not have support.
A quick tour of D-Bus terminology
Let’s have a look at some common D-Bus terminology and architecture, which is key to understanding how D-Bus systems work.
This first diagram shows the how different processes each have a socket connection to the system daemon, which acts as a broker for communication. When a process wants to send a message to another process, it sends it over the socket to the daemon, and the daemon forwards the message to the correct process.
If we focus in on just the daemon and two processes which want to communicate, we see the first bit of metadata we need to send a message. Each process is assigned a bus name, which is needed when addressing messages to that process.
The system daemon automatically assigns a numeric busname (e.g. “:1.23”) to each process. In addition, processes can choose to have a “well known” name to allow for other processes to easily find them.
The well known” bus names typically are reversed domain names (a format which is also known from java package names) but this is just a convention.
In the diagram, “your application” doesn’t have a well known bus name. Typically, a process only chooses a well known bus name if it both exposes a D-Bus API and only a single instance of the process is expected to exist.
Looking at just a single process within D-Bus, each process (or rather, each bus name) can expose multiple objects. These objects are completely fictituous. They don’t have to correspond to any specific construct in a program. The object paths, which identify objects, are nominally hierarchical, though this hierarchy also doesn’t have to correspond with any real hierarchy.
Within the wpa_supplicant, there is a root-object which contains some common functionality, per-interface objects which control functions related to specific network interfaces (though systems typically only have one interface, wlan0
), and additional objects for networks seen during scanning (“BSSs”).
Each object in D-Bus in turn can implement one or more interfaces. Each of these interfaces provide methods (which your application can call) and can publish signals (which your application can subscribe to). Interface names are, again by convention, in the same format as bus names.
When your application wants to call a D-Bus method, or subscribe to a signal, it needs to specify four parameters: Bus name, object path, interface name and member name. This can lead to some confusion at first, especially since in some cases bus names and interface names are identical strings.
Conclusion
We’ve seen how D-Bus can be relevant to Embedded systems. Since it is an integral part of many pieces of software written in Linux, using it can be both necessary and beneficial to properly integrate with these systems. Additionally, it can be used as a part of new software designs, for example to arbitrate access to a shared resource. The introspection capabilities provided by D-Bus help produce software that is easier to analyze and debug. Finally, we looked at some common terminology which can help you get started with evaluating whether D-Bus can be a suitable tool for your usecase.
To summarize, D-Bus can be a great choice, especially when traceability and debugability are more important than performance.