库pyudev是libudev的python封装,libudev提拱了对本地设备的列举与查询API。
1.安装
pip install pyudev
2. 使用
2.1 开始
导入pyudev,验证库版本。
In [1]: import pyudev In [2]: print pyudev.__version__ 0.21.0 In [3]: print pyudev.udev_version() 229
2.2 列举(Enumerate)设备
- 创建上下文(Context)对象。Context是pyudev的中心对象,在pyudev程序中几乎都会需要它。
In [5]: context = pyudev.Context()
- 列举全部设备。
In [6]: for device in context.list_devices(): ...: print device ...: Device(u'/sys/devices/LNXSYSTM:00') ...... Device(u'/sys/devices/pci0000:00/0000:00:1f.2/ata1/ata_port/ata1') Device(u'/sys/devices/pci0000:00/0000:00:1f.2/ata1/host0') Device(u'/sys/devices/pci0000:00/0000:00:1f.2/ata1/host0/scsi_host/host0') Device(u'/sys/devices/pci0000:00/0000:00:1f.2/ata1/link1/ata_link/link1') Device(u'/sys/devices/pci0000:00/0000:00:1f.2/ata1/link1/dev1.0/ata_device/dev1.0')
- 也可给带参数使用list_devices(),从而对设备进行过滤选择。设置过滤条件需要了解linux系统是如何对设备进行分类的。有2种过滤方法:一是使用关键参数(keyword arguments),二是使用自定义过滤器函数对象(matcher_*。
- 使用keyword arguments。
In [8]: for device in context.list_devices(subsystem='block', DEVTYPE='partition'): ...: print device ...: Device(u'/sys/devices/pci0000:00/0000:00:1f.2/ata5/host4/target4:0:0/4:0:0:0/block/sda/sda1') Device(u'/sys/devices/pci0000:00/0000:00:1f.2/ata5/host4/target4:0:0/4:0:0:0/block/sda/sda2') Device(u'/sys/devices/pci0000:00/0000:00:1f.2/ata5/host4/target4:0:0/4:0:0:0/block/sda/sda3') Device(u'/sys/devices/pci0000:00/0000:00:1f.2/ata5/host4/target4:0:0/4:0:0:0/block/sda/sda4') ......
-
- 使用matcher_*函数对象。例子略...
2.3 直接访问设备
可通过设备路径\子系统+设备名\设备文件等三种方法来直接创建设备对象。
In [10]: pyudev.Devices.from_path(context, '/sys/block/sda') Out[10]: Device(u'/sys/devices/pci0000:00/0000:00:1f.2/ata5/host4/target4:0:0/4:0:0:0/block/sda') In [11]: pyudev.Devices.from_name(context, 'block', 'sda') Out[11]: Device(u'/sys/devices/pci0000:00/0000:00:1f.2/ata5/host4/target4:0:0/4:0:0:0/block/sda') In [12]: pyudev.Devices.from_device_file(context, '/dev/sda') Out[12]: Device(u'/sys/devices/pci0000:00/0000:00:1f.2/ata5/host4/target4:0:0/4:0:0:0/block/sda')
这三种方法所获取的对象是同一个设备。
In [16]: pyudev.Devices.from_device_file(context, '/dev/sda') == pyudev.Devices.from_name(context, 'block', 'sda') Out[16]: True In [17]: pyudev.Devices.from_name(context, 'block', 'sda') == pyudev.Devices.from_path(context, '/sys/block/sda') Out[17]: True
2.4 查询设备属性
由列举设备或直接访问所返回的设备对象(Device)都对应于udev数据库中的一个设备,每个设备有“属性”,以描述设备的性能、特点,与其它设备的关系,...等等。查询设备的属性有三种方法:
- 像普通python对象一样获取属性。如下面的代码打印出所有块设备(block)的device_node和device_type属性。
In [19]: for device in context.list_devices(subsystem='block'): ....: print '{0}({1})'.format(device.device_node, device.device_type) ....: /dev/sda(disk) /dev/sda2(partition) /dev/sda3(partition) /dev/sda4(partition) /dev/sda5(partition) /dev/sda6(partition) /dev/sda7(partition) /dev/sda8(partition) /dev/sda9(partition) /dev/loop0(disk) /dev/loop1(disk) /dev/loop2(disk) ......
- 以类似字典的方法获取属性。
In [20]: for device in context.list_devices(subsystem='block'): ....: print '({0}({1})'.format(device['DEVNAME'], device['DEVTYPE']) ....: (/dev/sda(disk) (/dev/sda1(partition) (/dev/sda2(partition) (/dev/sda3(partition) (/dev/sda4(partition) (/dev/sda5(partition) (/dev/sda6(partition) (/dev/sda7(partition) (/dev/sda8(partition) (/dev/sda9(partition) (/dev/loop0(disk) (/dev/loop1(disk) (/dev/loop2(disk) ......
- 通过接口函数get()访问设备属性
In [24]: for device in context.list_devices(subsystem='block', DEVTYPE='partition'): ....: print '{0}({1})'.format(device.device_node, device.get('ID_FS_TYPE')) ....: /dev/sda1(ntfs) /dev/sda2(vfat) /dev/sda3(vfat) /dev/sda4(None) /dev/sda5(ntfs) /dev/sda6(ntfs) /dev/sda7(ntfs) /dev/sda8(None) /dev/sda9(swap) ......
上面三种方法中,推荐使用接口函数来返回设备属性。当试图返回一个并不存在的属性时,get函数可返回指定的缺省值(通过参数设置),也可抛出KeyError异常。
此外,可用Device.attributes来查看设备有哪些属性,尽管大多数时间不需要这么做。
2.5 检索设备层级(hierarchy)
udev中设备是具有层次属性的,即设备之间可能存在父-子关系,如分区设备(partition)就是某磁盘设备的子设备。用Device对象的parent属性可返回其父设备对象。
In [25]: for device in context.list_devices(subsystem='block', DEVTYPE='partition'): ....: print '{0} is located on {1}'.format(device.device_node, device.parent.device_node) ....: /dev/sda1 is located on /dev/sda /dev/sda2 is located on /dev/sda /dev/sda3 is located on /dev/sda /dev/sda4 is located on /dev/sda /dev/sda5 is located on /dev/sda /dev/sda6 is located on /dev/sda /dev/sda7 is located on /dev/sda /dev/sda8 is located on /dev/sda /dev/sda9 is located on /dev/sda
除了上面“硬”访问之外,更常用的是用搜索的方法返回父设备,即使用find_parent函数。
In [26]: for device in context.list_devices(subsystem='block', DEVTYPE='partition'): ....: print '{0} is located on {1}'.format(device.device_node, device.find_parent('block').device_node) ....: /dev/sda1 is located on /dev/sda /dev/sda2 is located on /dev/sda /dev/sda3 is located on /dev/sda /dev/sda4 is located on /dev/sda /dev/sda5 is located on /dev/sda /dev/sda6 is located on /dev/sda /dev/sda7 is located on /dev/sda /dev/sda8 is located on /dev/sda /dev/sda9 is located on /dev/sda
find_parent使用更加灵活,且能够简化多层设备间的追溯,可直接返回祖父级、曾祖父级...的设备而不需要逐级搜索。如从分区设备直接返回其所在磁盘的IDE控制器或SCSI控制器的PCI插槽的名字。
In [27]: for device in context.list_devices(subsystem='block', DEVTYPE='partition'): ....: print '{0} attatched to PCI slot {1}'.format(device.device_node, device.find_parent('pci')['PCI_SLOT_NAME']) ....: /dev/sda1 attatched to PCI slot 0000:00:1f.2 /dev/sda2 attatched to PCI slot 0000:00:1f.2 /dev/sda3 attatched to PCI slot 0000:00:1f.2 /dev/sda4 attatched to PCI slot 0000:00:1f.2 /dev/sda5 attatched to PCI slot 0000:00:1f.2 /dev/sda6 attatched to PCI slot 0000:00:1f.2 /dev/sda7 attatched to PCI slot 0000:00:1f.2 /dev/sda8 attatched to PCI slot 0000:00:1f.2 /dev/sda9 attatched to PCI slot 0000:00:1f.2
上面的例子直接从partition对象返回其相关的pci对象,打印其'PCI_SLOT_NAME‘属性。
2.6 设备监控(Monitor)
当添加、移除(如插、拔USB设备),或者设备属性变化(如电池充电等级变化)时,Linux核心将发送设备事件以便应用程序进行处理。pyudev程序监控系统设备事件的步骤是:创建监控器(Monitor)对象,设置监控器的过滤器(即设置需要处理的哪些事件),最后启用监控器。启用监控器有二种形式:同步监控、异步监控。
- 同步监控
应用程序收到设备事件后获取系统控制权并进行处理,系统核心挂起等待应用程序处理完毕,应用程序完成处理后返回并交回系统控制权。这种方式效率低,仅适合处理过程简单的情况。
In [28]: monitor = pyudev.Monitor.from_netlink(context) In [29]: monitor.filter_by('block') In [30]: for device in iter(monitor.poll, None): ....: if 'ID_FS_TYPE' in device: ....: print '{0} partition {1}'.format(device.action, device.get('ID_FS_LABLE')) ....: add partition None add partition None
停止同步监控需要从处理过程中break循环或抛出异常(raise exeption)。
此外,为Monitor设置过滤器可以用filter_by()或filter_by_tag()函数。
- 异步监控
应用程序创建监控器、事件处理程序(回调函数),将其插入系统核心的事件响应链,即安装观察器(Observer)。当期望的事件发生时,系统根据事件响应链启动一个新的线程调用回调函数,系统核心本身并不挂起。需要停止监控时,应从处理线程外部调用Obsever的stop函数从系统的事件响应链中观察器,如果要在事件处理程序中停止观察器,应调用send_stop()函数通知Observer进行间接停止。
In [3]: monitor = pyudev.Monitor.from_netlink(context) In [4]: monitor.filter_by('block') In [5]: def log_event(action, device): ...: if 'ID_FS_TYPE' in device: ...: with open('filesystem.log', 'a+') as stream: ...: stream.write('{0} - {1}/n'.format(action, device.get('ID_FS_LABLE'))) ...: In [6]: observer = pyudev.MonitorObserver(monitor, log_event) In [7]: observer.start() In [8]: observer.stop()
3. 与GUI库的集成
pyudev库支持多种GUI库,模块对应关系如下:
pyudev.pyqt5<--------->Qt5
pyudev.pyqt4<--------->Qt4
pyudev.glib<------------>PyGtk2
pyudev.wx<------------->wxWidgets / wxPython