Skip to content

RFC: Subsystem: A new API for managing the lifecycle of software services #97799

@vytvir

Description

@vytvir

Problem Description

I frequently need to implement common patterns for managing the lifecycle (e.g., starting, stopping, suspending) of distinct software components or modules. Currently, this is handled through ad-hoc, application-specific mechanisms, often involving custom message queues or direct function calls.

This leads to duplicated effort across different applications and a lack of a standardized, reusable approach within the Zephyr Project. Similar to how Zephyr provides a Power Management subsystem for hardware devices, a comparable framework for managing the state of software components would provide significant value.

Proposed Change (Summary)

The proposal is to introduce a new, optional subsystem called the Services API. It would provide a lightweight, iterable (via iterable sections) and configurable framework for defining self-contained software modules, referred to as "services".

Initially, it should be sufficient to provide:

  • A SERVICE_DEFINE(...) macro, analogous to SYS_INIT(...), for declaring a new service.
  • A standardized API to set and get the state of any services.
  • A method for a service to expose a custom, service-specific API.

Alongside this, an optional Supervisor component can be included. It could be set to automatically start any services that are configured for autostart during the system's boot sequence.

In the future, the subsystem could be expanded with more features, including (in no specific order):

  • Dependency Management.
  • Health Monitoring.
  • Resource Monitoring (e.g. Stack / CPU usage).
  • State Notifications.
  • Shell Integration.

PS. I'm not great at naming software components, so if you have a good suggestion - please share!

Proposed Change (Detailed)

Here is a very rough mock-up of what the first iteration of this could look like:

/* SPDX-License-Identifier: Apache-2.0 */

#ifndef SERVICES_H_
#define SERVICES_H_

#include <zephyr/kernel.h>
#include <zephyr/linker/sections.h>

struct service;

/* Standardized service states and requests */
typedef enum {
    SERVICE_STATE_STOPPED,
    SERVICE_STATE_RUNNING,
    SERVICE_STATE_SUSPENDED,
    SERVICE_STATE_ERROR,
} service_state_t;

typedef enum {
    SERVICE_REQUEST_START,
    SERVICE_REQUEST_STOP,
    SERVICE_REQUEST_SUSPEND,
} service_request_t;

/* The common API that every service must implement */
struct service_api {
    int (*set_state)(const struct service *svc, service_request_t request);
    service_state_t (*get_state)(const struct service *svc);
    const void *(*get_api)(const struct service *svc);
};

/* The common configuration for all services */
struct service_config {
    bool autostart;
};

/* The main registration struct for a service */
struct service {
    const char *name;
    const struct service_api *api;
    const struct service_config *config;
};

/**
 * @brief Defines and registers a new service.
 *
 * @param _handle       A unique C identifier for this service instance.
 * @param _name_str     The service's unique name string (from Kconfig).
 * @param _api_ptr      Pointer to the service_api struct.
 * @param _config_ptr   Pointer to the service_config struct.
 */
#define SERVICE_DEFINE(_handle, _name_str, _api_ptr, _config_ptr) \
	static const STRUCT_SECTION_ITERABLE(service, _service_##_handle) = { \
		.name = _name_str, \
		.api = _api_ptr, \
		.config = _config_ptr, \
	}

/**
 * @brief Retrieves a service instance by its string name.
 */
const struct service *services_get_by_name(const char *name);

/**
 * @brief Callback for iterating over services. Return true to continue.
 */
typedef bool (*service_visitor_t)(const struct service *svc, void *user_data);

/**
 * @brief Iterates over all registered services.
 */
void services_foreach(service_visitor_t visitor, void *user_data);

#endif /* SERVICES_H_ */

Example Kconfig.template.service:

# SPDX-License-Identifier: Apache-2.0

config $(module)_NAME
	string "Service Name"
	default "$(module | tolower)"
	help
	  Specifies the unique string name for this service.

config $(module)_AUTOSTART
	bool "Autostart $(module-str) service at boot"
	default y
	help
	  If enabled, the supervisor will automatically start this
	  service during system initialization.

Example service Kconfig:

# SPDX-License-Identifier: Apache-2.0

config SERVICE_MQTT
	bool "Enable MQTT Service"
	depends on SERVICES
	help
	  Enable this example MQTT service.

if SERVICE_MQTT

module = SERVICE_MQTT
module-str = "MQTT Service"

source "subsys/services/Kconfig.template.service"
source "subsys/logging/Kconfig.template.log_config"

endif # SERVICE_MQTT

We could also provide a default service supervisor. E.g.:

#if defined(CONFIG_SERVICES_SUPERVISOR_AUTOSTART)

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(supervisor, CONFIG_SERVICES_SUPERVISOR_LOG_LEVEL);

/**
 * @brief Visitor function to autostart a single service.
 */
static bool autostart_visitor(const struct service *svc, void *user_data)
{
	if (svc->config && svc->config->autostart) {
		LOG_INF("Autostarting service: %s", svc->name);
		int err = svc->api->set_state(svc, SERVICE_REQUEST_START);
		if (err) {
			LOG_ERR("Failed to request start for service '%s' (err %d)",
				svc->name, err);
		}
	}

	/* Return true to continue iterating over other services */
	return true;
}

/**
 * @brief The SYS_INIT function for the default supervisor.
 */
static int services_supervisor_init(void)
{
	LOG_INF("Default services supervisor initializing...");
	services_foreach(autostart_visitor, NULL);
	return 0;
}

SYS_INIT(services_supervisor_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);

#endif /* CONFIG_SERVICES_SUPERVISOR_AUTOSTART */

Dependencies

This proposal shouldn't interfere with any existing components.

Concerns and Unresolved Questions

Without having a global enum with a service list, I currently rely on a configured-name (see _name_str below) to identify a service. Ideally, this could be some global unique number-based identifier, but I am not sure how to implement this.

#define SERVICE_DEFINE(_handle, _name_str, _api_ptr, _config_ptr)         \
	static const STRUCT_SECTION_ITERABLE(service, _service_##_handle) = { \
		.name = _name_str,                                                \
		.api = _api_ptr,                                                  \
		.config = _config_ptr,                                            \
	}

Alternatives Considered

I tried browsing through the Discord/Issues list, but I was not able to find anything similar discussed previously.

Metadata

Metadata

Assignees

No one assigned

    Labels

    RFCRequest For Comments: want input from the community

    Type

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions