Android Input system 9 INotify and Epoll mechanism

I. overview

I have written several articles about the Input system before, but they are scattered. This article begins to prepare to take a look at the Android Input subsystem based on the Android 11 code. First, let's learn the INotify and Epoll mechanisms under Linux. These two mechanisms mainly monitor the addition and deletion of file nodes under the Input subsystem and the events of file nodes.

II. INotify mechanism

INotify is a mechanism provided by Linux to monitor kernel file system changes in user mode. It can monitor the addition and deletion of files / directories. The usage of INotify is very simple. First, you need to call the following code to create a file descriptor:

int inotifyfd = inotify_init();

Then you need to pass inotify_add_watch adds the events we focus on to the listener:

int wd = inotify_add_watch(inotifyfd, path, event_mask)

inotify_ add_ The first parameter of watch is inotify_init creates a file descriptor. The second parameter is the path to listen to, and the third parameter is the type of event, such as file creation IN_CREATE, file delete IN_DELETE, etc.

After the above two steps are completed, when the events we want to listen to occur in the specified path, they will be written to inotifyfd. At this time, we can read inotifyfd through the read function:

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

The read information is encapsulated as inotify_event structure. All events can be read out by using the while loop:

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

inotify_ The information of event structure is as follows:

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 */
  };

In fact, INotify can be used in three steps:

  • Using inotify_init creates an inotify object
  • Using inotify_add_watch listens to the file path
  • Use read to read the monitored events

In fact, the Android SDK provides a file monitoring class FileObserver. Its underlying principle is to use the INotify mechanism. If you are interested, you can take a look at several native functions inside it.

Next, enter the practice phase, write a simple INotify test class, and create two classes in the following directory:

main.cpp

//demo code
#include <sys/inotify.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <log/log.h>
int read_events(int fd) {
    char event_buf[512];
    int ret;
    int event_pos = 0;
    int event_size = 0;
    struct inotify_event *event;
    
    ALOGD("dongjiao...block read........");
    //Read the events occurring in the target file path through the read function. If there is no event, it will be blocked
    ret = read(fd, event_buf, sizeof(event_buf));
    //The return value of read indicates the actual read event size. If it is less than one event size, it indicates that the read event failed
    if(ret < (int)sizeof(struct inotify_event)) {
        ALOGD("dongjiao...read error,could get event");
        return -1;
    }
    //Take out all event loops
    while(ret > (int)sizeof(struct inotify_event)) {
        event = (struct inotify_event*)(event_buf + event_pos);
        ALOGD("dongjiao...event->len = :%d",event->len);
        if(event->len) {
            if(event->mask & IN_CREATE) {
                //File creation
                ALOGD("dongjiao...create file:%s successfully \n", event->name);
            } else {
                //File deletion
                ALOGD("dongjiao...delete file:%s successfully \n", 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 ends when it reads an event. Here, an endless loop is used
    while(true){
    	int inotifyFd;
    	int ret;
    	const char* path = argv[1];
        ALOGD("dongjiao...argc = :%d",argc);
    	//Initialize inotify
    	inotifyFd = inotify_init();
    	if(inotifyFd == -1) {
        	ALOGD("dongjiao...inotify_init error!\n");
        	return -1;
    	}
    	ALOGD("dongjiao...listen target patch: %s \n", path);
        //Listen to the target file path. The listening event is file creation IN_CREATE, and file delete IN_DELETE
    	ret = inotify_add_watch(inotifyFd, path, IN_CREATE | IN_DELETE) ;
        //Wait for the event for the destination file path to occur
     	read_events(inotifyFd);
        //Delete inotifyFd
     	if(inotify_rm_watch(inotifyFd, ret) == -1) {
        	ALOGD("dongjiao...notify_rm_watch error!\n");
        	return -1;
    	}
    	//Close inotifyFd
    	close(inotifyFd);
    }
    return 0;
}

Android.bp

cc_binary {
    name: "main",
    srcs: ["main.cpp"],
    shared_libs: [
        "liblog",
    ],
}

Mmm frameworks / native / services / inputlinker / temp /

After successful push into the phone:

Run the executable main:

The log is as follows:

Next, we operate the file in the dev/input directory, and first create a file 1 txt,

Take a look at the log:

Delete file log:

We can see that the use of INotify is still very simple. The Input subsystem uses INotify to listen for node changes in the dev/input / directory. We will see it later.

Three Epoll mechanism

One problem with INotify is that it needs to actively call the read function to read events, which is not what the Input system wants. The Input system needs to notify itself after the target path monitored by INotify changes, rather than actively read. This needs to be implemented in combination with another machine epoll. Epoll is an I/O multiplexing technology, The main function is to monitor fd under Linux. When these fd events occur, epoll will be called back.

Epoll provides three operation functions epoll_create,epoll_ctl,epoll_wait.

1.epoll_create

int epoll_create(int size);

epoll_create is used to create an epoll object, and size is used to tell the kernel the fd number to listen.
2. epoll_ctl

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

epoll_ctl is used to perform op operations on file descriptors (fd) to be monitored, epoll_ The first parameter epfd of CTL is epoll_ The return value of create. The second parameter op indicates the operation mode of fd:

EPOLL_CTL_ADD(add to),
EPOLL_CTL_DEL(delete),
EPOLL_CTL_MOD((modified)

The last parameter, event, indicates the specific event to listen to. This is a structure 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 Event type */
  	epoll_data_t data;  /*User data, including fd monitored*/
  }

Epoll event types are usually as follows:

EPOLLIN : Indicates that the corresponding file descriptor can be read (including the opposite end) SOCKET Normal shutdown);
EPOLLOUT: Indicates that the corresponding file descriptor can be written;
EPOLLPRI: Indicates that the corresponding file descriptor has urgent data readability (here it should indicate the arrival of out of band data);
EPOLLERR: Indicates that an error occurred in the corresponding file descriptor;
EPOLLHUP: Indicates that the corresponding file descriptor is hung up;
EPOLLET:  take EPOLL Set as edge trigger(Edge Triggered)Mode, which is triggered relative to the horizontal(Level Triggered)Say    
of
EPOLLONESHOT: Only monitor one event. After listening to this event, if you need to continue to monitor this event socket If so,  
You need to put this again socket Add to EPOLL In the queue
EPOLLWAKEUP:The system will keep waking up when events are queued, from epoll_wait The call starts and continues for the next time epoll_wait call

Usually epoll_ This is how CTL is used:

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

epoll_wait is used to wait for event reporting. The first parameter is epoll_ The return value of create. The second parameter, events, is used to obtain the collection of events obtained by the kernel, usually an epoll_event array. The third parameter maxevents is the maximum number of events, and the fourth parameter is the timeout return time.

The steps for using Epoll are also very simple:

  • Through epoll_create creates an epoll object.
  • Build an epoll for fd to listen_ Event structure and register with epoll_ctl to monitor.
  • Call epoll_wait enters the listening state and an epoll is passed in_ The event structure array is used to collect the monitored events.
  • Traverse the epoll in step 3_ Event structure array, and take out the event processing in turn.

Next, let's go to the practice link. We use INotify and Epoll together:

main.cpp

//demo code
#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>
int read_events(int fd) {
    char event_buf[512];
    int ret;
    int event_pos = 0;
    int event_size = 0;
    struct inotify_event *event;
    
    ALOGD("dongjiao...block read........");
    //Read the events occurring in the target file path through the read function. If there is no event, it will be blocked
    ret = read(fd, event_buf, sizeof(event_buf));
    //The return value of read indicates the actual read event size. If it is less than one event size, it indicates that the read event failed
    if(ret < (int)sizeof(struct inotify_event)) {
        ALOGD("dongjiao...read error,could get event");
        return -1;
    }
    //Take out all event loops
    while(ret > (int)sizeof(struct inotify_event)) {
        event = (struct inotify_event*)(event_buf + event_pos);
        ALOGD("dongjiao...event->len = :%d",event->len);
        if(event->len) {
            if(event->mask & IN_CREATE) {
                //File creation
                ALOGD("dongjiao...create file:%s successfully \n", event->name);
            } else {
                //File deletion
                ALOGD("dongjiao...delete file:%s successfully \n", 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 ends when it reads an event. Here, an endless loop is used
        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("dongjiao...argc = :%d",argc);
    	//Initialize inotify
    	inotifyFd = inotify_init();
        //Initialize epoll
        mEpollFd = epoll_create1(EPOLL_CLOEXEC);
        //Create a structure epoll that encapsulates inotifyFd_ event
        struct epoll_event eventItem = {};
        eventItem.events = EPOLLIN | EPOLLWAKEUP;
        eventItem.data.fd = inotifyFd;
        //Add inotifyFd to epoll for listening
        result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, inotifyFd, &eventItem);
        
    	if(inotifyFd == -1) {
        	ALOGD("dongjiao...inotify_init error!\n");
        	return -1;
    	}
        ALOGD("dongjiao...listen target patch: %s \n",path);
        //Listen to the target file path. The listening event is file creation IN_CREATE, and file delete IN_DELETE
        ret = inotify_add_watch(inotifyFd, path, IN_CREATE | IN_DELETE) ;


        ALOGD("dongjiao...epoll_wait.....");
        //Waiting for the event to occur will block
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, -1);
        ALOGD("dongjiao...epoll event happened pollResult = :%d",pollResult);
     	
        for(auto &event:mPendingEventItems){
             if(event.data.fd == inotifyFd){
                 //When an event occurs on inotifyFd, the event is read
     	         read_events(inotifyFd);
              }
        }
        
        //Delete inotifyFd
     	if(inotify_rm_watch(inotifyFd, ret) == -1) {
        	ALOGD("dongjiao...notify_rm_watch error!\n");
        	return -1;
    	}
    	//Close inotifyFd
    	close(inotifyFd);
        }
        return 0;
}

Android.bp

cc_binary {
    name: "main",
    srcs: ["main.cpp"],
    shared_libs: [
        "liblog",
    ],
}

After successful compilation, push to the phone: ADB push out / target / product / bank_ TF/system/bin/main /system/bin/

adb shell /system/bin/main dev/input runs, and the log is as follows:

You can see the blockage in epoll_wait, and then create the test file under dev/input:

The log is as follows:

Epoll will wake up from the blocking after listening to an event on inotifyFd. At this time, we determine that the current fd type is event data. fd = = inotifyFd can read the corresponding events. In this way, we can realize the function of passive monitoring file events by combining INotify and epoll mechanism. In fact, Android Input subsystem does so. The test code I write refers to the relevant implementation of Input. With this foundation, it is very easy to analyze this part of the code of Input system later.

Added by jaimitoc30 on Mon, 17 Jan 2022 03:38:06 +0200