5.19 Console 设备
2026/7/20大约 5 分钟设备驱动驱动设备控制台Console串口输出
5.19 Console 设备
📚 本节导读
学习时长: 约 20 分钟
难度级别: ⭐⭐☆☆☆
前置知识: 5.1 设备驱动框架、5.4 Serial 设备
🎯 学习目标
- 理解 Console 控制台设备在 OneOS 中的角色与架构
- 掌握控制台设备的注册与切换方法
- 理解
os_kprintf输出的底层重定向机制 - 能够将日志输出重定向到不同的物理设备(串口、USB、网络等)
一、概述
Console(控制台)设备是 OneOS 中用于系统日志输出的抽象设备。它不直接对应某个硬件外设,而是作为 os_kprintf 等内核日志函数的输出目标,通过一个 "Console 设备指针" 将所有日志输出重定向到实际的物理设备(如串口、USB CDC 等)。
1.1 Console 在输出体系中的位置
┌─────────────────────────────────┐
│ os_kprintf() / KLOG_xxx() │ ← 内核/应用日志 API
├─────────────────────────────────┤
│ os_hw_console_output() │ ← Console 输出接口
├─────────────────────────────────┤
│ gs_console_device (设备指针) │ ← 当前控制台设备
├──────────┬──────────┬───────────┤
│ Serial │ USB CDC │ ... │ ← 实际物理设备
└──────────┴──────────┴───────────┘1.2 核心特点
- 设备无关:Console 不绑定具体硬件,通过设备指针实现输出重定向
- 热切换:运行时可通过
os_console_set_device()切换输出设备 - 弱符号回退:当没有注册 Console 设备时,通过
__os_hw_console_output弱符号提供默认输出 - 自动初始化:通过
OS_INIT_CALL机制在设备初始化阶段自动注册
二、核心数据结构
2.1 全局控制台设备指针
static os_device_t *gs_console_device = OS_NULL;整个系统只有一个 Console 设备指针,指向当前用于输出的设备。
2.2 源文件位置
| 文件 | 路径 | 说明 |
|---|---|---|
console.h | drivers/console/console.h | Console 接口头文件 |
console.c | drivers/console/console.c | Console 实现源码 |
三、API 参考
3.1 设置 Console 设备
os_device_t *os_console_set_device(const char *name);参数:
name:要设置为 Console 设备的设备名称(如"uart0")
返回值:旧的 Console 设备指针(OS_NULL 表示之前没有设置)
功能:通过设备名称查找并打开设备,将其设置为新的 Console 输出目标。如果之前已有 Console 设备,会自动关闭旧设备。
/* 将串口1设置为控制台 */
os_console_set_device("uart1");
/* 将 USB CDC 虚拟串口设置为控制台 */
os_console_set_device("vcom");3.2 获取 Console 设备
os_device_t *os_console_get_device(void);返回值:当前 Console 设备指针(OS_NULL 表示未设置)
os_device_t *console = os_console_get_device();
if (console != OS_NULL)
{
os_kprintf("Console device: %s\n", console->name);
}3.3 控制台硬件输出
void os_hw_console_output(char *log_buff);功能:将日志缓冲区内容输出到当前 Console 设备。如果 Console 设备未设置,则调用弱符号 __os_hw_console_output(BSP 层可覆盖)。
四、实现原理
4.1 初始化流程
Console 设备通过 OS_INIT_CALL 机制在设备初始化阶段自动注册:
os_err_t os_console_init(void)
{
os_console_set_device(OS_CONSOLE_DEVICE_NAME);
return OS_SUCCESS;
}
OS_INIT_CALL(os_console_init, OS_INIT_LEVEL_PRE_DEVICE, OS_INIT_SUBLEVEL_MIDDLE);OS_CONSOLE_DEVICE_NAME由oneos_config.h或 BSP 的board.h定义- 初始化级别为
OS_INIT_LEVEL_PRE_DEVICE,在设备初始化之前执行,确保 Console 就绪后其他设备初始化日志可以正常输出
4.2 输出流程
os_hw_console_output 函数的输出流程:
void os_hw_console_output(char *log_buff)
{
os_device_t *console = os_console_get_device();
if (console == OS_NULL)
{
__os_hw_console_output(log_buff); // 弱符号回退
}
else
{
// 通过设备写入接口逐块输出
int send_index = 0;
int size = strlen(log_buff);
while (send_index < size)
{
#ifdef DLOG_USING_ASYNC_OUTPUT
if (os_is_irq_disabled())
count = os_device_write_nonblock(console, 0, log_buff + send_index, size - send_index);
else
count = os_device_write_block(console, 0, log_buff + send_index, size - send_index);
#else
count = os_device_write_nonblock(console, 0, log_buff + send_index, size - send_index);
#endif
if (count <= 0) break;
send_index += count;
}
}
}关键设计点:
- 弱符号回退:
__os_hw_console_output是弱符号,BSP 可覆盖实现(如直接操作 UART 寄存器打印字符),在 Console 设备未就绪时也能输出 - 异步输出模式:
DLOG_USING_ASYNC_OUTPUT宏启用时,在关中断状态下使用非阻塞写入,开中断时使用阻塞写入,避免中断中阻塞 - 分块写入:支持设备驱动返回部分写入长度,通过循环确保全部数据输出
4.3 弱符号回退
OS_WEAK void __os_hw_console_output(char *str)
{
// 默认空实现,BSP 可覆盖
}BSP 覆盖示例(在 board.c 中):
void __os_hw_console_output(char *str)
{
// 直接通过 UART 寄存器逐字符输出,不依赖设备框架
while (*str != '\0')
{
while (!(USART1->SR & USART_SR_TXE));
USART1->DR = *str++;
}
}五、使用示例
5.1 查看当前 Console 设备
os_device_t *console = os_console_get_device();
if (console)
{
os_kprintf("当前控制台设备: %s\n", console->name);
}
else
{
os_kprintf("未设置控制台设备\n");
}5.2 切换 Console 设备
/* 场景:系统启动时使用串口1,连接 USB 后切换到虚拟串口 */
void switch_to_usb_console(void)
{
os_device_t *old_dev;
os_kprintf("切换到 USB 虚拟串口...\n");
old_dev = os_console_set_device("vcom");
if (old_dev)
{
os_kprintf("已从 %s 切换到 vcom\n", old_dev->name);
}
}5.3 多设备日志输出
/* 同时向多个设备输出日志 */
void multi_output_log(const char *msg)
{
os_device_t *uart0 = os_device_open_s("uart0");
os_device_t *vcom = os_device_open_s("vcom");
if (uart0)
{
os_device_write_nonblock(uart0, 0, msg, strlen(msg));
os_device_close(uart0);
}
if (vcom)
{
os_device_write_nonblock(vcom, 0, msg, strlen(msg));
os_device_close(vcom);
}
}六、注意事项
- Console 设备必须支持写入操作:用作 Console 的设备必须实现
write方法(字符设备类型) - 初始化顺序:Console 初始化在
OS_INIT_LEVEL_PRE_DEVICE级别,早于设备驱动初始化,因此用作 Console 的设备需要尽早注册 - 中断安全:在中断上下文中调用
os_kprintf时,Console 输出使用非阻塞模式,避免中断阻塞 - 弱符号覆盖:BSP 必须覆盖
__os_hw_console_output弱符号,确保在 Console 设备未就绪时也能输出早期日志(如启动阶段的调试信息)
📖 相关资源
- 5.1 设备驱动框架 — 设备驱动核心框架
- 5.4 Serial 设备 — 最常用的 Console 物理设备
- 6.1 日志系统 — 日志输出框架
- 附录:console.h 源码 — Console 接口头文件