1. 问题缘起:一个看似无解的“设备消失”之谜
最近在折腾一个基于CH340G芯片的USB转串口设备,用来调试一块嵌入式开发板。环境是Ubuntu 22.04 LTS,按理说内核自带驱动,插上就能用。但诡异的事情发生了:设备插入后,ls /dev/ttyUSB*空空如也,dmesg里却能看到设备被成功识别并加载了ch341驱动。更奇怪的是,几秒钟后,系统日志里会闪过一条关于brltty服务的信息,然后这个串口设备就像从未出现过一样,从系统中“蒸发”了。
如果你也遇到了类似情况——无论是CH340、CH341,还是其他CH34x系列芯片的设备,在Linux系统下无法正常创建/dev/ttyUSB0这样的设备节点,那么你很可能也踩进了同一个坑。这个问题与一个名为brltty的系统服务有关,它会在后台“劫持”你的串口设备,导致正常的串口通信工具(如minicom,screen,picocom)根本无法访问。网络上相关的求助帖不少,但解决方案往往语焉不详,或者只给命令不给解释。今天,我就结合自己的排查过程和原理分析,把这个问题彻底讲透,并提供几种不同场景下的解决思路。
简单来说,brltty是一个盲文显示设备守护进程,它会自动检测并尝试接管系统上新出现的串行设备(包括USB转串口设备)。对于CH34x这类常见的USB转串口芯片,brltty的错误接管会导致内核的ch34x驱动无法正常创建设备文件,从而使你的串口工具“找不到设备”。
2. 深入拆解:brltty 是什么以及它为何“多管闲事”
要解决问题,必须先理解对手。brltty的全称是Braille Display Driver,是Linux上一个历史相当悠久的服务,主要功能是为视障用户提供盲文显示终端支持。它的设计初衷是好的:自动扫描系统上的串行端口(如/dev/ttyS*,/dev/ttyUSB*,/dev/ttyACM*),一旦发现可能兼容的盲文显示器,就主动建立连接并提供服务。
2.1 brltty 的工作机制与冲突根源
brltty通常以系统守护进程(daemon)的形式运行,由systemd或udev规则触发。其冲突的核心机制在于:
基于 udev 的自动捕获:现代Linux发行版使用
udev管理设备节点。当一个新的USB设备插入时,内核会通知udev,udev根据一系列规则创建设备节点并可能触发相关服务。brltty安装时,会向系统添加自己的udev规则(通常位于/lib/udev/rules.d/或/etc/udev/rules.d/下,如85-brltty.rules)。这条规则的内容本质上是:“如果检测到一个新的串行设备(满足特定条件),就通知brltty服务去尝试连接它”。设备节点占用:当
brltty尝试连接一个设备时,它会以独占模式打开该设备的文件描述符。在Linux中,一个设备文件被一个进程以读写方式打开后,其他进程通常就无法再以同样的方式打开了(尽管有些驱动支持共享,但串口设备通常不支持)。对于CH34x设备,brltty的连接尝试虽然不是永久成功(因为它无法与CH34x正常通信),但这个短暂的独占打开动作,足以干扰后续正常的ch34x驱动操作和用户空间程序(如minicom)的访问。与内核驱动的“竞争”:问题更微妙之处在于时序。理想流程是:设备插入 → 内核
ch341驱动绑定并创建设备节点/dev/ttyUSB0→ 用户程序打开/dev/ttyUSB0使用。但加入了brltty后,流程可能变成:设备插入 → 内核驱动绑定 →udev创建设备节点 →brltty的udev规则被触发,立即尝试打开该设备 → 打开失败或产生冲突 → 导致设备节点状态异常或无法被正常访问。
你可以通过以下命令验证brltty是否正在运行并监听设备:
systemctl status brltty或者查看是否有相关的udev规则:
grep -r "brltty" /lib/udev/rules.d/ /etc/udev/rules.d/ 2>/dev/null2.2 为什么偏偏是 CH34x 容易中招?
这并非CH34x芯片的“专利”,但CH34x系列作为市面上最廉价、最常见的USB转串口芯片之一,用户基数极大,因此暴露该问题的概率也最高。任何通过cdc_acm(USB通信设备类抽象控制模型)或ftdi_sio、pl2303、ch341等专用驱动实现的USB转串口设备,理论上都可能被brltty扫描到。CH34x只是其中之一。
关键在于,brltty的自动检测规则可能过于“宽泛”。它可能不仅仅针对真正的盲文显示器,而是对所有它“不认识”的串行设备都进行尝试连接。而大多数嵌入式开发者使用的USB转串口模块,显然不是盲文显示器,于是便产生了这场“美丽的误会”。
3. 诊断与确认:如何判断你的问题确实是 brltty 导致的
在动手解决之前,需要确凿的证据。以下是系统的诊断步骤,你可以跟着一步步确认。
3.1 查看内核日志(dmesg)
插入CH34x设备后,立即在终端运行:
sudo dmesg -w或者查看最近的日志:
sudo dmesg | tail -30你需要关注的关键信息序列应该是这样的:
[ 1234.567890] usb 1-2: new full-speed USB device number 10 using xhci_hcd [ 1234.721234] usb 1-2: New USB device found, idVendor=1a86, idProduct=7523 [ 1234.721245] usb 1-2: New USB device strings: Mfr=0, Product=2, SerialNumber=0 [ 1234.721250] usb 1-2: Product: USB Serial [ 1234.721876] ch341 1-2:1.0: ch341-uart converter detected [ 1234.723456] usb 1-2: ch341-uart converter now attached to ttyUSB0这表示ch341驱动识别成功,并创建了ttyUSB0。如果紧接着你看到类似下面的信息:
[ 1234.825678] brltty[12345]: /dev/ttyUSB0: unable to open device: No such device or address或者设备节点(/dev/ttyUSB0)出现后又很快消失,那么brltty就是头号嫌疑犯。
3.2 检查设备节点状态
在插入设备后,快速执行:
ls -la /dev/ttyUSB*如果没有任何输出,或者设备文件存在但你用minicom或cat /dev/ttyUSB0时提示“Permission denied”或“Device or resource busy”,可以进一步检查是哪个进程占用了它:
sudo lsof /dev/ttyUSB0如果ttyUSB0不存在,可以尝试查找相关的USB设备:
lsusb找到你的CH34x设备(通常厂商ID是1a86),记下总线号和设备号(如Bus 001 Device 010),然后查看该设备对应的内核驱动信息:
ls -la /sys/bus/usb/devices/1-2/进入该目录,查看driver符号链接指向哪里,以及tty子目录下是否有内容。
3.3 观察 systemd 单元日志
brltty服务如果被触发,会在 systemd 日志中留下记录:
sudo journalctl -u brltty -f插入设备,观察是否有新的日志条目出现。如果看到它正在尝试打开你的/dev/ttyUSB*设备,那就是铁证。
4. 解决方案大全:从临时禁用到永久根治
根据你的使用场景(临时调试、个人开发机、生产环境或需要兼顾无障碍功能),可以选择不同层级的解决方案。我按推荐程度和影响范围从低到高排列。
4.1 方案一:最直接粗暴——停止并禁用 brltty 服务(推荐给个人开发者)
这是最彻底、最一劳永逸的方法,前提是你和系统其他用户完全不需要盲文显示功能。
步骤:
立即停止当前运行的服务:
sudo systemctl stop brltty执行后,尝试重新插拔你的CH34x设备,看看
/dev/ttyUSB0是否出现并能正常使用。禁止 brltty 开机自启:
sudo systemctl disable brltty这可以防止下次重启后问题复现。
(可选)屏蔽 brltty 的 udev 规则: 停止服务后,
udev规则可能仍然会触发,虽然服务没运行不会造成占用,但可能会产生错误日志。我们可以通过覆盖规则的方式禁用它:# 创建一个本地 udev 规则,覆盖系统的规则 echo '# 禁用 brltty 对串行设备的自动捕获' | sudo tee /etc/udev/rules.d/85-brltty.rules # 或者更稳妥地,直接移除或重命名原规则文件 sudo mv /lib/udev/rules.d/85-brltty.rules /lib/udev/rules.d/85-brltty.rules.disabled注意:不同发行版规则文件的位置和名称可能略有不同(如可能是
69-brltty.rules),请根据之前grep命令的结果操作。重新加载 udev 规则并触发设备重载:
sudo udevadm control --reload-rules sudo udevadm trigger再次插拔设备,问题应该得到解决。
实操心得:对于绝大多数嵌入式开发者和单片机爱好者,
brltty服务是完全用不到的。直接禁用是最佳选择。在Ubuntu Desktop版本中,这个服务默认可能是启用的,而Server版本通常不安装。如果你不确定,禁用它不会有任何负面影响。
4.2 方案二:精准打击——修改 udev 规则,排除特定设备
如果你需要在系统上保留brltty服务(例如为其他硬件提供支持),或者你不想完全禁用一个系统服务,那么可以修改udev规则,让它忽略你的CH34x设备。
原理:udev规则可以通过设备的属性(如厂商IDID_VENDOR_ID、产品IDID_MODEL_ID、驱动程序DRIVER等)进行精细匹配。我们可以添加一条规则,让匹配到CH34x设备时,不执行触发brltty的动作。
步骤:
创建自定义 udev 规则:
sudo nano /etc/udev/rules.d/99-disable-brltty-for-ch34x.rules写入规则内容:
# 禁止 brltty 接管 CH34x 系列 USB 转串口设备 # 匹配条件:驱动程序为 ch341,并且子系统为 tty SUBSYSTEM=="tty", DRIVER=="ch341", ENV{PROGRAM}="/bin/true" # 或者使用更通用的方法:直接移除触发 brltty 的环境变量 SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", ENV{BRLTTY_BRAILLE_DRIVER}="", ENV{BRLTTY_DRIVER}=""规则解释:
SUBSYSTEM=="tty":匹配设备子系统为 tty(串口终端)。DRIVER=="ch341":匹配内核驱动为ch341。你也可以用ATTRS{idVendor}=="1a86"(CH34x的常见厂商ID)来匹配。ENV{PROGRAM}="/bin/true":这是一个“欺骗”brltty相关规则的小技巧。原brltty规则可能会检查PROGRAM执行结果,我们将其覆盖为一个总是成功(返回0)的空命令。- 第二行规则更直接:清空
BRLTTY_BRAILLE_DRIVER和BRLTTY_DRIVER这两个环境变量,这是brltty的udev规则用来决定是否触发服务的关键变量。清空它们,brltty就不会被唤醒了。
保存文件,并重新加载 udev 规则:
sudo udevadm control --reload-rules sudo udevadm trigger现在,插入CH34x设备,
brltty服务应该不会被触发,而/dev/ttyUSB0可以正常使用。
注意事项:你需要知道你的CH34x设备的确切
idVendor和idProduct。使用lsusb命令查看,格式为ID xxxx:xxxx。例如ID 1a86:7523表示idVendor=1a86,idProduct=7523。不同批次的CH340/CH341芯片ID可能略有不同。
4.3 方案三:临时应对——在打开串口前手动停止 brltty
如果你只是偶尔用一下,或者没有管理员权限(sudo),可以尝试这个临时方法。
步骤:
- 插入设备前,先检查
brltty是否活跃,并尝试用普通用户权限停止它(如果允许):systemctl --user stop brltty # 如果它以用户服务运行 # 或者,如果知道进程ID,用 pkill pkill -f brltty - 快速插入设备,并立即尝试打开串口工具。因为
brltty服务可能由udev自动重启,所以这个时间窗口可能很短。
这个方法很不稳定,仅作为权宜之计。
4.4 方案四:釜底抽薪——卸载 brltty 软件包
如果确定永远不需要,可以直接卸载:
# 对于 Debian/Ubuntu 系 sudo apt remove --purge brltty # 对于 RHEL/CentOS/Fedora 系 sudo yum remove brltty # 或 sudo dnf remove brltty卸载后,相关的udev规则、系统服务文件都会被清理,是最干净的做法。但请注意,在某些桌面环境中,无障碍功能套件可能依赖brltty,卸载前请确认。
5. 验证与测试:确保问题真正解决
采取上述任一方案后,必须进行验证。
重启 brltty 服务(如果未卸载):如果你选择的是方案二(修改规则),可以重启
brltty服务来测试规则是否生效。sudo systemctl restart brltty sudo journalctl -u brltty -n 20 --no-pager查看日志,不应该再有关于
/dev/ttyUSB设备的错误或尝试连接信息。模拟设备热插拔:
# 先拔掉设备 # 清除内核环缓冲区日志 sudo dmesg -C # 插入设备 # 查看 dmesg sudo dmesg | tail -20你应该看到
ch341驱动正常绑定,并且没有brltty相关的错误信息。使用串口工具测试:
# 设置权限(可选,如果当前用户不在 dialout 组) sudo chmod a+rw /dev/ttyUSB0 # 使用 screen 简单测试 screen /dev/ttyUSB0 115200如果能正常进入串口会话(可能是空白,或者看到开发板的启动日志),按
Ctrl+A然后K再Y退出。或者用minicom、picocom等工具进行正式通信测试。
6. 举一反三:其他可能引发类似冲突的服务与场景
brltty并非唯一一个会“自动抓取”串口设备的服务。理解这个模式后,你可以处理类似问题。
- ModemManager:这是一个管理移动宽带(WWAN)设备的服务。它也会扫描串行设备,尝试将其初始化为调制解调器。如果你用来做普通串口通信的USB转串口设备被
ModemManager当成 modem 并尝试进行AT命令对话,会导致设备被占用和干扰。解决方法类似:通过udev规则排除特定设备,或者禁用该服务(sudo systemctl stop ModemManager&&sudo systemctl disable ModemManager)。 - 蓝牙串口(RFCOMM):某些蓝牙配置可能会创建虚拟串口,虽然不直接冲突,但需要注意设备名分配。
- 多个同类USB转串口设备:同时插入多个相同芯片的转换器时,设备节点可能依次命名为
ttyUSB0,ttyUSB1… 顺序可能因插拔顺序或udev规则而变。为了稳定,建议使用udev规则通过设备的唯一序列号(如果芯片提供)或USB端口位置,创建固定的、有意义的符号链接,例如/dev/ttyMyBoard。
排查通用思路: 当任何USB或串口设备出现“时好时坏”、“突然消失”、“资源忙”的问题时,可以按以下顺序排查:
- 查日志:
dmesg和journalctl -f是第一时间要看的。 - 查进程:使用
lsof /dev/设备名或fuser -v /dev/设备名查看哪个进程占用了设备。 - 查服务:检查是否有像
brltty、ModemManager这样的系统服务在运行(systemctl list-units | grep -i serial或modem或brl)。 - 查规则:查看
/etc/udev/rules.d/和/lib/udev/rules.d/下是否有可疑的规则文件。 - 隔离测试:尝试在停止相关服务、卸载相关模块后,问题是否消失。
7. 深度剖析:udev 规则编写的核心技巧与避坑指南
在方案二中,我们通过编写udev规则来解决问题。这里分享一些编写高效、准确udev规则的经验。
7.1 如何获取设备的准确属性
udev规则依赖于设备属性。获取属性最可靠的方法是:
# 插入设备后,先找到它在 /sys 下的路径,例如 /sys/bus/usb/devices/1-2 # 然后使用 udevadm 查看所有属性 udevadm info -a -p /sys/bus/usb/devices/1-2或者更简单,通过设备节点反查:
udevadm info -a -n /dev/ttyUSB0在输出中,你会看到层层递进的属性块(从设备本身到它的父设备)。编写规则时,通常从最具体的(设备本身)属性开始匹配。例如,匹配 CH340 设备,使用ATTRS{idVendor}=="1a86"和ATTRS{idProduct}=="7523"是非常精准的。
7.2 规则的作用域与优先级
- 优先级:
/etc/udev/rules.d/中的规则优先级高于/lib/udev/rules.d/。规则文件按数字顺序读取,数字小的先读。因此,我们创建的99-开头的规则会在大部分系统规则之后生效,可以覆盖前面的设置。 - 作用域:一条规则中的多个匹配条件(用逗号分隔)是“与”的关系,必须全部满足。赋值操作(
=或:=)和运行程序(RUN+=)是规则匹配成功后执行的动作。
7.3 一个更健壮的排除规则示例
下面这条规则结合了多种匹配条件,并使用了更优雅的“跳过后续规则”的方法:
# 文件:/etc/udev/rules.d/99-ignore-ch34x-for-brlty.rules # 匹配 CH34x 设备,并设置一个环境变量来指示 brltty 跳过此设备 SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", ENV{BRLTTY_DRIVER}="skip", GOTO="brltty_end" SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="5512", ENV{BRLTTY_DRIVER}="skip", GOTO="brltty_end" LABEL="brltty_end"这里,我们设置BRLTTY_DRIVER="skip"。原始的brltty规则(如85-brltty.rules)可能会检查这个变量,如果值是 “skip” 或空,就不会触发服务。你需要查看原规则的具体逻辑来设计最合适的覆盖值。使用GOTO跳转到规则末尾,可以避免执行其他不必要的操作。
7.4 常见坑点
- 规则语法错误:多余的空格、错误的运算符(如
==写成=)、字符串引号不匹配都会导致规则失效。写完后可以用udevadm test模拟测试(需要root权限):sudo udevadm test /sys/bus/usb/devices/1-2/ 2>&1 | grep -A5 -B5 "你的规则内容" - 属性匹配错误:确保你使用的属性(如
idVendor)确实存在于udevadm info输出的正确层级中。ATTRS是匹配父设备属性,ATTR是匹配当前设备属性,容易混淆。 - 规则未生效:修改规则后,必须执行
sudo udevadm control --reload-rules && sudo udevadm trigger来重新加载并触发规则。或者更直接地,重新插拔设备。
经过以上从问题现象、原理分析到多种解决方案的详细拆解,相信你已经对 CH34x 设备与brltty的冲突问题有了透彻的理解。这个问题的本质是系统服务对硬件资源的自动管理策略与用户特定需求之间的冲突。在Linux桌面生态中,类似的情况并不少见,解决问题的关键在于学会使用dmesg、journalctl、lsof、udevadm这些强大的工具进行诊断,并灵活运用systemctl和udev规则进行精准控制。下次再遇到设备“神秘消失”或“资源被占”,你就能有条不紊地抓住那个“幕后黑手”了。