【OS zephyr】子系统logging
1、概述
logging子系统特点:运行模式可配,分级管理,多格式打印可选,支持同步异步打印,支持多后端,支持实例日志。底层都是vprintk()接口,vprintk()接口可能有多种实现实方式。
| 运行模式 | 打印格式 | 接口 | 说明 |
| mini模式 | str | z_log_minimal_printk() | |
| hex | z_log_minimal_hexdump_print() | ||
| fall模式 | str | Z_LOG_MSG_CREATE() | 支持立即模式(),线程模式( CONFIG_LOG_PROCESS_THREAD )可选 |
| hex | Z_LOG_MSG_CREATE() |
由于日志需要用到外设初始化,初始化的定义与执行控制如下:
/* Init level ordinals 宏定时引用*/
#define Z_INIT_ORD_EARLY 0
#define Z_INIT_ORD_PRE_KERNEL_1 1
#define Z_INIT_ORD_PRE_KERNEL_2 2
#define Z_INIT_ORD_POST_KERNEL 3
#define Z_INIT_ORD_APPLICATION 4
#define Z_INIT_ORD_SMP 5//levels[]中包含不同等级初始化段的段起始。static const struct init_entry *levels[] = {__init_EARLY_start,__init_PRE_KERNEL_1_start,__init_PRE_KERNEL_2_start,__init_POST_KERNEL_start,__init_APPLICATION_start,
#ifdef CONFIG_SMP__init_SMP_start,
#endif /* CONFIG_SMP *//* End marker */__init_end,};
//初始化时确定顺序时引用。与levels[]配置使用。
enum init_level {INIT_LEVEL_EARLY = 0,INIT_LEVEL_PRE_KERNEL_1,INIT_LEVEL_PRE_KERNEL_2,INIT_LEVEL_POST_KERNEL,INIT_LEVEL_APPLICATION,
#ifdef CONFIG_SMPINIT_LEVEL_SMP,
#endif /* CONFIG_SMP */
};2、子系统依赖
2.1..\zephyr\drivers\serial\
文件件夹下管理着众多平台的串口驱动。 static const struct uart_driver_api uart_acts_driver_api 结构体的定义,同时通过宏将串口添加为设备树的节点。
..\hal\actions\subsys\log\ 炬芯平台也有自己的打印子系统。
..\hal\actions\drivers\serial\uart_acts.c 炬芯平台串口文件。UART设备入口段的定义如下:
/* uart acts device init */
#define UART_ACTS_DEVICE_INIT(idx) \PINCTRL_DT_DEFINE(UARTNODE(idx)); \UART_ACTS_DEFINE_CONFIG(idx); \static struct acts_uart_data uart_acts_dev_data_##idx ;\DEVICE_DT_DEFINE(UARTNODE(idx), \&uart_acts_init, \NULL, \&uart_acts_dev_data_##idx, \&uart_acts_config_##idx, \PRE_KERNEL_1, \CONFIG_SERIAL_INIT_PRIORITY, \&uart_acts_driver_api);2.2..\zephyr\lib\os\printk.c
系统层打印接口。如果使用DMA方式接口需要再加一层__vprintk()内部接口。printk_dma.c会定义一个vprintk()接口。#ifdef CONFIG_ACTIONS_PRINTK_DMA void __vprintk(const char *fmt, va_list ap) #else void vprintk(const char *fmt, va_list ap) #endif
#ifdef CONFIG_ACTIONS_PRINTK_DMA
void __vprintk(const char *fmt, va_list ap)
#else
void vprintk(const char *fmt, va_list ap)
#endif
{//如果使用zephyr的loging模块执行if (IS_ENABLED(CONFIG_LOG_PRINTK)) {z_log_vprintk(fmt, ap);return;}//不使用zephyr的loging模块执行if (k_is_user_context()) {struct buf_out_context ctx = {
#ifdef CONFIG_PICOLIBC.file = FDEV_SETUP_STREAM((int(*)(char, FILE *))buf_char_out,NULL, NULL, _FDEV_SETUP_WRITE),
#else0
#endif};#ifdef CONFIG_PICOLIBC(void) vfprintf(&ctx.file, fmt, ap);
#elsecbvprintf(buf_char_out, &ctx, fmt, ap);
#endifif (ctx.buf_count) {buf_flush(&ctx);}} else {
#ifdef CONFIG_PRINTK_SYNCk_spinlock_key_t key = k_spin_lock(&lock);
#endif#ifdef CONFIG_PICOLIBCFILE console = FDEV_SETUP_STREAM((int(*)(char, FILE *))char_out,NULL, NULL, _FDEV_SETUP_WRITE);(void) vfprintf(&console, fmt, ap);
#elsecbvprintf(char_out, NULL, fmt, ap);
#endif#ifdef CONFIG_PRINTK_SYNCk_spin_unlock(&lock, key);
#endif}
}
开启了CONFIG_LOG_PRINTK子系统后使用 z_log_vprintk()->z_log_msg_runtime_vcreate()(由于启动了DMA这个接口是没有执行)。
2.3..\hal\actions\subsys\trace\printk_dma.c
SYS_INIT(printk_dma_init, APPLICATION, 1);//UART DMA设备入口段的定义,完成对static printk_ctx_t g_pr_ctx;结构体的变量。
printk_dma.c会定义一个vprintk()接口。如果使用DMA方式则引用该接口。当DMA方式开启时通过{cbvprintf(buf_char_out, &ctx_buf, fmt, args);ctx_buf_flush(&ctx_buf);dma_start_tx(pctx);}接口完成数据发送。
void vprintk(const char *fmt, va_list args)
{printk_ctx_t *pctx = &g_pr_ctx;struct buf_out_ctx ctx_buf;if(!pctx->init){__vprintk(fmt, args);return ;}if(pctx->panic){dma_send_sync(pctx);k_busy_wait(100000); //wait fifo send finsheduart_dma_switch(pctx, 0); // CPUpctx->init = FALSE;__vprintk(fmt, args);return;}#ifdef CONFIG_PRINTK_TIME_FREFIXif ((pctx->isprint_time) && (pctx->last_char == '\0' ||pctx->last_char == '\n')) {ctx_buf.count = get_time_prefix(ctx_buf.buf);} else {ctx_buf.count = 0;}#elsectx_buf.count = 0;#endifcbvprintf(buf_char_out, &ctx_buf, fmt, args);ctx_buf_flush(&ctx_buf);dma_start_tx(pctx);
}uart_dma_send_buf()//shell打印引用
uart_dma_send_buf_ex()//HCI打印专用接口。
int uart_dma_send_buf_prepare(); void uart_dma_send_buf_prepare_over();//dsp打印引用
3、logging主要文件
..\zephyr\subsys\logging\log_frontend_dict_uart.c
logging模块前端API,结合printk相关文件。
..\zephyr\subsys\logging\log_minimal.c
mini模式的核心接口。
..\zephyr\subsys\logging\log_msg.c
fall模式的核心接口。
..\zephyr\subsys\logging\log_core.c
logger模块有线程处理方式(异步模式)与同步模式。
logging子系统的入口段的定义如下:
static int enable_logger(void)
{if (IS_ENABLED(CONFIG_LOG_PROCESS_THREAD)) {k_timer_init(&log_process_thread_timer,log_process_thread_timer_expiry_fn, NULL);/* start logging thread */k_thread_create(&logging_thread, logging_stack,K_KERNEL_STACK_SIZEOF(logging_stack),log_process_thread_func, NULL, NULL, NULL,LOG_PROCESS_THREAD_PRIORITY, 0,COND_CODE_1(CONFIG_LOG_PROCESS_THREAD,K_MSEC(CONFIG_LOG_PROCESS_THREAD_STARTUP_DELAY_MS),K_NO_WAIT));k_thread_name_set(&logging_thread, "logging");} else {(void)z_log_init(false, false);}return 0;
}SYS_INIT(enable_logger, POST_KERNEL, CONFIG_LOG_CORE_INIT_PRIORITY);CONFIG_LOG_MODE_MINIMAL为真则为最小模式否则为完整模式。一般小型嵌入式系统只管理打印选择最小模式是完全够用了。
16制打印入口Z_LOG_HEXDUMP2():最小模式调用z_log_minimal_hexdump_print(),完整模式调用Z_LOG_MSG_CREATE()接口。
普通字符串打印入口Z_LOG2():最小模式调用z_log_minimal_printk(),完整模式调用Z_LOG_MSG_CREATE()接口。
#define Z_LOG2(_level, _inst, _source, _dsource, ...) do { \if (!Z_LOG_CONST_LEVEL_CHECK(_level)) { \break; \} \if (IS_ENABLED(CONFIG_LOG_MODE_MINIMAL)) { \Z_LOG_TO_PRINTK(_level, __VA_ARGS__); \break; \} \/* For instance logging check instance specific static level */ \if (_inst != 0 && !IS_ENABLED(CONFIG_LOG_RUNTIME_FILTERING)) { \if (_level > ((struct log_source_const_data *)_source)->level) { \break; \} \} \\bool is_user_context = k_is_user_context(); \if (IS_ENABLED(CONFIG_LOG_RUNTIME_FILTERING) && \!is_user_context && _level > Z_LOG_RUNTIME_FILTER((_dsource)->filters)) { \break; \} \int _mode; \void *_src = IS_ENABLED(CONFIG_LOG_RUNTIME_FILTERING) ? \(void *)(_dsource) : (void *)(_source); \bool string_ok; \LOG_POINTERS_VALIDATE(string_ok, __VA_ARGS__); \if (!string_ok) { \LOG_STRING_WARNING(_mode, _src, __VA_ARGS__); \break; \} \Z_LOG_MSG_CREATE(UTIL_NOT(IS_ENABLED(CONFIG_USERSPACE)), _mode, \Z_LOG_LOCAL_DOMAIN_ID, _src, _level, NULL,\0, __VA_ARGS__); \(void)_mode; \if (false) { \/* Arguments checker present but never evaluated.*/ \/* Placed here to ensure that __VA_ARGS__ are*/ \/* evaluated once when log is enabled.*/ \z_log_printf_arg_checker(__VA_ARGS__); \} \
} while (false)4、分级管理
系统定义了4个等级[DBG, INF, WRN, EERR],但除了这4种等级还有两个比较特殊的接口
WRN_ONC():警告等级只执行一次的模块。
Z_LOG_PRINTK(_is_raw, ...):无等级打印接口。is_raw = 1原始字符串模式,换行[\n]时不自动加[\r],is_raw 这个参数在Minimal Mode下是不起作用的。
5、多格式打印可选
Z_LOG(_level, ...):字符模式
Z_LOG_HEXDUMP(_level, _data, _length, ...)16进制输出方式。与字符串明显区别是有length参数,遇到0也不会结束。
6、运行模式
最小模式 (Minimal Mode)
完整模式 (Full Mode):包含所有高级功能:过滤、缓冲、多后端等
如果只用DMA会被DMA发送接口接管,不开DMA则最底层都是走z_log_msg_runtime_vcreate()接口。接下内容如下:
void z_log_msg_runtime_vcreate(uint8_t domain_id, const void *source,uint8_t level, const void *data, size_t dlen,uint32_t package_flags, const char *fmt, va_list ap)
{int plen;if (fmt) {va_list ap2;va_copy(ap2, ap);plen = cbvprintf_package(NULL, Z_LOG_MSG_ALIGN_OFFSET,package_flags, fmt, ap2);__ASSERT_NO_MSG(plen >= 0);va_end(ap2);} else {plen = 0;}if (plen > Z_LOG_MSG_MAX_PACKAGE) {LOG_WRN("Message dropped because it exceeds size limitation (%u)",(uint32_t)Z_LOG_MSG_MAX_PACKAGE);return;}size_t msg_wlen = Z_LOG_MSG_ALIGNED_WLEN(plen, dlen);struct log_msg *msg;uint8_t *pkg;struct log_msg_desc desc =Z_LOG_MSG_DESC_INITIALIZER(domain_id, level, plen, dlen);if (IS_ENABLED(CONFIG_LOG_MODE_DEFERRED) && BACKENDS_IN_USE()) {msg = z_log_msg_alloc(msg_wlen);if (IS_ENABLED(CONFIG_LOG_FRONTEND) && msg == NULL) {pkg = alloca(plen);} else {pkg = msg ? msg->data : NULL;}} else {msg = alloca(msg_wlen * sizeof(int));pkg = msg->data;}if (pkg && fmt) {plen = cbvprintf_package(pkg, (size_t)plen, package_flags, fmt, ap);__ASSERT_NO_MSG(plen >= 0);}if (IS_ENABLED(CONFIG_LOG_FRONTEND) && frontend_runtime_filtering(source, desc.level)) {log_frontend_msg(source, desc, pkg, data);//}if (BACKENDS_IN_USE()) {z_log_msg_finalize(msg, source, desc, data);//多后端处理}
}
log_frontend_init()功能:完成回调函数配置;MPSC(Multi producer single consumer) 包坏缓冲结构体初始化,static struct mpsc_pbuf_buffer buf,这个buf在Full Mode时才有使用。
//
void log_frontend_init(void)
{int err;if (IS_ENABLED(CONFIG_UART_ASYNC_API)) {err = uart_callback_set(uart_dev, uart_callback, NULL);//异步(完成)回调处理} else if (IS_ENABLED(CONFIG_UART_INTERRUPT_DRIVEN)) {uart_irq_callback_user_data_set(uart_dev, uart_isr_callback, NULL);//中断回调处理err = 0;}__ASSERT(err == 0, "Failed to set callback");if (err < 0) {return;}mpsc_pbuf_init(&buf, &config);//MPSC(Multi producer single consumer) packet buffer 初始化
}
7、过滤
9、同步异步打印
ats3239_dvb_ext_nor_defconfig:
CONFIG_LOG_MODE_MINIMAL//最小模式
CONFIG_LOG_MODE_IMMEDIATE//立即模式
enum z_log_msg_mode {
/* Runtime mode is least efficient but supports all cases thus it is treated as a fallback method when others cannot be used.*/
Z_LOG_MSG_MODE_RUNTIME,
/* Mode creates statically a string package on stack and calls a function for creating a message. It takes code size than Z_LOG_MSG_MODE_ZERO_COPY but is a bit slower.*/
Z_LOG_MSG_MODE_FROM_STACK,
/* Mode calculates size of the message and allocates it and writes directly to the message space. It is the fastest method but requires more code size.*/
Z_LOG_MSG_MODE_ZERO_COPY,
/* Mode optimized for simple messages with 0 to 2 32 bit word arguments.*/
Z_LOG_MSG_MODE_SIMPLE,
};
依赖线程的模式:
Z_LOG_MSG_MODE_RUNTIME- 唯一的线程依赖模式
不依赖线程的模式:
Z_LOG_MSG_MODE_FROM_STACK- 栈上处理,同步立即
Z_LOG_MSG_MODE_ZERO_COPY- 零拷贝优化,高性能同步
Z_LOG_MSG_MODE_SIMPLE- 简单消息特化,轻量同步
9、多后端
void z_log_msg_finalize(struct log_msg *msg, const void *source,const struct log_msg_desc desc, const void *data)
{if (!msg) {z_log_dropped(false);return;}if (data) {uint8_t *d = msg->data + desc.package_len;memcpy(d, data, desc.data_len);}msg->hdr.desc = desc;msg->hdr.source = source;
#if CONFIG_LOG_THREAD_ID_PREFIXmsg->hdr.tid = k_is_in_isr() ? NULL : k_current_get();
#endifz_log_msg_commit(msg);
}10、实例日志
实例打印接口
#define Z_LOG_INSTANCE(_level, _inst, ...) do { \(void)_inst; \Z_LOG2(_level, 1, \COND_CODE_1(CONFIG_LOG_RUNTIME_FILTERING, (NULL), (Z_LOG_INST(_inst))), \(struct log_source_dynamic_data *)COND_CODE_1( \CONFIG_LOG_RUNTIME_FILTERING, \(Z_LOG_INST(_inst)), (NULL)), \__VA_ARGS__); \
} while (0)11、紧急状态(panic模式)
核心作用
标识系统处于崩溃状态,需要特殊的日志处理策略
确保关键日志在系统崩溃时能够输出
避免在崩溃状态下使用复杂的日志机制
void log_frontend_panic(void){in_panic = true;/* Flush all data */while (atomic_get(&active_cnt) > 0) {tx();/* empty */}
}12、其打印模块
虽然zephyr系统有自己的底层vprintk()接口,宏定义层LOG_XXX()->Z_LOG()接口,但在一个平台的SDK经常会定义一套自己的printf与log接口。如炬芯的:SYS_LOG_XXX(),os_printk() ,ACT_LOG()。
12.1 ..\framework\porting\include\os_common_api.h
定义了SYS_LOG_XXX()打印 SYS_LOG_ERR() /SYS_LOG_WRN() /SYS_LOG_INF() /SYS_LOG_DBG()
#ifndef CONFIG_ACTLOG
#ifdef CONFIG_LOG
#define SYS_LOG_INF(...) \do { \if (LOG_PRINT_FUNC_NAME) { \if (Z_LOG_CONST_LEVEL_CHECK(LOG_LEVEL_INF)) { \Z_LOG_WITH_FUNC_NAME(LOG_LEVEL_INF, __VA_ARGS__); \} \} else { \Z_LOG_ACTS(LOG_LEVEL_INF, __VA_ARGS__); \} \} while (0)
#else
#define SYS_LOG_INF(...)
#endif
#else
#define SYS_LOG_INF(...) ACT_LOG(LOG_LEVEL_INF,__VA_ARGS__)
#endif定义了 os_printk() -> vprintk()
12.2..\hal\actions\subsys\log\act_log.c ..\hal\actions\include\logging\act_log.h
如果CONFIG_ACTLOG被定义则SYS_LOG_XXX()使用actions下的log模块接口。
12.3..\hal\actions\subsys\trace\printk_acts.c
如果CONFIG_ACTLOG没有定义SYS_LOG_XXX()使用zephyr的vprintk()接口。
