9.3.2 RIL_startEventLoop分析

第一个关键点是RIL_startEventLoop函数,这个函数实际上是由libRil.so实现的,它的代码在Ril.cpp中,代码如下所示:


[—>Ril.cpp]

extern"C"void RIL_startEventLoop(void){

int ret;

pthread_attr_t attr;

s_started=0;

pthread_mutex_lock(&s_startupMutex);

pthread_attr_init(&attr);

pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

//创建工作线程eventLoop。

ret=pthread_create(&s_tid_dispatch,&attr,eventLoop,NULL);

/*

工作线程eventLoop运行后会设置s_started为1,并触发s_startupCond。

这几个语句的目的是保证在RIL_startEventLoop返回前,工作线程一定是已经创建并运行了。

*/

while(s_started==0){

pthread_cond_wait(&s_startupCond,&s_startupMutex);

}

pthread_mutex_unlock(&s_startupMutex);

if(ret<0){

return;

}

}


从上面的代码可知,RIL_startEventLoop会等待工作线程创建并运行成功。这个线程为什么会如此重要呢?下面就来了解一下工作线程eventLoop。

1.工作线程eventLoop

工作线程eventLoop的代码如下所示:


[—>Ril.cpp]

static voideventLoop(voidparam){

int ret;

int filedes[2];

//①初始化请求队列。

ril_event_init();

//下面这几个操作告诉RIL_startEventLoop函数本线程已经创建并成功运行了。

pthread_mutex_lock(&s_startupMutex);

s_started=1;

pthread_cond_broadcast(&s_startupCond);

pthread_mutex_unlock(&s_startupMutex);

//创建匿名管道。

ret=pipe(filedes);

……

s_fdWakeupRead=filedes[0];

s_fdWakeupWrite=filedes[1];

//设置管道读端口的属性为非阻塞。

fcntl(s_fdWakeupRead,F_SETFL,O_NONBLOCK);

//②下面这两句话将匿名管道的读写端口加入到event队列中。

ril_event_set(&s_wakeupfd_event,s_fdWakeupRead,true,

processWakeupCallback,NULL);

rilEventAddWakeup(&s_wakeupfd_event);

//③进入事件等待循环中,等待外界触发事件并做对应的处理。

ril_event_loop();

return NULL;

}


工作线程的工作并不复杂,主要有三个关键点。

(1)ril_event_init分析

工作线程,顾名思义就是用来干活的。要让它干活,是否得有一些具体的任务呢?它是如何管理这些任务的呢?对这两问题的回答是:

工作线程使用了一个叫ril_event的结构体来描述一个任务,并且它将多个任务按时间顺序组织起来,保存在任务队列中。这个时间顺序是指该任务的执行时间,由外界设定,可以是未来的某时间。

ril_event_init函数就是用来初始化相关队列和管理结构的,代码如下所示:

注意 在代码中,“任务”也称为“事件”,如没有特殊说明必要,这两者以后不再做区分。


[—>Ril.cpp]

void ril_event_init()

{

MUTEX_INIT();//初始化一个mutex对象listMutex。

FD_ZERO(&readFds);//初始化readFds,看来Ril会使用select来做多路IO复用。

//下面的timer_list和pending_list分别是两个队列。

init_list(&timer_list);//初始化timer_list,任务插入的时候按时间排序。

init_list(&pending_list);//初始化pendling_list,保存每次需要执行的任务。

/*

watch_table(监控表)定义如下:

static struct ril_event*watch_table[MAX_FD_EVENTS];

其中MAX_FD_EVENTS的值为8。监控表主要用来保存那些FD已经加入到readFDs中的任务。

*/

memset(watch_table,0,sizeof(watch_table));

}


此ril_event_init函数没什么新鲜的内容。任务在代码中的对等物Ril_event结构的代码如下所示:


[—>Ril_event.h]

struct ril_event{

struct ril_event*next;

struct ril_event*prev;//next和prev将ril_event组织成了一个双向链表。

int fd;//该任务对应的文件描述符,以后简称FD。

int index;//这个任务在监控表中的索引。

/*

是否永久保存在监控表中,一个任务处理完毕后将根据这个persist参数来判断。是否需要从监控表中移除。

*/

bool persist;

struct timeval timeout;//该任务的执行时间。

ril_event_cb func;//任务函数。

void*param;//传给任务函数的参数。

};


ril_event_init刚初始化完任务队列,下面就有地方添加任务了。

(2)任务加入队列

下面这两行代码初始化一个FD为s_wakeupfd_event的任务,并将其加入到监控表中:


/*

s_wakeupfd_event定义为一个静态的ril_event,ril_event_set函数将初始化它的FD为管道的读端,任务函数ril_event_cb对应为processWakeupCallback,并设置persist为true。

*/

ril_event_set(&s_wakeupfd_event,s_fdWakeupRead,true,

processWakeupCallback,NULL);

//来看这个函数:

rilEventAddWakeup(&s_wakeupfd_event);


rilEventAddWakeup比较有意思,来看这个函数,代码如下所示:


[—>Ril.cpp]

static void rilEventAddWakeup(struct ril_event*ev){

ril_event_add(ev);//ev指向一项任务

triggerEvLoop();

}

//直接看ril_event_add函数和triggerEvLoop函数。

void ril_event_add(struct ril_event*ev)

{

……

MUTEX_ACQUIRE();//锁保护

for(int i=0;i<MAX_FD_EVENTS;i++){

//从监控表中找到第一个空闲的索引,然后把这个任务加到监控表中,

//index表示这个任务在监控中的索引。

if(watch_table[i]==NULL){

watch_table[i]=ev;

ev->index=i;

……

//将任务的FD加入到readFds中,这是select使用的标准方法。

FD_SET(ev->fd,&readFds);

if(ev->fd>=nfds)nfds=ev->fd+1;

……

break;

}

}

MUTEX_RELEASE();

……

}

//再来看triggerEvLoop函数,这个就更简单了:

static void triggerEvLoop(){

int ret;

/*

s_tid_dispatch是工作线程eventLoop的线程ID,pthread_self返回调用线程的线程ID。

由于这里调用triggerEvLoop的就是eventLoop自己,所以不会走if分支,但是可以看看里面的内容。

*/

if(!pthread_equal(pthread_self(),s_tid_dispatch)){

do{

//s_fdWakeupWrite为匿名管道的写端口,看来触发eventLoop工作的条件就是

//往这个端口写一点数据了。

ret=write(s_fdWakeupWrite,"",1);

}while(ret<0&&errno==EINTR);

}

}


一般的线程间通信使用同步对象来触发,而rild是通过往匿名管道写数据来触发工作线程工作的。

(3)ril_event_loop分析

来看最后一个关键函数ril_event_loop,其代码如下所示:


[—>Ril.cpp]

void ril_event_loop()

{

int n;

fd_set rfds;

struct timeval tv;

struct timeval*ptv;

for(;){

memcpy(&rfds,&readFds,sizeof(fd_set));

/*

根据timer_list来计算select函数的等待时间,timer_list已经按任务的执行时间排好序了。

*/

if(-1==calcNextTimeout(&tv)){

ptv=NULL;

}else{

ptv=&tv;

}

……;

//调用select进行多路IO复用。

n=select(nfds,&rfds,NULL,NULL,ptv);

……

//将timer_list中那些执行时间已到的任务移到pending_list队列。

processTimeouts();

//从监控表中转移那些有数据要读的任务到pending_list队列,如果任务的persisit不为

//true,则同时从监控表中移除这些任务。

processReadReadies(&rfds,n);

//遍历pending_list,执行任务的任务函数。

firePending();

}

}


根据对ril_event_Loop函数的分析可知,Rild支持两种类型的任务:

定时任务。它的执行由执行时间决定,和监控表没有关系,在Ril.cpp中由ril_timer_add函数添加。

非定时任务,也叫Wakeup Event。这些任务的FD将加入到select的读集合(readFDs)中,并且在监控表中存放了对应的任务信息。它们触发的条件是这些FD可读。对于管道和Socket来说,FD可读意味着接收缓冲区中有数据,这时调用recv不会因为没有数据而阻塞。

注意 对于处于listen端的socket来说,FD可读表示有客户端连接上了,此时需要调用accept接受连接。

2.关于RIL_startEventLoop的总结

总结一下RIL_startEventLoop的工作。从代码中看,这个函数将启动一个比较重要的工作线程eventLoop,该线程主要用来完成一些任务处理,而目前还没有给它添加任务。