Zephyr log系统原理之backend

本文说明zephyr log系统中backend的实现原理和被log core调用的方式。

概述 链接到标题

Zephyr log系统原理一文中介绍了zephyr log系统的架构,同时有提到log core的msg_process函数最后会将log_msg送到backend由backend进行显示。本文主要说明backend的实现方式以及如何和log core搭配工作

backend 链接到标题

定义 链接到标题

backend通过宏LOG_BACKEND_DEFINE定义,以log_backend_uart.c为例,观察这个宏的展开

LOG_BACKEND_DEFINE(log_backend_uart, log_backend_uart_api, true);

被展开为

	static struct log_backend_control_block backend_cb_log_backend_uart = \
	{								       \
		.active = false,					       \
		.id = 0,						       \
	};								       \
	static const struct log_backend log_backend_uart				       \
	__attribute__ ((section(".log_backends"))) __attribute__((used)) =     \
	{								       \
		.api = &log_backend_uart_api,						       \
		.cb = &backend_cb_log_backend_uart,			       \
		.name = “log_backend_uart”,				       \
		.autostart = true					       \
	}

可以看到定义了一个struct log_backend被放在log_backends中,之后所有的操作都是先找到log_backend,在通过其成员进行操作进行操作,对成员逐一说明

struct log_backend {
	const struct log_backend_api *api;          //这里面保存的是一系列函数指针,用于做实际的输出,后面进行详细介绍
	struct log_backend_control_block *cb;       //控制block, 保存其backend的id(backend在log_backends section中的位置序号)和backend的激活状态
	const char *name;                           //backend的name
	bool autostart;                             //标志初始化时backend是否自动启动
};

struct log_backend_api 链接到标题

backend api说明

struct log_backend_api {
	void (*put)(const struct log_backend *const backend,
		    struct log_msg *msg);       //输出函数
	void (*put_sync_string)(const struct log_backend *const backend,
			 struct log_msg_ids src_level, u32_t timestamp,
			 const char *fmt, va_list ap); //字符串格式同步输出函数
	void (*put_sync_hexdump)(const struct log_backend *const backend,
			 struct log_msg_ids src_level, u32_t timestamp,
			 const char *metadata, const u8_t *data, u32_t len); //raw data 格式同步输出函数

	void (*dropped)(const struct log_backend *const backend, u32_t cnt);    //drop数据
	void (*panic)(const struct log_backend *const backend);             //系统panic时调用,将剩余cache的log输出
	void (*init)(void);         //backend api初始化函数
};

这里put兼具字符串和raw data格式输出的功能,和put_sync_string/put_sync_hexdump的差异在于:实现上sync会得到一个字符输出一个字符,使用上sync只会在配置为log立即输出时使用,一般情况下为了log信息不影响系统运行,log都是被配置为异步的thread显示,所以这里只介绍put。 观察一下uart backend的api实现

const struct log_backend_api log_backend_uart_api = {
	.put = IS_ENABLED(CONFIG_LOG_IMMEDIATE) ? NULL : put,
	.put_sync_string = IS_ENABLED(CONFIG_LOG_IMMEDIATE) ?
			sync_string : NULL,
	.put_sync_hexdump = IS_ENABLED(CONFIG_LOG_IMMEDIATE) ?
			sync_hexdump : NULL,
	.panic = panic,
	.init = log_backend_uart_init,
	.dropped = IS_ENABLED(CONFIG_LOG_IMMEDIATE) ? NULL : dropped,
};

对于异步的thread显示可以简化为

const struct log_backend_api log_backend_uart_api = {
	.put = put,
	.put_sync_string = NULL,
	.put_sync_hexdump =  NULL,
	.panic = panic,
	.init = log_backend_uart_init,
	.dropped = dropped,
};

主要说明uart的put和init函数

init 链接到标题

主要完成的是获取uart dev并设置给log_out

static void log_backend_uart_init(void)
{
	struct device *dev;

	dev = device_get_binding(CONFIG_UART_CONSOLE_ON_DEV_NAME);
	assert(dev);

	log_output_ctx_set(&log_output, dev);
}

put 链接到标题

用于实际输出

static void put(const struct log_backend *const backend,
		struct log_msg *msg)
{
	log_msg_get(msg);	//引用当前msg,因为一个msg会被多个backend使用

	u32_t flags = LOG_OUTPUT_FLAG_LEVEL | LOG_OUTPUT_FLAG_TIMESTAMP;

	if (IS_ENABLED(CONFIG_LOG_BACKEND_SHOW_COLOR)) {
		flags |= LOG_OUTPUT_FLAG_COLORS;
	}

	if (IS_ENABLED(CONFIG_LOG_BACKEND_FORMAT_TIMESTAMP)) {
		flags |= LOG_OUTPUT_FLAG_FORMAT_TIMESTAMP;
	}

	log_output_msg_process(&log_output, msg, flags);	//通过log_output输出msg

	log_msg_put(msg);	//释放msg,当没有人在使用msg时会被释放

}

为什么说是通过log_output输出msg呢,log_output_msg_process只是一个中间商,它根据msg信息,组合成真正要输出的实际数据,例如加时间戳/level/id等信息。然后调用log_output内的函数指针对实际数据进行真正的显示,这里看一下log_output的定义

LOG_OUTPUT_DEFINE(log_output, char_out, &buf, 1);

展开上面宏就是

static int char_out(u8_t *data, size_t length, void *ctx)
{
	struct device *dev = (struct device *)ctx;

	for (size_t i = 0; i < length; i++) {
		uart_poll_out(dev, data[i]);
	}

	return length;
}

static u8_t buf;

	static struct log_output_control_block log_output_control_block;	\
	static const struct log_output log_output = {			\
		.func = char_out,						\
		.control_block = &log_output_control_block,		\
		.buf = &buf,						\
		.size = 1,						\
	}

然后我们再来看log_output_msg_process的输出流程,具体可以参考log_output.c这里只列出主要流程

log_output_msg_process(&log_output, msg, flags)->
log_output_flush(log_output)->
buffer_write(log_output->func, log_output->buf,
		     log_output->control_block->offset,
		     log_output->control_block->ctx)->

static void buffer_write(log_output_func_t outf, u8_t *buf, size_t len,
			 void *ctx)
{
	int processed;

	do {
		processed = outf(buf, len, ctx);	//这里的outf就是log_output->func,也就是char_out
		len -= processed;
		buf += processed;
	} while (len != 0);
}

初始化 链接到标题

backend是在log_core.c的log_init初始化的,主要流程如下

void log_init(void)
{
	assert(log_backend_count_get() < LOG_FILTERS_NUM_OF_SLOTS);
	int i;

	if (atomic_inc(&initialized) != 0) {
		return;
	}

	/* Assign ids to backends. */
	for (i = 0; i < log_backend_count_get(); i++) {
		const struct log_backend *backend = log_backend_get(i);		//这里就是重log_sections中一个一个取section

		if (backend->autostart) {	//检查自动开始标准
			if (backend->api->init != NULL) {
				backend->api->init();	//进行backend api初始化,对于uart来说,这里就是call log_backend_uart_init
			}

			log_backend_enable(backend, NULL, CONFIG_LOG_MAX_LEVEL);	//将backend enable
		}
	}
}

enable backend主要是做如下事情

void log_backend_enable(struct log_backend const *const backend,
			void *ctx,
			u32_t level)
{
	/* As first slot in filtering mask is reserved, backend ID has offset.*/
	u32_t id = LOG_FILTER_FIRST_BACKEND_SLOT_IDX;

	id += backend - log_backend_get(0);	//计算当前backend在log_sectins中的index

	log_backend_id_set(backend, id);	//初始化前面提到的log_backend_control_block.id
	backend_filter_set(backend, level);	//初始化backend filter的level,关于filter另有文章介绍
	log_backend_activate(backend, ctx);	//激活backend也就是设置log_backend_control_block.active和log_backend_control_block.ctx
	backend_attached = true;
}

输出 链接到标题

Zephyr-log系统原理一文中log msg显示backend显示章节有说明,最后是通过找到的log_backend_put进行输出,而log_backend_put的实现如下

static inline void log_backend_put(const struct log_backend *const backend,
				   struct log_msg *msg)
{
	__ASSERT_NO_MSG(backend);
	__ASSERT_NO_MSG(msg);
	backend->api->put(backend, msg);	//对于uart来说这里调用的也就是log_backend_uart_api.put
}

最终log_backend_uart_api.put会通过char_out将要显示的信息一个字符一个字符的送到串口

参考 链接到标题

https://lgl88911.pages.dev/zephyr/zephyr-log%E7%B3%BB%E7%BB%9F%E5%8E%9F%E7%90%86/