这样的消息机制已经耳熟能详,本文不拘泥与此,主要探讨消息机制的c++实现
void Java.MessageQueue.next() [
for(;;) {
nativePollOnce();
取消息;
}
for循环是用来找到Java层的消息的,在这个for循环里,msg会一直next去找。但是有一个问题,没消息了,next为空了该怎么办?
void Java.Looper.loop() {
for(;;) {
Message msg = queue.next();
if(msg == null) {
return;
}
}
}
毫无疑问,loop方法就退出了,接下来再也不会从queue中自动取消息了。可是实际上Looper是无限执行的。原因在于,如果没有msg了,那么我们就会在nativePollOnce方法上阻塞了,这也是为什么要有这个方法的原因。
nativePollOnce方法阻塞的原理?
c++是怎么获知我们queue中还有没有消息的呢?原因在于for(;;)中
if(msg.next == null) {
nextPollTimeoutMillis = -1;
}
在下一次跑到for(;;)后,这个值就会传到nativePollOnce中了,因此就会阻塞了。
但是一直会阻塞在这里吗,万一有新消息到来我们还是阻塞吗?
事实上新消息到来就会立马得到执行。我们需要看一看enqueueMessage方法。
void Java.enqueueMessage(Message msg) {
for(;;) {
找到msg应该插入的位置,并插入;
if(原来是没消息的,我插入到头部了) {
nativeWake();
}
}
}
显然我们调用了native方法进行了唤醒操作,这样一来nativePollOnce就跑完了,执行到了下面取消息并执行的代码块了
显然没有nativePollOnce方法就根本做不到上面的效果,因为这是我们很好的释放资源的手段。我们通过Java代码是很难释放cpu资源并且重新获取cpu资源的。nativePollOnce方法除了处理资源释放,还用于处理c++层的消息。
但是c++层又是怎么处理资源的释放呢?
c++阻塞
IO流时的cpu的处理规则有3大方法,select、poll、epoll。这里采用的就是epoll方法。epoll会监听某些流,一旦这些流中有几个流就绪,那么就会遍历这几个流。如果没有,cpu资源就会得到释放。
nativePollOnce方法会先执行到MessageQueue.pollOnce,然后会执行到Looper.pollOnce。内部会执行一个epoll_wait方法。上面也说了,如果Java msg为空就会改变某个值,在取值的无限循环的下一次中,调用nativePollOnce并传入-1,所以epoll_wait也会传入-1,如果传入-1,这里就会被阻塞了,所以我们需要重点研究epoll_wait方法是怎么阻塞的?
epoll_wait
if (list_empty(&ep->rdllist)) {
/* 没有事件,所以需要睡眠。当有事件到来时,睡眠会被ep_poll_callback函数唤醒。*/
init_waitqueue_entry(&wait, current); //将current进程放在wait这个等待队列中。
wait.flags |= WQ_FLAG_EXCLUSIVE;
/* 将当前进程加入到eventpoll的等待队列中,等待文件状态就绪或直到超时,或被信号中断。 */
__add_wait_queue(&ep->wq, &wait);
for (;;) {
/* 执行ep_poll_callback()唤醒时应当需要将当前进程唤醒,所以当前进程状态应该为“可唤醒”TASK_INTERRUPTIBLE */
set_current_state(TASK_INTERRUPTIBLE);
/* 如果就绪队列不为空,也就是说已经有文件的状态就绪或者超时,则退出循环。*/
if (!list_empty(&ep->rdllist) || !jtimeout)
break;
/* 如果当前进程接收到信号,则退出循环,返回EINTR错误 */
if (signal_pending(current)) {
res = -EINTR;
break;
}
spin_unlock_irqrestore(&ep->lock, flags);
/* 主动让出处理器,等待ep_poll_callback()将当前进程唤醒或者超时,返回值是剩余的时间。
从这里开始当前进程会进入睡眠状态,直到某些文件的状态就绪或者超时。
当文件状态就绪时,eventpoll的回调函数ep_poll_callback()会唤醒在ep->wq指向的等待队列中的进程。*/
jtimeout = schedule_timeout(jtimeout);
spin_lock_irqsave(&ep->lock, flags);
}
__remove_wait_queue(&ep->wq, &wait);
set_current_state(TASK_RUNNING);
}
可以看到schedule_timeout处,内部调用了内核某些方法,使得进程睡眠了。
唤醒
ep_poll_callback首先会更改一些参数,然后唤醒进程,上面的for循环就会继续执行,此时,逻辑就通过了。epoll_wait这个函数就成功返回了。
native handler总结
在创建Java层的MessageQueue的时候,native的MessageQueue也因此创建了。同时一个epoll_wake_fd就被创建出来了。然后Looper也得到了创建,这个Looper会被缓存到c++层的TLS中去了。
nativePollOnce。在Java层有一个long值,在执行nativePollOnce的时候,long会转化成native的MessageQueue,于此同时还有一个值标识Java层还有没有msg。最终会执行到Looper的pollOnce方法,然后会执行到epoll_wait方法。如果队列中没元素了,就会在schedule_time方法中使得线程睡眠。
在Java的MessageQueue.enqueueMessage方法中,会调用nativeWake,最终也是调用到Looper.wake,管道就会被写入1,代表了w,同时ep_poll_callback方法也会得到执行,进程就被唤醒了,这个时候epoll_wait方法检测到w,就会返回了,这个时候nativePollOnce也就返回了,所以下面就会继续从Java的Message中取next。
而一旦next为空了,某个值就变成了-1,在Message.next的下一次循环中,又会调用到nativePollOnce,然后转化native的MessageQueue,然后Looper.pollOnce,然后epoll_wait,以此类推。。。