Input子系统框架之system层的知识储备

1、必备的Linux知识 inotify和epoll

1.1、INotify介绍与使用

INotify是一个Linux内核所提供的一种文件系统变化通知机制。它可以为应用程序监控文件系统的变化,如文件的新建、删除、读写等。INotify机制有两个基本对象,分别为inotify对象与watch对象,都使用文件描述符表示。 inotify对象对应了一个队列,应用程序可以向inotify对象添加多个监听。当被监听的事件发生时,可以通过read()函数从inotify对象中将事件信息读取出来。Inotify对象可以通过以下方式创建:

int inotifyFd = inotify_init();

而watch对象则用来描述文件系统的变化事件的监听。它是一个二元组,包括监听目标和事件掩码两个元素。监听目标是文件系统的一个路径,可以是文件也可以是文件夹。而事件掩码则表示了需要需要监听的事件类型,掩码中的每一位代表一种事件。可以监听的事件种类很多,其中就包括文件的创建(IN_CREATE)与删除(IN_DELETE)。读者可以参阅相关资料以了解其他可监听的事件种类。以下代码即可将一个用于监听输入设备节点的创建与删除的watch对象添加到inotify对象中:

int wd = inotify_add_watch (inotifyFd,/dev/input”,IN_CREATE | IN_DELETE);

完成上述watch对象的添加后,当/dev/input/下的设备节点发生创建与删除操作时,都会将相应的事件信息写入到inotifyFd所描述的inotify对象中,此时可以通过read()函数从inotifyFd描述符中将事件信息读取出来。 事件信息使用结构体inotify_event进行描述:

struct inotify_event {
   __s32           wd;             /* 事件对应的Watch对象的描述符 */
   __u32           mask;           /* 事件类型,例如文件被删除,此处值为IN_DELETE */
   __u32           cookie;
   __u32           len;            /* name字段的长度 */
   char            name[0];        /* 可变长的字段,用于存储产生此事件的文件路径*/
   };

当没有监听事件发生时,可以通过如下方式将一个或多个未读取的事件信息读取出来:

size_t len = read (inotifyFd, events_buf,BUF_LEN);

其中events_buf是inotify_event的数组指针,能够读取的事件数量由取决于数组的长度。成功读取事件信息后,便可根据inotify_event结构体的字段判断事件类型以及产生事件的文件路径了。

总结一下INotify机制的使用过程:

· 通过inotify_init()创建一个inotify对象。

· 通过inotify_add_watch将一个或多个监听添加到inotify对象中。

· 通过read()函数从inotify对象中读取监听事件。当没有新事件发生时,inotify对象中无任何可读数据。

通过INotify机制避免了轮询文件系统的麻烦,但是还有一个问题,INotify机制并不是通过回调的方式通知事件,而需要使用者主动从inotify对象中进行事件读取。那么何时才是读取的最佳时机呢?这就需要借助Linux的另一个优秀的机制Epoll了。

使用inotify监听目录实例:

#include <unistd.h>
#include <stdio.h>
#include <sys/inotify.h>
#include <string.h>
#include <errno.h>
//参考: frameworks\native\services\inputflinger\EventHub.cpp
//Usage: inotify <dir>
int read_process_inotify_fd(int fd)
{
int res;
char event_buf[512];
int event_size;
int event_pos = 0;
struct inotify_event *event;
/* read */    
res = read(fd, event_buf, sizeof(event_buf));
if(res < (int)sizeof(*event)) {
    if(errno == EINTR) return 0;
    printf("could not get event, %s\n", strerror(errno));
    return -1;
}
//读到的数据是1个或多个inotify_event,它们的长度不一样,逐个处理
while(res >= (int)sizeof(*event)) {
    event = (struct inotify_event *)(event_buf + event_pos);
    //printf("%d: %08x \"%s\"\n", event->wd, event->mask, event->len ? event->name : "");
    if(event->len) {
        if(event->mask & IN_CREATE) {
            printf("create file: %s\n", event->name);
        } else {
            printf("delete file: %s\n", event->name);
        }
    }
    event_size = sizeof(*event) + event->len;
    res -= event_size;
    event_pos += event_size;
}
return 0;
}
int main(int argc, char **argv) {
 int mINotifyFd;
 int result;
 if (argc != 2) {
    printf("Usage: %s <dir>\n", argv[0]); return -1;
 }
 /* inotify_init */
 mINotifyFd = inotify_init();
 /* add watch */
 result = inotify_add_watch(mINotifyFd, argv[1], IN_DELETE | IN_CREATE);
 /* read */
 while (1) {
     read_process_inotify_fd(mINotifyFd);
 }
 return 0;
}

编译与验证:

 gcc -o inotify inotify.c //GCC编译 
 mkdir tmp //创建tmp文件夹 
 ./inotify tmp & //后台监测tmp文件夹
echo > tmp/1 //tmp文件夹新建文件1 
echo > tmp/2 //tmp文件夹新建文件2 
rm tmp/1 tmp/2 //移除tmp文件1/2

测试结果可以看到,inotify 成功的监测了tmp文件夹。
在这里插入图片描述

1.2、Epoll介绍与使用

无论是从设备节点中获取原始输入事件还是从inotify对象中读取文件系统事件,都面临一个问题,就是这些事件都是偶发的。也就是说,大部分情况下设备节点、inotify对象这些文件描述符中都是无数据可读的,同时又希望有事件到来时可以尽快地对事件作出反应。为解决这个问题,我们不希望不断地轮询这些描述符,也不希望为每个描述符创建一个单独的线程进行阻塞时的读取,因为这都将会导致资源的极大浪费。

此时最佳的办法是使用Epoll机制。Epoll可以使用一次等待监听多个描述符的可读/可写状态。等待返回时携带了可读的描述符或自定义的数据,使用者可以据此读取所需的数据后可以再次进入等待。因此不需要为每个描述符创建独立的线程进行阻塞读取,避免了资源浪费的同时又可以获得较快的响应速度。

Epoll机制的接口只有三个函数,十分简单。

epoll_create(int max_fds):
创建一个epoll对象的描述符,之后对epoll的操作均使用这个描述符完成。max_fds参数表示了此epoll对象可以监听的描述符的最大数量。

epoll_ctl (int epfd, int op,int fd, struct epoll_event *event):
用于管理注册事件的函数。这个函数可以增加/删除/修改事件的注册。

int epoll_wait(int epfd, structepoll_event * events, int maxevents, int timeout):
用于等待事件的到来。当此函数返回时,events数组参数中将会包含产生事件的文件描述符。

接下来以监控若干描述符可读事件为例介绍一下epoll的用法。

(1) 创建epoll对象
首先通过epoll_create()函数创建一个epoll对象:

Int epfd = epoll_create(MAX_FDS)

(2) 填充epoll_event结构体
接着为每一个需监控的描述符填充epoll_event结构体,以描述监控事件,并通过epoll_ctl()函数将此描述符与epoll_event结构体注册进epoll对象。epoll_event结构体的定义如下:

struct epoll_event {

__uint32_t events; /* 事件掩码,指明了需要监听的事件种类*/
 epoll_data_t data; /* 使用者自定义的数据,当此事件发生时该数据将原封不动地返回给使用者 */
 };

epoll_data_t联合体的定义如下,当然,同一时间使用者只能使用一个字段:

typedef union epoll_data {
void*ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;

epoll_event结构中的events字段是一个事件掩码,用以指明需要监听的事件种类,同INotify一样,掩码的每一位代表了一种事件。常用的事件有EPOLLIN(可读),EPOLLOUT(可写),EPOLLERR(描述符发生错误),EPOLLHUP(描述符被挂起)等。更多支持的事件读者可参考相关资料。

data字段是一个联合体,它让使用者可以将一些自定义数据加入到事件通知中,当此事件发生时,用户设置的data字段将会返回给使用者。在实际使用中常设置epoll_event.data.fd为需要监听的文件描述符,事件发生时便可以根据epoll_event.data.fd得知引发事件的描述符。当然也可以设置epoll_event.data.fd为其他便于识别的数据。

填充epoll_event的方法如下:

struct epoll_event eventItem;

memset(&eventItem, 0, sizeof(eventItem));

eventItem.events = EPOLLIN | EPOLLERR | EPOLLHUP; // 监听描述符可读以及出错的事件

eventItem.data.fd= listeningFd; // 填写自定义数据为需要监听的描述符

接下来就可以使用epoll_ctl()将事件注册进epoll对象了。epoll_ctl()的参数有四个:

· epfd是由epoll_create()函数所创建的epoll对象的描述符。

· op表示了何种操作,包括EPOLL_CTL_ADD/DEL/MOD三种,分别表示增加/删除/修改注册事件。

· fd表示了需要监听的描述符。

· event参数是描述了监听事件的详细信息的epoll_event结构体。

注册方法如下:

// 将事件监听添加到epoll对象中去
result =epoll_ctl(epfd, EPOLL_CTL_ADD, listeningFd, &eventItem);

重复这个步骤可以将多个文件描述符的多种事件监听注册到epoll对象中。完成了监听的注册之后,便可以通过epoll_wait()函数等待事件的到来了。

(3) 使用epoll_wait()函数等待事件

epoll_wait()函数将会使调用者陷入等待状态,直到其注册的事件之一发生之后才会返回,并且携带了刚刚发生的事件的详细信息。其签名如下:

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
· epfd是由epoll_create()函数所创建的epoll对象描述符。
· events是一个epoll_event的数组,此函数返回时,事件的信息将被填充至此。
· maxevents表示此次调用最多可以获取多少个事件,当然,events参数必须能够足够容纳这么多事件。
· timeout表示等待超时的事件。

epoll_wait()函数返回值表示获取了多少个事件。

(4) 处理事件
epoll_wait返回后,便可以根据events数组中所保存的所有epoll_event结构体的events字段与data字段识别事件的类型与来源。
Epoll的使用步骤总结如下:
· 通过epoll_create()创建一个epoll对象。
· 为需要监听的描述符填充epoll_events结构体,并使用epoll_ctl()注册到epoll对象中。
· 使用epoll_wait()等待事件的发生。
· 根据epoll_wait()返回的epoll_events结构体数组判断事件的类型与来源并进行处理。
· 继续使用epoll_wait()等待新事件的发生。

1.3、INotify与Epoll的小结

INotify与Epoll这两套由Linux提供的事件监听机制以最小的开销解决了文件系统变化以及文件描述符可读可写状态变化的监听问题。它们是Reader子系统运行的基石,了解了这两个机制的使用方法之后便为对Reader子系统的分析学习铺平了道路。

用途简介[稍后进行input system详细分析]: /dev/input 下有多个event文件,对应多个输入设备,如:/dev/input/event0, /dev/input/mouse0, /dev/input/misc 使用inotify 和 epoll 就可以监听输入设备的变化、如Android新连接一个鼠标可检测到改变。同时可监听是否有输入事件。

2、必备Linux知识_双向通信(scoketpair)

1、进程和APP通信
· 创建进程 · 读取、分发 · 进程发送输入事件给APP · 进程读取APP回应的事件 · 输入系统涉及双向的进程间通信

2、回顾Binder系统
· Server– 单向发出请求 · Client – 单向回复请求 · 每次请求只可以单方发出

3、引入Socketpair
原因:如果创建两组进程(Client,Server)进行双向通信,实现十分复杂 引入Socketpair: Socketpair();两次,获得两个fd,在内核获得缓冲区,一个作为sendbuf区一个作为receivebuf区 APP通过fd1将数据写入fd1的sendbuf区中,通过内核当中的socket机制就会写到fd2中receivebuf区,同理fd2也是如此 socketpair缺点:只适用于线程间、父子进程通信 解决方法:通过Binder机制通信可以访问任意进程,就解决了sockpair缺点

4、socketpair具体使用
创建一个线程–pthread_create(); 创建socketpair–socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets); 线程处理函数–往socket[1]写入数据,读取socket[0]读取数据 主函数–从socket[1]读取数据,往socket[0]写入数据

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#define SOCKET_BUFFER_SIZE      (32768U)
#define MAX 512
/* 参考:frameworks/native/libs/input/InputTransport.cpp
   */
   /* 线程执行函数 */
int *function_thread(void *arg)
{
int thread1_fd = (int)arg;int cnt=0;
int len;
char buf[MAX];
while(1){
    /* 向 main线程发出: Hello, main thread   */
    len = sprintf(buf,"Hello , main thread , cnt = %d",cnt++);
    write(thread1_fd,buf,len);
    /* 读取数据(main线程发回的数据) */
    len = read(thread1_fd,buf,MAX);
    buf[len] = '\0';
    printf("thread1 read : %s\n",buf);
    sleep(5);
}
close(thread1_fd);
return NULL;
}
int main(int argc,char *argv[])
{
pthread_t threadID;
int sockets[2];
int bufferSize = SOCKET_BUFFER_SIZE;
socketpair(AF_UNIX,SOCK_SEQPACKET,0,sockets);  //创建socketpair
//初始化
setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
pthread_create(threadID,NULL,function_thread,(void *)sockets[1]);  //创建线程
int mainThread_fd = sockets[0];
int cnt=0;
int len;
char buf[MAX];
while(1){
    /* 读数据: 线程1发出的数据 */
    len = read(mainThread_fd,buf,MAX);
    buf[len] = '\0';
    printf("main thread read : %s\n",buf);
    /* main thread向thread1 发出: Hello, thread1 */
    len = sprintf(buf,"Hello , thread1 , cnt = %d",cnt++);
    write(mainThread_fd,buf,len);       
}
close(mainThread_fd);
return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值