【问题标题】:How to find all serial devices (ttyS, ttyUSB, ..) on Linux without opening them?如何在不打开 Linux 的情况下找到所有串行设备(ttyS、ttyUSB、..)?
【发布时间】:2010-03-27 16:56:46
【问题描述】:

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

也就是说,当我遍历/dev/中的所有设备时,我如何以经典的方式判断哪些是串口,即通常支持波特率和RTS/CTS流控制的那些?

解决方案将用 C 编码。

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

所以我需要找到检测串口设备的官方方法。问题是似乎没有记录,或者我找不到。

我想一种方法是从/dev/tty* 打开所有文件并在它们上调用一个特定的ioctl(),它只在串行设备上可用。不过,这会是一个好的解决方案吗?

更新

hrickards 建议查看“setserial”的来源。 它的代码完全符合我的想法:

首先,它打开一个设备:

fd = open (path, O_RDWR | O_NONBLOCK)

然后它调用:

ioctl (fd, TIOCGSERIAL, &serinfo)

如果该调用没有返回错误,那么它显然是一个串行设备。

我在Serial Programming/termios中发现了类似的代码,建议也添加O_NOCTTY选项。

不过,这种方法存在一个问题:

当我在 BSD Unix(即 Mac OS X)上测试这段代码时,它运行良好。 但是,通过蓝牙提供的串行设备会导致系统(驱动程序)尝试连接到蓝牙设备,这需要一段时间才能返回并超时错误。这是由于刚刚打开设备造成的。我可以想象在 Linux 上也会发生类似的事情——理想情况下,我不需要打开设备来确定它的类型。我想知道是否还有一种方法可以在不打开的情况下调用ioctl 函数,或者以一种不会导致建立连接的方式打开设备?

我该怎么办?

【问题讨论】:

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

标签: linux serial-port


【解决方案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/

【讨论】:

  • @entalpi 你会找到/dev/zero。你真的以为这是串口设备吗?
  • 在 /dev 中搜索是没有用的,因为您已经在 /sys/class/tty 中有名称(因为默认情况下 udev 创建 /dev/DEVNAME 节点)。您感兴趣的是 /dev 中指向此类设备的任何“符号”链接。这要难得多。
【解决方案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/目录不存在。希望这会有所帮助:)。

【讨论】:

  • 这是2.5引入的udev的功能(具体是在/lib/udev/rules.d/??-persistent-serial.rules中的配置)。
  • 很棒的提示!不幸的是,我认为这不会显示内置串行端口,仅显示 USB 串行端口(连接时由 udev 看到)。我在 VMware VM(VM 提供 ttyS0/COM1)中的 Ubuntu 14 中看不到 /dev/serial 的任何内容,并且 udev 规则(60-persistent-serial.rules)仅查看 udev 设备——我认为 udev 不会发现“内置”ttyS* 串行端口,它们必须使用 ioctl 或其他答案中的类似方法进行测试。
  • ls /dev/serial/ ls: cannot access '/dev/serial/': No such file or directory Slackware 14.2 current x64
  • @jpka:如果找不到串行设备,就会发生这种情况。我按照上面的方法做了,它奏效了。然后我从 USB 上拔下我的(FTDI)串行设备,然后它产生了你描述的错误。
【解决方案3】:

我正在做类似以下代码的事情。它适用于 USB 设备以及我们都有 30 个的愚蠢的 serial8250 设备 - 但只有几个真正有效。

基本上我使用以前答案中的概念。首先枚举 /sys/class/tty/ 中的所有 tty-devices。不包含 /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;   
}

【讨论】:

  • 单独的链接是considered a poor answer,因为它本身没有意义,并且不能保证目标资源将来仍然存在。请尝试至少包含您要链接的信息的摘要。
  • 感谢 Soren,即使我们知道 API 和一些想法,但 Soren 做得非常好,再次感谢。
【解决方案4】:

我找到了

dmesg | grep tty

做好工作。

【讨论】:

    【解决方案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

    【讨论】:

    • 是的,这似乎有效。但是,此解决方案需要我读取文本文件并对其进行解析。我想知道是否有更好的方法,即让我以结构化二进制格式获取这些内容的 API。
    【解决方案6】:

    带有 -g 选项的 setserial 似乎可以满足您的需求,C 源代码可在 http://www.koders.com/c/fid39344DABD14604E70DF1B8FEA7D920A94AF78BF8.aspx 获得。

    【讨论】:

    • 我查看了代码,它有我在最后的问题中解释的缺陷,因为它必须打开设备,这可能已经导致连接尝试 - 这反过来又不好。但是,在蓝牙支持方面,Linux 驱动程序可能比当前的 OSX 驱动程序更智能,因为它们不会立即打开连接?谁知道?也许我应该提出一个新问题来具体澄清这一点。如果事实证明没问题,那么我也可以在这里接受您的回答。嗯...
    【解决方案7】:

    我这里没有串口设备可以测试,但是如果你有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”是否有不同的名称。

    HTH

    【讨论】:

      【解决方案8】:

      我没有 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 的版本/发行版,这看起来不错。
      【解决方案9】:

      使用 /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

      【讨论】:

      • 显然这是依赖于发行版的。我在我的机器上找不到 /dev/serial(运行 Debian)
      • 在 Ubuntu 上工作。
      • raspios 32 位 buster (debian) 在 2021 年 5 月 7 日发布时没有 /dev/serial,尽管它是基于 arm 的树莓派上的 debian buster。 /dev/serial/by-path 和 /dev/serial/by-id 依赖于发行版。
      【解决方案10】:

      我通过组 dialout 使用用户“dialout”获取每个 tty 的方法 ls -l /dev/tty* | grep 'dialout' 只获取它的文件夹 ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev

      轻松收听 tty 输出,例如当arduino串行输出时: head --lines 1 &lt; /dev/ttyUSB0

      只听一行的每个 tty: for i in $(ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev); do head --lines 1 &lt; $i; done

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

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

      【讨论】:

        【解决方案11】:

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

        【讨论】:

          【解决方案12】:

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

          【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-12-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多