Skip to content

[Bug] [Smart][tty] tty_wait_background 未守卫 td->lwp==NULL,finsh 首次 read(stdin) page fault #11717

Description

@loongson-gz

RT-Thread Version

master

Affected area

RT-Smart

Hardware/BSP vendor

Loongson

Architecture

Not applicable / Other

Board and hardware details

LS2K0300

Develop Toolchain

GCC

Describe the bug

问题现象

LoongArch/LS2K300 启用 RT-Thread Smart 模式后,finsh 启动到 msh 提示符,第一次按键就 page fault

era = tty_wait_background
fp   = NULL

shell 线程首次 read(stdin) 时,进入 tty_wait_background,执行到 pg = p->pgrp 这行,p 是 NULL,直接解引用 → page fault。

调用链

tty_wait_background 是从 ttydisc_read_*tty_ttydisc.c)一路调上来的,完整调用链:

finsh tshell 线程
  → read(fd) / finsh 调 rt_posix_stdio_get_console() 拿 fd
    → ttydev_read (tty.c)
      → ttydisc_read (tty_ttydisc.c:340)
        → ttydisc_read_canonical / ttydisc_read_raw_* 
          → tty_wait_background(tp, curthread, SIGTTIN)
            → p = td->lwp;   // ← p == NULL
            → pg = p->pgrp;  // ← 解引用 NULL,page fault

触发条件:两个条件同时成立

条件 1:finsh 的 tshell 线程走到 ttydev_read,即读到的是 tty 设备(/dev/ttyS0),而不是 raw 串口(/dev/uart0)。

条件 2:curthread->lwp 为 NULL,即调用者是内核线程而非 lwp 用户态进程。

LoongArch/LS2K300 撞上是因为它同时满足两个条件:Smart 模式下 finsh 切到 /dev/ttyS0(条件 1),finsh 的 tshell 是内核线程(条件 2),首次 read(stdin) 就进 tty_wait_backgroundpg = p->pgrp 解引用 NULL → page fault。

为什么其他架构没触发

架构组合 条件 1:finsh 读 tty? 条件 2:tshell 是内核线程? 触发?
非 Smart + newlib(raspi/qemu-vexpress) ✗ finsh 绑 raw /dev/uart0,不走 tty 层 ✓ 是 不触发(条件 1 不成立)
Smart + musl(d1/k230/cv18xx) ✓ finsh 切到 /dev/ttyS0 ✓ 是 理论触发,但没爆出来
Smart + bare-metal(imx6ull/raspi-dm2.0/rockchip) 看是否切 tty ✓ 是 同上
LoongArch/LS2K300 Smart(已修) ✓ 切到 /dev/ttyS0 ✓ 是 已触发,已修

其他架构没触发,主要是因为:

  1. 大多数 Smart BSP 还没把 finsh 切到 tty——它们要么没开 Smart,要么 finsh 还绑在 raw /dev/uart0 上,根本没走到 ttydisc_readtty_wait_background 这条路。

  2. 只有 finsh 真的去读 /dev/ttyS0、并且 tshell 是内核线程时,才会触发 pg = p->pgrp 的 NULL 解引用。LoongArch/LS2K300 是首批把这条路径走通的 BSP,所以第一个撞到。

根因:BSD tty 代码移植到 RT-Thread 时的遗漏

从代码角度看,tty_wait_backgroundtd->lwp == NULL 的处理本来就应该有守卫——这是 BSD tty 代码移植到 RT-Thread 时的一个遗漏。

BSD 原版里 td->td_proc 不会为 NULL(FreeBSD 内核线程也有 proc),但 RT-Thread 的内核线程 lwp 字段是 NULL,所以这个移植性差异在"内核线程读 tty"这个场景下暴露了。

复现环境

  • BSP: bsp/loongarch/ls2k300_dev
  • 配置: CONFIG_RT_USING_SMART=y + musl 工具链 (loongarch64-unknown-linux-musl-)
  • 现象: finsh 启动到 msh 提示符后首次按键 page fault,era = tty_wait_backgroundfp = NULL

修复方案

补丁在 tty_wait_background() 入口加了一个 NULL 守卫:

// components/lwp/terminal/freebsd/tty.c
int tty_wait_background(struct lwp_tty *tp, struct rt_thread *td, int sig)
{
    struct rt_lwp *p;
    ...
    p = td->lwp;
    if (p == NULL)        // ← 新增守卫
        return 0;         // ← 内核线程无 lwp,直接放行
    for (;;)
    {
        pg = p->pgrp;     // ← 原来这里解引用 NULL 就 page fault
        ...
    }
}

内核线程无 lwp/进程组,本不参与终端前后台作业控制,所以直接返回 0 放行 read/write 是正确的语义。

关键结论

这个 NULL 守卫补丁修的是一个所有 Smart 架构(finsh 走 tty 路径 + tshell 是内核线程)共有的潜在 bug,不是 LoongArch 独有。其他架构没爆出来,主要是因为:

  1. 大多数 Smart BSP 还没把 finsh 切到 tty——它们要么没开 Smart,要么 finsh 还绑在 raw /dev/uart0 上,根本没走到 ttydisc_readtty_wait_background 这条路。

  2. 只有 finsh 真的去读 /dev/ttyS0、并且 tshell 是内核线程时,才会触发 pg = p->pgrp 的 NULL 解引用。LoongArch/LS2K300 是首批把这条路径走通的 BSP,所以第一个撞到。

  3. 从代码角度看,tty_wait_backgroundtd->lwp == NULL 的处理本来就应该有守卫——这是 BSD tty 代码移植到 RT-Thread 时的一个遗漏。BSD 原版里 td->td_proc 不会为 NULL(FreeBSD 内核线程也有 proc),但 RT-Thread 的内核线程 lwp 字段是 NULL,所以这个移植性差异在"内核线程读 tty"这个场景下暴露了。

一句话:这个补丁修的是"内核线程读 tty 时 NULL 解引用"的普遍性 bug,对其他 Smart 架构是预防性修复——只要它们哪天把 finsh 切到 /dev/ttyS0,就会触发同样的 page fault。补丁本身架构无关,加在公共 tty.c 里是正确的位置。

参考

  • 修复 commit: 546bbac22c serial/tty: tty_wait_background 加 p==NULL 守卫,内核线程直接放行
  • 关键代码位置:
    • components/lwp/terminal/freebsd/tty.c:414tty_wait_background 函数)
    • components/lwp/terminal/freebsd/tty_ttydisc.c:143(调用 tty_wait_background 的位置)
  • 详细分析文档: bsp/loongarch/ls2k300_dev/docs-bak/tty_wait_background_NULL守卫分析.md

Other additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    BSPBSP: LoongsonBSP related with LoongsonRT-SmartRT-Thread Smart related PR or issuesbugThis PR/issue is a bug in the current code.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions