linux/android uevent

来源:互联网 发布:淘宝中老年模特招聘 编辑:程序博客网 时间:2024/06/07 00:22

文章转载自 http://www.verydemo.com/demo_c131_i97476.html


http://wenku.baidu.com/view/3f08de275901020207409cd4.html linux uevent

http://blog.csdn.net/fengkehuan/article/details/6200210

http://dongyulong.blog.51cto.com/1451604/389159

 http://blog.163.com/ac952_hmz/blog/static/9479151320120364719795/   android ueventobserver

 http://blog.chinaunix.net/space.php?uid=24605155&do=blog&cuid=2363481 abdroid uevent

http://blog.csdn.net/datangsoc/article/details/5928132: android vold, 代替udev

http://wenku.baidu.com/view/cde97ff9941ea76e58fa0414.html: vold

http://blog.csdn.net/sustzombie/article/details/6118256: vold

 http://blog.csdn.net/magicyu2/article/details/6974074: 这篇讲vold还是比较清楚的。从这篇来看android中的phonewin manager的ueventobserver不是来自vold, 而是直接监听kernel来的uevent的socket(NETLINK_KOBJECT_UEVENT), 来自hardware_legacy的uevent。 vold支持    DhcpListener(system\core\nexus)、FrameworkListener(system\core\libsysutils\src)、NetlinkListener(system\core\libsysutils\src)、SupplicantListener(system\core\nexus)、TiwlanEventListener(system\core\nexus);(这里也有netlink, 这个实际上是vold里调用, 作为uevent的client端处理onevent的, netlinklistener使用侦听NETLINK_KOBJECT_UEVENT的socket读取kernel来的uevent消息, 进过netlinkevent decode, 再调用netlinkhandler提供的onevent根据volumn manager的block, switch等处理event,  之后volumn manager会触发注册进vold的其它service的socket(服务器端)的处理(流程是sendbroadcaster调用socket写, 服务侧的listner的ondataavailable会处理相应的消息, 这些消息由commandlistner的runcomd处理, 不同的runcmd将调用volumemanger的具体的处理函数), 对于其中的framework, vold注册了.  block: 通常指mount/umont,  switch指connect/disconnect

    registerCmd(new DumpCmd());

    registerCmd(new VolumeCmd());

    registerCmd(new AsecCmd());

    registerCmd(new ObbCmd());

    registerCmd(new ShareCmd());

    registerCmd(new StorageCmd());

    registerCmd(new XwarpCmd());

 

几个listner。


 http://www.kuqin.com/networkprog/20080512/8361.html: socket,

Sendto()和recvfrom()用于在无连接的数据报socket方式下进行数据传输。Send()和recv()这两个函数用于面向连接的socket上进行数据传输。

 

http://hi.baidu.com/leoispace/blog/item/8aa5f41cc8609d04304e15bf.html: android socket


 

 

用于内核空间向用户空间通知消息, 比如hotplug。 内核空间的uevent通过socket向用户空间的uevent helper发送消息。这个用户空间的侦听者在pc上用udevd, 在嵌入式设备上用mdev。 侦听者的作用扫描注册的device, 向sysfs的device的uevent的属性文件写入add, 触发uevent事件, 这样用户空间就有机会处理这些事件,根据匹配规则作一定的处理, 比如生成设备节点, 使用modprobe加载驱动等hotplug事件。

 

 

1.kobject, ktype, kset

kobject代表sysfs中的目录。

ktype代表kobject的类型,主要包含release函数和attr的读写函数。比如,所有的bus都有同一个bus_type;所有的class都有同一个class_type。

kset包含了subsystem概念,kset本身也是一个kobject,所以里面包含了一个kobject对象。另外,kset中包含kset_uevent_ops,里面主要定义了三个函数

       int (*filter)(struct kset *kset, struct kobject *kobj);

       const char *(*name)(struct kset *kset, struct kobject *kobj);

       int (*uevent)(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env);

这三个函数都与uevent相关。filter用于判断uevent是否要发出去。name用于得到subsystem的名字。uevent用于填充env变量。

2.uevent内核部分

uevent是sysfs向用户空间发出的消息。比如,device_add函数中,会调用kobject_uevent(&dev->kobj, KOBJ_ADD); 这里kobj是发消息的kobj,KOBJ_ADD是发出的事件。uevent的事件在kobject_action中定义:

enum kobject_action {

       KOBJ_ADD,

       KOBJ_REMOVE,

       KOBJ_CHANGE,

       KOBJ_MOVE,

       KOBJ_ONLINE,

       KOBJ_OFFLINE,

       KOBJ_MAX

};

 

int kobject_uevent(struct kobject *kobj, enum kobject_action action)

{

       return kobject_uevent_env(kobj, action, NULL);

}

 

kobject_uevent_env:

       由kobject的parent向上查找,直到找到一个kobject包含kset。

       如果kset中有filter函数,调用filter函数,看看是否需要过滤uevent消息。

       如果kset中有name函数,调用name函数得到subsystem的名字;否则,subsystem的名字是kset中kobject的名字。

       分配一个kobj_uevent_env,并开始填充env环境变量:

       增加环境变量ACTION=<action name>

       增加环境变量DEVPATH=<kobj’s path>

       增加环境变量SUBSYSTEM=<subsystem name>

       增加环境变量kobject_uevent_env中参数envp_ext指定的环境变量。

       调用kset的uevent函数,这个函数会继续填充环境变量。

       增加环境变量SEQNUM=<seq>,这里seq是静态变量,每次累加。

       调用netlink发送uevent消息。

       调用uevent_helper,最终转换成对用户空间sbin/mdev的调用。

3.uevent用户空间部分

uevent的用户空间程序有两个,一个是udev,一个是mdev。

udev通过netlink监听uevent消息,它能完成两个功能:

       1.自动加载模块

       2.根据uevent消息在dev目录下添加、删除设备节点。

另一个是mdev,mdev在busybox的代码包中能找到,它通过上节提到的uevent_helper函数被调用。

 

下面简要介绍udev的模块自动加载过程:

etc目录下有一个uevent规则文件/etc/udev/rules.d/50-udev.rules

udev程序收到uevent消息后,在这个规则文件里匹配,如果匹配成功,则执行这个匹配定义的shell命令。例如,规则文件里有这么一行:

ACTION=="add", SUBSYSTEM=="?*", ENV{MODALIAS}=="?*", RUN+="/sbin/modprobe $env{MODALIAS}"

所以,当收到uevent的add事件后,shell能自动加载在MODALIAS中定义的模块。

 

mdev的模块自动加载过程与之类似,它的配置文件在/etc/mdev.conf中。例如:

$MODALIAS=.* 0:0 660 @modprobe "$MODALIAS"

这条规则指的是:当收到的环境变量中含有MODALIAS,那么加载MODALIAS代表的模块。

mdev的详细说明在busybox的docs/mdev.txt中。

4.uevent在设备驱动模型中的应用

在sys目录下有一个子目录devices,代表一个kset。

创建设备时,调用的device_initialize函数中,默认会把kset设置成devices_kset,即devices子目录代表的kset。

devices_kset中设置了uevent操作集device_uevent_ops。

static struct kset_uevent_ops device_uevent_ops = {

       .filter =    dev_uevent_filter,

       .name =   dev_uevent_name,

       .uevent = dev_uevent,

};

 

dev_uevent_filter中,主要是规定了要想发送uevent,dev必须有class或者bus。

dev_uevent_name中,返回dev的class或者bus的名字。

dev_uevent函数:

       如果dev有设备号,添加环境变量MAJOR与MINOR。

       如果dev->type有值,设置DEVTYPE=<dev->type->name>。

       如果dev->driver,设置DRIVER=<dev->driver->name>。

       如果有bus,调用bus的uevent函数。

       如果有class,调用class的uevent函数。

如果有dev->type,调用dev->type->uevent函数。

 

一般在bus的uevent函数中,都会添加MODALIAS环境变量,设置成dev的名字。这样,uevent传到用户空间后,就可以通过对MODALIAS的匹配自动加载模块。这样的bus例子有platform和I2C等等。

 

 

 

android的vold:

现在可能很少有人会用mknod这个命令了,也很少有使用它的机会,但就在几年前,这还是一项linux工程师的必备技能,在制作文件系统前或加载新的驱动前,我们必须小心翼翼的创建设备节点。

不需要使用mknod并不是他消失了,而是我们有了更好更智能的方法。

linux对于热插拔的支持并不是生来就有的,而是经历了一个复杂而有戏剧性的过程,全球linux爱好者用脚投出了他们保贵的一票,udev最终成为事实上的标准。

在android中,取代udev的是vold,我们这里不去过多的讨论为什么android不继续使用udev,但要知道vold的机制和udev是一样的,理解了udev,也就理解了vold。android一出生就没有尊守传统linux的许多标准,当然也不能指望udev能很好的服务于android。android社区的选择是别起炉灶,为android定做一套udev,这就是vold了。

无论是udev还是vold,都是基于sysfs的,sysfs为内核与用户层的通讯提供了一种全新的方式,并将这种方式加以规范。

kernel层能检测到有新的设备接入,并能为之加载相应的驱动,但如何通知用户层呢?这就是sysfs的工作,内核中的sysfs机制要求当有新的驱动加载时给用户层发送相应的event.但这些event只尽告知的义务,具体怎么处理,这就是vold(或者udev)的事了。

对于用户层而言,我们无需关心sysfs的细节,只要知道sysfs能向用户层提供什么就行了。

首先,我们要知道如何接收来自内核的event.

Netlink socket大家应该不会陌生吧,socket这套东西不仅能用于网络间的通讯,也用能用于进程间的通讯,像这种内核态与用户沟通的活,自然也少不了它。

下面的内容摘自vold(NetlinkManager.cpp)

if ((mSock = socket(PF_NETLINK,

SOCK_DGRAM,NETLINK_KOBJECT_UEVENT)) < 0) {

SLOGE("Unable to create uevent socket: %s", strerror(errno));

return -1;

}

if (setsockopt(mSock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0) {

SLOGE("Unable to set uevent socket options: %s", strerror(errno));

return -1;

}

if (bind(mSock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) {

SLOGE("Unable to bind uevent socket: %s", strerror(errno));

return -1;

}

 

这就是监听sysfs的uevent的socket的关键设置,有网络编程背影的人很容易理解上面这段代码。

下面紧接着的问题就是这个socket通路会给我们什么消息:

我们进入/sys/block/mmcblk0(也可以是/sys/block下的其它目录),执行:

cat *

MAJOR=179

MINOR=0

DEVNAME=mmcblk0

DEVTYPE=disk

PHYSDEVPATH=/class/mmc_host/mmc0/mmc0:1234

PHYSDEVBUS=mmc

PHYSDEVDRIVER=mmcblk

NPARTS=1

......

 

我们通过socket从内核处到的envent中所包含的信息也与此相似,是一个包含这些信息的文本,可能是如下格式的

add@/block/PHYSDEVDRIVER=mmcblk

PHYSDEVPATH=/class/mmc_host/mmc0/mmc0:1234

DEVNAME=mmcblk0

MAJOR=179

MINOR=0

PHYSDEVDRIVER=mmcblk

......

 

sysfs传上来的是一个多行的文本(这点要特别注意,并不只是add@/block/PHYSDEVDRIVER=mmcblk

这一行),vold要对这个多行文档进行解析,然后决定怎么做。vold会把解协出来的消息再通可别一个vold的socket传到其它的进程,同时接收其它进程的反馈。

向sysfs目录(或子目录)下面的uevent文件写入”add/n”字符也会触发内核上发这些uevent,相当于重新执行了一次热插拔。

例:echo "add" > /sys/block/mmcblk0/uevent

系统启动时vold错过了的消息可以用这个特性重新触发。

clip_image002

分析vold的源码,要有一定的C++的基础和设计模式的知识,习惯过程式设计的程序员在读vold时会有很大的困难,不过幸好vold代码不多。另外,与vold相关的大量机密都在libsysutils中,千万不要漏掉这个库。

先看看下图SocketListener这套架构,监听sysfs与其它进程的消息,全仰仗这套框架。

clip_image004

在netLinkListener中,VOLD的重点是OnEvent这个虚接口的实现,而CommandSistener中,VOLD处理的重点则是分发VoldCommand类。VoldCommand是由FrameworkCommand派生出的,而VolumeCmdShareCmd等子类则是各种操作的封装。

先看看对NetlinkHandler::onEvent的处理

void NetlinkHandler::onEvent(NetlinkEvent *evt) {

VolumeManager *vm = VolumeManager::Instance();

const char *subsys = evt->getSubsystem();

if (!subsys) {

SLOGW("No subsystem found in netlink event");

return;

}

if (!strcmp(subsys, "block")) {

vm->handleBlockEvent(evt);

} else if (!strcmp(subsys, "switch")) {

vm->handleSwitchEvent(evt);

} else if (!strcmp(subsys, "battery")) {

} else if (!strcmp(subsys, "power_supply")) {

}

}

 

这里主要是通过基类解析的uevent消息分别调用不同的处理。如果看了上面的图还有人问NetlinkHandler::onEvent是在什么时候调用的,那就要补一下C++了,这不是一两句话能说得清楚的。

 

VoldCommand主要是实现对runcommand动作的封装,在FrameworkListener会根据收到的消息选择相应的派生类。

bool FrameworkListener::onDataAvailable(SocketClient *c) {

char buffer[255];

int len;

if ((len = read(c->getSocket(), buffer, sizeof(buffer) -1)) < 0) {

SLOGE("read() failed (%s)", strerror(errno));

return errno;

} else if (!len)

return false;

int offset = 0;

int i;

for (i = 0; i < len; i++) {

if (buffer[i] == '/0') {

dispatchCommand(c, buffer + offset);

offset = i + 1;

}

}

return true;

}

 

void FrameworkListener::dispatchCommand(SocketClient *cli, char *data) {

FrameworkCommandCollection::iterator i;

int argc = 0;

char *argv[FrameworkListener::CMD_ARGS_MAX];

char tmp[255];

char *p = data;

char *q = tmp;

bool esc = false;

bool quote = false;

int k;

memset(argv, 0, sizeof(argv));

memset(tmp, 0, sizeof(tmp));

while(*p) {

if (*p == '//') {

if (esc) {

*q++ = '//';

esc = false;

} else

esc = true;

p++;

continue;

} else if (esc) {

if (*p == '"')

*q++ = '"';

else if (*p == '//')

*q++ = '//';

else {

cli->sendMsg(500, "Unsupported escape sequence", false);

goto out;

}

p++;

esc = false;

continue;

}

if (*p == '"') {

if (quote)

quote = false;

else

quote = true;

p++;

continue;

}

*q = *p++;

if (!quote && *q == ' ') {

*q = '/0';

argv[argc++] = strdup(tmp);

memset(tmp, 0, sizeof(tmp));

q = tmp;

continue;

}

q++;

}

argv[argc++] = strdup(tmp);

#if 0

for (k = 0; k < argc; k++) {

SLOGD("arg[%d] = '%s'", k, argv[k]);

}

#endif

if (quote) {

cli->sendMsg(500, "Unclosed quotes error", false);

goto out;

}

for (i = mCommands->begin(); i != mCommands->end(); ++i) {

FrameworkCommand *c = *i;

if (!strcmp(argv[0], c->getCommand())) {

if (c->runCommand(cli, argc, argv)) {//调用派生类的接口。

SLOGW("Handler '%s' error (%s)", c->getCommand(), strerror(errno));

}

goto out;

}

}

cli->sendMsg(500, "Command not recognized", false);

out:

int j;

for (j = 0; j < argc; j++)

free(argv[j]);

return;

}

   

clip_image006

Volume定义了各种磁盘的操作,属于工具类。

clip_image008

要将这些类是如何组织在一起的呢,关键是下面两个工厂类。

  

clip_image010

  

clip_image012

从上图可以看出,VolumeManager和NetlinkManager将整 个系统组织在一 起,NetlinkManager负责翻译sysfs的uevent事件并传递给其它的进程,VolumeManager则负责接收其它进程反馈的消息,并分发给VoldCommand类作相应的处理。

最后,我们分析一下vold是如何初始化这些类的:

int main() {

VolumeManager *vm;

CommandListener *cl;

NetlinkManager *nm;

SLOGI("Vold 2.1 (the revenge) firing up");

mkdir("/dev/block/vold", 0755);

/* Create our singleton managers */

if (!(vm = VolumeManager::Instance())) {//实例化

SLOGE("Unable to create VolumeManager");

exit(1);

};

if (!(nm = NetlinkManager::Instance())) {//实例化

SLOGE("Unable to create NetlinkManager");

exit(1);

};

cl = new CommandListener(); //创建vold socket,用于向其它进程转发解析的sysfs event,并接收其进程的命令。

vm->setBroadcaster((SocketListener *) cl);

nm->setBroadcaster((SocketListener *) cl);

if (vm->start()) {

SLOGE("Unable to start VolumeManager (%s)", strerror(errno));

exit(1);

}

if (process_config(vm)) { //解析配置

SLOGE("Error reading configuration (%s)... continuing anyways", strerror(errno));

}

if (nm->start()) {//创建监听sysfs的socket

SLOGE("Unable to start NetlinkManager (%s)", strerror(errno));

exit(1);

}

coldboot("/sys/block"); //冷启动,vold错过了一些uevent,重新触发。向sysfs的uevent文件写入”add/n” 字符也可以触发sysfs事件,相当执行了一次热插拔。

/*

* Switch uevents are broken.

* For now we manually bootstrap

* the ums switch

*/

{

FILE *fp;

char state[255];

/*

* Now that we're up, we can respond to commands

*/

if (cl->startListener()) { //监听上层的反馈

SLOGE("Unable to start CommandListener (%s)", strerror(errno));

exit(1);

}

// Eventually we'll become the monitoring thread

while(1) {

sleep(1000);

}

SLOGI("Vold exiting");

exit(0);

}

 

etc/ vold.fstab的配置文件

例:(每一行的结束不能有空格等任何字符,vold对这个地方的处理有bug.)

dev_mount sdcard /data/disk auto /block/sda

 

格式是:

type label mount_point part sysfs_path sysfs_path

sysfs_path可以有多个,但最后不要有空格,否则会解析错误

part指定分区个数,如果是auto则只有一个分区

 

 

 

另一篇:

 

1. 总体架构

2. 流程概览

2.1 开启Vold

2.2 引导Uevent

2.3 处理事件


 

Vold - Volume Daemon存储类的守护进程,作为Android的一个本地服务,负责处理诸如SD、USB等存储类设备的插拔等事件。

1. 总体架构

Vold服务由volumeManager统一管控,它将具体任务分别分派给netlinkManager, commandListener, directVolume, Volume去完成。

Vold服务向下通过socket机制与底层驱动交互,向上通过JNI, intent, socket, doCommand等机制与Java Framework交互。

 

 2 流程概览

2.1 开启服务

初始化Android系统时开启Vold本地服务,

Vold在/dev/block下创建vold文件夹,开启VolumeManager, NetlinkManager, CommandListener。

2.2 引导Uevent

NetlinkManager负责监听底层Linux上报的uevent事件。

 

系统的SocketListner统一管理所有socket事件。

NetlinkListner负责解析socket事件。

最后由onEvent()将vold事件交还给NetlinkManager处理。

2.3 处理Block和Switch事件

NetlinkManager调用VolumeManager中处理vold事件的类。

handleBlockEvent()完成SD的挂载和卸载,具体交由DirectVolume完成。

handleSwitchEvent()完成由USB实现的U盘的连接。

两者最后都是通过setBroadcast()将ResponsibleCode经过nativeDaemonConnector的socket监听机制,最终上传到MountService作统一规划。

 

MountService里的onEvent()得到解析后的事件,完成两大任务,

-> 发送命令doCommand()通过commandListener传递给volumeManager

-> 将事件信息广播给相关服务,供上层应用使用。

 

 

又一篇

Uevent是内核通知android有状态变化的一种方法,比如USB线插入、拔出,电池电量变化等等。其本质是内核发送(可以通过socket)一个字符串,应用层(android)接收并解释该字符串,获取相应信息。

一、Kernel侧:

UEVENT的发起在Kernel端,主要是通过函数

int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,

                     char *envp_ext[])

该函数的主要功能是根据参数组合一个字符串并发送。一个典型的字符串如下:change@/devices/platform/msm-battery/power_supply/usb纮ACTION=change纮DEVPATH=/devices/platform/msm-battery/power_supply/usb纮SUBSYSTEM=power_supply纮POWER_SUPPLY_NAME=usb纮POWER_SUPPLY_ONLINE=0纮SEQNUM=1486纮

首先,准备各个字符串:

1.准备字符串

1)获取action字符串

*action_string = kobject_actions[action];

Action为KOBJ_ADD等,kobject_actions的定义如下:

static const char *kobject_actions[] = {

       [KOBJ_ADD] =            "add",

       [KOBJ_REMOVE] =           "remove",

       [KOBJ_CHANGE] =            "change",

       [KOBJ_MOVE] =         "move",

       [KOBJ_ONLINE] =             "online",

       [KOBJ_OFFLINE] =     "offline",

};

2)获取subsystem字符串

subsystem = kobject_name(&kset->kobj);

static inline const char *kobject_name(const struct kobject *kobj)

{

       return kobj->name;

}

这里主要获取kobj的名字。

以“power_supply”为例,在power_supply_core.c中注册class power_supply:

power_supply_class = class_create(THIS_MODULE, "power_supply");

将调用以下函数class_createà __class_createà __class_registerà kobject_set_name:

error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name);

其中的cls->name就是“power_class”最终在kobject_set_name_vargs中赋值给kobject->name

3)devpath字符串,是改变了的uevent所在的sysfs中的位置

devpath = kobject_get_path(kobj, GFP_KERNEL);

2.填充字符串

然后分配一个env空间存储字符串,

env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);将上面这些字符串填充到其中去,

retval = add_uevent_var(env, "ACTION=%s", action_string);

retval = add_uevent_var(env, "DEVPATH=%s", devpath);

retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);

接着加入不同class的附加的字符串

retval = add_uevent_var(env, "%s", envp_ext[i]);

retval = uevent_ops->uevent(kset, kobj, env);

然后加上该Uenvent的序号,该序号是不断递增的。

add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)seq)。

3.发送

字符串准备完毕,就要准备发送了,由于Android的CONFIG_NET选项是选上的,因此可以通过socket发送:

首先分配一个skb用于存储网络发送的数据

scratch = skb_put(skb, len);

sprintf(scratch, "%s@%s", action_string, devpath);

此时scratch中就增加了change@/devices/platform/msm-battery/power_supply/usb的字符,然后将之前准备好的各个字符传加在后面

for (i = 0; i < env->envp_idx; i++) {

       len = strlen(env->envp[i]) + 1;

scratch = skb_put(skb, len);

       strcpy(scratch, env->envp[i]);

}

最后发送调用retval = netlink_broadcast_filtered发送就OK了。

二、Android侧:

1.启动监视

private UEventObserver mPowerSupplyObserver = new UEventObserver()

{

   @Override

public void onUEvent(UEventObserver.UEvent event) {

            update();

        }
}

申明一个observer对象,然后调用startObserving启动对该对象的监视。

mPowerSupplyObserver.startObserving("SUBSYSTEM=power_supply");

最终会调用到UEventObserver的addObserver:

private ArrayList<Object> mObservers = new ArrayList<Object>();

public void addObserver(String match, UEventObserver observer) {

   synchronized(mObservers) {

       mObservers.add(match);

      mObservers.add(observer);

   }

}

该函数最终会将”SUBSYSTEM=power_supply”增加到匹配序列中,当kernel发送具有该字符串的数据时,就返回匹配成功,然后调用mPowerSupplyObserver的onUEvent函数;

public void run() {

            ………………….

            while (true) {

                len = next_event(buffer);

                if (len > 0) {

                    String bufferStr = new String(buffer, 0, len);  // easier to search a String

                    synchronized (mObservers) {

                        for (int i = 0; i < mObservers.size(); i += 2) {

                            if (bufferStr.indexOf((String)mObservers.get(i)) != -1) {

                                ((UEventObserver)mObservers.get(i+1))

                                        .onUEvent(new UEvent(bufferStr));

                            }

                        }

                    }

                }

            }

        }

next_event(buffer)从底层接收数据,然后在for循环中比较,如果符合,则调用onUevent。之所以for循环时要加2,是因为一次startObserving是调用了两次mObservers.add,其中第一次的是匹配字符串。

2.JNI函数

其中next_event是一个JNI函数(android_os_UEventObserver.c):

private static native int next_event(byte[] buffer);

static JNINativeMethod gMethods[] = {

    {"native_setup", "()V",   (void *)android_os_UEventObserver_native_setup},

    {"next_event",   "([B)I", (void *)android_os_UEventObserver_next_event},

};

android_os_UEventObserver_next_event会调用到uevent_next_event,

3.Socket接口

在hardware/libhardware_legacy/uevent/vim uevent.c中,

int uevent_next_event(char* buffer, int buffer_length)

该函数监听socket,并将socket收到的数据保存到buffer中

nr = poll(&fds, 1, -1);

   

if(nr > 0 && fds.revents == POLLIN) {

    int count = recv(fd, buffer, buffer_length, 0);

if (count > 0) {

………………………………..

}

该socket是在int uevent_init()中创建的

s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);

fd=s;

 

1.kobject, ktype, kset

kobject代表sysfs中的目录。

ktype代表kobject的类型,主要包含release函数和attr的读写函数。比如,所有的bus都有同一个bus_type;所有的class都有同一个class_type。

kset包含了subsystem概念,kset本身也是一个kobject,所以里面包含了一个kobject对象。另外,kset中包含kset_uevent_ops,里面主要定义了三个函数

       int (*filter)(struct kset *kset, struct kobject *kobj);

       const char *(*name)(struct kset *kset, struct kobject *kobj);

       int (*uevent)(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env);

这三个函数都与uevent相关。filter用于判断uevent是否要发出去。name用于得到subsystem的名字。uevent用于填充env变量。

2.uevent内核部分

uevent是sysfs向用户空间发出的消息。比如,device_add函数中,会调用kobject_uevent(&dev->kobj, KOBJ_ADD); 这里kobj是发消息的kobj,KOBJ_ADD是发出的事件。uevent的事件在kobject_action中定义:

enum kobject_action {

       KOBJ_ADD,

       KOBJ_REMOVE,

       KOBJ_CHANGE,

       KOBJ_MOVE,

       KOBJ_ONLINE,

       KOBJ_OFFLINE,

       KOBJ_MAX

};

 

int kobject_uevent(struct kobject *kobj, enum kobject_action action)

{

       return kobject_uevent_env(kobj, action, NULL);

}

 

kobject_uevent_env:

       由kobject的parent向上查找,直到找到一个kobject包含kset。

       如果kset中有filter函数,调用filter函数,看看是否需要过滤uevent消息。

       如果kset中有name函数,调用name函数得到subsystem的名字;否则,subsystem的名字是kset中kobject的名字。

       分配一个kobj_uevent_env,并开始填充env环境变量:

       增加环境变量ACTION=<action name>

       增加环境变量DEVPATH=<kobj’s path>

       增加环境变量SUBSYSTEM=<subsystem name>

       增加环境变量kobject_uevent_env中参数envp_ext指定的环境变量。

       调用kset的uevent函数,这个函数会继续填充环境变量。

       增加环境变量SEQNUM=<seq>,这里seq是静态变量,每次累加。

       调用netlink发送uevent消息。

       调用uevent_helper,最终转换成对用户空间sbin/mdev的调用。

3.uevent用户空间部分

uevent的用户空间程序有两个,一个是udev,一个是mdev。

udev通过netlink监听uevent消息,它能完成两个功能:

       1.自动加载模块

       2.根据uevent消息在dev目录下添加、删除设备节点。

另一个是mdev,mdev在busybox的代码包中能找到,它通过上节提到的uevent_helper函数被调用。

 

下面简要介绍udev的模块自动加载过程:

etc目录下有一个uevent规则文件/etc/udev/rules.d/50-udev.rules

udev程序收到uevent消息后,在这个规则文件里匹配,如果匹配成功,则执行这个匹配定义的shell命令。例如,规则文件里有这么一行:

ACTION=="add", SUBSYSTEM=="?*", ENV{MODALIAS}=="?*", RUN+="/sbin/modprobe $env{MODALIAS}"

所以,当收到uevent的add事件后,shell能自动加载在MODALIAS中定义的模块。

 

mdev的模块自动加载过程与之类似,它的配置文件在/etc/mdev.conf中。例如:

$MODALIAS=.* 0:0 660 @modprobe "$MODALIAS"

这条规则指的是:当收到的环境变量中含有MODALIAS,那么加载MODALIAS代表的模块。

mdev的详细说明在busybox的docs/mdev.txt中。

4.uevent在设备驱动模型中的应用

在sys目录下有一个子目录devices,代表一个kset。

创建设备时,调用的device_initialize函数中,默认会把kset设置成devices_kset,即devices子目录代表的kset。

devices_kset中设置了uevent操作集device_uevent_ops。

static struct kset_uevent_ops device_uevent_ops = {

       .filter =    dev_uevent_filter,

       .name =   dev_uevent_name,

       .uevent = dev_uevent,

};

 

dev_uevent_filter中,主要是规定了要想发送uevent,dev必须有class或者bus。

dev_uevent_name中,返回dev的class或者bus的名字。

dev_uevent函数:

       如果dev有设备号,添加环境变量MAJOR与MINOR。

       如果dev->type有值,设置DEVTYPE=<dev->type->name>。

       如果dev->driver,设置DRIVER=<dev->driver->name>。

       如果有bus,调用bus的uevent函数。

       如果有class,调用class的uevent函数。

如果有dev->type,调用dev->type->uevent函数。

 

一般在bus的uevent函数中,都会添加MODALIAS环境变量,设置成dev的名字。这样,uevent传到用户空间后,就可以通过对MODALIAS的匹配自动加载模块。这样的bus例子有platform和I2C等等。

1. 总体架构

2. 流程概览

2.1 开启Vold

2.2 引导Uevent

2.3 处理事件


 

Vold - Volume Daemon存储类的守护进程,作为Android的一个本地服务,负责处理诸如SD、USB等存储类设备的插拔等事件。

1. 总体架构

Vold服务由volumeManager统一管控,它将具体任务分别分派给netlinkManager, commandListener, directVolume, Volume去完成。

Vold服务向下通过socket机制与底层驱动交互,向上通过JNI, intent, socket, doCommand等机制与Java Framework交互。

 

 2 流程概览

2.1 开启服务

初始化Android系统时开启Vold本地服务,

Vold在/dev/block下创建vold文件夹,开启VolumeManager, NetlinkManager, CommandListener。

2.2 引导Uevent

NetlinkManager负责监听底层Linux上报的uevent事件。

 

系统的SocketListner统一管理所有socket事件。

NetlinkListner负责解析socket事件。

最后由onEvent()将vold事件交还给NetlinkManager处理。

2.3 处理Block和Switch事件

NetlinkManager调用VolumeManager中处理vold事件的类。

handleBlockEvent()完成SD的挂载和卸载,具体交由DirectVolume完成。

handleSwitchEvent()完成由USB实现的U盘的连接。

两者最后都是通过setBroadcast()将ResponsibleCode经过nativeDaemonConnector的socket监听机制,最终上传到MountService作统一规划。

 

MountService里的onEvent()得到解析后的事件,完成两大任务,

-> 发送命令doCommand()通过commandListener传递给volumeManager

-> 将事件信息广播给相关服务,供上层应用使用。


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 善心汇会员被限制出行怎么办 苹果手机屏碎了怎么办 三国志2017仓库满了怎么办 至尊宝密保手机被改了怎么办 电脑版qq游戏途中死机怎么办 登陆企业qq服务器超时怎么办 qq登录不上怎么办错误00001 苹果手机白屏进不了界面怎么办 苹果8p手机白屏怎么办 苹果5s无法关机怎么办 三星手机白屏了怎么办 电脑能上qq打不开网页怎么办? 电脑打不开机怎么办黑屏 英雄杀连接不上怎么办 炫舞时登陆器卸下载失败怎么办 电视为什么会出现登录超时怎么办 登录app时提示请求超时怎么办 手机来短信断网怎么办 百度网络不给力怎么办 网络连接不给力怎么办 抖音网络不给力怎么办 玩lol老是掉线怎么办 无线网络连接有防火墙怎么办啊 千牛重新获取enc-k怎么办 为什么说不能获取对方信息怎么办 qq炫舞客户端被修改怎么办 win7玩穿越卡顿怎么办 玩dnf就蓝屏怎么办win7 平板玩fgo闪退怎么办 微信总是说空间不足怎么办 激活卡时遇到服务器错误怎么办 悦平台服务器错误是怎么办 手机银行提示登录服务器错误怎么办 qq漂流瓶封了怎么办 我的世界被冻结怎么办 qq里被屏蔽了怎么办 qq领手游礼包账号存在异常怎么办 笔记本电脑太卡怎么办最有效 华为平板电脑忘记开机密码怎么办 平板电脑忘记开机密码怎么办 平板电脑忘了开机密码怎么办