容器环境与可插拔设备(一)---udev
近期在调试一个环境,运行的情况主要是宿主机+lxc容器+linux容器系统。有点类似于在宿主机上运行一个docker,然后docker中运行了一个容器系统。
然后有同事反馈了一个问题:为什么我在容器环境中配置了udev的一些rule,但是这些rule全部都没有生效。最典型的一个rule就是:
$ cat /etc/udev/rules.d/uinput.rules
KERNEL=="uinput", MODE="0666"
但是系统的uinput权限始终都是0664。
linux内核中,有三个比较关键同时也很特殊的文件系统,其文件系统中的内容,均是内核的数据映射出来的。
- proc
- dev
- sys
proc
proc主要是对于内核中管理的进程的状态信息做了映射,我们通过proc这个文件系统,能够很方便的在用户空间里面获取各个进程的状态信息。最简单的,ps命令,大部分的数据就是从/proc中读取出来的。
$ strace ps -ef
openat(AT_FDCWD, "/proc/548404/status", O_RDONLY) = 6
read(6, "Name:\tkworker/3:3-mm_percpu_wq\nU"..., 2048) = 767
close(6) = 0
openat(AT_FDCWD, "/proc/548404/cmdline", O_RDONLY) = 6
read(6, "", 131072) = 0
close(6)
另外再举一个例子。比如我想要获取weston这个进程运行时候的环境变量(不包括运行过程中创建的环境变量)。
sed 's:\x0:\n:g' /proc/$(pidof weston)/environ
SHELL=/usr/bin/bash
QT_ACCESSIBILITY=1
XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session0
GTK_IM_MODULE=fcitx
LANGUAGE=zh_CN.UTF-8
XDG_DATA_HOME=/home/user/.local/share
XDG_CONFIG_HOME=/home/user/.config
XMODIFIERS=@im=fcitx
DESKTOP_SESSION=deepin
XDG_SEAT=seat0
PWD=/home/user
XDG_SESSION_DESKTOP=deepin
LOGNAME=user
XDG_SESSION_TYPE=wayland
GPG_AGENT_INFO=/run/user/1000/gnupg/S.gpg-agent:0:1
XDG_GREETER_DATA_DIR=/var/lib/lightdm/data/user
HOME=/home/user
LANG=zh_CN.UTF-8
XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0
XDG_CACHE_HOME=/home/user/.cache
XDG_SESSION_CLASS=user
USER=user
DISPLAY=:0
SHLVL=0
QT_IM_MODULE=fcitx
XDG_VTNR=1
XDG_SESSION_ID=5
XDG_RUNTIME_DIR=/run/user/1000
GDK_BACKEND=x11
PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/sbin:/usr/sbin
GDMSESSION=deepin
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
QT_FONT_DPI=144
可以看到我们只需要获取weston这个进程的PID,然后读取
/proc/$PID/environ
这个文件,就可以获取对应进程的环境变量了,但是由于获取出来的环境变量是通过\0来分割的,所以为了方便阅读,需要通过sed进行换行替换。
sys
sys是linux内核用来管理系统设备的一个内存文件系统。系统的大部分设备的信息,均可以从这个文件系统中获取到(读文件操作),同时用户空间还能通过这个文件系统还来修改设备的状态(写文件操作)。
dev
dev是内核中设备的文件映射,用来与实际的设备进行数据交互的。比如鼠标设备(usb),其对应的设备为
/dev/input/event7
那么鼠标移动、左键右键点击、滚轮滑动等数据信息,均是从这个节点读取来的。
但是想这个鼠标对应的usb设备,是什么名称、是什么设备类型,厂商id是什么,设备编号是什么等,这些信息,均由sys文件系统提供
$ ls -al /sys/devices/pci0000:00/0000:00:14.0/usb3/3-5/3-5:1.0/0003:093A:2510.0002/input/input7
总用量 0
drwxr-xr-x 7 root root 0 5月 29 13:43 .
drwxr-xr-x 3 root root 0 5月 29 13:43 ..
drwxr-xr-x 2 root root 0 5月 29 13:43 capabilities
lrwxrwxrwx 1 root root 0 5月 29 13:43 device -> ../../../0003:093A:2510.0002
drwxr-xr-x 3 root root 0 5月 29 13:43 event6
drwxr-xr-x 2 root root 0 5月 29 13:43 id
-rw-r--r-- 1 root root 4096 5月 29 13:43 inhibited
-r--r--r-- 1 root root 4096 5月 29 13:43 modalias
drwxr-xr-x 3 root root 0 5月 29 13:43 mouse2
-r--r--r-- 1 root root 4096 5月 29 13:43 name
-r--r--r-- 1 root root 4096 5月 29 13:43 phys
drwxr-xr-x 2 root root 0 5月 29 13:43 power
-r--r--r-- 1 root root 4096 5月 29 13:43 properties
lrwxrwxrwx 1 root root 0 5月 29 13:43 subsystem -> ../../../../../../../../../class/input
-rw-r--r-- 1 root root 4096 5月 29 13:43 uevent
-r--r--r-- 1 root root 4096 5月 29 13:43 uniq
其中一个比较关键的文件是uevent,我们通过往这个文件中写入不同的action值,可以控制这个设备的状态。
比如往这个设备写入change,内核就会重新加载这个设备,并使用netlink上报change event上来。
echo change > uevent
我们在linux系统中,经常会顺手udevadm trigger这个命令,这个命令的作用是重新触发设备加载流程,其原理就是往uevent里面写入一个action(默认就是写change)
如下是udevadm中trigger的逻辑片段:
static int exec_list(sd_device_enumerator *e, const char *action, Set *settle_set) {
sd_device *d;
int r;
FOREACH_DEVICE_AND_SUBSYSTEM(e, d) {
_cleanup_free_ char *filename = NULL;
filename = path_join(syspath, "uevent");
r = write_string_file(filename, action, WRITE_STRING_FILE_DISABLE_BUFFER);
return 0;
= lxc配置
正是因为考虑到/sys /dev /proc这三个文件系统是很特殊的,所以lxc需要对这三个文件系统进行特殊处理,特别是/sys和/proc。
lxc.mount.auto = cgroup:mixed proc:mixed sys:mixed
格式就是
文件系统:权限
比如sys:mixed就是说对sys文件系统挂载成mixed模式(大部分目录是只读的,只要net部分是可读可写的)。
其他的可选模式有:
- sys:ro sys只读
- sys:rw sys可读可写
我们在上面sys文件系统介绍部分有说明,如果要对设备进行重新加载,则需要往对应的uevent节点写数据,则不可避免的需要/sys文件系统可读可写。
= 宿主机和容器的冲突
既然/sys文件系统是映射的内核资源,而这部分资源在内核中是只有一份的,那么如果容器中,只有一方对该文件系统拥有写权限,则不很产生很大的冲突问题。
如果有两个环境同时对该文件系统拥有写权限,那么可能会对该文件系统的状态产生很大的应用,导致资源访问异常,进而出现黑屏、卡死、重启等问题。
主流容器策略
docker中的sys
docker对于sys的处理策略,默认情况下也是设置成ro模式,避免了容器环境和宿主机对于/sys同时写入导致的资源冲突问题。
lxc 中sys默认配置。
lxc中对sys中默认配置就是mixed状态,即/sys只读,只有内部的net部分可写。并且在ubuntu镜像模板中也是集成lxc的这个默认配置。
= 现状分析
linux系统中通常有一个udev服务,这个服务用来监听kernel的netlink event事件,并根据事件属性来匹配对应的rule规则,
如果规则匹配成功,则对规则进行应用。
目前linux系统下的udevd已经被systemd接管,进程为systemd-udevd。
systemd-udevd未启动
systemd-udevd.service是由systemd服务进程管理的。容器中systemd-udevd未启动的原因也很简单,因为/sys被lxc挂载成了只读的状态,而systemd-udevd作为udev服务,需要处理的不只是监听uevent事件,
中途还会有trigger逻辑存在,即需要往uevent节点中写入数据。因此systemd-udevd需要/sys可读可写这个条件是合理的。
但是安装正常逻辑来推论,如果/sys是只读的话,大部分功能应该是能够正常使用的,估计只有trigger相关的逻辑会失效。
因此第一步需要处理的就是修改systemd-udevd的启动条件,屏蔽对/sys的可读可写检查。
cat systemd-udevd.service
[Unit]
Description=udev Kernel Device Manager
Documentation=man:systemd-udevd.service(8) man:udev(7)
DefaultDependencies=no
After=systemd-sysusers.service systemd-hwdb-update.service
Before=sysinit.target
#ConditionPathIsReadWrite=/sys
[Service]
ExecStart=/lib/systemd/systemd-udevd
这时候systemd-udevd确实如预料的情况一样,能够正常运行起来。但是rule并没有生效。
为了分析udev rule没有生效的原因,我们需要对systemd-udevd进行简单的调试,先打开日志:
systemd的日志,均可以通过两个环境变量来控制其日志行为:
- SYSTEMD_LOG_LEVEL 用来设置日志等级,我们设置为debug即可输出尽可能多的日志。
- SYSTEMD_LOG_TARGET 日志输出的目的地。考虑到systemd-udevd是systemd管理的,会自动把标准输出的日志用journalctl管理起来,所以我们简单设置成console即可用journalctl捕捉到日志。
通过查看日志,发现udevd已经加载了所有rule。既然rule能够正常加载,但是没有起作用,那么就需要理解一下rule生效的流程了。
为了测试方便,编写了一个简单的监听netlink事件的程序,发现容器在启动过程中,并没有可以匹配rule的事件发送出来。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/types.h>
#include <linux/netlink.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#ifndef NEWLINE
#define NEWLINE "\r\n"
#endif
#define UEVENT_BUFFER_SIZE 2048
static int init_hotplug_sock()
const int buffersize = 1024;
int ret;
struct sockaddr_nl snl;
bzero(&snl, sizeof(struct sockaddr_nl));
snl.nl_family = AF_NETLINK;
snl.nl_pid = getpid();
snl.nl_groups = 1;
int s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
if (s == -1)
perror("socket");
return -1;
setsockopt(s, SOL_SOCKET, SO_RCVBUF, &buffersize, sizeof(buffersize));
ret = bind(s, (struct sockaddr *)&snl, sizeof(struct sockaddr_nl));
if (ret < 0)
perror("bind");
close(s);
return -1;
return s;
char *truncate_nl(char *s) {
s[strcspn(s, NEWLINE)] = 0;
return s;
int device_new_from_nulstr(uint8_t *nulstr, size_t len) {
int i = 0;
int r;
while (i < len) {
char *key;
const char *end;
key = (char*)&nulstr[i];
end = memchr(key, '\0', len - i);
if (!end)
return 0;
i += end - key + 1;
truncate_nl(key);
printf("%s\n", key);
int main(int argc, char* argv[])
int hotplug_sock = init_hotplug_sock();
int bufpos;
while(1)
int len;
/* Netlink message buffer */
char buf[UEVENT_BUFFER_SIZE * 2];
memset(&buf, 0x00, sizeof(buf));
len = recv(hotplug_sock, &buf, sizeof(buf), 0);
if (len <= 0)
continue;
printf("\nnew message:\n");
bufpos = strlen(buf) + 1;
printf("%s\n", buf);
device_new_from_nulstr((uint8_t*)&buf[bufpos], len - bufpos);
return 0;
所以原因就很清楚了,因为容器是后启动的,在容器启动的时候,所有的设备其实已经加载完成了,并且uevent事件也已经发送过了,所以后面启动的systemd-udevd并不会捕捉到对应的事件。
重新触发uevent
udevadm trigger
可以让内核重新触发uevent。但是执行该命令后,发现只有
/sys/devices/virtual/net
相关的下面的设备触发了uevent,其他的均没有任何反应。
这是才想起来我们在配置lxc sys权限的时候,默认使用的mixed,这个权限会将sys配置成只读,但是net会额外处理成可读可写。而udevadm trigger需要对uevent进行写入,所以此时只会有
/sys/devices/virtual/net
能够触发出来。
经过初步评估,rule中的配置,大部分是usb类型,可以优先考虑对uinput设备和usb设备进行处理。
$ cat 99_camera.rules
SUBSYSTEM=="usb", ATTR{idVendor}=="1234", ATTR{idProduct}=="0101", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="1234", ATTR{idProduct}=="0201", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="2285", ATTR{idProduct}=="2f11", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="0ac8", ATTR{idProduct}=="010c", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="0c45", ATTR{idProduct}=="1a11", MODE="0666"
$ cat uinput.rules
KERNEL=="uinput", MODE="0666"
重新触发uinput设备的change事件
为了能够让uinput这个设备在使用udevadm trigger命令的时候能够正常触发,需要保证uinput设备所在的目录有写权限。
先看一下uinput设备所在的路径
/sys/devices/virtual/misc/uinput/uevent
对于usb设备,可以查看如下目录的内容来确定路径:
$ ls -al /sys/bus/usb/devices/
总用量 0
drwxr-xr-x. 2 root root 0 5月 29 10:46 .
drwxr-xr-x. 4 root root 0 5月 29 10:46 ..
lrwxrwxrwx. 1 root root 0 5月 29 16:00 1-0:1.0 -> ../../../devices/platform/soc/soc:aon/5fff0000.usb/musb-hdrc.0.auto/usb1/1-0:1.0
lrwxrwxrwx. 1 root root 0 5月 29 16:00 usb1 -> ../../../devices/platform/soc/soc:aon/5fff0000.usb/musb-hdrc.0.auto/usb1
可以观察到系统带有一个usb,即usb1,路径为
/sys/devices/platform/soc/soc:aon/5fff0000.usb/musb-hdrc.0.auto/usb1
既然
/sys/devices/virtual/net
能够实现可读写的配置,那么参考这个配置,理论上也可以做到保持usb和uinput同样保持可读可写的权限。
lxc sys:mixed作用
virtual/net
可读可写是通过mixed配置实现的,为了让uinput也实现可读写,我们就需要了解这个配置的具体原理。
先看一下net的mount信息
$ mount|grep virtual/net
sysfs on /sys/devices/virtual/net type sysfs (rw,relatime,seclabel)
sysfs on /sys/devices/virtual/net type sysfs (rw,nosuid,nodev,noexec,relatime,seclabel)
此处暂时还看不出来具体产生这个挂载的原因。
在lxc中搜索一下mixed配置处理的逻辑,可以找到如下代码:
static int lxc_mount_auto_mounts(struct lxc_conf *conf, int flags, struct lxc_handler *handler)
int i, r;
static struct {
int match_mask;
int match_flag;
const char *source;
const char *destination;
const char *fstype;
unsigned long flags;
const char *options;
} default_mounts[] = {
{ LXC_AUTO_SYS_MASK, LXC_AUTO_SYS_MIXED, "sysfs", "%r/sys", "sysfs", MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL },
{ LXC_AUTO_SYS_MASK, LXC_AUTO_SYS_MIXED, NULL, "%r/sys", NULL, MS_REMOUNT|MS_BIND|MS_RDONLY, NULL },
{ LXC_AUTO_SYS_MASK, LXC_AUTO_SYS_MIXED, "sysfs", "%r/sys/devices/virtual/net", "sysfs", 0, NULL },
{ LXC_AUTO_SYS_MASK, LXC_AUTO_SYS_MIXED, "%r/sys/devices/virtual/net/devices/virtual/net", "%r/sys/devices/virtual/net", NULL, MS_BIND, NULL },
{ LXC_AUTO_SYS_MASK, LXC_AUTO_SYS_MIXED, NULL, "%r/sys/devices/virtual/net", NULL, MS_REMOUNT|MS_BIND|MS_NOSUID|MS_NODEV|MS_NOEXEC, NULL },
{ 0, 0, NULL, NULL, NULL, 0, NULL }
for (i = 0; default_mounts[i].match_mask; i++) {
return 0;
观察三条virtual/net的记录
"sysfs", "%r/sys/devices/virtual/net", "sysfs", 0,
"%r/sys/devices/virtual/net/devices/virtual/net", "%r/sys/devices/virtual/net", NULL, MS_BIND,
NULL, "%r/sys/devices/virtual/net", NULL, MS_REMOUNT|MS_BIND|MS_NOSUID|MS_NODEV|MS_NOEXEC
- 现在/sys/devices/virtual/net目录挂载sysfs文件系统。
- 将/sys/devices/virtual/net/devices/virtual/net目录bind到/sys/devices/virtual/net。因为/sys本身就是一个sysfs,然后net下面又挂载了一个可读写的sysfs,那么原来的net目录下的内容就被掩藏了,然后通过bind操作将net下面内容重新映射过来。
- 设置/sys/devices/virtual/net挂载权限。
这时候基本就能够分析出来有两条挂载记录的原因。
- 首先将目录挂载成sysfs,但是挂载的是整个sysfs,所以原本目录的内容会被掩藏,并体现在嵌套之后的目录中。
- 重新通过mout bind将掩藏的内容重定向回来。
uinput设备目录重新挂载可读写。
理解了上面mixed的原理,我们在lxc的hook中模拟配置uinput的目录,为了方便,我们直接操作
/sys/devices/virtual/misc
这个目录
export PATH=/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/bin:/sbin
mount -t sysfs -o rw,nosuid,nodev,noexec,relatime,seclabel sysfs /sys/devices/virtual/misc
mount --bind /sys/devices/virtual/misc/devices/virtual/misc /sys/devices/virtual/misc
但是怎么让这个命令自动执行呢?而且这个命令需要在容器内部执行。
查看lxc的配置选项可以知道,有一个配置
lxc.hook.start
这个配置可以在执行容器的 init 之前立即在容器的命名空间中运行的一个钩子。这要求该程序在容器中可用。这个钩子可以设置成一个可执行程序或者脚本。
为了考虑方便集成,我们此处采用脚本的方式来处理:
lxc.hook.start = /vendor/bin/lxc-hook-start.sh
配置好了后,重新启动容器,我们就可以看到我们想要的效果了:
$ mount|grep misc
sysfs on /sys/devices/virtual/misc type sysfs (rw,nosuid,nodev,noexec,relatime,seclabel)
sysfs on /sys/devices/virtual/misc type sysfs (rw,nosuid,nodev,noexec,relatime,seclabel)
$ ls /sys/devices/virtual/misc/uinput/
dev power subsystem uevent
可以看到挂载的文件系统已经是可读可写的了,并且目录内容也是符合我们的预期的。
usb设备重新挂载可读写
上我我们处理了uinput所在的misc类型设备的挂载,由于misc的设备路径是固定的
/sys/devices/virtual/misc
,
所以我们只需要固定写死即可。但是对于usb设备,则不那么实用了。以我手上的这台设备为例,其usb3的路径为:
ls -al /sys/devices/pci0000:00/0000:00:14.0/usb3
前面的
pci0000:00/0000:00:14.0
这一块路径,在不同的设备上,会体现的不一样,而且还可能会存在多个usb设备,具体会使用那个设备也是不可知的。
那么如何能够稳定的找到设备上所有的usb的实际路径,就是一个问题。
我们都知道,linux驱动也固有的标准:driver、device和bus。而usb就是一类常用的bus,我们可以尝试着直接从bus上面入手:
ls -l /sys/bus/usb/devices/
总用量 0
lrwxrwxrwx 1 root root 0 5月 30 11:26 1-0:1.0 -> ../../../devices/pci0000:00/0000:00:0d.0/usb1/1-0:1.0
lrwxrwxrwx 1 root root 0 5月 30 11:26 2-0:1.0 -> ../../../devices/pci0000:00/0000:00:0d.0/usb2/2-0:1.0
lrwxrwxrwx 1 root root 0 5月 24 15:14 3-0:1.0 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-0:1.0
lrwxrwxrwx 1 root root 0 5月 30 11:26 3-1 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-1
lrwxrwxrwx 1 root root 0 5月 30 11:26 3-10 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-10
lrwxrwxrwx 1 root root 0 5月 24 15:14 3-10:1.0 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-10/3-10:1.0
lrwxrwxrwx 1 root root 0 5月 30 11:26 3-10:1.1 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-10/3-10:1.1
lrwxrwxrwx 1 root root 0 5月 30 11:26 3-1:1.0 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-1/3-1:1.0
lrwxrwxrwx 1 root root 0 5月 30 10:15 3-1.2 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-1/3-1.2
lrwxrwxrwx 1 root root 0 5月 30 10:15 3-1.2:1.0 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-1/3-1.2/3-1.2:1.0
lrwxrwxrwx 1 root root 0 5月 30 10:15 3-1.2:1.1 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-1/3-1.2/3-1.2:1.1
lrwxrwxrwx 1 root root 0 5月 30 10:15 3-1.2:1.2 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-1/3-1.2/3-1.2:1.2
lrwxrwxrwx 1 root root 0 5月 30 10:15 3-1.2:1.3 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-1/3-1.2/3-1.2:1.3
lrwxrwxrwx 1 root root 0 5月 30 11:26 3-1.4 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-1/3-1.4
lrwxrwxrwx 1 root root 0 5月 30 11:26 3-1.4:1.0 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-1/3-1.4/3-1.4:1.0
lrwxrwxrwx 1 root root 0 5月 30 11:26 3-1.4:1.1 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-1/3-1.4/3-1.4:1.1
lrwxrwxrwx 1 root root 0 5月 30 11:26 3-5 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-5
lrwxrwxrwx 1 root root 0 5月 30 11:26 3-5:1.0 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-5/3-5:1.0
lrwxrwxrwx 1 root root 0 5月 30 11:26 3-7 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-7
lrwxrwxrwx 1 root root 0 5月 30 11:26 3-7:1.0 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-7/3-7:1.0
lrwxrwxrwx 1 root root 0 5月 30 11:26 3-7:1.1 -> ../../../devices/pci0000:00/0000:00:14.0/usb3/3-7/3-7:1.1
lrwxrwxrwx 1 root root 0 5月 30 11:26 4-0:1.0 -> ../../../devices/pci0000:00/0000:00:14.0/usb4/4-0:1.0
lrwxrwxrwx 1 root root 0 5月 30 11:26 4-1 -> ../../../devices/pci0000:00/0000:00:14.0/usb4/4-1
lrwxrwxrwx 1 root root 0 5月 30 11:26 4-1.1 -> ../../../devices/pci0000:00/0000:00:14.0/usb4/4-1/4-1.1
lrwxrwxrwx 1 root root 0 5月 29 11:15 4-1:1.0 -> ../../../devices/pci0000:00/0000:00:14.0/usb4/4-1/4-1:1.0
lrwxrwxrwx 1 root root 0 5月 30 11:17 4-1.1:1.0 -> ../../../devices/pci0000:00/0000:00:14.0/usb4/4-1/4-1.1/4-1.1:1.0
lrwxrwxrwx 1 root root 0 5月 30 11:26 usb1 -> ../../../devices/pci0000:00/0000:00:0d.0/usb1
lrwxrwxrwx 1 root root 0 5月 30 11:26 usb2 -> ../../../devices/pci0000:00/0000:00:0d.0/usb2
lrwxrwxrwx 1 root root 0 5月 30 11:26 usb3 -> ../../../devices/pci0000:00/0000:00:14.0/usb3
lrwxrwxrwx 1 root root 0 5月 30 11:26 usb4 -> ../../../devices/pci0000:00/0000:00:14.0/usb4
可以看到
/sys/bus/usb/devices/
这个目录会列表所有的usb,而这些路径其实是一个软连接,那么只需要过滤出实际的usb设备,并找到对应的device路径即可。直接上脚本:
过滤设备:
$ ls /sys/bus/usb/devices/|grep "usb[0-9]"
获取软链接实际的路径
$ readlink -f /sys/bus/usb/devices/usb1
/sys/devices/pci0000:00/0000:00:0d.0/usb1
目录找出来了,剩下的就是挂载了,既然我们需要对多个目录进行挂载,就需要重复调用之前挂载misc情况下的两次mount流程。为了方便复用,直接封装一个函数:
mount_device() {
mount -t sysfs -o rw,nosuid,nodev,noexec,relatime,seclabel sysfs /sys/devices/$1
mount --bind /sys/devices/$1/devices/$1 /sys/devices/$1
该函数接受一个路径参数,但是该路径是去除了前面的
/sys/devices
因此整个逻辑就很明确了:
#!/usr/bin/bash
export PATH=/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/bin:/sbin
mount_device() {
mount -t sysfs -o rw,nosuid,nodev,noexec,relatime,seclabel sysfs /sys/devices/$1
mount --bind /sys/devices/$1/devices/$1 /sys/devices/$1
mount_misc() {
mount_device "virtual/misc"
mount_all_usb() {
for usb in $(ls /sys/bus/usb/devices/ | grep "usb[0-9]")
device=$(readlink -f "/sys/bus/usb/devices/$usb")
mount_device ${device:13}