跳转至

INotify与Epoll机制

INotify机制

INotify 是Linux提供给用户态监听内核文件系统变化的机制,可以监听文件/目录的增删等。INotify 的用法很简单,首先需要调用如下代码创建一个文件描述符:

int inotifyfd = inotify_init();

接着需要通过 inotify_add_watch 将我们关注的事件添加到监听:

int wd = inotify_add_watch(inotifyfd, path, event_mask)

inotify_add_watch 的第一个参数是 inotify_init 创建的文件描述符,第二个参数是要监听的路径,第三个参数是事件的类型,如文件创建 IN_CREATE,文件删除 IN_DELETE 等。

上面两步完成之后当指定路径发生了我们想要监听的事件就会写到inotifyfd中,此时就可以通过read函数对inotifyfd进行读取:

char event_buf[512];
int ret;
struct inotify_event *event;
ret = read(inotifyfd, event_buf, sizeof(event_buf));

读取到的信息封装为 inotify_event 结构体,使用while循环就可以将所有事件读取出来:

while(ret > (int)sizeof(struct inotify_event)) {
    event = (struct inotify_event*)(event_buf + event_pos);
    ......
}

inotify_event 结构体的信息如下

struct inotify_event {
    __s32       wd;     /* watch descriptor */
    __u32       mask;       /* watch mask */
    __u32       cookie;     /* cookie to synchronize two events */
    __u32       len;        /* length (including nulls) of name */
    char        name[0];    /* stub for possible name */
};

其实INotify的用法就三步:

  • 使用inotify_init创建一个inotify对象
  • 使用inotify_add_watch对文件路径进行监听
  • 使用read对读取监听到的事件
  • 其实Android SDK就提供了一个监听文件的类FileObserver,它的底层原理就是使用的INotify机制,有兴趣可以去看看它内部的几个native函数。

接下来进入实践环节,写一个简单的INotify测试类,如下目录创建两个文件:

实践

Android.bp

cc_binary {
    name: "inotify-demo",
    srcs: ["INotifyDemo.cpp"],
    shared_libs: [
        "liblog",
    ],
}

INotifyDemo.cpp

#include <sys/inotify.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <log/log.h>

#define TAG_DEMO "INotifyDemo"

#undef ALOGD
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG_DEMO, __VA_ARGS__)

#undef ALOGE
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG_DEMO, __VA_ARGS__)


int read_events(int fd) {
    char event_buf[512];
    int ret;
    int event_pos = 0;
    int event_size = 0;
    struct inotify_event *event;

    ALOGD("block read!");

    //通过read函数读取目标文件路径发生的事件,没有事件则阻塞
    ret = read(fd, event_buf, sizeof(event_buf));

    //read的返回值表示实际读取的事件大小,如果小于一个事件大小则说明读取事件失败
    if (ret < (int) sizeof(struct inotify_event)) {
        ALOGE("read error, could get event!");
        return -1;
    }

    //将所有事件循环取出来
    while (ret > (int) sizeof(struct inotify_event)) {
        event = (struct inotify_event *) (event_buf + event_pos);
        ALOGD("name = %s, len = %d", event->name, event->len);
        if (event->len) {
            ALOGD("mask = %d", event->mask);
            if (event->mask & IN_CREATE) {
                //文件创建
                ALOGD("create file[%s] successfully", event->name);
            } else {
                //文件删除
                ALOGD("delete file[%s] successfully", event->name);
            }
        }
        event_size = sizeof(struct inotify_event) + event->len;
        ret -= event_size;
        event_pos += event_size;
    }
    return 0;
}

int main(int argc, char **argv) {
    //inotify读取到一次事件就会结束,这里使用死循环读取
    while (true) {
        int inotifyFd;
        int ret;
        const char *path = argv[1];
        ALOGD("argc = %d", argc);

        //初始化inotify
        inotifyFd = inotify_init();
        if (inotifyFd == -1) {
            ALOGD("inotify_init error!\n");
            return -1;
        }

        ALOGD("listen target patch = [%s] \n", path);

        //对目标文件路径进行监听,监听的事件是文件创建IN_CREATE,和文件删除IN_DELETE
        ret = inotify_add_watch(inotifyFd, path, IN_CREATE | IN_DELETE);

        //等待目标文件路径的事件发生
        read_events(inotifyFd);

        //删除inotifyFd
        if (inotify_rm_watch(inotifyFd, ret) == -1) {
            ALOGD("notify_rm_watch error!\n");
            return -1;
        }

        //关闭inotifyFd
        close(inotifyFd);
    }

    return 0;
}

编译

make inotify-demo
  • 编译结果
[ 99% 322/323] target Prebuilt: inotify-demo (out/target/product/qssi_64/obj/EXECUTABLES/inotify-demo_intermediates/inotify-demo)
[100% 323/323] Install: out/target/product/qssi_64/system/bin/inotify-demo

##### build completed successfully (02:40 (mm:ss)) ####
  • push
adb push out/target/product/qssi_64/system/bin/inotify-demo /system/bin/inotify-demo

运行

  • 抓log:
adb logcat -v threadtime | grep -iE "INotifyDemo"

运行时的log:

12-15 10:24:35.654 12746 12746 D INotifyDemo: argc = 2
12-15 10:24:35.654 12746 12746 D INotifyDemo: listen target patch = [dev/input] 
12-15 10:24:35.654 12746 12746 D INotifyDemo: block read!

新增文件的log:

12-15 10:26:09.834 12746 12746 D INotifyDemo: name = inotify.txt, len = 16
12-15 10:26:09.834 12746 12746 D INotifyDemo: mask = 256
12-15 10:26:09.834 12746 12746 D INotifyDemo: create file[inotify.txt] successfully

12-15 10:26:09.848 12746 12746 D INotifyDemo: argc = 2
12-15 10:26:09.848 12746 12746 D INotifyDemo: listen target patch = [dev/input] 
12-15 10:26:09.848 12746 12746 D INotifyDemo: block read!

新增文件方法: aosp-phone:/dev/input ## touch inotify.txt

删除文件的log:

12-15 10:27:54.214 12746 12746 D INotifyDemo: name = inotify.txt, len = 16
12-15 10:27:54.214 12746 12746 D INotifyDemo: mask = 512
12-15 10:27:54.214 12746 12746 D INotifyDemo: delete file[inotify.txt] successfully

12-15 10:27:54.228 12746 12746 D INotifyDemo: argc = 2
12-15 10:27:54.228 12746 12746 D INotifyDemo: listen target patch = [dev/input] 
12-15 10:27:54.229 12746 12746 D INotifyDemo: block read!

删除文件方法: aosp-phone:/dev/input ## rm -rf inotify.txt

  • 运行
adb shell /system/bin/inotify-demo dev/input

Epoll机制

INotify 有个问题就是需要主动调用 read 函数去读取事件,这并不是 Input 系统想要的,Input 系统需要的是 INotify 监听的目标路径发生变化之后来能通知自己而不是自己主动去读,这就需要结合另一个机制 Epoll 来实现,Epoll 是一种 I/O 多路复用技术,主要作用就是去监听 Linux 下的 fd,当这些 fd 发生事件之后会通过回调来通 Epoll。

Epoll提供三个操作函数epoll_create,epoll_ctl,epoll_wait。

epoll_create

int epoll_create(int size);

epoll_create 用于创建一个 epoll 对象,size 用来告诉内核需要监听的fd数量。

epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event)

epoll_ctl 用于对需要监听的文件描述符(fd)执行op操作,epoll_ctl 的第一个参数 epfd 就是 epoll_create 的返回值,第二个参数op表示对fd的操作方式:

EPOLL_CTL_ADD(添加),
EPOLL_CTL_DEL(删除),
EPOLL_CTL_MOD(修改)

最后一个参数 event 表示要监听的具体事件,这是一个结构体 epoll_event :

typedef union epoll_data {
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

struct epoll_event {
    uint32_t events; /* Epoll事件类型 */
    epoll_data_t data;  /*用户数据,包含监听的fd*/
}

Epoll 事件类型通常有如下:

EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说    
的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,  
需要再次把这个socket加入到EPOLL队列里
EPOLLWAKEUP:系统会在事件排队时就保持唤醒,从epoll_wait调用开始,持续要下一次epoll_wait调用

通常 epoll_ctl 的用法就是这样:

struct epoll_event eventItem = {};
eventItem.events = EPOLLIN | EPOLLWAKEUP;
eventItem.data.fd = mINotifyFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem);

epoll_wait

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epoll_wait 用于等待事件的上报,第一个参数是 epoll_create 的返回值,第二个参数events是用来获取内核得到事件的集合,通常是一个epoll_event数组,第三个参数maxevents是最大事件的数量,第四个参数是超时返回时间。

Epoll的使用步骤也很简单:

  • 通过epoll_create创建epoll对象。
  • 为需要监听的fd构建一个epoll_event结构体,并注册到epoll_ctl进行监听。
  • 调用epoll_wait进入监听状态,传入一个epoll_event结构体数组,用于收集监听到的事件。
  • 遍历第三步的epoll_event结构体数组,依次取出事件处理。

实践

接着进入实践环节,我们结合 INotify 和 Epoll 一起来使用:

Android.bp

cc_binary {
    name: "inotify-epoll-demo",
    srcs: ["INotifyEpollDemo.cpp"],
    shared_libs: [
        "liblog",
    ],
}

INotifyEpollDemo.cpp

#include <sys/inotify.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <log/log.h>

#define TAG_DEMO "INotifyEpollDemo"

#undef ALOGD
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG_DEMO, __VA_ARGS__)

#undef ALOGE
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG_DEMO, __VA_ARGS__)

int read_events(int fd) {
    char event_buf[512];
    int ret;
    int event_pos = 0;
    int event_size = 0;
    struct inotify_event *event;

    ALOGD("block read!");
    //通过read函数读取目标文件路径发生的事件,没有事件则阻塞
    ret = read(fd, event_buf, sizeof(event_buf));

    //read的返回值表示实际读取的事件大小,如果小于一个事件大小则说明读取事件失败
    if (ret < (int) sizeof(struct inotify_event)) {
        ALOGE("read error, could get event");
        return -1;
    }

    //将所有事件循环取出来
    while (ret > (int) sizeof(struct inotify_event)) {
        event = (struct inotify_event *) (event_buf + event_pos);
        ALOGD("name = %s, len = %d", event->name, event->len);
        if (event->len) {
            ALOGD("mask = %d", event->mask);
            if (event->mask & IN_CREATE) {
                //文件创建
                ALOGD("create file[%s] successfully", event->name);
            } else {
                //文件删除
                ALOGD("delete file[%s] successfully", event->name);
            }
        }
        event_size = sizeof(struct inotify_event) + event->len;
        ret -= event_size;
        event_pos += event_size;
    }
    return 0;
}

int main(int argc, char **argv) {
    //inotify读取到一次事件就会结束,这里使用死循环读取
    while (true) {
        int inotifyFd;
        int ret;
        int mEpollFd;
        int result;
        int EPOLL_MAX_EVENTS = 16;
        struct epoll_event mPendingEventItems[EPOLL_MAX_EVENTS];
        const char *path = argv[1];
        ALOGD("argc = %d", argc);

        //初始化inotify
        inotifyFd = inotify_init();

        //初始化epoll
        mEpollFd = epoll_create1(EPOLL_CLOEXEC);

        //创建封装inotifyFd的结构体epoll_event
        struct epoll_event eventItem = {};
        eventItem.events = EPOLLIN | EPOLLWAKEUP;
        eventItem.data.fd = inotifyFd;
        //将inotifyFd添加到epoll进行监听
        result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, inotifyFd, &eventItem);

        if (inotifyFd == -1) {
            ALOGD("inotify_init error!\n");
            return -1;
        }
        ALOGD("listen target patch = [%s] \n", path);
        //对目标文件路径进行监听,监听的事件是文件创建IN_CREATE,和文件删除IN_DELETE
        ret = inotify_add_watch(inotifyFd, path, IN_CREATE | IN_DELETE);


        ALOGD("epoll_wait.....");
        //等待事件的发生,会阻塞
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, -1);
        ALOGD("epoll event happened pollResult = %d", pollResult);

        for (auto &event: mPendingEventItems) {
            if (event.data.fd == inotifyFd) {
                //当inotifyFd上有事件发生,则读取事件
                read_events(inotifyFd);
            }
        }

        //删除inotifyFd
        if (inotify_rm_watch(inotifyFd, ret) == -1) {
            ALOGD("notify_rm_watch error!\n");
            return -1;
        }

        //关闭inotifyFd
        close(inotifyFd);
    }

    return 0;
}

编译

make inotify-epoll-demo
  • 编译结果
[ 99% 322/323] target Prebuilt: inotify-epoll-demo (out/target/product/qssi_64/obj/EXECUTABLES/inotify-epoll-demo_intermediates/inotify-epoll-demo)
[100% 323/323] Install: out/target/product/qssi_64/system/bin/inotify-epoll-demo

##### build completed successfully (02:24 (mm:ss)) ####
  • push
adb push out/target/product/qssi_64/system/bin/inotify-epoll-demo /system/bin/inotify-epoll-demo

运行

  • 抓log:
adb logcat -v threadtime | grep -iE "INotifyEpollDemo"

运行时的log:

12-15 09:44:45.868  9945  9945 D INotifyEpollDemo: argc = 2
12-15 09:44:45.870  9945  9945 D INotifyEpollDemo: listen target patch = [dev/input] 
12-15 09:44:45.870  9945  9945 D INotifyEpollDemo: epoll_wait.....

新增文件的log:

12-15 09:45:28.266  9945  9945 D INotifyEpollDemo: epoll event happened pollResult = 1
12-15 09:45:28.266  9945  9945 D INotifyEpollDemo: block read!
12-15 09:45:28.266  9945  9945 D INotifyEpollDemo: name = epoll.txt, len = 16
12-15 09:45:28.266  9945  9945 D INotifyEpollDemo: mask = 256
12-15 09:45:28.266  9945  9945 D INotifyEpollDemo: create file[epoll.txt] successfully

12-15 09:45:28.268  9945  9945 D INotifyEpollDemo: argc = 2
12-15 09:45:28.269  9945  9945 D INotifyEpollDemo: listen target patch = [dev/input] 
12-15 09:45:28.269  9945  9945 D INotifyEpollDemo: epoll_wait.....

新增文件方法: aosp-phone:/dev/input ## touch epoll.txt

删除文件的log:

12-15 09:46:32.554  9945  9945 D INotifyEpollDemo: epoll event happened pollResult = 1
12-15 09:46:32.554  9945  9945 D INotifyEpollDemo: block read!
12-15 09:46:32.554  9945  9945 D INotifyEpollDemo: name = epoll.txt, len = 16
12-15 09:46:32.554  9945  9945 D INotifyEpollDemo: mask = 512
12-15 09:46:32.554  9945  9945 D INotifyEpollDemo: delete file[epoll.txt] successfully

12-15 09:46:32.555  9945  9945 D INotifyEpollDemo: argc = 2
12-15 09:46:32.556  9945  9945 D INotifyEpollDemo: listen target patch = [dev/input] 
12-15 09:46:32.556  9945  9945 D INotifyEpollDemo: epoll_wait.....

删除文件方法: aosp-phone:/dev/input ## rm -rf epoll.txt

  • 运行
adb shell /system/bin/inotify-epoll-demo dev/input

epoll 监听到 inotifyFd 上有事件发生之后,便会从阻塞中醒来,我们此时判定当前 fd 类型为 event.data.fd == inotifyFd 便可以去读取对应事件了,这样我们就结合 INotify 与 Epoll 机制实现了被动监听文件事件的功能,其实 Android 的 Input 子系统就是这么干的,我写的测试代码就是参考 Input 的相关实现,有了这个基础后面再分析 Input 系统这部分代码时就非常容易了。

评论