如何在不打开Linux的情况下查找所有串行设备(ttyS,ttyUSB等)?


113

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

换句话说,当我遍历中的所有设备时/dev/,如何以经典方式分辨哪些设备是串行端口,即那些通常支持波特率和RTS / CTS流控制的设备?

解决方案将用C编码。

我问是因为我正在使用第三方库,这样做显然是错误的:它似乎仅在上进行迭代/dev/ttyS*。问题是,例如,存在USB上的串行端口(由USB-RS232适配器提供),而这些端口在/ dev / ttyUSB *下列出。在Linux.org上阅读Serial-HOWTO时,我发现随着时间的流逝还将有其他名称空间。

因此,我需要找到检测串行设备的官方方法。问题是似乎没有文件记录在案,或者我找不到它。

我想一种方法是打开其中的所有文件/dev/tty*并对其进行调用ioctl(),这些文件仅在串行设备上可用。但是,这将是一个好的解决方案吗?

更新资料

希拉克们建议看看“ setserial”的来源。它的代码完全符合我的想法:

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

fd = open (path, O_RDWR | O_NONBLOCK)

然后它调用:

ioctl (fd, TIOCGSERIAL, &serinfo)

如果该调用没有返回错误,则显然是串行设备。

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

但是,这种方法存在一个问题:

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

我该怎么办?


1
某位匿名人士建议进行此编辑,但该编辑被拒绝,因此,我将其留在此处作为注释:如果在ioctl调用中使用TIOCGSERIAL标志而不是TIOCMGET,则该调用不会返回错误,并且某些错误路径不会指的是COM(串行)端口。使用TIOCMGET标志,ioctl仅与可用于在TTY和TTYUSB可能路径中访问的COM端口一起使用。
Thomas Tempelmann '16

Answers:


78

/sys文件系统应该包含你的追求大量的信息。我的系统(2.6.32-40-通用#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。您真的认为这是串行设备吗?
AH

在/ dev中进行搜索是没有用的,因为您已经在/ sys / class / tty中有了该名称(默认情况下,udev会创建/ dev / DEVNAME节点)。您感兴趣的是/ dev中指向该设备的任何“符号”链接。这很难找到。
xryl669 '16

28

在最近的内核中(不确定何时),您可以列出/ 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
这是udev的功能(特别是在/lib/udev/rules.d/??-persistent-serial.rules中的配置),该功能在2.5中引入。
ergosys

4
大提示!不幸的是,我认为这不会显示内置的串行端口,而只会显示USB串行端口(连接后由udev看到)。我在VMware VM(使用由VM提供的ttyS0 / COM1)的Ubuntu 14中看不到/ dev / serial的任何内容,而udev规则(60-persistent-serial.rules)仅查看udev设备-我认为udev不会发现“内置” ttyS *串行端口,必须使用ioctl或与其他答案类似的方法对其进行测试。
Reed Hedges's

ls / dev / serial / ls:无法访问“ / dev / serial /”:没有这样的文件或目录Slackware 14.2当前x64
jpka

2
@jpka:如果找不到串行设备,就会发生这种情况。我做的和上面一样。然后,我从USB拔出(FTDI)串行设备,然后产生您所描述的错误。
Warpspace,

13

我正在做类似下面的代码。它适用于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;   
}

孤独的链接被认为是一个糟糕的答案,因为它本身毫无意义,并且不能保证目标资源在将来仍然有效。请尝试至少包含您要链接的信息的摘要。
2012年

感谢Soren的帮助,即使我们也了解API和其中的一些想法,但您的Soren确实做得很好,再次感谢。
ind79ra

12

我想我在内核源文档中找到了答案:/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。
Thomas Tempelmann 2011年


3

使用-g选项的setserial似乎可以满足您的要求,并且可以从http://www.koders.com/c/fid39344DABD14604E70DF1B8FEA7D920A94AF78BF8.aspx获得C源代码。


我看了一下代码,并发现了我在问题中最后解释的缺陷,因为它必须打开设备,这可能已经导致连接尝试-反过来又不好。但是,那么在蓝牙支持方面,也许Linux驱动程序比当前的OSX驱动程序更聪明,因为它们不会立即打开连接吗?谁知道?也许我应该提出一个新的问题来具体说明这一点。如果事实证明还可以,那么我也可以在这里接受您的回答。嗯...-
托马斯·坦佩尔曼

3

我这里没有串行设备可以对其进行测试,但是如果您有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”是否具有其他名称。

高温超导


2

我没有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's

2

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


显然,这是依赖发行版的。我在盒子上(运行Debian)找不到/ dev / serial
SimonC

0

我通过组拨出的方法来使用用户“拨出”获取每个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-Name: ls /sys/class/tty/*/device/driver | grep 'driver' | cut -d "/" -f 5



0

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

看来您的代码解析了stackoverflow.com/a/4701610/43615所指的答案。如果是这样,请在回复中提及?
托马斯·坦佩尔曼
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.