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

2024-04-27 03:37:55 发布

您现在位置:Python中文网/ 问答频道 /正文

获取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函数,或者以不会导致建立连接的方式打开设备?

我该怎么办?


Tags: 文件方法端口代码devlinux系统错误
3条回答

/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

目录包含所有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/

在最近的内核(不确定从什么时候开始)中,您可以列出/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/目录不存在。希望这有帮助:)。

我正在做如下代码。它适用于USB设备和愚蠢的串行8250设备,我们都有30个-但只有两个真正的工作。

基本上我使用的概念来自以前的答案。首先枚举/sys/class/tty/中的所有tty设备。不包含/device subdir的设备将被过滤掉。/sys/class/tty/console就是这样一个设备。然后根据驱动程序symlink 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;   
}

相关问题 更多 >