【转】PF_NETLINK应用实例NETLINK_KOBJECT_UEVENT具体实现--udev实现原理
相对于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>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define UEVENT_BUFFER_SIZE 2048
static int init_hotplug_sock()
{
const int buffersize = 1024;
int ret;
struct sockaddr_nl snl;
bzero(&snl, sizeof(struct sockaddr_nl));
snl.nl_family = AF_NETLINK;
snl.nl_pid = getpid();
snl.nl_groups = 1;
int s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
if (s == -1)
{
perror("socket");
return -1;
}
setsockopt(s, SOL_SOCKET, SO_RCVBUF, &buffersize, sizeof(buffersize));
ret = bind(s, (struct sockaddr *)&snl, sizeof(struct sockaddr_nl));
if (ret < 0)
{
perror("bind");
close(s);
return -1;
}
return s;
}
int main(int argc, char* argv[])
{
int hotplug_sock = init_hotplug_sock();
while(1)
{
/* Netlink message buffer */
char buf[UEVENT_BUFFER_SIZE * 2] = {0};
recv(hotplug_sock, &buf, sizeof(buf), 0);
printf("%s\n", buf);
/* USB 设备的插拔会出现字符信息,通过比较不同的信息确定特定设备的插拔,在这添加比较代码 */
}
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"
最后,添加自己基于QT的U盘升级程序,基于NETLINK。
usb update ... UpdateThread::UpdateThread() { updateType = 0; } void UpdateThread::setUpdateType(int type) { updateType = type; } void UpdateThread::run() { char cmd[MAX_CHAR_256] = {0}; if(updateType == CONFIRMMODE_UPDATE) { sleep(1); sprintf(cmd, "cp -rf %s/dvrmain /opt/dvr_rdk/ti816x/bin/", updateFilePath); system(cmd); system("chmod 777 /opt/dvr_rdk/ti816x/bin/dvrmain"); system("sync"); sleep(1); memset(cmd, 0, MAX_CHAR_256); sprintf(cmd, "cp -rf %s/libdm816x.so /opt/dvr_rdk/ti816x/firmware/", updateFilePath); system(cmd); system("chmod 777 /opt/dvr_rdk/ti816x/firmware/libdm816x.so"); system("sync"); sleep(1); } else { sleep(1); sprintf(cmd, "cp %s/uImage /media/card/", updateFilePath); system(cmd); system("sync"); sleep(1); memset(cmd, 0, MAX_CHAR_256); sprintf(cmd, "cp %s/itnvrupdatepack.tar.gz /media/mmcblk0p2/", updateFilePath); system(cmd); system("sync"); sleep(1); system("tar -zvxf /media/mmcblk0p2/itnvrupdatepack.tar.gz"); system("sync"); sleep(1); } sleep(2); bCheckUpdateComplete = true; updateMsgBoxTimerCount = 0; } void dvr_setup::StartUpdate(int updateType) { bCheckUpdateComplete = false; if(updateType == CONFIRMMODE_UPDATE) { pMsgBox.SetText("应用升级开始.."); } else { pMsgBox.SetText("系统升级开始.."); } pMsgBox.show(); UpdateMsgtimer = new QTimer(this); connect(UpdateMsgtimer, SIGNAL(timeout()), this, SLOT(updateSystemWarn())); updateMsgBoxTimerCount =0; UpdateMsgtimer->start(1000); pUpdateThread = new UpdateThread; pUpdateThread ->setUpdateType(updateType); pUpdateThread->start(); } void dvr_setup::CancelUpdate() { m_pUpdateBox->hide(); m_pUpdateBox->setEnabled(false); setEnabled(true); } void dvr_setup::updateSystemWarn() { updateMsgBoxTimerCount++; updateMsgBoxTimerCount %= 5; switch(updateMsgBoxTimerCount) { case 0: pMsgBox.SetText("系统升级中..\n"); break; case 1: pMsgBox.SetText("系统升级中...."); break; case 2: pMsgBox.SetText("系统升级中......"); break; case 3: pMsgBox.SetText("系统升级中........"); break; case 4: pMsgBox.SetText("系统升级中.........."); break; } if(bCheckUpdateComplete == true) { delete pUpdateThread; pUpdateThread = NULL; UpdateMsgtimer->stop(); pMainClass->ShowNotifyDialog((char*)"系统升级完成,重启系统!"); pMainClass->EventLogAdd((char*)"系统升级完成!",true); pSetup->SystemRebootBtnSlot(); } } void dvr_setup::confirmIsUpdate(int updateType) { if(m_pUpdateBox != NULL) { setEnabled(false); m_pUpdateBox->SetConfirmMode(updateType); m_pUpdateBox->show(); m_pUpdateBox->setEnabled(true); } } #ifdef UPDATE_DEBUG #define zDebug(fmt, ...) qDebug("[UPDATE]: %s: "#fmt, __FUNCTION__, ##__VA_ARGS__) #else #define zDebug(fmt, ...) #endif #define UEVENT_BUFFER_SIZE 2048 HotplugThread::HotplugThread() { 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;//UDEV_MONITOR_KERNEL; netlink_socket = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); //netlink_socket = socket(PF_NETLINK, SOCK_DGRAM|SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT); //SOCK_CLOEXEC may be not available if (netlink_socket == -1) { zDebug("error getting socket: %s \n", strerror(errno)); } else { /* set receive buffersize */ setsockopt(netlink_socket, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize)); retval = bind(netlink_socket, (struct sockaddr*) &snl, sizeof(struct sockaddr_nl)); if (retval < 0) { zDebug("bind failed: %s \n", strerror(errno)); close(netlink_socket); netlink_socket = -1; } else if (retval == 0) { //from libudev-monitor.c struct sockaddr_nl _snl; socklen_t _addrlen; /* * get the address the kernel has assigned us * it is usually, but not necessarily the pid */ _addrlen = sizeof(struct sockaddr_nl); retval = getsockname(netlink_socket, (struct sockaddr *)&_snl, &_addrlen); if (retval == 0) snl.nl_pid = _snl.nl_pid; } } } HotplugThread::~HotplugThread() { if(-1 != netlink_socket) { close(netlink_socket); netlink_socket = -1; } } void HotplugThread::run() { QByteArray data; while (1) { data.resize(UEVENT_BUFFER_SIZE*2); data.fill(0); size_t len = recv(netlink_socket, data.data(), data.size(), 0); zDebug("read fro socket %d bytes \n", len); data.resize(len); data = data.replace(0, '\n').trimmed(); if (buffer.isOpen()) buffer.close(); buffer.setBuffer(&data); buffer.open(QIODevice::ReadOnly); QByteArray line = buffer.readLine(); while(!line.isNull()) { parseLine(line.trimmed()); line = buffer.readLine(); } buffer.close(); } } void HotplugThread::parseLine(const QByteArray &line) { if (!line.contains("/block/")) //hotplug return; QString action_str = line.left(line.indexOf('@')).toLower(); QString dev = line.right(line.length() - line.lastIndexOf('/') - 1); if (action_str ==QLatin1String("add")) { int ret = 0; char filePath[MAX_CHAR_32] = {0}; char *data; QByteArray ba = dev.toLatin1(); if (!ba.contains("1") && !ba.contains("2")) //hotplug return; data = ba.data(); sprintf(filePath, "/media/%s/%s", data, HNVR_UPDATE_DIR); zDebug("filePath = %s \n", filePath); sleep(3); ret = confirmUpdateFile(filePath); if(ret == 1) { memset(updateFilePath, 0, MAX_CHAR_32); strcpy(updateFilePath, filePath); pSetup->newUsbDisk = 1; pSetup->confirmIsUpdate(CONFIRMMODE_UPDATE); } else if(ret == 2) { memset(updateFilePath, 0, MAX_CHAR_32); strcpy(updateFilePath, filePath); pSetup->newUsbDisk = 1; pSetup->confirmIsUpdate(CONFIRMMODE_UPDATE_SYSTEM); } else { zDebug("filePath = %s has no update file !!!\n", filePath); } } else if (action_str==QLatin1String("remove")) { } else if (action_str==QLatin1String("change")) { } } #define IPNC_UPDATE_INI_FILE_VER "versionnum" #define IPNC_UPDATE_INI_FILE_TYPE "updatetype" int ReadUpdateVersionInfo(char* path) { int i, totalIpncNum, ret = 0; char temp[256] = {0}; char *name; dictionary * ini = NULL ; ini = iniparser_load(path); totalIpncNum = iniparser_getnsec(ini); zDebug("update version number = %d \n", totalIpncNum); for (i = 0; i < totalIpncNum; i++) { name = iniparser_getsecname(ini, 0); sprintf(temp,"%s:%s",name,IPNC_UPDATE_INI_FILE_VER); ret = iniparser_getint(ini,temp,NULL); } iniparser_freedict(ini); return ret; } int ReadUpdateTypeInfo(char* path) { int i, totalIpncNum, ret = 0; char temp[256] = {0}; char *name; dictionary * ini = NULL ; ini = iniparser_load(path); totalIpncNum = iniparser_getnsec(ini); zDebug("update version number = %d \n", totalIpncNum); for (i = 0; i < totalIpncNum; i++) { name = iniparser_getsecname(ini, 1); sprintf(temp,"%s:%s",name,IPNC_UPDATE_INI_FILE_TYPE); ret = iniparser_getint(ini,temp,NULL); } iniparser_freedict(ini); return ret; } int HotplugThread::confirmUpdateFile(const char* path) { DIR *dp; //声明一个句柄 struct dirent *file; //readdir函数的返回值就存放在这个结构体中 int version = 0, updatetype = 0; char versionFile[MAX_CHAR_256] = {0}; dp = opendir(path); if(NULL == dp) { perror("perror"); return -1; } else { closedir(dp); sprintf(versionFile, "%s/%s", path, HNVR_UPDATE_VER_FILE); version =ReadUpdateVersionInfo(versionFile); if(version >= DIGITAL_VERSION) { updatetype = ReadUpdateTypeInfo(versionFile); if(updatetype == 1) { printf("---------->app update !!!"); return updatetype; } else if(updatetype == 2) { printf("---------->system update !!!"); return updatetype; } else { printf("---------->do not update !!!"); return 0; } } else { return 0; } }
}
posted on 2015-02-06 16:19 CSlunatic 阅读(1075) 评论(0) 编辑 收藏 举报