9.3.2 RIL_startEventLoop分析
最后更新于:2022-04-02 05:53:33
第一个关键点是RIL_startEventLoop函数,这个函数实际上是由libRil.so实现的,它的代码在Ril.cpp中,代码如下所示:
**Ril.cpp**
~~~
extern "C" void RIL_startEventLoop(void){
intret;
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 void * eventLoop(void *param) {
intret;
intfiledes[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();
returnNULL;
}
~~~
工作线程的工作并不复杂,主要有三个关键点。
(1)ril_event_init的分析
工作线程,顾名思义就是用来干活的。要让它干活,是否得有一些具体的任务呢?它是如何管理这些任务的呢?对这两问题的回答是:
- 工作线程使用了一个叫ril_event的结构体,来描述一个任务,并且它将多个任务按时间顺序组织起来,保存在任务队列中。这个时间顺序是指该任务的执行时间,由外界设定,可以是未来的某时间。
r
il_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 {
structril_event *next;
structril_event *prev;//next和prev将ril_event组织成了一个双向链表
intfd; //该任务对应的文件描述符,以后简称FD。
intindex; //这个任务在监控表中的索引
/*
是否永久保存在监控表中,一个任务处理完毕后将根据这个persist参数来判断
是否需要从监控表中移除。
*/
boolpersist;
structtimeval 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() {
intret;
/*
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_setrfds;
structtimeval tv;
structtimeval * 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,该线程主要用来完成一些任务处理,而目前还没有给它添加任务。
';