RT-Thread论坛
直播中

李星童

8年用户 1148经验值
私信 关注
[问答]

telnet连接后,当外部中断触发rt_kprintf时候,telnet服务会卡死怎么解决?

环境:
STM32F407VET6+LAN8720A+RT-Thread4.1.0
1、用的netutils里的telnet服务
2、rt_pin_attach_irq注册的外部中断服务

回帖(1)

凤毛麟角

2025-9-15 17:48:11

针对Telnet连接卡死的问题,其根本原因是在外部中断(ISR)中调用rt_kprintf导致系统阻塞。以下是详细分析和解决方案:




问题分析




  1. 中断上下文限制



    • rt_kprintf依赖控制台设备(如串口),而控制台通常使用互斥锁(rt_device_t的互斥机制)。

    • 中断中不可获取互斥锁:若Telnet线程已持有锁,中断尝试获取时会导致死锁(优先级反转)。




  2. Telnet内部机制



    • Telnet服务在输出时使用了rt_device_write,同样涉及互斥锁。

    • 中断中调用rt_kprintf可能打断Telnet的锁操作,导致其永久阻塞。




  3. 风险操作



    • 中断中调用任何可能阻塞的API(如信号量、互斥锁、内存分配)均可能导致系统不稳定。






解决方案


1. 避免在ISR中直接打印


将日志操作移至线程上下文


static struct rt_ringbuffer *log_rb = RT_NULL;  // 环形缓冲区
static struct rt_semaphore log_sem;           // 信号量通知

// 外部中断服务函数
void EXTI_IRQHandler(void) {
    rt_uint32_t length;
    const char *msg = "[ISR] Triggered!n";

    // 非阻塞写入环形缓冲区
    length = rt_ringbuffer_put_force(log_rb, msg, rt_strlen(msg));
    rt_sem_release(&log_sem);  // 通知日志线程
}

// 专用日志线程
static void log_thread_entry(void *param) {
    rt_uint8_t buffer[64];
    rt_size_t size;

    while (1) {
        rt_sem_take(&log_sem, RT_WAITING_FOREVER);  // 等待信号量
        // 从缓冲区读取数据并安全打印
        while ((size = rt_ringbuffer_get(log_rb, buffer, sizeof(buffer))) {
            rt_device_write(rt_console_get_device(), 0, buffer, size);
        }
    }
}

// 初始化函数
int log_system_init(void) {
    // 创建缓冲区 (2KB)
    log_rb = rt_ringbuffer_create(2048);
    rt_sem_init(&log_sem, "log_sem", 0, RT_IPC_FLAG_FIFO);

    // 创建日志线程
    rt_thread_t thread = rt_thread_create("log", log_thread_entry, RT_NULL, 4096, 10, 10);
    rt_thread_startup(thread);
    return 0;
}
INIT_APP_EXPORT(log_system_init);  // 自动初始化

2. 使用内核日志钩子(扩展方案)


启用RT_USING_CONSOLE的日志钩子功能:


void rt_hw_console_output(const char *str) {
    // 仅在非中断时输出
    if (rt_interrupt_get_nest() == 0) {
        rt_device_write(console_dev, 0, str, rt_strlen(str));
    } else {
        // 中断中写入缓冲区(同上方案)
    }
}

3. 优化Telnet线程优先级


确保Telnet线程优先级低于关键中断,避免中断被延迟:


// 在启动Telnet时调整优先级
tid = rt_thread_create("telnet", ...);
rt_thread_control(tid, RT_THREAD_CTRL_CHANGE_PRIORITY, (void*)0x10); // 设置较低优先级
rt_thread_startup(tid);

4. 检查中断优先级冲突


通过NVIC_SetPriority()确认中断优先级:


// 确保外部中断优先级不高于系统关键中断(如网络中断)
NVIC_SetPriority(EXTI_IRQn, 5);  // 数值越大优先级越低



关键优化点



  • 环形缓冲区:使用rt_ringbuffer_put_force()避免阻塞,覆盖旧数据防止溢出。

  • 信号量同步:通过非阻塞方式唤醒日志线程。

  • 线程优先级:日志线程优先级应低于Telnet线程,避免日志操作影响网络服务。

  • 系统配置:在rtconfig.h中确保RT_USING_RINGBUFFERRT_USING_SEMAPHORE已启用。




最终建议



  1. 使用 log_thread_entry 方案作为核心解决方法。

  2. 优先保证中断服务函数的执行时间<10μs,仅做标记或简单数据入队。

  3. rt_kprintf调用处添加条件检查:
    if (rt_interrupt_get_nest() == 0) {
       rt_kprintf("Safe in threadn");
    }


通过以上方法,可彻底解决中断中打印导致Telnet阻塞的问题,同时确保系统稳定性。

举报

更多回帖

发帖
×
20
完善资料,
赚取积分