相对于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 = ;
} void UpdateThread::setUpdateType(int type)
{
updateType = type;
} void UpdateThread::run()
{
char cmd[MAX_CHAR_256] = {}; if(updateType == CONFIRMMODE_UPDATE)
{
sleep(); 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(); memset(cmd, , 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();
}
else
{
sleep(); sprintf(cmd, "cp %s/uImage /media/card/", updateFilePath);
system(cmd); system("sync");
sleep(); memset(cmd, , MAX_CHAR_256);
sprintf(cmd, "cp %s/itnvrupdatepack.tar.gz /media/mmcblk0p2/", updateFilePath);
system(cmd); system("sync");
sleep(); system("tar -zvxf /media/mmcblk0p2/itnvrupdatepack.tar.gz");
system("sync");
sleep();
} sleep();
bCheckUpdateComplete = true;
updateMsgBoxTimerCount = ;
} 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 =; UpdateMsgtimer->start(); 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 %= ;
switch(updateMsgBoxTimerCount)
{
case :
pMsgBox.SetText("系统升级中..\n");
break;
case :
pMsgBox.SetText("系统升级中....");
break;
case :
pMsgBox.SetText("系统升级中......");
break;
case :
pMsgBox.SetText("系统升级中........");
break;
case :
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 = * * ;
int retval; memset(&snl, 0x00, sizeof(struct sockaddr_nl));
snl.nl_family = AF_NETLINK;
snl.nl_pid = getpid();
snl.nl_groups = ;//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 == -)
{
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 < )
{
zDebug("bind failed: %s \n", strerror(errno));
close(netlink_socket);
netlink_socket = -;
}
else if (retval == )
{
//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 == )
snl.nl_pid = _snl.nl_pid;
}
}
} HotplugThread::~HotplugThread()
{
if(- != netlink_socket)
{
close(netlink_socket);
netlink_socket = -;
}
} void HotplugThread::run()
{
QByteArray data; while ()
{
data.resize(UEVENT_BUFFER_SIZE*);
data.fill();
size_t len = recv(netlink_socket, data.data(), data.size(), );
zDebug("read fro socket %d bytes \n", len);
data.resize(len);
data = data.replace(, '\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('/') - ); if (action_str ==QLatin1String("add"))
{
int ret = ;
char filePath[MAX_CHAR_32] = {};
char *data; QByteArray ba = dev.toLatin1();
if (!ba.contains("") && !ba.contains("")) //hotplug
return; data = ba.data(); sprintf(filePath, "/media/%s/%s", data, HNVR_UPDATE_DIR);
zDebug("filePath = %s \n", filePath);
sleep(); ret = confirmUpdateFile(filePath);
if(ret == )
{
memset(updateFilePath, , MAX_CHAR_32); strcpy(updateFilePath, filePath);
pSetup->newUsbDisk = ; pSetup->confirmIsUpdate(CONFIRMMODE_UPDATE);
}
else if(ret == )
{
memset(updateFilePath, , MAX_CHAR_32); strcpy(updateFilePath, filePath);
pSetup->newUsbDisk = ; 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 = ;
char temp[] = {};
char *name;
dictionary * ini = NULL ; ini = iniparser_load(path); totalIpncNum = iniparser_getnsec(ini);
zDebug("update version number = %d \n", totalIpncNum); for (i = ; i < totalIpncNum; i++)
{
name = iniparser_getsecname(ini, );
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 = ;
char temp[] = {};
char *name;
dictionary * ini = NULL ; ini = iniparser_load(path); totalIpncNum = iniparser_getnsec(ini);
zDebug("update version number = %d \n", totalIpncNum); for (i = ; i < totalIpncNum; i++)
{
name = iniparser_getsecname(ini, );
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 = , updatetype = ;
char versionFile[MAX_CHAR_256] = {}; dp = opendir(path);
if(NULL == dp)
{
perror("perror");
return -;
}
else
{
closedir(dp); sprintf(versionFile, "%s/%s", path, HNVR_UPDATE_VER_FILE);
version =ReadUpdateVersionInfo(versionFile);
if(version >= DIGITAL_VERSION)
{
updatetype = ReadUpdateTypeInfo(versionFile);
if(updatetype == )
{
printf("---------->app update !!!");
return updatetype;
}
else if(updatetype == )
{
printf("---------->system update !!!");
return updatetype;
}
else
{
printf("---------->do not update !!!");
return ;
}
}
else
{
return ;
}
}
}

【转】PF_NETLINK应用实例NETLINK_KOBJECT_UEVENT具体实现--udev实现原理的更多相关文章

  1. PF_NETLINK应用实例NETLINK_KOBJECT_UEVENT具体实现--udev实现原理

    PF_NETLINK应用实例NETLINK_KOBJECT_UEVENT具体实现--udev实现原理 相对于linux来说,udev还是一个新事物.然而,尽管它03年才出现,尽管它很低调(J),但它无 ...

  2. Omi框架学习之旅 - 通过对象实例来实现组件通讯 及原理说明

    组件通讯不是讲完了吗(上帝模式还没讲哈),怎么又多了种方式啊. 你484傻,多一种选择不好吗? 其实这个不属于组件通讯啦,只是当父组件实例安装和渲染完毕后,可以执行installed这个方法(默认是空 ...

  3. tomcat容器是如何创建servlet类实例?用到了什么原理?

    当容器启动时,会读取在webapps目录下所有的web应用中的web.xml文件,然后对 xml文件进行解析,并读取servlet注册信息.然后,将每个应用中注册的servlet类都进行加载,并通过  ...

  4. udev规则以及编写

    主要内容: udev简介 如何配置和使用udev 如何编写udev规则 字符串替换和匹配 udev主要作用 编写udev规则实例 难点解析 1. udev简介 1.1 什么是udev? udev是Li ...

  5. python: DOM 小实例

    一.全选 全部取消  反选 全选:选择指定的所有项目. 全部取消: 取消所有选定的项目. 反选: 选择未选定的,之前已选定的则取消. <!DOCTYPE html> <html la ...

  6. Loadrunner之HTTP接口测试脚本实例

    接口测试的原理是通过测试程序模拟客户端向服务器发送请求报文,服务器接收请求报文后对相应的报文做出处理然后再把应答报文发送给客户端,客户端接收应答报文结果与预期结果进行比对的过程,接口测试可以通过Jav ...

  7. Silverlight实例教程 - Validation数据验证开篇

    Silverlight 4 Validation验证实例系列 Silverlight实例教程 - Validation数据验证开篇 Silverlight实例教程 - Validation数据验证基础 ...

  8. CyclicBarrier的工作原理及其实例

    CyclicBarrier是多线程中一个重要的类,主要用于线程组内部之间的线程的相互等待问题. 1.CyclicBarrier的工作原理 CyclicBarrier大致是可循环利用的屏障,顾名思义,这 ...

  9. 读《实战GUI产品的自动化测试》:第一步——了解自动化测试,简单RFT的录制回放实例

    1.了解自动化测试,什么是自动化测试?(可以参数百度百科“自动化测试”) 2.了解自动化测试 * 自动化测试如何改善产品的质量 * 自动化测试无法完全替代手工测试 * 自动化测试无法发现新的问题——适 ...

随机推荐

  1. C# - List操作- 去掉重复

    ChangeList里面会有重复的数据,这时可以这样去掉重复的item // Remove duplicated info var dup = ChangeList.Where(item => ...

  2. 外观模式之C++实现

    说明:本文仅供学习交流,转载请标明出处.欢迎转载. 在我们学习程序设计时经常会用到模块化设计的思想,这一思想是我们首先把要实现的功能用一个模块表示,当用户想完毕某个人物时依次调用相应的函数. 然而.假 ...

  3. App上线流程全攻略(续)-iOS8之后的改动与所遇日常错误

    随着iOS8的公布,iTunes Connect的界面也是发生了非常大的改变,App 上传到 Store上面的步骤也是发生了些改变.以下继续用图说话: /*********************** ...

  4. CentOS7--DNS处理模块DnsPython的简单使用

    初步了解: DnsPython是Python实现的一个DNS工具包,支持几乎所有的记录类型. 安装: # wget http://www.dnspython.org/kits/1.9.4/dnspyt ...

  5. mysql 主从复制配置步骤

    1.准备两台数据库环境,或者单台多实例环境,能否正常启动和登录. 2.配置my.cnf文件,主库配置log-bin和server-id参数,从库配置server-id,不能和主库及其他从库一样,一般不 ...

  6. Web项目去掉Js文件红叉

    项目用到jquery,但将Jquery拷进去后,js文件有个红叉,看上去非常不爽.如下图: 解决方法: 1.找到项目下的.project文件 2.去掉Javascript验证 <?xml ver ...

  7. NSDictionary使用小结

    http://blog.csdn.net/ms2146/article/details/8656787

  8. C#基本语句与C++区别

    条件语句必须为bool表达式 int a = 1; if(a) { ... } 在c++中可以,但c#报错 但 bool b = true;//不能写成b = 1了: if(b) { ... } 是可 ...

  9. redis之lua脚本

    背景介绍 redis数据库提供了一些管理功能比如 流水线:打包发送多条命令,并在一个回复里面接收所有被执行命令的结果.事务:一次执行多条命令,被执行的命令要么就全部都被执行,要么就一个也不执行.并且事 ...

  10. Oracle11g R2学习系列 之二基本概念和环境介绍

    昨天安装好了之后,发现用Chrome打开OEM发现是英文的,搞得我好奇怪:安装时明明自动显示的是中文的,为何会是英文的呢.后来想想会不会是Oracle用的是浏览器的语言呢,果断打开Chrome的设置, ...