【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_SMP
    INIT_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),
#else
			0
#endif
		};
#ifdef CONFIG_PICOLIBC
		(void) vfprintf(&ctx.file, fmt, ap);
#else
		cbvprintf(buf_char_out, &ctx, fmt, ap);
#endif
		if (ctx.buf_count) {
			buf_flush(&ctx);
		}
	} else {
#ifdef CONFIG_PRINTK_SYNC
		k_spinlock_key_t key = k_spin_lock(&lock);
#endif
#ifdef CONFIG_PICOLIBC
		FILE console = FDEV_SETUP_STREAM((int(*)(char, FILE *))char_out,
						 NULL, NULL, _FDEV_SETUP_WRITE);
		(void) vfprintf(&console, fmt, ap);
#else
		cbvprintf(char_out, NULL, fmt, ap);
#endif
#ifdef CONFIG_PRINTK_SYNC
		k_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 finshed
		uart_dma_switch(pctx, 0); // CPU
		pctx->init = FALSE;
		__vprintk(fmt, args);
		return;
	}
	#ifdef CONFIG_PRINTK_TIME_FREFIX
	if ((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;
	}
	#else
	ctx_buf.count = 0;
	#endif
	cbvprintf(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_PREFIX
	msg->hdr.tid = k_is_in_isr() ? NULL : k_current_get();
#endif
	z_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()接口。

posted @ 2025-12-07 18:21  gccbuaa  阅读(8)  评论(0)    收藏  举报