過年前跑去書店打發無聊的時間,看到這個月又出了兩本跟linux device driver相關的書籍,隨手拿起來翻翻,發現其中有一篇寫到udev framework,裡面詳盡解釋device node在insert kernel module時如何自動建立,並且可隨著使用者更改規則而產生persistent node…等不同於devfs的變化
在 udev的官方網頁有篇不錯的conference paper,裡面有提到幾個重點,udev的出現為了解決目前在devfs上碰到的3個問題
1.udev能夠根據規則建立device node(這可以解決probing時,device node可能會依probing的順序不同而改變)
2.udev動態建立device node,不會像以前在/dev資料夾下擺一堆多而無用的device node
3.提供user更方便的API存取目前device的資訊,在kernel 2.6以上已提供sysfs這個device管理機制
udev運作的原理很簡單,它透過netlink得知目前kernel有那些module新增了,在收到kernel module新增的netlink訊息之後,它會先掃描user是否有指派device node rule,如果沒有,會自動根據module的major和minor number建立device node.有一點比較特殊的地方是,udev用inotify監聽rule變化的event,所以可以即時改變device node的狀態,udev的架構圖可參考如下

那我做個小實驗,看看udev到底是如何運作的,這個實驗分為兩個部份,一個是character kernel module,而另外一個就是udev的user space program(下載 udev-137.tar.gz)
character kernel module寫作的重點在於init時會建立class與create device
- charmodule_class = class_create ( THIS_MODULE , DEVICE_NAME ) ;
- if ( IS_ERR ( charmodule_class
)) - return - EFAULT ;
- device_create ( charmodule_class , NULL , MKDEV ( driver_major , 0 ) , DEVICE_NAME ) ;
character kernel moduel移除時會remove class和destroy device
device_destroy ( charmodule_class , MKDEV ( driver_major , 0 )) ; - class_destroy ( charmodule_class ) ;
我把udevd和udevadm移植到實驗平台,並且執行指令udevd –d,當我insert character kernel module時,udevd會自動幫我建立device node,整個實驗過程如下圖

character kernel module程式如下
- #include
< linux/module.h > - #include < linux/kernel.h >
- #include < linux/init.h >
- #include < linux/major.h >
- #include < linux/device.h >
- #include < linux/poll.h
> -
- #define dbg ( fmt , args ... ) printk ( " [%s]:%d => " fmt , __FUNCTION__ , __LINE__ ,## args )
- #define DBG () printk ( " [%s]:%d => \ n " , __FUNCTION__ , __LINE__ )
- #define DEVICE_NAME " charmodule "
- #define STRING_SIZE 256
- #define STRING_CHANGE_TIME 2 //seconds
-
- static int driver_major ;
- static int string_counter =
1 ; - static struct class * charmodule_class ;
-
- typedef struct _driver_private_data
- {
- wait_queue_head_t wait ;
- struct fasync_struct *
fasync ; - struct timer_list char_timer ;
- char my_string [ 256 ] ;
- int flag ;
-
- } driver_private_data ;
-
- driver_private_data
* gpd ; - void MyTimerFunction ( unsigned long data ) ;
-
- static ssize_t char_read ( struct file * file , char
__user * buffer , size_t count , loff_t * ppos ) - {
- int retval = 0 ;
- driver_private_data * dpd =
file -> private_data ; -
- DBG () ;
- if ( count < STRING_SIZE ) return - EINVAL ;
- while (( retval +
STRING_SIZE ) <= count ) - {
- if ( copy_to_user ( buffer , dpd -> my_string , STRING_SIZE ))
- return - EFAULT
; - retval += STRING_SIZE ;
- }
- dpd -> flag = 0 ;
- return retval ;
- }
-
- static ssize_t
char_write ( struct file * file , const char __user * buffer , size_t count , loff_t * ppos ) - {
return STRING_SIZE ; //just a demo, I dont implement this - }
-
- static int char_open ( struct inode * inode , struct file * file )
- {
-
- driver_private_data * dpd ;
-
- DBG () ;
- dpd = ( driver_private_data * ) file -> private_data ;
- if
( ! dpd ) - {
- dpd = ( driver_private_data * ) kmalloc ( sizeof ( driver_private_data ) , GFP_ATOMIC ) ;
- file ->
private_data = dpd ; - sprintf ( dpd -> my_string , " string_counter %d \ n " , string_counter ) ;
- dpd -> flag = 0 ;
- string_counter ++;
- init_waitqueue_head ( & dpd -> wait ) ;
- gpd = dpd ;
- init_timer ( & dpd -> char_timer ) ;
- dpd ->
char_timer . function = MyTimerFunction ; - dpd -> char_timer . expires = jiffies + STRING_CHANGE_TIME * HZ ;
- dpd -> char_timer . data = (
unsigned long ) gpd ; - add_timer ( & dpd -> char_timer ) ;
- }
- return 0 ;
- }
- static unsigned
int char_poll ( struct file * file , poll_table * wait ) - {
- driver_private_data * dpd = file -> private_data ;
- int
mask = 0 ; -
- DBG () ;
- poll_wait ( file , & dpd -> wait , wait ) ;
- if ( dpd ->
flag == 1 ) - mask |= POLLIN | POLLRDNORM ;
- return mask ;
- }
-
- static int char_release ( struct
inode * inode , struct file * file ) - {
- driver_private_data * dpd ;
-
- DBG () ;
- dpd = ( driver_private_data
* ) file -> private_data ; - del_timer ( & dpd -> char_timer ) ;
- return 0 ;
- }
-
-
-
-
- static const struct file_operations chardev_fops =
- {
- . owner = THIS_MODULE ,
- . read = char_read ,
- . write = char_write
, - . poll = char_poll ,
- . open = char_open ,
- . release = char_release ,
- } ;
-
- void MyTimerFunction ( unsigned long
data ) - {
- driver_private_data * dpd = ( driver_private_data * ) data ;
- if ( dpd )
- {
sprintf ( dpd -> my_string , " string_counter %d \ n " , string_counter ) ; - string_counter ++;
- //wake_up_interruptible(&dpd->wait);
- dpd -> flag = 1 ;
- init_timer ( & dpd -> char_timer ) ;
- dpd -> char_timer . function = MyTimerFunction ;
- dpd -> char_timer . expires = jiffies +
STRING_CHANGE_TIME * HZ ; - dpd -> char_timer . data = ( unsigned long ) dpd ;
- add_timer ( & dpd -> char_timer ) ;
} - }
-
- static int __init charmodule_init ( void )
- {
-
- dbg ( " char module demo \ n
" ) ; - driver_major = register_chrdev ( 0 , DEVICE_NAME , & chardev_fops ) ;
- if ( driver_major < 0 )
- {
dbg ( " Register character device failed \ n " ) ; - return - EFAULT ;
- }
- else dbg ( " mknod /dev/%s c %d 0 \ n "
, DEVICE_NAME , driver_major ) ; - charmodule_class = class_create ( THIS_MODULE , DEVICE_NAME ) ;
- if ( IS_ERR ( charmodule_class ))
- return
- EFAULT ; - device_create ( charmodule_class , NULL , MKDEV ( driver_major , 0 ) , DEVICE_NAME ) ;
-
- return 0 ;
- }
-
- static void __exit charmodule_exit ( void )
- {
- unregister_chrdev ( driver_major , DEVICE_NAME ) ;
- device_destroy ( charmodule_class
, MKDEV ( driver_major , 0 )) ; - class_destroy ( charmodule_class ) ;
- dbg ( " remove driver successfully \ n " ) ;
- }
module_init ( charmodule_init ) ; - module_exit ( charmodule_exit ) ;
-
- MODULE_DESCRIPTION ( " led module " ) ;
- MODULE_AUTHOR ( " Joey Cheng<jemicheng@gmail.com> "
) ; - MODULE_LICENSE ( " GPL " ) ;
- MODULE_ALIAS ( " QT2410:char module " ) ;