對這文章發表回應
發表限制: 非會員 可以發表
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 #include < string .h> #include < ctype .h> #include <sys/un.h> #include <sys/ioctl.h> #include <sys/ socket .h> #include <linux/types.h> #include < errno .h>
static int init_hotplug_sock ( void ) { struct sockaddr_nl snl ; int retval ;
memset (& snl , 0x00, sizeof ( struct sockaddr_nl)); 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 = if ( retval < 0) { printf ( "bind failed: %s" , strerror close ( hotplug_sock ); hotplug_sock = -1; return -1; }
}
#define UEVENT_BUFFER_SIZE 2048
int main ( int argc , char * argv []) { int hotplug_sock = init_hotplug_sock ();
while (1) { recv ( hotplug_sock , & buf , sizeof ( buf ), 0); }
return 0; } |
編譯:
gcc -g hotplug.c -o hotplug_monitor
運行後插 / 拔 U 盤,可以看到:
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的实现原理 - Linux mobile development - 博客频道 - CSDN.NET