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,该线程主要用来完成一些任务处理,而目前还没有给它添加任务。
';