Zephyr log系统原理之backend

Creative Commons
本作品采用知识共享署名

本文说明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为例,观察这个宏的展开

1
LOG_BACKEND_DEFINE(log_backend_uart, log_backend_uart_api, true);

被展开为

1
2
3
4
5
6
7
8
9
10
11
12
13
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,在通过其成员进行操作进行操作,对成员逐一说明

1
2
3
4
5
6
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说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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实现

1
2
3
4
5
6
7
8
9
10
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显示可以简化为

1
2
3
4
5
6
7
8
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

1
2
3
4
5
6
7
8
9
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

用于实际输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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的定义

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

展开上面宏就是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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这里只列出主要流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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初始化的,主要流程如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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主要是做如下事情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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的实现如下

1
2
3
4
5
6
7
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.gitee.io/2019/03/10/Zephyr-log%E7%B3%BB%E7%BB%9F%E5%8E%9F%E7%90%86/