在linux下使用udev获取热插拔(hotplug)事件
?
udev是一種工具,它能夠根據(jù)系統(tǒng)中的硬件設(shè)備的狀態(tài)動(dòng)態(tài)更新設(shè)備文件,包括設(shè)備文件的創(chuàng)建,刪除等,設(shè)備文件通常放在/dev目錄下。使用udev后,在/dev目錄下就只包含系統(tǒng)中真正存在的設(shè)備。udev同時(shí)提供了監(jiān)視接口,當(dāng)設(shè)備的狀態(tài)改變時(shí),監(jiān)視接口可以向應(yīng)用程序報(bào)告發(fā)生的事件,當(dāng)設(shè)備加入系統(tǒng)或從系統(tǒng)移除時(shí)都可以接到通知。
udev只支持linux-2.6及以上版本的內(nèi)核,因?yàn)閡dev嚴(yán)重依賴(lài)于sysfs文件系統(tǒng)提供的信息,而sysfs文件系統(tǒng)只在linux-2.6內(nèi)核中才有。
udev能夠?qū)崿F(xiàn)所有devfs實(shí)現(xiàn)的功能。但udev運(yùn)行在用戶(hù)模式中,而devfs運(yùn)行在內(nèi)核模式中。
?
作用:
1. 動(dòng)態(tài)創(chuàng)建或刪除設(shè)備文件
2. 遍歷sysfs設(shè)備文件
3. hotplug(利用netlink)
?
使用udev需要先安裝libudev庫(kù),在程序中包含libudev.h頭文件,并且在編譯時(shí)加上-ludev告訴編譯器去鏈接udev庫(kù)。
?
1. 安裝libudev
sudo apt-get install libudev-dev
?
2. 編寫(xiě)測(cè)試代碼udev-hotplugin.c
?
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <signal.h> #include <sys/time.h> #include <sys/socket.h> #include <sys/un.h> #include <sys/select.h> #include <linux/types.h> #include <linux/netlink.h> #include <libudev.h>#undef asmlinkage #ifdef __i386__ #define asmlinkage __attribute__((regparm(0))) #else #define asmlinkage #endifstatic int udev_exit;static void asmlinkage sig_handler(int signum) {if (signum == SIGINT || signum == SIGTERM)udev_exit = 1; }static void print_device(struct udev_device *device, const char *source, int env) {struct timeval tv;struct timezone tz;gettimeofday(&tv, &tz);printf("%-6s[%llu.%06u] %-8s %s (%s)\n",source,(unsigned long long) tv.tv_sec, (unsigned int) tv.tv_usec,udev_device_get_action(device),udev_device_get_devpath(device),udev_device_get_subsystem(device));if (env) {struct udev_list_entry *list_entry;udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(device))printf("%s=%s\n",udev_list_entry_get_name(list_entry),udev_list_entry_get_value(list_entry));printf("\n");}}int udevadm_monitor(struct udev *udev) {struct sigaction act;int env = 0;int print_kernel = 1;struct udev_monitor *kernel_monitor = NULL;fd_set readfds;int rc = 0;if (getuid() != 0) {fprintf(stderr, "root privileges needed to subscribe to kernel events\n");goto out;}/* set signal handlers */memset(&act, 0x00, sizeof(struct sigaction));act.sa_handler = (void (*)(int)) sig_handler;sigemptyset(&act.sa_mask);act.sa_flags = SA_RESTART;sigaction(SIGINT, &act, NULL);sigaction(SIGTERM, &act, NULL);printf("monitor will print the received events.\n");if (print_kernel) {kernel_monitor = udev_monitor_new_from_netlink(udev, "udev"); //這里的udev源碼中沒(méi)有"udev"這個(gè)參數(shù),不加進(jìn)去返回值就為NULL,所以要加這個(gè)if (kernel_monitor == NULL) {rc = 3;printf("udev_monitor_new_from_netlink() error\n");goto out;}if (udev_monitor_enable_receiving(kernel_monitor) < 0) {rc = 4;goto out;}printf("UEVENT the kernel uevent: \n");}printf("\n");while (!udev_exit) {int fdcount;FD_ZERO(&readfds);if (kernel_monitor != NULL)FD_SET(udev_monitor_get_fd(kernel_monitor), &readfds);fdcount = select(udev_monitor_get_fd(kernel_monitor)+1, &readfds, NULL, NULL, NULL);if (fdcount < 0) {if (errno != EINTR)fprintf(stderr, "error receiving uevent message: %m\n");continue;}if ((kernel_monitor != NULL) && FD_ISSET(udev_monitor_get_fd(kernel_monitor), &readfds)) {struct udev_device *device;device = udev_monitor_receive_device(kernel_monitor);if (device == NULL)continue;print_device(device, "UEVENT", env);udev_device_unref(device);}}out:udev_monitor_unref(kernel_monitor);return rc; }int main(int argc, char *argv[]) {struct udev *udev;int rc = 1;udev = udev_new();if (udev == NULL)goto out;udevadm_monitor(udev);goto out;rc = 2;out:udev_unref(udev);return rc; }?
?
3. 測(cè)試
1)編譯
gcc -o udevhotplug udev-hotplugin.c -ludev
2)以root權(quán)限執(zhí)行
sudo ./udevhotplug
當(dāng)插拔一個(gè)USB設(shè)備時(shí),顯示如下:
?
4. libudev API介紹
4.1 初始化
首先調(diào)用udev_new,創(chuàng)建一個(gè)udev library context。udev library context采用引用記數(shù)機(jī)制,創(chuàng)建的context默認(rèn)引用記數(shù)為1,使用udev_ref和udev_unref增加或減少引用記數(shù),如果引用記數(shù)為0,則釋放內(nèi)部資源。
?
4.2 枚舉設(shè)備
使用udev_enumrate_new創(chuàng)建一個(gè)枚舉器,用于掃描系統(tǒng)已接設(shè)備。使用udev_enumrate_ref和udev_enumrate_unref增加或減少引用記數(shù)。
使用udev_enumrate_add_match/nomatch_xxx系列函數(shù)增加枚舉的過(guò)濾器,過(guò)濾關(guān)鍵字以字符表示,如"block"設(shè)備。
使用udev_enumrate_scan_xxx系列函數(shù)掃描/sys目錄下,所有與過(guò)濾器匹配的設(shè)備。掃描完成后的數(shù)據(jù)結(jié)構(gòu)是一個(gè)鏈表,使用udev_enumerate_get_list_entry獲取鏈表的首個(gè)結(jié)點(diǎn),使用udev_list_entry_foreach遍歷整個(gè)鏈表。
?
4.3 監(jiān)控設(shè)備插拔?udev的設(shè)備插拔基于netlink實(shí)現(xiàn)。
1)使用udev_monitor_new_from_netlink創(chuàng)建一個(gè)新的monitor,函數(shù)的第二個(gè)參數(shù)是事件源的名稱(chēng),可選"kernel"或"udev"?;?#34;kernel"的事件通知要早于"udev",但相關(guān)的設(shè)備結(jié)點(diǎn)未必創(chuàng)建完成,所以一般應(yīng)用的設(shè)計(jì)要基于"udev"進(jìn)行監(jiān)控。
2)使用udev_monitor_filter_add_match_subsystem_devtype增加一個(gè)基于設(shè)備類(lèi)型的udev事件過(guò)濾器,例如: "block"設(shè)備。
3)使用udev_monitor_enable_receiving啟動(dòng)監(jiān)控過(guò)程。監(jiān)控可以使用udev_monitor_get_fd獲取一個(gè)文件描述符,基于返回的fd可以執(zhí)行poll操作,簡(jiǎn)化程序設(shè)計(jì)。
4)插拔事件到達(dá)后,可以使用udev_monitor_receive_device獲取產(chǎn)生事件的設(shè)備映射。調(diào)用udev_device_get_action可以獲得一個(gè)字符串:"add"或者"remove",以及"change", "online", "offline"等,但后三個(gè)未知什么情況下會(huì)產(chǎn)生。
?
4.4 獲取設(shè)備信息
使用udev_list_entry_get_name可以得到一個(gè)設(shè)備結(jié)點(diǎn)的sys路徑,基于這個(gè)路徑使用udev_device_new_from_syspath可以創(chuàng)建一個(gè)udev設(shè)備的映射,用于獲取設(shè)備屬性。獲取設(shè)備屬性使用udev_device_get_properties_list_entry,返回一個(gè)存儲(chǔ)了設(shè)備所有屬性信息的鏈表,使用udev_list_entry_foreach遍歷鏈表,使用udev_list_entry_get_name和udev_list_entry_get_value獲取屬性的名稱(chēng)和值。
?
總結(jié)
以上是生活随笔為你收集整理的在linux下使用udev获取热插拔(hotplug)事件的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 三星S23系列最全爆料:超大杯独享1TB
- 下一篇: linux2.6版及以后内核:支持实时、