Android 中的Looper如何实现阻塞与唤醒的

来源:互联网 发布:饥荒mac版mod 编辑:程序博客网 时间:2024/06/03 12:41

说该问题之前我们需要首先介绍一下eventfd的作用:

一、eventfd作用

#include <sys/eventfd.h>
int eventfd(unsigned int initval, intflags);
参数解释:
如果是2.6.26或之前版本的内核,flags 必须设置为0。


Initval:初始化计数器值,该值(暂时取名为A)保存在内核。


Flags支持一下标志位:
EFD_NONBLOCK        类似于使用O_NONBLOCK标志设置文件描述符。
EFD_CLOEXEC         类似open以O_CLOEXEC标志打开, 
O_CLOEXEC           应该表示执行exec()时,之前通过open()打开的文件描述符会自动关闭测试时,
在open()之后,调用一下exec(),在新的进程中检测描述符是否已经关闭


返回值:
函数返回一个文件描述符,与打开的其他文件一样,可以进行读写操作。




read/write操作返回的文件描述符
Read:如果计数器A的值不为0时,读取成功,获得到该值。
      如果A的值为0,非阻塞模式时,会直接返回失败,并把error置为EINVAL
      如果为阻塞模式,一直会阻塞到A为非0为止。
Write:将缓冲区写入的8字节整形值加到内核计数器上,即会增加8字节的整数在计数器A上,
       如果A的值达到0xfffffffffffffffe时,就会阻塞(在阻塞模式下),直到A的值被read。阻塞和非阻塞情况同上面read一样。


所以当调用weite函数将1,2,3,4依次写入eventfd()函数返回的文件描述符后,当调用read读取该文件描述符,读取到的值是:(initval+1+2+3+4)的结果


总结:通过man  eventfd提供的例子,作用大概和管道差不多

例子:

#include <sys/eventfd.h>  
#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <stdint.h>             /* Definition of uint64_t */  
  
#define handle_error(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)  
  
int main(int argc, char *argv[])  
{  
   uint64_t u;  
      
   int efd = eventfd(10, 0);  
   if (efd == -1)  
       handle_error("eventfd");  
     
   int ret = fork();  
   if(ret == 0)  
   {  int j = 0;
       for ( j= 1; j < argc; j++) {  
           printf("Child writing %s to efd\n", argv[j]);  
           u = atoll(argv[j]);  
           ssize_t s = write(efd, &u, sizeof(uint64_t));  
           if (s != sizeof(uint64_t))  
               handle_error("write");  
       }  
       printf("Child completed write loop\n");  
  
       exit(EXIT_SUCCESS);  
   }  
   else  
   {  
       sleep(2);  
  
       ssize_t s = read(efd, &u, sizeof(uint64_t));  
       if (s != sizeof(uint64_t))  
           handle_error("read");  
       printf("Parent read %llu from efd\n",(unsigned long long)u);  
       exit(EXIT_SUCCESS);  
   }  
}  


运行结果:比较简单,不做解释。子进程写入命令行中传入的参数,父进程读取其中计数器的值。
运行结果:
./eventfd 10 20 30  
Child writing 10 to efd  
Child writing 20 to efd  
Child writing 30 to efd  
Child completed write loop  
Parent read 70 from efd  
命令行传入的是10、20、30其和应为60,为啥读取的是70呢?
因为在eventfd(10, 0)传入的第一个参数是10,这个参数是创建eventfd时初始化计数器的值。所以读取到的最终结果是(60+10)


二、Android 中的Looper如何实现阻塞与唤醒的

system\core\libutils\Looper.cpp



其中Looper的pollOnce()用于睡眠等待;其中Looper的wake()  用于唤醒。
接下来我们分析Looper与eventfd的关系(需要在Android6.0及6.0之后的代码中看)


构造:
Looper::Looper(bool allowNonCallbacks)
===》mWakeEventFd = eventfd(0, EFD_NONBLOCK);//调用eventfd返回文件描述符mWakeEventFd
===》...

===》rebuildEpollLocked();

=======》int  mEpollFd = epoll_create(EPOLL_SIZE_HINT);//使用epoll机制
======》struct epoll_event eventItem;
======》eventItem.events = EPOLLIN;
======》eventItem.data.fd = mWakeEventFd;
======》int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);//使用epoll监控mEpollFd




阻塞:
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData)
===》result = pollInner(timeoutMillis);
======》  //使用epoll进行监控eventfd返回的文件描述符,当有数据时则解除阻塞状态
======》 int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);


唤醒:
void Looper::wake() 
===》TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));//其中mWakeEventFd就是eventfd返回的文件描述符


在此我们就搞懂了Looper的休眠与唤醒机制。


在Android旧版本使用管道与epoll来完成Looper的休眠与唤醒的
在Android6新版本中使用的是eventfd与epoll来完成Looper的休眠与唤醒的
原创粉丝点击