如何唯一标识 USB 设备?

发布于 2024-10-05 09:57:23 字数 229 浏览 1 评论 0原文

我想知道如何获取 USB 存储设备的唯一 ID。 我已经知道如何从这篇文章中获取 SCSI 串行 ID:USB 驱动器串行linux C++下的数字 该帖子提到使用设备描述符来获取 ID。有人可以发布一些代码来确定 Linux 下的设备描述符信息吗?

I was wondering how to get the unique id of a USB storage device.
I already know how to fetch the SCSI serial id from this post : USB-drive serial number under linux C++
The post mentions using the Device Descriptor to get the ID. Can someone post some code to determine the Device Descriptor information under Linux?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

吻泪 2024-10-12 09:57:28

使用 USB,设备的“设备名称”可以更改,具体取决于设备的连接顺序。
令人惊讶的是,很少有设备有真正的序列号。
如果无法从设备本身获得唯一标识,唯一的解决方案就是依赖连接的物理地址。这样做的缺点是,如果将设备插入另一个 USB 连接器,地址会发生变化。

通过编程,您可以使用 sysfs 来获取内核拥有的有关设备的信息。 Sysfs 是内核看到的设备的类似文件系统的表示。 (它不是磁盘上的真实文件)

有了它,您可以:
- 使用产品和供应商 ID 识别设备类型
- 读取设备的序列号(如果有)。
- 读取 USB 集线器上的物理连接编号

您可以首先在 /sys/class 中查找您的设备类型。在此示例中,我使用 USB→LPT 端口。但原理是一样的。

$ ls -l /sys/class/usbmisc
lp1 -> ../../devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.5/4-1.5:1.0/usbmisc/lp1
lp2 -> ../../devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.6/4-1.6:1.0/usbmisc/lp2

从 uevent 文件中获取设备名称:

cat /sys/class/usbmisc/lp1/uevent
MAJOR=180
MINOR=1
DEVNAME=__usb/lp1__

添加 /dev 以便获得要打开的设备名称:/dev/usb/lp1

使用真实路径:
$ cd -P /sys/class/usbmisc/lp1

退一步 3 个分支:

$ cd ../../../
/sys/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.5

该目录包含设备上的大量信息:

idProductidVendor 可用于唯一标识设备类型。

如果有一个序列文件并且它包含唯一的序列号,那么您就完成了。

否则,您的选择是使用物理连接作为标识,即目录名称“4-1.5”,它对于物理连接来说是唯一的,并且正如您已经提到的,如果您将设备插入另一个港口。

With USB, the "device name" of a device can change, depending on the order of which, the device was connected.
Surprisingly few devices have a real serial number.
If you can't get a unique identification from the device itself, the only solution is to depend on the physical address of connection. The drawback on this, is that the address changes, if you plug the device into another USB connector.

Programmatically you can use sysfs to get the information the kernel has, about the device. Sysfs is a file-system-like representation of devices as the kernel sees them. (Its not real files on the disk)

With it, you can:
- identify the device type with product and vendor ID
- read the serial number of the device, if it has one.
- read the physical connection number on the USB hub

You could start by finding your type of devices in /sys/class. In this example I use an USB→LPT port. But the principle is the same.

$ ls -l /sys/class/usbmisc
lp1 -> ../../devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.5/4-1.5:1.0/usbmisc/lp1
lp2 -> ../../devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.6/4-1.6:1.0/usbmisc/lp2

Grap the device name from the uevent file:

cat /sys/class/usbmisc/lp1/uevent
MAJOR=180
MINOR=1
DEVNAME=__usb/lp1__

add /dev so you get the device name to open: /dev/usb/lp1

Use the real path:
$ cd -P /sys/class/usbmisc/lp1

Step back 3 branches:

$ cd ../../../
/sys/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.5

This directory contains a lot of the information on the device:

idProduct and idVendor can be used to uniquely identify the device type.

If there is a serial file and it contains a unique serial number, you are done.

Otherwise your option is to use the physical connection as identification, wich is this directory name “4-1.5” It is unique for the physical connection, and will as you already mentioned change if you plug the device to another port.

我只土不豪 2024-10-12 09:57:28

补充一下其他人所说的:

USB 设备并不总是有序列号;即使存在,也不能保证它是全球唯一的。 (例如,我的 Apple USB 键盘没有序列号,而 GoPro 相机均具有相同的伪造序列号 123456789ABC。)因此,并不总是能够唯一地标识设备。

To add to what everyone else has said:

USB devices do not always have a serial number; even when one is present, it is not guaranteed to be globally unique. (For instance, my Apple USB keyboard has no serial number, and GoPro cameras all have the same bogus serial number of 123456789ABC.) As such, it is not always possible to uniquely identify a device.

不交电费瞎发啥光 2024-10-12 09:57:28

执行sudo blkid,它将列出带有文件系统的所有已安装设备的ID

do sudo blkid and it will list the id of all mounted devices with a filesystem

绿光 2024-10-12 09:57:27
ls -l /dev/disk/by-id
ls -l /dev/disk/by-id
脱离于你 2024-10-12 09:57:27

概括 Simon Rigét 的答案,我想出了这个 bash 函数,给定可选的供应商 id 和产品 id,返回一个列表设备节点名称,与供应商 ID 和产品 ID(如果给定)相关。

getDevNodes() {
    if [ -n "$1" ] && [ "$1" != "no_class" ]; then
        2>/dev/null find -L /sys/class/$1 -maxdepth 2 -mindepth 2 -name uevent -exec realpath "{}" +
    else
        find /sys/devices -name uevent
    fi | {        
        if [ -z "$1" ]; then
            readarray -t lines < <(find /sys/class -maxdepth 2 -mindepth 2 -type l -print -exec realpath "{}" +)

            local -i count=${#lines[@]} sys_dev=count/2 sys_class=0
            local -A classes

            while [ $sys_dev -lt $count ]; do
                    class="${lines[$sys_class]#/*/*/}"
                    class="${class%/*}"
                    classes["${lines[$sys_dev]}"]="$class"

                    sys_dev+=1
                    sys_class+=1                    
            done
        fi

        readarray -t uevents

        for u in "${uevents[@]}"; do       
            DEVNAME=; DEVTYPE=no_type; while IFS="=" read key value; do {
                [ "$key" = "DEVNAME" ] && DEVNAME=/dev/"$value" 
            } || {
                [ "$key" = "DEVTYPE" ] && DEVTYPE="$value"                 
            }; done < "$u"

            if [ -n "$DEVNAME" ]; then              
                path="${u%/uevent}"
                while [ "$path" != "/sys/devices" ] && ! [ -f "$path"/idVendor ]; do
                    path="${path%/*}"
                done

                [ "$path" != "/sys/devices" ] && {
                    read readIdVendor < "$path"/idVendor
                    read readIdProduct < "$path"/idProduct
                } || {
                    readIdVendor=----
                    readIdProduct=----
                }

                echo "${1:-${classes[${u%/uevent}]:-no_class}}" "$DEVTYPE" "$readIdVendor" "$readIdProduct" "$DEVNAME" 
            fi
        done
    } | grep "^${1:-[[:graph:]]\+} ${2:-[[:graph:]]\+} ${3:-....} ${4:-....}" | cat
}

例如,这是我的 lsusb 告诉我的:

$ lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 008: ID 0bda:b719 Realtek Semiconductor Corp. 
Bus 001 Device 006: ID 0bda:57b5 Realtek Semiconductor Corp. 
Bus 001 Device 004: ID 0bda:0129 Realtek Semiconductor Corp. RTS5129 Card Reader Controller
Bus 001 Device 097: ID 1004:6344 LG Electronics, Inc. G2 Android Phone [tethering mode]
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

因此,如果我想查看哪些设备节点与供应商 id 0x1004 和产品 id 0x6344 关联,我会执行以下操作:

$ getDevNodes "" "" 1004 6344
no_class        usb_device      1004 6344 /dev/bus/usb/001/097 
tty             no_type         1004 6344 /dev/ttyACM0 

所以我们有两个设备节点,一个其中一个是 tty 类,没有 devtype,另一个是未知类,但具有 devtype usb_device

也可以只提供供应商 ID,如下所示:

$ getDevNodes "" "" 0bda 
no_class        usb_device      0bda 0129 /dev/bus/usb/001/004 
no_class        usb_device      0bda b719 /dev/bus/usb/001/008 
no_class        no_type         0bda 57b5 /dev/media0 
video4linux     no_type         0bda 57b5 /dev/video0 
input           no_type         0bda 57b5 /dev/input/event14 
no_class        usb_device      0bda 57b5 /dev/bus/usb/001/006 

如果我只想要供应商 ID 为 0bda 的 video4linux 类 设备,那么我会执行以下操作:

$ getDevNodes video4linux "" "" 0bda
video4linux     no_type         0bda 57b5 /dev/video0 

参数基本上是对完整设备列表的过滤器节点及其相关信息。省略这些参数之一,或使用空字符串 "" 作为参数,将禁用该特定参数的过滤器。

参数按以下顺序给出:1:类,2:类型,3:供应商 ID,4:产品 ID。


下面是上述函数的精简版,它运行速度更快,但牺牲了一些功能:打印设备节点时没有附加信息,并且没有设备类型的过滤器。

getDevNodesLite() {
    if [ -n "$1" ]; then
        2>/dev/null find -L /sys/class/$1 -maxdepth 2 -mindepth 2 -name uevent -exec realpath "{}" +
    else
        find /sys/devices -name uevent
    fi | {
        if [ -n "$2" ]; then
            readarray -t uevents              

            for u in "${uevents[@]}"; do
                path="${u%/uevent}"
                while [ "$path" != "/sys/devices" ] && ! [ -f "$path"/idVendor ]; do
                    path="${path%/*}"
                done

                [ "$path" != "/sys/devices" ] && read readValue < "$path"/idVendor && [ "$readValue" = "$2" ] && {
                    if [ -n "$idProduct" ]; then
                        read readValue < "$path"/idProduct && [ "$readValue" = "$3" ]
                    fi
                } && echo "$u"
            done
        else
            cat
        fi
    } | {
        readarray -t uevents              

        [ ${#uevents[@]} -gt 0 ] && sed -n 's,DEVNAME=\(.*\),/dev/\1,p' "${uevents[@]}"
    }
}

Generalizing Simon Rigét's answer, I came up with this bash function that, given optional vendor id and product id, returns a list of device node names, related to that vendor id and that product id if given.

getDevNodes() {
    if [ -n "$1" ] && [ "$1" != "no_class" ]; then
        2>/dev/null find -L /sys/class/$1 -maxdepth 2 -mindepth 2 -name uevent -exec realpath "{}" +
    else
        find /sys/devices -name uevent
    fi | {        
        if [ -z "$1" ]; then
            readarray -t lines < <(find /sys/class -maxdepth 2 -mindepth 2 -type l -print -exec realpath "{}" +)

            local -i count=${#lines[@]} sys_dev=count/2 sys_class=0
            local -A classes

            while [ $sys_dev -lt $count ]; do
                    class="${lines[$sys_class]#/*/*/}"
                    class="${class%/*}"
                    classes["${lines[$sys_dev]}"]="$class"

                    sys_dev+=1
                    sys_class+=1                    
            done
        fi

        readarray -t uevents

        for u in "${uevents[@]}"; do       
            DEVNAME=; DEVTYPE=no_type; while IFS="=" read key value; do {
                [ "$key" = "DEVNAME" ] && DEVNAME=/dev/"$value" 
            } || {
                [ "$key" = "DEVTYPE" ] && DEVTYPE="$value"                 
            }; done < "$u"

            if [ -n "$DEVNAME" ]; then              
                path="${u%/uevent}"
                while [ "$path" != "/sys/devices" ] && ! [ -f "$path"/idVendor ]; do
                    path="${path%/*}"
                done

                [ "$path" != "/sys/devices" ] && {
                    read readIdVendor < "$path"/idVendor
                    read readIdProduct < "$path"/idProduct
                } || {
                    readIdVendor=----
                    readIdProduct=----
                }

                echo "${1:-${classes[${u%/uevent}]:-no_class}}" "$DEVTYPE" "$readIdVendor" "$readIdProduct" "$DEVNAME" 
            fi
        done
    } | grep "^${1:-[[:graph:]]\+} ${2:-[[:graph:]]\+} ${3:-....} ${4:-....}" | cat
}

For instance, this is what my lsusb tells me:

$ lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 008: ID 0bda:b719 Realtek Semiconductor Corp. 
Bus 001 Device 006: ID 0bda:57b5 Realtek Semiconductor Corp. 
Bus 001 Device 004: ID 0bda:0129 Realtek Semiconductor Corp. RTS5129 Card Reader Controller
Bus 001 Device 097: ID 1004:6344 LG Electronics, Inc. G2 Android Phone [tethering mode]
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

So, if I wanted to see which device nodes are associated with the vendor id 0x1004 and the product id 0x6344 I'd do the following:

$ getDevNodes "" "" 1004 6344
no_class        usb_device      1004 6344 /dev/bus/usb/001/097 
tty             no_type         1004 6344 /dev/ttyACM0 

So we've got two device nodes, one of which is of class tty with no devtype and the other is of unknown class but with a devtype usb_device.

One can also only give the vendor id, like this:

$ getDevNodes "" "" 0bda 
no_class        usb_device      0bda 0129 /dev/bus/usb/001/004 
no_class        usb_device      0bda b719 /dev/bus/usb/001/008 
no_class        no_type         0bda 57b5 /dev/media0 
video4linux     no_type         0bda 57b5 /dev/video0 
input           no_type         0bda 57b5 /dev/input/event14 
no_class        usb_device      0bda 57b5 /dev/bus/usb/001/006 

If I only wanted video4linux class devices whose vendor id is 0bda, then I'd do the following:

$ getDevNodes video4linux "" "" 0bda
video4linux     no_type         0bda 57b5 /dev/video0 

Arguments are basically filters over the complete list of device nodes and their associated info. Omitting one of those arguments, or using the empty string "" as an argument, disables the filter for that specific argument.

Arguments are given in this order: 1: the class, 2: the type, 3: the vendor id, 4: the product id.


Here follows a lite version of the above function that runs faster at the expense of some functionalities: the device nodes are printed without the additional info and there's no filter for the device type.

getDevNodesLite() {
    if [ -n "$1" ]; then
        2>/dev/null find -L /sys/class/$1 -maxdepth 2 -mindepth 2 -name uevent -exec realpath "{}" +
    else
        find /sys/devices -name uevent
    fi | {
        if [ -n "$2" ]; then
            readarray -t uevents              

            for u in "${uevents[@]}"; do
                path="${u%/uevent}"
                while [ "$path" != "/sys/devices" ] && ! [ -f "$path"/idVendor ]; do
                    path="${path%/*}"
                done

                [ "$path" != "/sys/devices" ] && read readValue < "$path"/idVendor && [ "$readValue" = "$2" ] && {
                    if [ -n "$idProduct" ]; then
                        read readValue < "$path"/idProduct && [ "$readValue" = "$3" ]
                    fi
                } && echo "$u"
            done
        else
            cat
        fi
    } | {
        readarray -t uevents              

        [ ${#uevents[@]} -gt 0 ] && sed -n 's,DEVNAME=\(.*\),/dev/\1,p' "${uevents[@]}"
    }
}
隔岸观火 2024-10-12 09:57:27

我建议使用libusb。您可以在此处找到文档。

I suggest to use libusb. You can find the documentation here.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文