android-uevent 简记

本文介绍了Linux的sysfs机制及UEvent的工作原理,并详细解释了Android系统中UEvent的应用方式,包括Java层的UEventObserver类使用方法、JNI层的具体实现及HAL层的事件监听流程。
摘要由CSDN通过智能技术生成

简述

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(&drv->p->kobj, KOBJ_ADD);  
kobject_uevent_env(kobj, action, NULL);  
retval = netlink_broadcast_filtered(uevent_sock, skb,0, 1, GFP_KERNEL,kobj_bcast_filter,kobj);  

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

  KOBJ_ADD,  
  KOBJ_REMOVE,  
  KOBJ_CHANGE,  
  KOBJ_MOVE,  
  KOBJ_ONLINE,                                                                                                                             
  KOBJ_OFFLINE,  

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

其中, kset_uevent_ops 有以下几种:

  slab_uevent_ops  
  bus_uevent_ops  
  device_uevent_ops  
  gfs2_uevent_ops  
  module_uevent_ops  

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

Userspace

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

Android

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

@Override
    public void run() {                                                                                                                                  
        nativeSetup();

        while (true) {
            String message = nativeWaitForNextEvent();
            if (message != null) {
                if (DEBUG) {
                    Log.d(TAG, message);
                }
                sendEvent(message);
            }
        }
    }

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

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

public BatteryService(Context context) {
    // watch for invalid charger messages if the invalid_charger switch exists
    if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) {
        UEventObserver invalidChargerObserver = new UEventObserver() {
            @Override
            public void onUEvent(UEvent event) {
                final int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0;
                synchronized (mLock) {
                    if (mInvalidCharger != invalidCharger) {
                        mInvalidCharger = invalidCharger;
                    }
                }
            }
        };
        invalidChargerObserver.startObserving(
                "DEVPATH=/devices/virtual/switch/invalid_charger");
    } 
}

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

其发生的调用过程如下:

  startObserving()  
  addObserver()  
  nativeAddMatch()  

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

JNI

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

static void nativeSetup(JNIEnv *env, jclass clazz);  
static jstring nativeWaitForNextEvent(JNIEnv *env, jclass clazz);  
static void nativeAddMatch(JNIEnv* env, jclass clazz, jstring matchStr);  
static void nativeRemoveMatch(JNIEnv* env, jclass clazz, jstring matchStr);  

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

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

HAL

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

int uevent_init();  
int uevent_next_event(char* buffer, int buffer_length);

它们详细实现如下:

int uevent_init()
{
    struct sockaddr_nl addr;
    int sz = 64*1024;
    int s;

    memset(&addr, 0, sizeof(addr));
    addr.nl_family = AF_NETLINK;
    addr.nl_pid = getpid();
    addr.nl_groups = 0xffffffff;

    s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
    if(s < 0)
        return 0;

    setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz));

    if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
        close(s);
        return 0;
    }

    fd = s;
    return (fd > 0);
}  

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

int uevent_next_event(char* buffer, int buffer_length)
{
    while (1) {
        struct pollfd fds;
        int nr;
    
        fds.fd = fd;
        fds.events = POLLIN;
        fds.revents = 0;
        nr = poll(&fds, 1, -1);
     
        if(nr > 0 && (fds.revents & POLLIN)) {
            int count = recv(fd, buffer, buffer_length, 0);
            if (count > 0) {
                struct uevent_handler *h;
                pthread_mutex_lock(&uevent_handler_list_lock);
                LIST_FOREACH(h, &uevent_handler_list, list)
                    h->handler(h->handler_data, buffer, buffer_length);
                pthread_mutex_unlock(&uevent_handler_list_lock);

                return count;
            } 
        }
    }
    
    // won't get here
    return 0;
}  

以上的实现中,采用了 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这个类中做了一个单例的线程,

[cpp]  view plain  copy
  1. private static UEventThread getThread() {  
  2.     synchronized (UEventObserver.class) {  
  3.         if (sThread == null) {  
  4.             sThread = new UEventThread();  
  5.             sThread.start();  
  6.         }  
  7.         return sThread;  
  8.     }  
  9. }  
下面我们分析下这个UEventThread类的run函数

[cpp]  view plain  copy
  1. @Override  
  2. public void run() {  
  3.     nativeSetup();  
  4.   
  5.     while (true) {  
  6.         String message = nativeWaitForNextEvent();  
  7.         if (message != null) {  
  8.             if (DEBUG) {  
  9.                 Log.d(TAG, message);  
  10.             }  
  11.             sendEvent(message);  
  12.         }  
  13.     }  
  14. }  

我们先来看nativeSetup函数

[cpp]  view plain  copy
  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");  
  5.     }  
  6. }  
而uevent_init函数,也是创建了一个Netlink socket和正常接收uevent事件流程一样。

[cpp]  view plain  copy
  1. int uevent_init()  
  2. {  
  3.     struct sockaddr_nl addr;  
  4.     int sz = 64*1024;  
  5.     int s;  
  6.   
  7.     memset(&addr, 0, sizeof(addr));  
  8.     addr.nl_family = AF_NETLINK;  
  9.     addr.nl_pid = getpid();  
  10.     addr.nl_groups = 0xffffffff;  
  11.   
  12.     s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);  
  13.     if(s < 0)  
  14.         return 0;  
  15.   
  16.     setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz));  
  17.   
  18.     if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {  
  19.         close(s);  
  20.         return 0;  
  21.     }  
  22.   
  23.     fd = s;  
  24.     return (fd > 0);  
  25. }  
然后在UEventThread一直循环调用nativeWaitForNextEvent函数

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

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

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

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

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

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

[cpp]  view plain  copy
  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.             }  
  11.         }  
  12.     }  
  13.   
  14.     if (!mTempObserversToSignal.isEmpty()) {  
  15.         final UEvent event = new UEvent(message);  
  16.         final int N = mTempObserversToSignal.size();  
  17.         for (int i = 0; i < N; i++) {  
  18.             final UEventObserver observer = mTempObserversToSignal.get(i);  
  19.             observer.onUEvent(event);//遍历所有满足的Observer,调用器onUEvent函数  
  20.         }  
  21.         mTempObserversToSignal.clear();  
  22.     }  
  23. }  


二、注册监控

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

[cpp]  view plain  copy
  1. public final void startObserving(String match) {  
  2.     if (match == null || match.isEmpty()) {  
  3.         throw new IllegalArgumentException("match substring must be non-empty");  
  4.     }  
  5.   
  6.     final UEventThread t = getThread();  
  7.     t.addObserver(match, this);  
  8. }  

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

[cpp]  view plain  copy
  1. public void addObserver(String match, UEventObserver observer) {  
  2.     synchronized (mKeysAndObservers) {  
  3.         mKeysAndObservers.add(match);  
  4.         mKeysAndObservers.add(observer);  
  5.         nativeAddMatch(match);  
  6.     }  
  7. }  

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

[cpp]  view plain  copy
  1. static void nativeAddMatch(JNIEnv* env, jclass clazz, jstring matchStr) {  
  2.     ScopedUtfChars match(env, matchStr);  
  3.   
  4.     AutoMutex _l(gMatchesMutex);  
  5.     gMatches.add(String8(match.c_str()));  
  6. }  

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

三、实例

我们举个BatteryService中的实例

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

[cpp]  view plain  copy
  1. if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) {  
  2.     mInvalidChargerObserver.startObserving(  
  3.             "DEVPATH=/devices/virtual/switch/invalid_charger");  
  4. }  
onUEvent就是监测到之后的处理函数

[cpp]  view plain  copy
  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;  
  8.             }  
  9.         }  
  10.     }  
  11. };  


Android系统中,可以使用uevent来检测U盘的挂载地址。具体步骤如下: 1. 在Android系统中,U盘的插入和拔出都会发送uevent消息,可以通过注册一个uevent监听器来接收这些消息。 2. 在uevent消息中,会包含有U盘的一些信息,比如设备名称、挂载路径等。 3. 在接收到U盘插入消息时,可以解析uevent消息中的挂载路径信息,即可获得U盘的挂载地址。 以下是一个使用uevent检测U盘挂载地址的示例代码: ``` import android.os.Handler; import android.os.Looper; import android.os.MessageQueue; import android.util.Log; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; public class UdiskMonitor { private static final String TAG = "UdiskMonitor"; private static final String UDISK_MOUNT_PATH = "/sys/class/android_usb/android0/f_mass_storage/lun/file"; private OnUdiskMountedListener mListener; public void startMonitor(final OnUdiskMountedListener listener) { mListener = listener; // 在主线程中监听uevent消息 new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { // 开始监听uevent消息 startUeventMonitor(); return false; } }); } }); } private void startUeventMonitor() { BufferedReader reader = null; try { // 打开uevent监听文件 reader = new BufferedReader(new FileReader(new File("/proc/net/netlink"))); String line; while ((line = reader.readLine()) != null) { // 解析uevent消息 String[] parts = line.split(" "); if (parts.length >= 6 && parts[0].equals("1")) { String action = parts[1]; String devPath = parts[4]; if (action.equals("add") && devPath.equals(UDISK_MOUNT_PATH)) { // U盘被插入,获取挂载路径并回调监听器 String mountPath = getUdiskMountPath(); if (mountPath != null) { if (mListener != null) { mListener.onUdiskMounted(mountPath); } } } } } } catch (IOException e) { Log.e(TAG, "startUeventMonitor: " + e.getMessage()); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { Log.e(TAG, "startUeventMonitor: " + e.getMessage()); } } } } private String getUdiskMountPath() { String mountPath = null; try { // 读取U盘挂载路径 BufferedReader reader = new BufferedReader(new FileReader(new File(UDISK_MOUNT_PATH))); mountPath = reader.readLine().trim(); reader.close(); } catch (IOException e) { Log.e(TAG, "getUdiskMountPath: " + e.getMessage()); } return mountPath; } public interface OnUdiskMountedListener { void onUdiskMounted(String mountPath); } } ``` 在上述代码中,我们通过监听uevent消息来检测U盘的插入和拔出事件。当接收到U盘插入事件时,我们通过读取U盘的挂载路径来获取U盘的挂载地址,并通过回调监听器来通知外部应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值