利用UDEV机制固定存储设备的名称

本文详细介绍了udev的功能及工作原理,包括udev如何动态管理设备目录、udev的组成及其规则文件的应用。此外,还提供了两个实用示例,展示如何利用udev规则来自定义设备名称。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是udev
  udev为一些实际的设备提供了一个动态的设备目录,这些目录里包含这些实际设备所对应的文件。它创建或移除在/dev目录里的设备节点文件,或者重命名网络接口。
  通常udev运行udevd,如果一个设备增加进系统或者从系统中移除,它就会直接从内核中接受到这个udev的事件。
  如果udev接受到一个设备事件,它就会从它的配置(或是规则)文件(有三个位置/etc/udev/rules.d、/dev/.udev/rules.d、/lib/udev/rules.d)中去匹配从sysfs中得到的设备属性来辨识这个设备。这些rules配置文件会去比较所提供的附加(新增加进来的)的设备的信息或者指定一个设备节点名并操作符号链接名来指示udev运行附加的程序来作为设备事件的处理。

udev的组成
  udev由namedev、libsysfs和udev三部分组成。其中,namedev是设备命名子系统;libsysfs提供访问sysfs文件系统,并从中获取信息的标准接口;udev提供/dev设备节点文件的动态创建和删除策略。udev程序负责namedev和libsysfs库交互的任务,当/sbin/hotplug程序被内核调用时,udev将运行。
  udev有很多以.rules为后缀的规则文件,以行为单位,除去以“#”开头的代表注释的行外,每一行代表一个规则。每个规则分成一个或多个匹配和赋值部分。匹配部分用匹配专用的关键字来表示,相应的赋值部分用专用的关键字来表示。

udev的工作原理
  设备节点的创建,是通过sysfs接口分析dev规则文件取得设备节点号。udevd通过netlink socket通讯机制在内核和用户空间之间传递信息,来得知内核里模块的变化情况,通过hotplug机制得知设备的插入移除情况。
  内核调用kobject_uevent函数发送netlink message给用户空间,这部分工作通常无需驱动去自己处理,在统一设备模型里面,在子系统这一层面,已将这部分代码处理好了,包括在设备对应的特定的Kobject创建和移除的时候都会发送相应add和remove消息,当然前提是您在内核中配置了hotplug的支持。
  Netlink socket作为一种内核和用户空间的通信方式,不仅用在hotplug机制中,同样还应用在其他很多真正和网络相关的内核子系统中。Udevd通过标准的socket机制,创建socket连接来获取内核广播的uevent事件并解析这些uevent事件。

应用
  要使用udev自定义设备名,就需要获取到设备的相关且唯一的信息,用于书写udev规则:
在rhel5,可以使用udevinfo -a -p $(udevinfo -q path -n /dev/sdb)来获取设备信息,在rhel6,使用udevadm info -a -n /dev/sdb来获取设备信息。
  针对iscsi设备,使用udevadm info -a -n /dev/sdb可能获取不到类似序列号之类的信息,可以使用udevadm info -q all -n /dev/sdb试试,但是通常我们会这么去使用,在rhel5, scsi_id -g -s $(udevinfo -q path -n /dev/sdc)获取信息,在rhel6,scsi_id --whitelisted --replace-whitespace /dev/sdc获取信息,用作书写udev规则。

下面看看例子:
例一:
如我们rhel6系统插入了一个u盘,自动识别为/dev/sdb,而我们希望将其固定为/dev/MyUsbDisk。

则我们可以使用udevadm info -a -n /dev/sdb获取到ATTRS{serial}=="AC681DEB"信息,书写如下规则即可。
KERNEL=="sd*", SUBSYSTEMS=="usb",ATTRS{serial}=="AC681DEB",NAME="MyUsbDisk"


例二:
rhel6下,已经挂载一个iscsi target端导出的存储,没写udev规则之前,自动识别为sdc,我们想将这个iscsi存储的节点固定为叫做Idisk,如果做了分区,则sdc1固定为Idisk1。
我们书写一个udev规则
KERNEL=="sd*", SUBSYSTEM=="block", PROGRAM="/sbin/scsi_id --whitelisted --replace-whitespace /dev/$name", RESULT=="1IET_00010001",NAME="Idisk%n"
内核检测信息是sd*,子系统信息为块设备,执行程序scsi_id --whitelisted --replace-whitespace /dev/$name,结果为1IET_00010001,则重命名节点为Idisk%n,   $name为内核检测出信息是sdb则$name=sdb,%n为内核检测信息为sdb,则%n为空,检测信息为sdb1,则%n为1,这么写的作用在于挂载iscsi导出的一个lun后,我们对这个lun进行分区的情况,如果一个lun为一个分区,则无需写上%n。

扩展:
  用如下规则之一也可实现上条规则的效果,只是上条规则在iscsi设备login时能触发,如下两条规则必须由udev触发,如手动start_udev。所以通常应使用上述规则较好。
KERNEL=="sd*", SUBSYSTEM=="block", PROGRAM="/sbin/udevadm info -q all -n /dev/$name", RESULT=="*beaf11*", NAME="Idisk%n"
KERNEL=="sd*", SUBSYSTEM=="block", PROGRAM="/sbin/udevadm info -q all -p %p", RESULT=="*beaf11*", NAME="Idisk%n"

关于udev的调试:
udevadm trigger是udev触发器,执行此指令也是触发rules规则,执行结果同start_udev。
udevadm test /block/sdc可以看到udev规则执行流程,显示出来的信息较多,需要从中提取需要的信息,达到调试的目的。

<think>我们要解决两个问题:1.如何通过udev规则为串口设备分配固定的设备名称2.如何在C程序中动态读取该名称(即从用户输入获取,或者从某个配置文件读取)首先,我们解决第一个问题:使用udev规则固定串口设备名称。###步骤1:创建udev规则在Linux系统中,udev规则可以用来管理设备的命名。我们可以通过设备的属性(如供应商ID、产品ID、序列号等)来为设备分配一个固定名称。假设我们有一个USB转串口设备,通常被系统识别为`/dev/ttyACM0`或`/dev/ttyUSB0`等,我们希望给它一个固定名称,比如`/dev/my_custom_serial`。####操作步骤:1.首先,连接设备,使用`lsusb`命令找到设备的供应商ID和产品ID。例如:```$lsusb...Bus001Device004:ID0403:6001FutureTechnologyDevicesInternational,LtdFT232Serial(UART)IC...```这里,ID后面的`0403:6001`就是供应商ID和产品ID。2.使用`udevadminfo`命令获取更多设备信息(需要知道设备当前路径,假设为`/dev/ttyUSB0`):```$udevadminfo-a-p$(udevadminfo-qpath-n/dev/ttyUSB0)```在输出中,我们可以找到一些属性,比如`ATTRS{idVendor}`,`ATTRS{idProduct}`,`ATTRS{serial}`等。3.在`/etc/udev/rules.d/`目录下创建一个新的规则文件,例如`99-usb-serial.rules`:```$sudonano/etc/udev/rules.d/99-usb-serial.rules```4.在文件中添加规则,例如:```SUBSYSTEM=="tty",ATTRS{idVendor}=="0403",ATTRS{idProduct}=="6001",ATTRS{serial}=="A101D7VD",SYMLINK+="my_custom_serial"```这里:-`SUBSYSTEM=="tty"`表示设备属于tty子系统。-`ATTRS{idVendor}=="0403"`和`ATTRS{idProduct}=="6001"`分别对应供应商ID和产品ID。-`ATTRS{serial}=="A101D7VD"`是设备的序列号(可选,但如果有多个相同型号的设备,用序列号可以区分)。-`SYMLINK+="my_custom_serial"`表示创建一个符号链接`/dev/my_custom_serial`指向实际的设备文件(比如`/dev/ttyUSB0`)。注意:如果没有序列号,可以省略,但这样如果有多个相同厂商和产品的设备插入时,规则可能会同时匹配多个设备,导致不可预知的行为。5.重新加载udev规则并触发:```$sudoudevadmcontrol--reload-rules$sudoudevadmtrigger```6.重新插拔设备,然后检查`/dev/my_custom_serial`是否存在:```$ls-l/dev/my_custom_serial```这样,无论设备被系统分配为`ttyUSB0`还是`ttyACM0`,我们都可以通过`/dev/my_custom_serial`访问。###步骤2:在C程序中动态读取设备名称在程序中,我们可以让用户输入这个固定的设备名称(如`my_custom_serial`),或者我们也可以直接在程序中使用这个固定名称。但是注意,由于我们创建的是符号链接,所以在程序中我们可以直接使用`/dev/my_custom_serial`。但是,为了灵活性,我们可以让程序从命令行参数读取设备路径。####修改原始程序:原始程序中使用宏定义`MODEMDEVICE`固定为`"/dev/ttyACM13"`,现在我们要修改为从命令行参数获取。修改后的主函数如下(简化示例):```cintmain(intargc,char*argv[]){intfd,res;structtermiosoldtio,newtio;char*command="smartconfig1\r\n";//检查命令行参数if(argc<2){fprintf(stderr,"Usage:%s<serial_port>\n",argv[0]);exit(EXIT_FAILURE);}constchar*device=argv[1];//从命令行参数获取设备名fd=open(device,O_RDWR|O_NOCTTY|O_NDELAY);if(fd<0){perror(device);exit(EXIT_FAILURE);}//...后续代码不变...return0;}```这样,在运行程序时,我们只需要传递固定好的设备名称即可:```bash./program/dev/my_custom_serial```或者,如果我们不想每次运行都输入设备名,也可以将设备名写入配置文件,程序从配置文件读取。###示例:从配置文件读取我们可以创建一个简单的配置文件(如`config.ini`),内容为:```SERIAL_PORT=/dev/my_custom_serial```然后在程序中读取这个文件,解析出设备名。这里我们可以使用简单的文件读取和字符串处理,或者使用现成的库(如`iniparser`)。但考虑到简单性,这里采用命令行参数的方式更直接。###总结1.通过udev规则为串口设备创建固定名称的符号链接。2.修改C程序,从命令行参数获取设备路径(或从配置文件读取)。这样,即使系统分配的原始设备名发生变化(比如不同的USB端口插入后分配的编号不同),我们仍可以通过固定名称访问设备,而程序只需要使用这个固定名称即可。###相关问题1.如何调试udev规则?2.如果多个相同型号的USB串口设备同时插入,如何为每个设备分别指定不同的固定名称?3.在C程序中如何优雅地处理串口连接失败的情况?</think> ### 如何通过udev规则固定串口设备名称并在C程序中动态读取 以下是完整的实现方案,分为两个核心部分: #### 第一部分:创建udev规则固定设备名 1. **获取设备识别信息** ```bash udevadm info -a -n /dev/ttyACM0 | grep -E '({idVendor}|{idProduct}|{serial})' # 输出示例: # ATTRS{idVendor}=="0403" # ATTRS{idProduct}=="6010" # ATTRS{serial}=="A101D7VD" ``` 2. **创建udev规则文件** ```bash sudo nano /etc/udev/rules.d/99-usb-serial.rules ``` 添加规则(替换为你的实际参数): ```bash SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", ATTRS{serial}=="A101D7VD", SYMLINK+="my_custom_serial" ``` 3. **应用规则** ```bash sudo udevadm control --reload-rules sudo udevadm trigger ``` 4. **验证结果** ```bash ls -l /dev/my_custom_serial # 输出示例:/dev/my_custom_serial -> ttyACM0 ``` #### 第二部分:修改C程序动态读取设备名 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <termios.h> #include <time.h> #define BAUDRATE B9600 #define FALSE 0 #define TRUE 1 volatile int STOP = FALSE; int wait_for_ok(int fd) { /* 原有实现保持不变 */ } void listen_for_data(int fd) { /* 原有实现保持不变 */ } int main(int argc, char *argv[]) { // 修改1:添加命令行参数处理 int fd, res; struct termios oldtio, newtio; char *command = "smartconfig 1\r\n"; // 修改2:命令行参数校验 if(argc != 2) { fprintf(stderr, "用法: %s <串口设备路径>\n", argv[0]); fprintf(stderr, "示例: %s /dev/ttyACM0\n", argv[0]); fprintf(stderr, "或使用固定名称: %s /dev/my_custom_serial\n", argv[0]); exit(EXIT_FAILURE); } // 修改3:使用动态设备路径 char *device_path = argv[1]; printf("正在打开串口: %s\n", device_path); fd = open(device_path, O_RDWR | O_NOCTTY | O_NDELAY); if (fd < 0) { perror(device_path); exit(EXIT_FAILURE); } /* 后续原有代码保持不变... */ tcgetattr(fd, &oldtio); /* ... */ } ``` #### 使用示例 1. **编译程序** ```bash gcc serial_program.c -o serial_program ``` 2. **使用固定设备名运行** ```bash ./serial_program /dev/my_custom_serial ``` 3. **使用动态设备名运行** ```bash ./serial_program /dev/ttyACM0 ``` ### 关键原理说明 1. **udev规则工作原理** - 当检测到符合`ATTRS`标准的设备时,创建`SYMLINK`符号链接 - 符号链接名称在规则中定义(示例:`my_custom_serial`) - 匹配条件基于设备的唯一标识:$ \text{idVendor} \times \text{idProduct} \times \text{serial} $ 2. **程序动态路径处理** - 通过`main(int argc, char *argv[])`获取命令行参数 - `argv[1]`存储用户输入的设备路径 - 使用前校验参数数量:$ \text{argc} == 2 $ ### 相关问题 1. 当多个相同型号的串口设备同时连接时,如何为每个设备分别创建唯一标识的udev规则?[^1] 2. 如何让C程序在udev设备插入时自动启动并连接串口?[^2] 3. 在Linux系统中,$ \text{/dev/ttyACM} $ 和 $ \text{/dev/ttyUSB} $ 设备节点有何区别?[^3] 4. 当使用固定设备名时,如何确保程序在设备未插入时优雅地处理错误?[^4] [^1]: 基于 udev 的设备序列号匹配机制 [^2]: 结合 udev 规则与 systemd 服务 [^3]: Linux 内核的 USB 串行设备驱动架构 [^4]: POSIX 文件操作错误处理规范
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值