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

2024-10-25 08:42:00
admin
原创
65
摘要:问题描述:获取 Linux 系统上所有可用串行端口/设备的列表的正确方法是什么?换句话说,当我遍历中的所有设备时/dev/,如何以经典方式判断哪些是串行端口,即通常支持波特率和RTS/CTS流控制的端口?该解决方案将用 C 语言编码。我之所以问这个问题,是因为我正在使用一个第三方库,而这个库显然是错误的:它似...

问题描述:

获取 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:

文件系统/sys应该包含大量有关您任务的信息。我的系统 (2.6.32-40-generic #87-Ubuntu) 建议:

/sys/class/tty

它为您提供了系统已知的所有 TTY 设备的描述。精简示例:

# ll /sys/class/tty/ttyUSB*
lrwxrwxrwx 1 root root 0 2012-03-28 20:43 /sys/class/tty/ttyUSB0 -> ../../devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.4/2-1.4:1.0/ttyUSB0/tty/ttyUSB0/
lrwxrwxrwx 1 root root 0 2012-03-28 20:44 /sys/class/tty/ttyUSB1 -> ../../devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.3/2-1.3:1.0/ttyUSB1/tty/ttyUSB1/

点击以下其中一个链接:

# ll /sys/class/tty/ttyUSB0/
insgesamt 0
drwxr-xr-x 3 root root    0 2012-03-28 20:43 ./
drwxr-xr-x 3 root root    0 2012-03-28 20:43 ../
-r--r--r-- 1 root root 4096 2012-03-28 20:49 dev
lrwxrwxrwx 1 root root    0 2012-03-28 20:43 device -> ../../../ttyUSB0/
drwxr-xr-x 2 root root    0 2012-03-28 20:49 power/
lrwxrwxrwx 1 root root    0 2012-03-28 20:43 subsystem -> ../../../../../../../../../../class/tty/
-rw-r--r-- 1 root root 4096 2012-03-28 20:43 uevent

dev文件包含以下信息:

# cat /sys/class/tty/ttyUSB0/dev
188:0

这是主要/次要节点。可以在目录中搜索这些节点/dev以获取用户友好的名称:

# ll -R /dev |grep "188, *0"
crw-rw----   1 root dialout 188,   0 2012-03-28 20:44 ttyUSB0

/sys/class/tty目录包含所有 TTY 设备,但您可能希望排除那些讨厌的虚拟终端和伪终端。我建议您只检查那些具有以下device/driver条目的设备:

# ll /sys/class/tty/*/device/driver
lrwxrwxrwx 1 root root 0 2012-03-28 19:07 /sys/class/tty/ttyS0/device/driver -> ../../../bus/pnp/drivers/serial/
lrwxrwxrwx 1 root root 0 2012-03-28 19:07 /sys/class/tty/ttyS1/device/driver -> ../../../bus/pnp/drivers/serial/
lrwxrwxrwx 1 root root 0 2012-03-28 19:07 /sys/class/tty/ttyS2/device/driver -> ../../../bus/platform/drivers/serial8250/
lrwxrwxrwx 1 root root 0 2012-03-28 19:07 /sys/class/tty/ttyS3/device/driver -> ../../../bus/platform/drivers/serial8250/
lrwxrwxrwx 1 root root 0 2012-03-28 20:43 /sys/class/tty/ttyUSB0/device/driver -> ../../../../../../../../bus/usb-serial/drivers/ftdi_sio/
lrwxrwxrwx 1 root root 0 2012-03-28 21:15 /sys/class/tty/ttyUSB1/device/driver -> ../../../../../../../../bus/usb-serial/drivers/ftdi_sio/

解决方案 2:

在最近的内核中(不确定从什么时候开始),您可以列出 /dev/serial 的内容以获取系统上的串行端口列表。它们实际上是指向正确 /dev/ 节点的符号链接:

flu0@laptop:~$ ls /dev/serial/
total 0
drwxr-xr-x 2 root root 60 2011-07-20 17:12 by-id/
drwxr-xr-x 2 root root 60 2011-07-20 17:12 by-path/
flu0@laptop:~$ ls /dev/serial/by-id/
total 0
lrwxrwxrwx 1 root root 13 2011-07-20 17:12 usb-Prolific_Technology_Inc._USB-Serial_Controller-if00-port0 -> ../../ttyUSB0
flu0@laptop:~$ ls /dev/serial/by-path/
total 0
lrwxrwxrwx 1 root root 13 2011-07-20 17:12 pci-0000:00:0b.0-usb-0:3:1.0-port0 -> ../../ttyUSB0

如您所见,这是一个 USB 串行适配器。请注意,当系统上没有串行端口时,/dev/serial/ 目录不存在。希望这有帮助 :)。

解决方案 3:

我发现

dmesg | grep tty

做这项工作。

解决方案 4:

我正在做类似下面的代码。它适用于 USB 设备,也适用于我们每个人都有的 30 个愚蠢的 serial8250 设备 - 但只有其中几个真正起作用。

基本上我使用以前答案中的概念。首先枚举 /sys/class/tty/ 中的所有 tty 设备。不包含 /device 子目录的设备将被过滤掉。/sys/class/tty/console 就是这样的设备。然后,根据驱动程序符号链接 fx 的目标,实际包含设备的设备将被接受为有效串行端口。

$ ls -al /sys/class/tty/ttyUSB0//device/driver
lrwxrwxrwx 1 root root 0 sep  6 21:28 /sys/class/tty/ttyUSB0//device/driver -> ../../../bus/platform/drivers/usbserial

对于 ttyS0

$ ls -al /sys/class/tty/ttyS0//device/driver
lrwxrwxrwx 1 root root 0 sep  6 21:28 /sys/class/tty/ttyS0//device/driver -> ../../../bus/platform/drivers/serial8250

所有由serial8250驱动的驱动程序都必须使用前面提到的ioctl进行探测。

        if (ioctl(fd, TIOCGSERIAL, &serinfo)==0) {
            // If device type is no PORT_UNKNOWN we accept the port
            if (serinfo.type != PORT_UNKNOWN)
                the_port_is_valid

仅报告有效设备类型的端口才有效。

枚举串行端口的完整源代码如下所示。欢迎补充。

#include <stdlib.h>
#include <dirent.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <linux/serial.h>

#include <iostream>
#include <list>

using namespace std;

static string get_driver(const string& tty) {
    struct stat st;
    string devicedir = tty;

    // Append '/device' to the tty-path
    devicedir += "/device";

    // Stat the devicedir and handle it if it is a symlink
    if (lstat(devicedir.c_str(), &st)==0 && S_ISLNK(st.st_mode)) {
        char buffer[1024];
        memset(buffer, 0, sizeof(buffer));

        // Append '/driver' and return basename of the target
        devicedir += "/driver";

        if (readlink(devicedir.c_str(), buffer, sizeof(buffer)) > 0)
            return basename(buffer);
    }
    return "";
}

static void register_comport( list<string>& comList, list<string>& comList8250, const string& dir) {
    // Get the driver the device is using
    string driver = get_driver(dir);

    // Skip devices without a driver
    if (driver.size() > 0) {
        string devfile = string("/dev/") + basename(dir.c_str());

        // Put serial8250-devices in a seperate list
        if (driver == "serial8250") {
            comList8250.push_back(devfile);
        } else
            comList.push_back(devfile); 
    }
}

static void probe_serial8250_comports(list<string>& comList, list<string> comList8250) {
    struct serial_struct serinfo;
    list<string>::iterator it = comList8250.begin();

    // Iterate over all serial8250-devices
    while (it != comList8250.end()) {

        // Try to open the device
        int fd = open((*it).c_str(), O_RDWR | O_NONBLOCK | O_NOCTTY);

        if (fd >= 0) {
            // Get serial_info
            if (ioctl(fd, TIOCGSERIAL, &serinfo)==0) {
                // If device type is no PORT_UNKNOWN we accept the port
                if (serinfo.type != PORT_UNKNOWN)
                    comList.push_back(*it);
            }
            close(fd);
        }
        it ++;
    }
}

list<string> getComList() {
    int n;
    struct dirent **namelist;
    list<string> comList;
    list<string> comList8250;
    const char* sysdir = "/sys/class/tty/";

    // Scan through /sys/class/tty - it contains all tty-devices in the system
    n = scandir(sysdir, &namelist, NULL, NULL);
    if (n < 0)
        perror("scandir");
    else {
        while (n--) {
            if (strcmp(namelist[n]->d_name,"..") && strcmp(namelist[n]->d_name,".")) {

                // Construct full absolute file path
                string devicedir = sysdir;
                devicedir += namelist[n]->d_name;

                // Register the device
                register_comport(comList, comList8250, devicedir);
            }
            free(namelist[n]);
        }
        free(namelist);
    }

    // Only non-serial8250 has been added to comList without any further testing
    // serial8250-devices must be probe to check for validity
    probe_serial8250_comports(comList, comList8250);

    // Return the lsit of detected comports
    return comList;
}


int main() {
    list<string> l = getComList();

    list<string>::iterator it = l.begin();
    while (it != l.end()) {
        cout << *it << endl;
        it++;
    }

    return 0;   
}

解决方案 5:

我想我在内核源文档中找到了答案:/usr/src/linux-2.6.37-rc3/Documentation/filesystems/proc.txt

1.7 TTY info in /proc/tty
-------------------------

Information about  the  available  and actually used tty's can be found in the
directory /proc/tty.You'll  find  entries  for drivers and line disciplines in
this directory, as shown in Table 1-11.


Table 1-11: Files in /proc/tty
..............................................................................
 File          Content                                        
 drivers       list of drivers and their usage                
 ldiscs        registered line disciplines                    
 driver/serial usage statistic and status of single tty lines 
..............................................................................

To see  which  tty's  are  currently in use, you can simply look into the file
/proc/tty/drivers:

  > cat /proc/tty/drivers 
  pty_slave            /dev/pts      136   0-255 pty:slave 
  pty_master           /dev/ptm      128   0-255 pty:master 
  pty_slave            /dev/ttyp       3   0-255 pty:slave 
  pty_master           /dev/pty        2   0-255 pty:master 
  serial               /dev/cua        5   64-67 serial:callout 
  serial               /dev/ttyS       4   64-67 serial 
  /dev/tty0            /dev/tty0       4       0 system:vtmaster 
  /dev/ptmx            /dev/ptmx       5       2 system 
  /dev/console         /dev/console    5       1 system:console 
  /dev/tty             /dev/tty        5       0 system:/dev/tty 
  unknown              /dev/tty        4    1-63 console 

这是该文件的链接:
http://git.kernel.org/? p=linux/kernel/git/next/linux-next.git;a=blob_plain;f=Documentation/filesystems/proc.txt;hb=e8883f8057c0f7c9950fa9f20568f37bfa62f34a

解决方案 6:

带有 -g 选项的 setserial 似乎可以执行您想要的操作,并且 C 源代码可在http://www.koders.com/c/fid39344DABD14604E70DF1B8FEA7D920A94AF78BF8.aspx获得。

解决方案 7:

使用 /proc/tty/drivers 仅指示已加载哪些 tty 驱动程序。如果您要查找串行端口列表,请查看 /dev/serial,它将有两个子目录:by-id 和 by-path。

前任:

# find . -type l
./by-path/usb-0:1.1:1.0-port0
./by-id/usb-Prolific_Technology_Inc._USB-Serial_Controller-if00-port0

感谢这篇文章: https: //superuser.com/questions/131044/how-do-i-know-which-dev-ttys-is-my-serial-port

解决方案 8:

我通过组拨出的方式让每个使用用户“拨出”的 tty
ls -l /dev/tty* | grep 'dialout'
仅获取其文件夹
ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev

轻松监听 tty 输出,例如当 arduino 串行输出时:
head --lines 1 < /dev/ttyUSB0

仅监听每条 tty 输出的一行:
for i in $(ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev); do head --lines 1 < $i; done

我真的很喜欢通过寻找驱动程序的方法:
ll /sys/class/tty/*/device/driver

您现在可以选择 tty 名称:
ls /sys/class/tty/*/device/driver | grep 'driver' | cut -d "/" -f 5

解决方案 9:

我这里没有串行设备来测试它,但是如果你有 python 和 dbus,你可以自己尝试一下。

import dbus
bus = dbus.SystemBus()
hwmanager = bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager')
hwmanager_i = dbus.Interface(hwmanager, 'org.freedesktop.Hal.Manager')
print hwmanager_i.FindDeviceByCapability("serial")

如果失败,您可以在内部搜索,hwmanager_i.GetAllDevicesWithProperties()看看我刚刚猜测的功能名称“serial”是否有不同的名称。

高血压

解决方案 10:

我没有 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 中,一些串行卡也允许您命名设备。假设串行设备名称中的模式是错误的。

解决方案 11:

我的解决方案基于 udev 库,以下代码基于示例2:

#include <string.h>
#include <libudev.h>

bool enumerate_serial_ports(void)
{
    struct udev* udev;
    struct udev_enumerate* enumerate;
    struct udev_list_entry* devices, *dev_list_entry;

    /* create udev object */
    udev = udev_new();
    if (!udev) 
    {
        SPDLOG_ERROR("Cannot create udev context.");
        return false;
    }

    /* create enumerate object */
    enumerate = udev_enumerate_new(udev);
    if (!enumerate) 
    {
        SPDLOG_ERROR("Cannot create enumerate context.");

        udev_unref(udev);
        return false;
    }

    udev_enumerate_add_match_subsystem(enumerate, "tty");
    udev_enumerate_scan_devices(enumerate);

    /* fillup device list */
    devices = udev_enumerate_get_list_entry(enumerate);
    if (!devices) 
    {
        SPDLOG_ERROR("Failed to get device list.");

        udev_enumerate_unref(enumerate);
        udev_unref(udev);
        return false;
    }

    udev_list_entry_foreach(dev_list_entry, devices) 
    {
        struct udev_device* dev = udev_device_new_from_syspath(udev, udev_list_entry_get_name(dev_list_entry));

        // filter out virtual ports
        if((udev_device_get_sysnum(dev) != NULL) && (strstr(udev_device_get_devpath(dev), "/devices/virtual/") == NULL))
        {
            SPDLOG_DEBUG("subsystem={}", udev_device_get_subsystem(dev));
            SPDLOG_DEBUG("syspath={}", udev_device_get_syspath(dev));
            SPDLOG_DEBUG("sysname={}", udev_device_get_sysname(dev));
            SPDLOG_DEBUG("sysnum={}", udev_device_get_sysnum(dev));
            SPDLOG_DEBUG("devnode={}", udev_device_get_devnode(dev));
            SPDLOG_DEBUG("-----------------------------------------");
        }    
        
        /* free dev */
        udev_device_unref(dev);
    }

    /* free enumerate */
    udev_enumerate_unref(enumerate);
    /* free udev */
    udev_unref(udev);

    return true;
}

带有 USB 串行适配器的 RPI4 上的输出:

[ debug ][11:50:47.645] - subsystem=tty
[ debug ][11:50:47.645] - syspath=/sys/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.3/1-1.3:1.0/ttyUSB0/tty/ttyUSB0
[ debug ][11:50:47.645] - sysname=ttyUSB0
[ debug ][11:50:47.645] - sysnum=0
[ debug ][11:50:47.645] - devnode=/dev/ttyUSB0
[ debug ][11:50:47.645] - -----------------------------------------
                           
[ debug ][11:50:47.645] - subsystem=tty
[ debug ][11:50:47.645] - syspath=/sys/devices/platform/soc/fe201000.serial/tty/ttyAMA0
[ debug ][11:50:47.645] - sysname=ttyAMA0
[ debug ][11:50:47.645] - sysnum=0
[ debug ][11:50:47.645] - devnode=/dev/ttyAMA0
[ debug ][11:50:47.645] - -----------------------------------------
                           
[ debug ][11:50:47.646] - subsystem=tty
[ debug ][11:50:47.646] - syspath=/sys/devices/platform/soc/fe215040.serial/tty/ttyS0
[ debug ][11:50:47.646] - sysname=ttyS0
[ debug ][11:50:47.646] - sysnum=0
[ debug ][11:50:47.646] - devnode=/dev/ttyS0
[ debug ][11:50:47.646] - -----------------------------------------

解决方案 12:

串行通信管理器库具有许多针对所需任务的 API 和功能。如果设备是 USB-UART,则可以使用其 VID/PID。如果设备是 BT-SPP,则可以使用平台特定的 API。查看此串行端口编程项目:https ://github.com/RishiGupta12/serial-communication-manager

解决方案 13:

是的,我知道,我来晚了(一如既往)。这是我的代码片段(基于 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;
}

解决方案 14:

#dmesg | grep tty

此命令显示每个端口

解决方案 15:

使用 setserial 工具:

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

如果您只希望输出端口设备路径:

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

可能这个解决方案并不适用于所有需求,因为一些 USB 设备可以通过 UDEV 以另一种方式命名,因此更通用但不太理想(不推荐):

setserial -gG /dev/* 2>/dev/null | grep -Ev "ttyS[0-9]+.*irqs0s*" | cut -d' ' -f1
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用