From 39e512cdf84e136ae04d4a0397ea3251681b2c0b Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 6 Jun 2022 23:00:15 +0800 Subject: [PATCH] doc: formated output Add formated output backend and cbprintf doc. Signed-off-by: Frank Li --- doc/source/develop/index.rst | 1 + .../osserivces/formated_output_cbprintf.rst | 223 ++++++++++ .../formated_output_userapi_backend.rst | 409 ++++++++++++++++++ doc/source/develop/osserivces/index.rst | 12 + 4 files changed, 645 insertions(+) create mode 100644 doc/source/develop/osserivces/formated_output_cbprintf.rst create mode 100644 doc/source/develop/osserivces/formated_output_userapi_backend.rst create mode 100644 doc/source/develop/osserivces/index.rst diff --git a/doc/source/develop/index.rst b/doc/source/develop/index.rst index 829405a..7227079 100644 --- a/doc/source/develop/index.rst +++ b/doc/source/develop/index.rst @@ -8,3 +8,4 @@ subsys/index.rst driver/index.rst + osserivces/index.rst diff --git a/doc/source/develop/osserivces/formated_output_cbprintf.rst b/doc/source/develop/osserivces/formated_output_cbprintf.rst new file mode 100644 index 0000000..c3f1903 --- /dev/null +++ b/doc/source/develop/osserivces/formated_output_cbprintf.rst @@ -0,0 +1,223 @@ +.. _osservices_formated_output_cbprintf: + +CBPRINTF +############################## + +本文说明Zephyr格式化输出的配置和限制。 + +基本概念 +======== + +常规C99标准libc库中中的\*printf实现是可以支持流式输出设备,并且也能提供内部缓存。但对于嵌入式设备来说可能都不要求持这两种形式以达到降低使用资源的目的。因此zephyr专门设计了cbprintf用来转换C99指定的格式化字符串和参数。cbprintf可以支持所有的C99格式规范。 + + +同步回调输出 +~~~~~~~~~~~~ + +cbprintf通过回调机制,对格式化字符串和参数进行一边解析一边输出,避免使用大量缓存。 +printk/printf/shell_print使用的同步机制,也就是使用下面的API + +.. code:: c + + int cbprintf(cbprintf_cb out, void *ctx, const char *format, ...); + int cbvprintf(cbprintf_cb out, void *ctx, const char *format, va_list ap); + + +异步cbprintf包 +~~~~~~~~~~~~~~~ + +在有些情况下,格式化会被推迟,例如log系统就是在API被调用的时候打包加入到buffer,core task获取包后用下列标准化函数处理。在这种情况下,格式化字符串和参数被转换成了一个独立的包。包的主要内容类似于va_list堆栈框架,因此标准的格式化函数被用来处理一个包。 + +.. code:: c + + int cbprintf_package(void *packaged, size_t len, uint32_t flags, const char *format, ...); + int cbvprintf_package(void *packaged, size_t len, uint32_t flags, const char *format, va_list ap); + CBPRINTF_STATIC_PACKAGE(packaged, inlen, outlen, align_offset, flags, ...) + + +当格式为%s时,对应的参数是一个指向有字符串的指针,默认情况下包只会有该指针而不包含字符串内容,使用下面API可以将字符串的内容加入到包中 + +.. code:: c + + int cbprintf_fsc_package(void *in_packaged, size_t in_len, void *packaged, size_t len); + +下面API可以将包以同步的方式通过回调函数一边解析一边输出 + +.. code:: c + + int cbpprintf(cbprintf_cb out, void *ctx, void *packaged); + + +代码及位置 +========== + +格式化字符串解析和包生成解析是cbprintf的核心实现内容,本文无意进行分析,只对其文件位置和作用进行说明: +\ ``include\sys\cbprintf.h`` cbprintf.h头文件提供所有外部函数和宏 +\ ``lib\os\cbprintf_packaged.c`` 实现了cbprintf包的处理 +\ ``lib\os\cbprintf.c`` 格式化输出函数封装实现,通过配置文件在nano和complete当作二选一 +\ ``lib\os\cbprintf_nano.c`` 格式化输出函数的nano实现,可以优化对资源的需求,但大多数格式都不被支持。 +\ ``lib\os\cbprintf_complete.c`` 格式化输出函数的完整实现,默认情况下会使用该实现。 +\ ``lib\os\Kconfig.cbprintf`` cbprintf的配置文件 + +配置及影响 +========== + +CBORINTF实现 +~~~~~~~~~~~~ + +决定使用Complete还是nano实现,在risc-v上nano的二进制代码比Complete小1.5KByte左右, ``CONFIG_CBPRINTF_COMPLETE`` 和 ``CONFIG_CBPRINTF_NANO`` 这两个选项只能二选一配置。要分析二者的差异先要看格式化字符串的构成: +\ ``%[flags][field width][.precision][length]type`` 。 +例如 + +.. code:: c + + printk("%04lld", a); // flags: 0, filed width: 4, length: ll, type: d + printk("%.3s", "abcdefg"); // .precision: 3 , type: s + +这里对比说明两种配置对格式化字符串的支持情况。 + +flags +^^^^^^^ + +符号,可以包含下列一个或多个: + +* \+ : 显示数值符号 +* 空格 : 用空格补齐数值符号 +* \- : 左对齐,默认是右对齐 +* 0 : 宽度不足补0 +* \# : 添加进制数符号(0x, o),显示浮点数小数后面的0 + +\ ``CONFIG_CBPRINTF_COMPLETE`` 和 ``CONFIG_CBPRINTF_NANO`` 都支持。 + +field width +^^^^^^^^^^^^^^ + +给出显示数值的最小宽度,典型用于制表输出时填充固定宽度的表目。实际输出字符的个数不足域宽,则根据左对齐或右对齐进行填充。实际输出字符的个数超过域宽并不引起数值截断,而是显示全部。宽度值的前导0被解释为0填充标志;前导的负值被解释为其绝对值,负号解释为左对齐标志。如果域宽值为*,则由对应的函数参数的值为当前域宽。 + +\ ``CONFIG_CBPRINTF_COMPLETE`` 和 ``CONFIG_CBPRINTF_NANO`` 都支持。 + +\.precision +^^^^^^^^^^^^^^ + +指明输出的最大长度,依赖于特定的格式化类型。对于d、i、u、x、o的整型数值,是指最小数字位数,不足的位要在左侧补0,如果超过也不截断,缺省值为1。对于a,A,e,E,f,F的浮点数值,是指小数点右边显示的数字位数,必要时四舍五入或补0;缺省值为6。对于g,G的浮点数值,是指有效数字的最大位数;缺省值为6。对于s的字符串类型,是指输出的字节的上限,超出限制的其它字符将被截断。如果域宽为*,则由对应的函数参数的值为当前域宽。如果仅给出了小数点,则域宽为0。 + +\ ``CONFIG_CBPRINTF_COMPLETE`` 和 ``CONFIG_CBPRINTF_NANO`` 都支持。 + +length +^^^^^^^^^^^^^^ + +指出浮点型参数或整型参数的长度。 + +* hh : 期待一个从char提升的int尺寸的参数 +* h : 期待一个从short提升的int尺寸的参数 +* l : 期待一个long尺寸的参数(整数)/期待一个double尺寸的参数(浮点) +* ll : 期待一个long long尺寸的参数 +* L : 期待一个long double尺寸的参数 +* z : 期待一个size_t尺寸的参数 +* j : 期待一个intmax_t尺寸的参数 +* t : 待一个ptrdiff_t尺寸的参数 + +\ ``CONFIG_CBPRINTF_NANO`` 不支持L,j,t,其它都支持。 +\ ``CONFIG_CBPRINTF_COMPLETE`` 不支持L,其它都支持。 + + +type +^^^^^^^^^^^^^^ + +转换说明 + +* d, i : 有符号十进制数值int +* u : 十进制unsigned int +* f, F : double型输出10进制定点表示 +* e, E : double值,输出形式为10进制指数 +* g, G : double型数值,精度定义为全部有效数字位数 +* x, X : 16进制unsigned int +* o : 8进制unsigned int +* c : 字符 +* s : 字符串 +* p : void*指针 +* a,A : double型的16进制表示 +* n : 不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量 + +\ ``CONFIG_CBPRINTF_NANO`` 不支持f,F,e,E,g,G,a,A,n,其它都支持。 +\ ``CONFIG_CBPRINTF_COMPLETE`` 配合以下配置支持所有以上type: +* \ ``CBPRINTF_FP_SUPPORT=y`` 时,支持f,F,e,E,g,G +* \ ``CONFIG_CBPRINTF_FP_A_SUPPORT=y`` 时,支持a,A +* \ ``CONFIG_CBPRINTF_N_SPECIFIER=y`` 时,支持n + +当配置`CONFIG_CBPRINTF_FP_ALWAYS_A=y`` 时f,F,e,E,g,G都将以a,A的的形式格式化输出。 + + +整型宽度 +~~~~~~~~~~ + +\ ``CONFIG_CBPRINTF_FULL_INTEGRAL`` 或 ``CONFIG_CBPRINTF_REDUCED_INTEGRAL`` 这两个选项只能二选一配置整型宽度,当配置为 ``CONFIG_CBPRINTF_REDUCED_INTEGRAL=y`` 时整型被限制为32bit,会影响到size_t和intmax_t以及指针的转换。 + + + +包配置 +~~~~~~~ + +包的配置只对LOG系统有影响 +\ ``CONFIG_CBPRINTF_PACKAGE_LONGDOUBLE`` 打包时支持long double类型参数。 +\ ``CONFIG_CBPRINTF_STATIC_PACKAGE_CHECK_ALIGNMENT`` 用于静态包对齐检查。 + +产生C库兼容函数 +~~~~~~~~~~~~~~~ + +\ ``CONFIG_CBPRINTF_LIBC_SUBSTS=y`` 用于产生C库兼容函数, 函数名在标准输出函数后面加上cb,这样就使用的是cbprint的格式化程序,而不用使用libc中的。 + +.. code:: c + + int fprintfcb(FILE *stream, const char *format, ...); + int vfprintfcb(FILE *stream, const char *format, va_list ap); + int printfcb(const char *format, ...); + int vprintfcb(const char *format, va_list ap); + int snprintfcb(char *str, size_t size, const char *format, ...); + int vsnprintfcb(char *str, size_t size, const char *format, va_list ap); + + +Zephyr格式化输出总结说明 +========================= + +当使用printk/shell_print/minilibc中的\*printf/LOG*进行格式化输出时: +Zephyr在不进行手配置情况下,系统默认配置如下,**不支持浮点数的打印**: + +.. code:: + + CONFIG_CBPRINTF_COMPLETE=y + CONFIG_CBPRINTF_FULL_INTEGRAL=y + +浮点%f, %e, %g打印需要添加配置: +.. code:: + + CBPRINTF_FP_SUPPORT=y + +%a支持需要添加配置: +.. code:: + + CONFIG_CBPRINTF_FP_A_SUPPORT=y + +%n支持需要添加配置: +.. code:: + + CONFIG_CBPRINTF_N_SPECIFIER=y + +要缩小代码尺寸,注意前面nano的格式化限制: +.. code:: + + CONFIG_CBPRINTF_NANO=y + +要缩小堆栈占用: +.. code:: + + CONFIG_CBPRINTF_REDUCED_INTEGRAL=y + + + +参考 +===== + +https://docs.zephyrproject.org/3.0.0/reference/misc/formatted_output.html +https://www.dii.uchile.cl/~daespino/files/Iso_C_1999_definition.pdf +https://zh.wikipedia.org/wiki/%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2 diff --git a/doc/source/develop/osserivces/formated_output_userapi_backend.rst b/doc/source/develop/osserivces/formated_output_userapi_backend.rst new file mode 100644 index 0000000..dfb0793 --- /dev/null +++ b/doc/source/develop/osserivces/formated_output_userapi_backend.rst @@ -0,0 +1,409 @@ +.. _osservices_formated_output_userapi_backend: + +Zephyr格式化输出-用户API和后端 +############################### + + +在Zephyr中提供以下API提供格式化输出 + +* printk: 内核调试信息打印 +* \*printf:printf/fprintf/vfprintf/vprintf等标准C输出 +* shell_print: Shell系统打印 +* LOG\_\*: LOG_INF/LOG_DBG/LOG_ERR/LOG_WAR,LOG系统打印 + + +以下分别说明其格式化处理函数及用什么后端输出,也可以直接跳到最后看总结。 + +prink +====== + +函数 ``printk`` 通常用于格式化输出内核调试信息的打印,为了方便我们通常也会在自己写的应用中调用该API进行打印输出。 + + +格式化处理函数 +~~~~~~~~~~~~~~~ + +函数 ``printk`` 源代码在 ``lib/os/printk.c`` 中通过调用 ``vprintk`` 完成 + +.. code:: c + + void vprintk(const char *fmt, va_list ap) + { + struct out_context ctx = { 0 }; + + cbvprintf(char_out, &ctx, fmt, ap); + } + + +\ ``vprintk`` 使用 ``cbvprintf`` 完成字符串格式化,并同时使用 ``char_out`` 一个一个字符输出 + +.. code:: c + + static int char_out(int c, void *ctx_p) + { + struct out_context *ctx = ctx_p; + + ctx->count++; + return _char_out(c); + } + + +后端 +~~~~~ + +\ ``char_out`` 由 ``__printk_hook_install`` 注册的 ``_char_out`` 进行字符输出 + +.. code:: c + + void __printk_hook_install(int (*fn)(int)) + { + _char_out = fn; + } + + +\ ``__printk_hook_install`` 会在zephyr console初始化的时候调用,设置入console的输出,根据平台硬件的不同zephyr的console支持UART,RAM, RTT, IPM等等。一般情况下我们默认会使用UART,也就是printk输出到串口。 +console的代码在 ``driver/console/`` 下,串口的文件是 ``uart_console.c`` 在其初始化函数 ``uart_console_init`` 中调用 ``uart_console_hook_install`` 进行printk hook注册 + +.. code:: c + + static void uart_console_hook_install(void) + { + #if defined(CONFIG_STDOUT_CONSOLE) + __stdout_hook_install(console_out); + #endif + #if defined(CONFIG_PRINTK) + __printk_hook_install(console_out); + #endif + } + + +\*printf +========= + +Zephyr在 ``lib/libc/minimal/source/stdout`` 下实现了 ``printf/fprintf/vfprintf/vprintf`` 等标准C输出, 以在 ``fprint.c`` 中的 ``printf`` 进行说明 + +格式化处理函数 +~~~~~~~~~~~~~~~ + +.. code:: c + + int printf(const char *ZRESTRICT format, ...) + { + va_list vargs; + int r; + + va_start(vargs, format); + r = cbvprintf(fputc, DESC(stdout), format, vargs); + va_end(vargs); + + return r; + } + + +\ ``printf`` 使用 ``cbvprintf`` 完成字符串格式化,并同时使用 ``fputc`` 一个一个字符输出, 在 ``stdout_console.c`` 中 + +.. code:: c + + int fputc(int c, FILE *stream) + { + return zephyr_fputc(c, stream); + } + + +**注意,当Zephyr使用第三方libc时,例如newlib,库中的标准格式化输出函数将使用第三方库中格式化处理而不是使用 cbvprintf。** + +后端 +~~~~~ + +\ ``zephyr_fputc`` 实现为在 ``z_impl_zephyr_fputc`` + +.. code:: c + + int z_impl_zephyr_fputc(int c, FILE *stream) + { + return (stream == stdout || stream == stderr) ? _stdout_hook(c) : EOF; + } + +其hook函数由``__stdout_hook_install注册 + +.. code:: c + + void __stdout_hook_install(int (*hook)(int)) + { + _stdout_hook = hook; + } + + +和 ``printk`` 一样其hook函数会在console driver中调用 ``__stdout_hook_install`` 进行注册(前面代码 ``uart_console_hook_install`` 可以看到) + +shell_print +============== + +shell系统要控制自己的输入输出,当同为一个后端时,例如都在串口上,为了不被 ``printk`` 和 ``printf`` 干扰,建议使用 ``shell_print`` 进行打印。 +\ ``shell_print`` 是一个 ``include\shell\shell.h`` 中的宏,实际使用的是 ``subsys\shell\shell.c`` 中的 ``shell_fprintf`` 为简化说明,列出调用关系: +\ ``shell_print->shell_fprintf->shell_vfprintf->z_shell_vfprintf->z_shell_fprintf_fmt`` 。 + +格式化处理函数 +~~~~~~~~~~~~~~ + +在 ``shell_fprintf.c`` 中实现的 ``z_shell_fprintf_fmt`` + +.. code:: c + + void z_shell_fprintf_fmt(const struct shell_fprintf *sh_fprintf, + const char *fmt, va_list args) + { + (void)cbvprintf(out_func, (void *)sh_fprintf, fmt, args); + + if (sh_fprintf->ctrl_blk->autoflush) { + z_shell_fprintf_buffer_flush(sh_fprintf); + } + } + + +\ ``z_shell_fprintf_fmt`` 使用 ``cbvprintf`` 完成字符串格式化,并同时使用 ``out_func`` 一个一个字符输出。 + +.. code:: c + + static int out_func(int c, void *ctx) + { + const struct shell_fprintf *sh_fprintf; + const struct shell *shell; + + sh_fprintf = (const struct shell_fprintf *)ctx; + shell = (const struct shell *)sh_fprintf->user_ctx; + + if ((shell->shell_flag == SHELL_FLAG_OLF_CRLF) && (c == '\n')) { + (void)out_func('\r', ctx); + } + //装入buffer + sh_fprintf->buffer[sh_fprintf->ctrl_blk->buffer_cnt] = (uint8_t)c; + sh_fprintf->ctrl_blk->buffer_cnt++; + + //装满后才进行真正的flush写到后端 + if (sh_fprintf->ctrl_blk->buffer_cnt == sh_fprintf->buffer_size) { + z_shell_fprintf_buffer_flush(sh_fprintf); + } + + return 0; + } + + void z_shell_fprintf_buffer_flush(const struct shell_fprintf *sh_fprintf) + { + //写到后端 + sh_fprintf->fwrite(sh_fprintf->user_ctx, sh_fprintf->buffer, + sh_fprintf->ctrl_blk->buffer_cnt); + sh_fprintf->ctrl_blk->buffer_cnt = 0; + } + + +\ ``sh_fprintf->fwrite`` 是 ``SHELL_DEFINE->Z_SHELL_FPRINTF_DEFINE`` 注册函数 ``z_shell_print_stream`` 最后会调用到 ``z_shell_write`` + +.. code:: c + + void z_shell_write(const struct shell *shell, const void *data, + size_t length) + { + __ASSERT_NO_MSG(shell && data); + + size_t offset = 0; + size_t tmp_cnt; + + while (length) { + int err = shell->iface->api->write(shell->iface, + &((const uint8_t *) data)[offset], length, + &tmp_cnt); + (void)err; + __ASSERT_NO_MSG(err == 0); + __ASSERT_NO_MSG(length >= tmp_cnt); + offset += tmp_cnt; + length -= tmp_cnt; + if (tmp_cnt == 0 && + (shell->ctx->state != SHELL_STATE_PANIC_MODE_ACTIVE)) { + shell_pend_on_txdone(shell); + } + } + + +这里的 ``shell->iface->api->write`` 就是shell的后端write + +后端 +~~~~~~ + +shell的后端的所有实现都放在 ``subsys/shell/backends`` 下,支持uart, rtt, telnet, dummy,当选择串口作为后端时 ``shell_print`` 将输出到串口,串口后端实现的代码是shell_uart.c + +.. code:: c + + const struct shell_transport_api shell_uart_transport_api = { + .init = init, + .uninit = uninit, + .enable = enable, + .write = write, + .read = read, + #ifdef CONFIG_MCUMGR_SMP_SHELL + .update = update, + #endif /* CONFIG_MCUMGR_SMP_SHELL */ + }; + + static int write(const struct shell_transport *transport, + const void *data, size_t length, size_t *cnt) + { + const struct shell_uart *sh_uart = (struct shell_uart *)transport->ctx; + const uint8_t *data8 = (const uint8_t *)data; + + //使用串口直接输出 + for (size_t i = 0; i < length; i++) { + uart_poll_out(sh_uart->ctrl_blk->dev, data8[i]); + } + + *cnt = length; + + sh_uart->ctrl_blk->handler(SHELL_TRANSPORT_EVT_TX_RDY, + sh_uart->ctrl_blk->context); + + + return 0; + } + + +LOG\_\* +========= + +\ ``LOG_INF/LOG_DBG/LOG_ERR/LOG_WAR`` 是LOG系统打印,Zephyr提供这些格式化打印接口方便过滤和控制打印。 +其调用关系可简化为: +\ ``LOG_\*->Z_LOG->Z_LOG2-Z_LOG_MSG2_CREATE->Z_LOG_MSG2_CREATE2`` +\ ``Z_LOG_MSG2_CREATE2`终于会根据配置的不同调用`z_log_msg2_runtime_create`` 或 ``Z_LOG_MSG2_SIMPLE_CREATE`` 或 ``Z_LOG_MSG2_STACK_CREATE`` + +格式化处理函数 +~~~~~~~~~~~~~~ + +*动态生成* + +\ ``z_log_msg2_runtime_create->z_log_msg2_runtime_vcreate->z_impl_z_log_msg2_runtime_vcreate`` + +.. code:: c + + void z_impl_z_log_msg2_runtime_vcreate(uint8_t domain_id, const void *source, + uint8_t level, const void *data, size_t dlen, + const char *fmt, va_list ap) + { + int plen; + + if (fmt) { + va_list ap2; + + va_copy(ap2, ap); + plen = cbvprintf_package(NULL, Z_LOG_MSG2_ALIGN_OFFSET, 0, + fmt, ap2); + __ASSERT_NO_MSG(plen >= 0); + va_end(ap2); + } else { + plen = 0; + } + + size_t msg_wlen = Z_LOG_MSG2_ALIGNED_WLEN(plen, dlen); + struct log_msg2 *msg; + struct log_msg2_desc desc = + Z_LOG_MSG_DESC_INITIALIZER(domain_id, level, plen, dlen); + + if (IS_ENABLED(CONFIG_LOG2_MODE_IMMEDIATE)) { + msg = alloca(msg_wlen * sizeof(int)); + } else { + msg = z_log_msg2_alloc(msg_wlen); + } + + if (msg && fmt) { + plen = cbvprintf_package(msg->data, (size_t)plen, 0, fmt, ap); + __ASSERT_NO_MSG(plen >= 0); + } + + z_log_msg2_finalize(msg, source, desc, data); + } + + +使用 ``cbvprintf_package`` 打包格式化,使用 ``z_log_msg2_finalize`` 对打包后的数据进行输出 + +*静态生成* + +\ ``Z_LOG_MSG2_SIMPLE_CREATE`` 先使用 ``CBPRINTF_STATIC_PACKAGE`` 打包格式化,再使用 ``z_log_msg2_finalize`` 对打包后的数据进行输出 +\ ``Z_LOG_MSG2_STACK_CREATE``先使用 ``CBPRINTF_STATIC_PACKAGE`` 打包格式化,再通过 ``z_log_msg2_static_create->z_impl_z_log_msg2_static_create->z_log_msg2_finalize`` 对打包后的数据进行输出 + +后端 +~~~~~ + +\ ``z_log_msg2_finalize`` 只是将 ``cbvprintf_package`` 或 ``CBPRINTF_STATIC_PACKAGE`` 打包后的数据送到log core, log core会将包送给后端进行显示。 +log的backend实现文件放到 ``subsys\logging\`` 中以名字为 ``log_backend_\*.c`` , log系统的backend可以根据硬件平台的不同选择uart, rtt, swo, fs, net等等。其中uart实现在 ``log_backend_uart.c`` +显示的执行流程是 ``process->log_output_msg2_process`` 简化如下 + +.. code:: c + + void log_output_msg2_process(const struct log_output *output, + struct log_msg2 *msg, uint32_t flags) + { + //读取包数据 + uint8_t *data = log_msg2_get_package(msg, &len); + + if (len) { + int err = cbpprintf(raw_string ? cr_out_func : out_func, + (void *)output, data); + + (void)err; + __ASSERT_NO_MSG(err >= 0); + } + + //使用cbpprintf解析包数据,并使用out_func输出 + if (len) { + int err = cbpprintf(raw_string ? cr_out_func : out_func, + (void *)output, data); + + (void)err; + __ASSERT_NO_MSG(err >= 0); + } + } + + +\ ``out_func`` 实现在log_output.c中,收到字符会先放到buffer,达到一定量后调用 ``log_output_flush->buffer_write->(output->func)`` 进行输出 +output使用的是 ``LOG_OUTPUT_DEFINE(log_output_uart, char_out, uart_output_buf, sizeof(uart_output_buf))`` uart的 ``char_out`` 实现如下 + +.. code:: c + + static int char_out(uint8_t *data, size_t length, void *ctx) + { + ARG_UNUSED(ctx); + int err; + + if (IS_ENABLED(CONFIG_LOG_BACKEND_UART_OUTPUT_DICTIONARY_HEX)) { + dict_char_out_hex(data, length); + return length; + } + + if (!IS_ENABLED(CONFIG_LOG_BACKEND_UART_ASYNC) || in_panic || !use_async) { + for (size_t i = 0; i < length; i++) { + uart_poll_out(uart_dev, data[i]); + } + + return length; + } + + err = uart_tx(uart_dev, data, length, SYS_FOREVER_US); + __ASSERT_NO_MSG(err == 0); + + err = k_sem_take(&sem, K_FOREVER); + __ASSERT_NO_MSG(err == 0); + + (void)err; + + return length; + } + +可以看到是使用的串口驱动直接输出。 + +总结 +====== + +printk: 使用cbvprintf完成字符串格式化,输出由console决定 +\*printf: 当使用zephyr自己的minilibc时,使用cbvprintf完成字符串格式化,输出由console决定 +shell_print: 使用 ``cbvprintf`` 完成字符串格式化,输出由shell自己配置的后端决定 +LOG\_\*:使用 ``cbvprintf_package`` 或 ``CBPRINTF_STATIC_PACKAGE`` 打包格式化字符串,由 ``cbpprintf`` 根据包数据完成字符串格化,输出由log自己配置的后端决定 + +当console和shell后端还有log后端都选择为串口时,由于大家最后都是通过串口驱动输出,以上4类格式化API同时在多线程或中断中存在时会相互干扰,使用时需要留意。 diff --git a/doc/source/develop/osserivces/index.rst b/doc/source/develop/osserivces/index.rst new file mode 100644 index 0000000..cdcf379 --- /dev/null +++ b/doc/source/develop/osserivces/index.rst @@ -0,0 +1,12 @@ +.. _osservices: + +OS服务 +###### + +分析Zephyr内核服务的使用和实现方法。 + +.. toctree:: + :maxdepth: 1 + + formated_output_userapi_backend.rst + formated_output_cbprintf.rst -- Gitee