2

android-uevent 简记

 2 years ago
source link: https://blog.csdn.net/prike/article/details/78492613
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

android-uevent 简记

颇锐克 于 2017-11-09 18:28:37 发布 4577

sysfs 是 Linux userspace 和 kernel 进行交互的一个媒介。通过 sysfs,userspace 可以主动去读写 kernel 的一些数据,同样的, kernel 也可以主动将一些“变化”告知给 userspace。也就是说,通过sysfs,userspace 和 kernel 的交互,本质上是双向的。

userspace 通过 sysfs 访问 kernel 数据的方法,便是大名鼎鼎的 show() / store() 方法:只要在 kernel 提供了对应的 show() / store() 方法,用户便可以通过 shell 用户,cd 进入到相应的目录,使用 cat / echo 操作对应的文件节点即可。而 kernel ,通过 sysfs 将一些 kernel 的“变化”“告知”给 userspace 则是通过 uevent 的方式。

一般来说,Kernel 会发送一个字符串给 userspace,然后 userspace 来解析处理该字符串,比如android7.0上 HDMI 热插拔的 event 字符串: change@/devices/virtual/switch/hdmi 。 该字符串的路径为 “/sys/devices/virtual/switch/hdmi/change” ,届时 userspace 的监听线程去读取该文件节点即可。

在 Linux-3.x 上是基于 NetLink 来实现的。其实现思路是,首先在内核中调用 netlink_kernel_create() 函数创建一个socket套接字;当有事件发生的时候,则通过 kobject_uevent() 最终调用 netlink_broadcast_filtered() 向 userspace 发送数据。如果同时在 userspace ,有在监听该事件,则两相一合,kernel 的“变化”,userspace 即刻知晓。

Kernel

kernel ,关于 uevent 的实现代码,大约可参考文件 kobject_uevent.c ,其简要调用如下:

其中,kobject_uevent(struct kobject *kobj, enum kobject_action action) 中的 action 对应着以下几种:

而 kobject_uevent() 其实就是直接调用了 kobject_uevent_env() 函数。一切的操作,将在该函数中完成,比如 kset uevent ops (struct kset_uevent_ops)的获取、字符串的填充组合、netlink message 的发送等。

其中, kset_uevent_ops 有以下几种:

这些 uevent ops 在 start_kernel() 就会被注册。

Userspace

此处仅记述 android 的学习,理论上,非 android 的实现原理应该也是一样的。 android 实现则是按照 android 的体系架构,java 文件通过 jni 到 hal 层来实现的 userspace 监听。

Android

在高通平台的 android 7.0 版本上,Android java 提供了一个 UEventObserver 类。在该类中有一个事件线程 UEventThread,该线程中将重新实现了一个 run() 方法:

其中的 nativeSetup() 和 nativeWaitForNextEvent() 即是通过 JNI 来实现的: nativeSetup() 创立绑定 socket 套接字;nativeWaitForNextEvent() 则是通过调用recv()函数监听套接字事件。

那么如何在 android-java 使用该类呢?下面以 BatteryService 为例:

第一步: new 一个 UEventObserver();
第二步: startObserving();

其发生的调用过程如下:

nativeAddMatch() 依然是通过 JNI 来实现的,其目的是为了将 startObserving() 的参数增加到匹配序列中,当内核发送具有该参数的数据时,就返回匹配成功,然后调用 BatteryService 的onUEvent函数。
以上函数,大约可参考文件 UEventObserver.java 和 BatteryService.java。

在 JNI 层为 uevent 提供了4个封装:

nativeSetup() 调用 uevent_init()创建绑定套接字;
nativeWaitForNextEvent() 调用 uevent_next_event() 循环接收套接字数据;
nativeAddMatch() 添加需要监听的字符串到 gMatches 全局变量;
nativeRemoveMatch() 做 nativeAddMatch 逆操作;

以上内容,在android7.0 上可参考文件 android_os_UEventObserver.cpp

在 HAL 层,最主要的就是以下两个函数:

它们详细实现如下:

以上函数就是Linux编程中最基本的套接字操作;

以上的实现中,采用了 poll() 函数 + recv() 函数的方式实现了对事件的监听。其中, poll() 和 select() 类似,在一定的条件下可以互相替用; recv() 相当于 read() 函数,其有阻塞和非阻塞两种用法。是否阻塞,需要使用函数 setsockopt() 来设置套接字的属性。

以上内容,在 android 7.0 上可以参看 hardware/ 目录下的 uevent.c 文件。

从网上看到了一点资料,说是在 uevent 这个部分还可以优化的。基本的思路就是,把收不到的和永远不会使用到的 uevent 去掉,不让它在 kernel 发出来。相关文章链接如下: Udev 内核机制(kobject_uevent) 性能优化

android提供了UEventObserver这个类来使java可以监听uevent事件,这个类是一个抽象类,使用这个类必须实现onUEvent函数。

一、监控过程

在UEventObserver这个类中做了一个单例的线程,

  1. private static UEventThread getThread() {  
  2.     synchronized (UEventObserver.class) {  
  3.         if (sThread == null) {  
  4.             sThread = new UEventThread();  
  5.             sThread.start();  
  6.         return sThread;  

下面我们分析下这个UEventThread类的run函数

  1. @Override  
  2. public void run() {  
  3.     nativeSetup();  
  4.     while (true) {  
  5.         String message = nativeWaitForNextEvent();  
  6.         if (message != null) {  
  7.             if (DEBUG) {  
  8.                 Log.d(TAG, message);  
  9.             sendEvent(message);  

我们先来看nativeSetup函数

  1. static void nativeSetup(JNIEnv *env, jclass clazz) {  
  2.     if (!uevent_init()) {  
  3.         jniThrowException(env, "java/lang/RuntimeException",  
  4.                 "Unable to open socket for UEventObserver");  

而uevent_init函数,也是创建了一个Netlink socket和正常接收uevent事件流程一样。

  1. int uevent_init()  
  2.     struct sockaddr_nl addr;  
  3.     int sz = 64*1024;  
  4.     int s;  
  5.     memset(&addr, 0, sizeof(addr));  
  6.     addr.nl_family = AF_NETLINK;  
  7.     addr.nl_pid = getpid();  
  8.     addr.nl_groups = 0xffffffff;  
  9.     s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);  
  10.     if(s < 0)  
  11.         return 0;  
  12.     setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz));  
  13.     if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {  
  14.         close(s);  
  15.         return 0;  
  16.     fd = s;  
  17.     return (fd > 0);  

然后在UEventThread一直循环调用nativeWaitForNextEvent函数

  1. static jstring nativeWaitForNextEvent(JNIEnv *env, jclass clazz) {  
  2.     char buffer[1024];  
  3.     for (;;) {  
  4.         int length = uevent_next_event(buffer, sizeof(buffer) - 1);  
  5.         if (length <= 0) {  
  6.             return NULL;  
  7.         buffer[length] = '\0';  
  8.         ALOGV("Received uevent message: %s", buffer);  
  9.         if (isMatch(buffer, length)) {//是否匹配  
  10.             // Assume the message is ASCII.  
  11.             jchar message[length];  
  12.             for (int i = 0; i < length; i++) {  
  13.                 message[i] = buffer[i];  
  14.             return env->NewString(message, length);  

uevent_next_event函数就是接收Netlink socket的数据

  1. int uevent_next_event(char* buffer, int buffer_length)  
  2.     while (1) {  
  3.         struct pollfd fds;  
  4.         int nr;  
  5.         fds.fd = fd;  
  6.         fds.events = POLLIN;  
  7.         fds.revents = 0;  
  8.         nr = poll(&fds, 1, -1);  
  9.         if(nr > 0 && (fds.revents & POLLIN)) {  
  10.             int count = recv(fd, buffer, buffer_length, 0);  
  11.             if (count > 0) {  
  12.                 struct uevent_handler *h;  
  13.                 pthread_mutex_lock(&uevent_handler_list_lock);  
  14.                 LIST_FOREACH(h, &uevent_handler_list, list)  
  15.                     h->handler(h->handler_data, buffer, buffer_length);  
  16.                 pthread_mutex_unlock(&uevent_handler_list_lock);  
  17.                 return count;  
  18.     // won't get here  
  19.     return 0;  

我们再来看isMatch函数,就是看数据和我们的gMatches中是否有匹配的

  1. static bool isMatch(const char* buffer, size_t length) {  
  2.     AutoMutex _l(gMatchesMutex);  
  3.     for (size_t i = 0; i < gMatches.size(); i++) {  
  4.         const String8& match = gMatches.itemAt(i);  
  5.         // Consider all zero-delimited fields of the buffer.  
  6.         const char* field = buffer;  
  7.         const char* end = buffer + length + 1;  
  8.             if (strstr(field, match.string())) {  
  9.                 ALOGV("Matched uevent message with pattern: %s", match.string());  
  10.                 return true;  
  11.             field += strlen(field) + 1;  
  12.         } while (field != end);  
  13.     return false;  

最后由匹配的数据,我们再UEventThread中调用sendEvent函数

  1. private void sendEvent(String message) {  
  2.     synchronized (mKeysAndObservers) {  
  3.         final int N = mKeysAndObservers.size();  
  4.         for (int i = 0; i < N; i += 2) {  
  5.             final String key = (String)mKeysAndObservers.get(i);  
  6.             if (message.contains(key)) {//注册的observer时候有匹配的  
  7.                 final UEventObserver observer =  
  8.                         (UEventObserver)mKeysAndObservers.get(i + 1);//match的下一个就是Observer  
  9.                 mTempObserversToSignal.add(observer);//把匹配的Observer保存在临时变量中  
  10.     if (!mTempObserversToSignal.isEmpty()) {  
  11.         final UEvent event = new UEvent(message);  
  12.         final int N = mTempObserversToSignal.size();  
  13.         for (int i = 0; i < N; i++) {  
  14.             final UEventObserver observer = mTempObserversToSignal.get(i);  
  15.             observer.onUEvent(event);//遍历所有满足的Observer,调用器onUEvent函数  
  16.         mTempObserversToSignal.clear();  

二、注册监控

我们在调用startObserving后,就把我们的监控放入线程汇总

  1. public final void startObserving(String match) {  
  2.     if (match == null || match.isEmpty()) {  
  3.         throw new IllegalArgumentException("match substring must be non-empty");  
  4.     final UEventThread t = getThread();  
  5.     t.addObserver(match, this);  

UEventThread函数的addObserver函数,把match和Observer都放入了mKeysAndObservers中,在使用的时候我们取match的下一个就是Observer了

  1. public void addObserver(String match, UEventObserver observer) {  
  2.     synchronized (mKeysAndObservers) {  
  3.         mKeysAndObservers.add(match);  
  4.         mKeysAndObservers.add(observer);  
  5.         nativeAddMatch(match);  

我们再看下nativeAddMatch函数,在这个函数中把match直接接入到gMatches的全局变量中。

  1. static void nativeAddMatch(JNIEnv* env, jclass clazz, jstring matchStr) {  
  2.     ScopedUtfChars match(env, matchStr);  
  3.     AutoMutex _l(gMatchesMutex);  
  4.     gMatches.add(String8(match.c_str()));  

注销监控过程比较简单就不说了。

我们举个BatteryService中的实例

DEVPATH=/devices/virtual/switch/invalid_charger 就是我们监测的match

  1. if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) {  
  2.     mInvalidChargerObserver.startObserving(  
  3.             "DEVPATH=/devices/virtual/switch/invalid_charger");  

onUEvent就是监测到之后的处理函数

  1. private final UEventObserver mInvalidChargerObserver = new UEventObserver() {  
  2.     @Override  
  3.     public void onUEvent(UEventObserver.UEvent event) {  
  4.         final int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0;  
  5.         synchronized (mLock) {  
  6.             if (mInvalidCharger != invalidCharger) {  
  7.                 mInvalidCharger = invalidCharger;  

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK