udev的实现原理

原文:《《转载时请注明出处和作者联系方式:http://blog.csdn.net/absurd作者联系方式:李先静<xianjimli
at hotmail dot com>更新时间:2007-4-29》》

相对于linux来说,udev还是一个新事物。然而,尽管它03年才出现,尽管它很低调(J),但它无疑已经成为linux下不可或缺的组件了。udev是什么?它是如何实现的?最近研究Linux设备管理时,花了一些时间去研究udev的实现。

udev是什么?u 是指user space,dev是指device,udev是用户空间的设备驱动程序吗?最初我也这样认为,调试内核空间的程序要比调试用户空间的程序复杂得多,内核空间的程序的BUG所引起的后果也严重得多,device driver是内核空间中所占比较最大的代码,如果把这些device
driver中硬件无关的代码,从内核空间移动到用户空间,自然是一个不错的想法。

但我的想法并不正确,udev的文档是这样说的:

1.         dynamic replacement for /dev。作为devfs的替代者,传统的devfs不能动态分配major(直译:主要的)和minor(直译:次要的)的值,而major和minor非常有限,很快就会用完了。udev能够像DHCP动态分配IP地址一样去动态分配major和minor。

 

2.         device naming。提供设备命名持久化的机制。传统设备命名方式不具直观性,像/dev/hda1这样的名字肯定没有boot_disk这样的名字直观。udev能够像DNS解析域名一样去给设备指定一个有意义的名称。

 

3.         API to access info about current system devices 。提供了一组易用的API去操作sysfs,避免重复实现同样的代码,这没有什么好说的。

 

我们知道,用户空间的程序与设备通信的方法,主要有以下几种方式:

1.         通过ioperm获取操作IO端口的权限,然后用inb/inw/ inl/ outb/outw/outl等函数,避开设备驱动程序,直接去操作IO端口。(没有用过)

2.         用ioctl函数去操作/dev目录下对应的设备,这是设备驱动程序提供的接口。像键盘、鼠标和触摸屏等输入设备一般都是这样做的。

3.         用write/read/mmap去操作/dev目录下对应的设备,这也是设备驱动程序提供的接口。像framebuffer等都是这样做的。

 

上面的方法在大多数情况下,都可以正常工作,但是对于热插拨(hotplug)的设备,比如像U盘,就有点困难了,因为你不知道:什么时候设备插上了,什么时候设备拔掉了。这就是所谓的hotplug问题了。

处理hotplug传统的方法是,在内核中执行一个称为hotplug的程序,相关参数通过环境变量传递过来,再由hotplug通知其它关注hotplug事件的应用程序。这样做不但效率低下,而且感觉也不那么优雅。

新的方法是采用NETLINK实现的,这是一种特殊类型的socket,专门用于内核空间与用户空间的异步通信。下面的这个简单的例子,可以监听来自内核hotplug的事件。


#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>

 

static int init_hotplug_sock(void)

{

    struct sockaddr_nl snl;

    const int buffersize =
16 * 1024 * 1024;

    int retval;

 

    memset(&snl, 0x00, sizeof(struct sockaddr_nl));

    snl.nl_family = AF_NETLINK;

    snl.nl_pid = getpid();

    snl.nl_groups = 1;

 

    int hotplug_sock = socket(PF_NETLINK, SOCK_DGRAM,
NETLINK_KOBJECT_UEVENT);

    if (hotplug_sock == -1) {

        printf("error getting socket: %s", strerror(errno));

        return -1;

    }

 

    /* set receive buffersize */

    setsockopt(hotplug_sock, SOL_SOCKET,
SO_RCVBUFFORCE, &buffersize, sizeof(buffersize));

 

    retval = bind(hotplug_sock,
(struct sockaddr *) &snl, sizeof(struct sockaddr_nl));

    if (retval < 0) {

        printf("bind failed: %s", strerror(errno));

        close(hotplug_sock);

        hotplug_sock = -1;

        return -1;

    }

 

    return hotplug_sock;

}

 

#define UEVENT_BUFFER_SIZE      2048

 

int main(int argc, char* argv[])

{

         int hotplug_sock       = init_hotplug_sock();

        

         while(1)

         {

                   char buf[UEVENT_BUFFER_SIZE*2]
= {0};

                   recv(hotplug_sock,
&buf, sizeof(buf), 0); 

                   printf("%s/n", buf);

         }

 

         return 0;

}

 

编译:

gcc -g hotplug.c -o hotplug_monitor

 

运行后插/拔U盘,可以看到:


add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1

add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/usbdev2.2_ep00

add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0

add@/class/scsi_host/host2

add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/usbdev2.2_ep81

add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/usbdev2.2_ep02

add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/usbdev2.2_ep83

add@/class/usb_device/usbdev2.2

add@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/host2/target2:0:0/2:0:0:0

add@/class/scsi_disk/2:0:0:0

add@/block/sda

add@/block/sda/sda1

add@/class/scsi_device/2:0:0:0

add@/class/scsi_generic/sg0

remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/usbdev2.2_ep81

remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/usbdev2.2_ep02

remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/usbdev2.2_ep83

remove@/class/scsi_generic/sg0

remove@/class/scsi_device/2:0:0:0

remove@/class/scsi_disk/2:0:0:0

remove@/block/sda/sda1

remove@/block/sda

remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0/host2/target2:0:0/2:0:0:0

remove@/class/scsi_host/host2

remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/2-1:1.0

remove@/class/usb_device/usbdev2.2

remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1/usbdev2.2_ep00

remove@/devices/pci0000:00/0000:00:1d.1/usb2/2-1

 

udev的主体部分在udevd.c文件中,它主要监控来自4个文件描述符的事件/消息,并做出处理:

1.         来自客户端的控制消息。这通常由udevcontrol命令通过地址为/org/kernel/udev/udevd的本地socket,向udevd发送的控制消息。其中消息类型有:

l         UDEVD_CTRL_STOP_EXEC_QUEUE 停止处理消息队列。

l         UDEVD_CTRL_START_EXEC_QUEUE 开始处理消息队列。

l         UDEVD_CTRL_SET_LOG_LEVEL 设置LOG的级别。

l         UDEVD_CTRL_SET_MAX_CHILDS 设置最大子进程数限制。好像没有用。

l         UDEVD_CTRL_SET_MAX_CHILDS_RUNNING 设置最大运行子进程数限制(遍历proc目录下所有进程,根据session的值判断)。

l         UDEVD_CTRL_RELOAD_RULES 重新加载配置文件。

2.         来自内核的hotplug事件。如果有事件来源于hotplug,它读取该事件,创建一个udevd_uevent_msg对象,记录当前的消息序列号,设置消息的状态为EVENT_QUEUED,然后并放入running_list和exec_list两个队列中,稍后再进行处理。

3.         来自signal handler中的事件。signal handler是异步执行的,即使有signal产生,主进程的select并不会唤醒,为了唤醒主进程的select,它建立了一个管道,在signal handler中,向该管道写入长度为1个子节的数据,这样就可以唤醒主进程的select了。

4.         来自配置文件变化的事件。udev通过文件系统inotify功能,监控其配置文件目录/etc/udev/rules.d,一旦该目录中文件有变化,它就重新加载配置文件。

 

其中最主要的事件,当然是来自内核的hotplug事件,如何处理这些事件是udev的关键。udev本身并不知道如何处理这些事件,也没有必要知道,因为它只实现机制,而不实现策略。事件的处理是由配置文件决定的,这些配置文件即所谓的rule。

 

关于rule的编写方法可以参考《writing_udev_rules》,udev_rules.c实现了对规则的解析。

 

在规则中,可以让外部应用程序处理某个事件,这有两种方式,一种是直接执行命令,通常是让modprobe去加载驱动程序,或者让mount去加载分区。另外一种是通过本地socket发送消息给某个应用程序。

 

在udevd.c:udev_event_process函数中,我们可以看到,如果RUN参数以”socket:”开头则认为是发到socket,否则认为是执行指定的程序。

 

下面的规则是执行指定程序:

60-pcmcia.rules:                RUN+="/sbin/modprobe pcmcia"

 

下面的规则是通过socket发送消息:

90-hal.rules:RUN+="socket:/org/freedesktop/hal/udev_event"

 

hal正是我们下一步要关心的,接下来我会分析HAL的实现原理。

 

~~end~~

udev 代替了hotplug 和 hwdetect两个工具.

“udev是 Linux 内核的设备管理器。总的来说,它取代了 devfs 和 hotplug,负责管理 /dev 中的设备节点,。这意味着 udev 要管理/dev目录以及用户空间中所有的硬件添加删除操作。”
摘自: Wikipedia:Udev

与传统的顺序加载不同,udev 可以并行加载内核模块,具有潜在的性能优势。异步加载模块的方式也有一个天生的缺点:无法保证每次加载模块的顺序,如果机器具有多个块设备,那么它们的设备节点可能随机变化。例如如果有两个硬盘,/dev/sda 可能会随机变成/dev/sdb。后面有更详细的信息。

Contents

 [hide] 

  • 1 udev规则
  • 2 使用技巧
  • 3 问题解决
    • 3.1 屏蔽模块
    • 3.2 已知的硬件问题
      • 3.2.1 BusLogic
      • 3.2.2 一些移动设备不可移除
    • 3.3 自动加载问题
      • 3.3.1 声音问题和一些不能自动加载的模块
    • 3.4 多个同类型设备(网卡,声卡)每次启动的都不同
    • 3.5 自己编译内核造成的问题
      • 3.5.1 Udev无法启动
  • 4 Running HAL

udev规则

管理员编写的 udev 的规则保存在/etc/udev/rules.d/,其中的文件名要以.rules结尾。

各种软件包提供的规则文件位于 /lib/udev/rules.d/。如果两个目录中有相同名称的文件,会使用 /etc 中的文件。

要想学习写udev规则,请访问编写 udev 规则:http://www.reactivated.net/writing_udev_rules.html

要得到编写规则可以使用的全部属性,请使用下面的指令:

# udevadm info -a -p [device name]

用系统中的设备替换上面的[device name],比如/dev/sda 或者/dev/ttyUSB0

udev 会自动检测规则文件的修改,所以更改不需要重启 udev 就会立即生效。但是规则不会被已有设备重新触发,所以热插拔设备(如USB设备等)可能需要重新连接才会使新规则生效。

使用技巧

问题解决

屏蔽模块

极个别情况下,udev 也会犯错或加载错误的模块。为了防止错误的发生,你可以使用模块禁用列表。只要模块加入该列表,无论是启动时,或者是运行时(如usb硬盘等)udev都不会加载这些模块。参见blacklisting:https://wiki.archlinux.org/index.php/Blacklisting#Blacklisting.

已知的硬件问题

BusLogic

BusLogic 设备被损坏而且导致启动时死机。这是一个内核的Bug目前还没有修正。

一些移动设备不可移除

创建自定义 udev 规则,设置 UDISKS_SYSTEM_INTERNAL=0。参见 udisks 手册。

自动加载问题

声音问题和一些不能自动加载的模块

一些用户发现 /etc/modprobe.d/sound.conf 中的遗留配置会引起这些问题,请清理配置并重试。

注意: 从 udev>=171 开始 OSS 模拟模块(snd_seq_oss, snd_pcm_oss,
snd_mixer_oss
) 默认不会自动装载。

多个同类型设备(网卡,声卡)每次启动的都不同

因为udev同时加载所有模块,所以一些设备可能初始化顺序不同。例如同时有两个网卡时,它们总是在eth0eth1之间变来变去。

常用的解决办法是在您的rc.conf文件中通过修改MODULES队列来指明顺序。这个队列里的模块将在udev自动加载之前由系统加载,因此您可以控制模块在启动时加载顺序。

# 在e100之前加载8139too
MODULES=(8139too e100)

另一个解决网卡的方法是使用udev-sanctified方法为每个网卡静态命名。创建文件/etc/udev/rules.d/10-network.rules然后将不同的网卡通过MAC地址绑定到不同的名字上:

SUBSYSTEM=="net", ATTR{address}=="aa:bb:cc:dd:ee:ff", NAME="lan0"
SUBSYSTEM=="net", ATTR{address}=="ff:ee:dd:cc:bb:aa", NAME="wlan0"

同时,您需要注意以下内容:

  • 您可以通过下面的命令获得网卡的MAC地址:: udevadm info -a -p /sys/class/net/<你的网卡>
  • 注意在udev规则文件中使用小写的16进制MAC地址,因为udev无法识别大写的MAC地址。
  • 一些用户在使用旧的命名方式时出现问题,例如: eth0, eth1, 等等. 如果出现这个问题,试试使用 "lan"或者"wlan"之类的名字.

注意不要忘记修改其它使用ethX命名的配置文件。

自己编译内核造成的问题

Udev无法启动

请确定您的内核版本大于或等于2.6.32。较早的内核没有udev自动装载所需要的 uevent 功能。

Running HAL

Some programs still require HAL (like Flash DRM content). Hal can be installed from hal and hal-info.

Using Systemd: one can start and stop the hal service using the following systemd commands:

Start HAL: # systemctl start hal.service

Stop HAL: # systemctl stop hal.service

Alternatively, one can use the following script:

#!/bin/bash

## written by Mark Lee <bluerider>
## using information from <https://wiki.archlinux.org/index.php/Chromium#Google_Play_.26_Flash>

## Start and stop Hal service on command for Google Play Movie service

function main () {  ## run the main insertion function
     clear-cache;  ## remove adobe cache
     start-hal;  ## start the hal daemon
     read -p "Press 'enter' to stop hal";  ## pause the command line with a read line
     stop-hal;  ## stop the hal daemon
}

function clear-cache () {  ## remove adobe cache
     cd ~/.adobe/Flash_Player;  ## go to Flash player user directory
     rm -rf NativeCache AssetCache APSPrivateData2;  ## remove cache
}

function start-hal () {  ## start the hal daemon
     sudo systemctl start hal.service && ( ## systemd : start hal daemon
          echo "Started hal service..."
) || (
          echo "Failed to start hal service!")
}

function stop-hal () {  ## stop the hal daemon
sudo systemctl stop hal.service && (  ## systemd : stop hal daemon
          echo "Stopped hal service..."
     ) || (
          echo "Failed to stop hal service!"
     )
}

main;  ## run the main insertion function
时间: 2024-10-22 17:50:08

udev的实现原理的相关文章

嵌入式 使用udev高效、动态地管理Linux 设备文件

本文以通俗的方法阐述 udev 及相关术语的概念.udev 的配置文件和规则文件,然后以 Red Hat Enterprise Server 为平台演示一些管理设备文件和查询设备信息的实例.本文会使那些需要高效地.方便地管理 Linux 设备的用户受益匪浅,这些用户包括 Linux 最终用户.设备驱动开发人员.设备测试人员和系统管理员等等.   概述: Linux 用户常常会很难鉴别同一类型的设备名,比如 eth0, eth1, sda, sdb 等等.通过观察这些设备的内核设备名称,用户通常能

关于实现udev/mdev自动挂载与卸载

在网上有很多关于讲mdev的自动挂载基本上都是一个版本,经过测试自动挂载确实可行,但是关于自动卸载mdev似乎不能很好的支持,经过修改已经可以做到与udev的效果相似.不能在挂载的目录中进行热插拔,否则会出现问题,不过此问题在下次插入U盘时不会造成影响,可能对U盘有损坏. 本文介绍了mdev与udev两种方法来实现自动挂载,读者可根据需要任选其一即可.         首先介绍一下mdev与udev之间的关系:         mdev是busybox中的一个udev管理程序的一个精简版,他也可

linux文件系统初始化过程(6)---执行init程序

一.目的     内核加载完initrd文件后,为挂载磁盘文件系统做好了必要的准备工作,包括挂载了sysfs.proc文件系统,加载了磁盘驱动程序驱动程序等.接下来,内核跳转到用户空间的init程序,由init完成创建磁盘设备文件.加载磁盘文件系统.从rootfs切换到磁盘根文件系统等工作.     由于在不同的linux发行版中,init的实现方式差异很大,不能将所有的发行版都分析一遍,因此本文选取ubuntu12.04发行版来描述如何从rootfs切换到磁盘根文件系统.   二.创建磁盘设备

Linux inotify功能及实现原理

1. inotify主要功能 它是一个内核用于通知用户空间程序文件系统变化的机制. 众所周知,Linux 桌面系统与 MAC 或 Windows 相比有许多不如人意的地方,为了改善这种状况,开源社区提出用户态需要内核提供一些机制,以便用户态能够及时地得知内核或底层硬件设备发生了什么,从而能够更好地管理设备,给用户提供更好的服务,如 hotplug.udev 和 inotify 就是这种需求催生的.Hotplug 是一种内核向用户态应用通报关于热插拔设备一些事件发生的机制,桌面系统能够利用它对设备

init系统中Upstart简介及工作原理说明

本文简要介绍了这三种 init 系统的使用和原理,每个 Linux 系统管理员和系统软件开发者都应该了解它们,以便更好地管理系统和开发应用.本文是系列的第二部分,主要讲述 UpStart 的特点和使用.假如您使用的 Linux 发行版是 Ubuntu,很可能会发现在您的计算机上找不到/etc/inittab 文件了,这是因为 Ubuntu 使用了一种被称为 upstart 的新型 init 系统. 开发 Upstart 的缘由 大约在 2006 年或者更早的时候, Ubuntu 开发人员试图将

Linux 文件操作监控inotify功能及实现原理【转】

转自:http://blog.chinaunix.net/uid-26585427-id-5012973.html 1. inotify主要功能 它是一个内核用于通知用户空间程序文件系统变化的机制. 众所周知,Linux 桌面系统与 MAC 或 Windows 相比有许多不如人意的地方,为了改善这种状况,开源社区提出用户态需要内核提供一些机制,以便用户态能够及时地得知内核或底层硬件设备发生了什么,从而能够更好地管理设备,给用户提供更好的服务,如 hotplug.udev 和 inotify 就是

Linux inotify功能及实现原理【转】

转自:http://blog.csdn.net/myarrow/article/details/7096460 1. inotify主要功能 它是一个内核用于通知用户空间程序文件系统变化的机制. 众所周知,Linux 桌面系统与 MAC 或 Windows 相比有许多不如人意的地方,为了改善这种状况,开源社区提出用户态需要内核提供一些机制,以便用户态能够及时地得知内核或底层硬件设备发生了什么,从而能够更好地管理设备,给用户提供更好的服务,如 hotplug.udev 和 inotify 就是这种

路由器QOS功能原理和工作方式

设置路由器时,大多会用到路由器的安全机制,也就常说的QOS功能,QOS功能可以保护整个网络的安全,本篇带你了解其具体的原理和工作的方式. 一.QOS用来解决带宽解决网络延迟和阻塞等问题的一种技术,一般里面包含优先级别.弹性带宽管理等等,主要用来解决各种网络的攻击和病毒,保护网络的正常运行,它主要有以下几个方面的功能: 1.端口优先:可针对源端口.目的端口进行设置优先的级别,一般来说如果是玩游戏为主.那么我可以针对一些主流游戏的端口.优先这些游戏的带宽. 2.IP/网段优先:可针对源IP.目的IP

PhotoShop中正片负片叠底的原理介绍

关于正片叠底,正片,负片,通道,色相,色相环等等的相关理论一堆,大家可以从网上查到,原理就不讲了. 感觉单通道正片叠底效果应该属于填充色一类,但却与填充色又有很大的差异,与照片滤镜功能也有所差异,运用得当,最大的优点是在叠底后仍能保持比较好的照片通透度,而且简单易用,特别适合不太熟悉PS操作的朋友,此类方法运用广泛,配合起来使用比较方便,慢慢介绍吧. photoshop教程注:以下介绍的为RGB模式下的叠底,与CMYK模式下有所区别 方法一,单通道正片叠底 例一,叠出阳光色.提示:图片应尽量少漏