如何在Linux上找到所有串行设备(ttyS、ttyUSB等)而不打开它们?

133

在Linux系统上,获取所有可用串口/设备列表的正确方法是什么?

换句话说,当我遍历/dev/中的所有设备时,如何判断哪些是经典意义上的串口,即通常支持波特率和 RTS / CTS 流控制的串口?

解决方案将使用C进行编码。

我提问是因为我正在使用一个明显错误的第三方库:它似乎只迭代/dev/ttyS * 。问题在于,例如通过USB提供的串口(由USB-RS232适配器提供),这些串口列在/dev/ttyUSB * 下。阅读Linux.org上的Serial-HOWTO,我得到了这样一个想法,即随着时间的推移,还会有其他名称空间。

因此,我需要找到检测串行设备的官方方法。问题在于没有正式记录,或者我找不到它。

我想象一种方式是打开来自 /dev/tty * 的所有文件,并在其中调用特定的 ioctl(),该函数仅在串行设备上可用。但是,那是一个好的解决方案吗?

更新

hrickards 建议查看“setserial”的源代码。 它的代码正是我想要的:

首先,它使用以下命令打开设备:

fd = open (path, O_RDWR | O_NONBLOCK)

然后它调用:

ioctl (fd, TIOCGSERIAL, &serinfo)
如果这个调用没有返回错误,那么显然它是一个串行设备。
我在 串口编程/termios 中找到了类似的代码,建议同时添加 O_NOCTTY 选项。
然而,这种方法存在一个问题:
当我在 BSD Unix(即 Mac OS X)上测试此代码时,它也能正常工作。但是,通过蓝牙提供的串行设备会导致系统(驱动程序)尝试连接到蓝牙设备,在超时错误返回之前需要一段时间。这是由于只打开设备引起的。我可以想象在Linux上也可能发生类似的情况-理想情况下,我不应该需要打开设备来确定其类型。我想知道是否有一种方法可以在不打开设备的情况下调用ioctl函数,或者以不会导致连接被建立的方式打开设备?
我该怎么办?

1
有人匿名建议了这个编辑,但被拒绝了,所以我把它留在这里作为评论:如果您在ioctl调用中使用TIOCGSERIAL标志,而不是TIOCMGET,则调用不会返回错误,即使某些错误路径不引用COM(串行)端口。使用TIOCMGET标志,ioctl仅适用于可以在TTY和TTYUSB可能路径中访问的COM端口。 - Thomas Tempelmann
15个回答

3

我没有USB串口设备,但是可以直接使用HAL库找到真实的端口:

====================================================================
#! /usr/bin/env bash
#
# Uses HAL to find existing serial hardware
#

for sport in $(hal-find-by-capability --capability serial) ; do
  hal-get-property --udi "${sport}" --key serial.device
done

====================================================================

发布的python-dbus代码和这个sh脚本都没有列出蓝牙/dev/rfcomm*设备,因此不是最佳解决方案。

请注意,在其他Unix平台上,串口的名称不是ttyS?甚至在Linux中,某些串行卡允许您命名设备。假设串行设备名称中存在模式是错误的。


很遗憾,HAL在Ubuntu(12.04之后)被移除了,它有一些不错的易于使用的工具。有人知道是否有替代品吗?但如果你使用的版本/发行版有HAL,这看起来很不错。 - Reed Hedges

2
#dmesg | grep tty

这个命令可以显示所有端口。

2

1

是的,我知道我太晚了(一如既往)。这是我的代码片段(基于mk2的回复)。也许这能帮助某些人:

std::vector<std::string> find_serial_ports()
{
 std::vector<std::string> ports;
    std::filesystem::path kdr_path{"/proc/tty/drivers"};
    if (std::filesystem::exists(kdr_path))
    {
        std::ifstream ifile(kdr_path.generic_string());
        std::string line;
        std::vector<std::string> prefixes;
        while (std::getline(ifile, line))
        {
            std::vector<std::string> items;
            auto it = line.find_first_not_of(' ');
            while (it != std::string::npos)
            {

                auto it2 = line.substr(it).find_first_of(' ');
                if (it2 == std::string::npos)
                {
                    items.push_back(line.substr(it));
                    break;
                }
                it2 += it;
                items.push_back(line.substr(it, it2 - it));
                it = it2 + line.substr(it2).find_first_not_of(' ');
            }
            if (items.size() >= 5)
            {
                if (items[4] == "serial" && items[0].find("serial") != std::string::npos)
                {
                    prefixes.emplace_back(items[1]);
                }
            }
        }
        ifile.close();
        for (auto& p: std::filesystem::directory_iterator("/dev"))
        {
            for (const auto& pf : prefixes)
            {
                auto dev_path = p.path().generic_string();
                if (dev_path.size() >= pf.size() && std::equal(dev_path.begin(), dev_path.begin() + pf.size(), pf.begin()))
                {
                    ports.emplace_back(dev_path);
                }
            }
        }
    }
    return ports;
}

看起来你的代码解析了 https://dev59.com/PXE85IYBdhLWcg3w9odJ#4701610 的答案。如果是这样,请在你的回复中提到一下,谢谢。 - Thomas Tempelmann

0

使用 setserial 工具:

setserial -gG /dev/{ttyUSB,ttyS,ttyACM}* 2>/dev/null | grep -Ev "ttyS[0-9]+.*irq\s0\s*"

如果你只想在输出中返回端口设备路径:

setserial -gG /dev/{ttyUSB,ttyS,ttyACM}* 2>/dev/null | grep -Ev "ttyS[0-9]+.*irq\s0\s*" | cut -d' ' -f1

可能这些解决方案并不适用于所有需求,因为一些USB设备可能会被UDEV以另一种方式命名,所以更通用但不太优化(不建议使用):

setserial -gG /dev/* 2>/dev/null | grep -Ev "ttyS[0-9]+.*irq\s0\s*" | cut -d' ' -f1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接