我主要参考网上的资料和相关书籍。
这个只是初稿,没有整理,而且代码还没看完,还会继续完善。
RIL
Rild
rild中实现的main函数是ril层的入口,负责完成初始化工作。
Libreference-ril.so
Rild通过dlopen方式加载,结合稍微松散,这也是因为libreference-ril.so主要负责与modem硬件通信的缘故。这样做方便替换或修改以适合更多的modem产品。并将libril.so传递来的请求转换为AT命令,然后传递给modem,同时监控modem的反馈信息,并将反馈信息传给libril.so。在初始化时,rild通过RIL_Init获取一组函数指针并与之建立关系。
Radiooptions
Radiooptions通过获取启动参数,通过socket与rild通信,可供调试时配置modem参数。
rild.c中的main函数,主要完成三个任务:
1. 开启libril.so中的event机制,由RIL_startEventLoop()实现,最核心的是一个多路I/O消息循环。
RIL_startEventLoop()函数在ril.cpp中,主要通过pthread_create(&s_tid_dispatch,&attr,eventLoop,NULL)创建一个dispatch线程,入口点在eventLoop,它在ril.cpp中实现,并调用ril_event_loop()(在RIL_event.cpp中)函数,建立消息队列机制。
struct ril_event{
struct ril_event *next; //下一个消息
struct ril_event *prev; //前一个消息
int fd; //fd句柄,与struct ril_event绑定
int index; //侦听列表(watch_list)中的索引,侦听列表采用的是数组而不是链表
bool persist; //是否常驻侦听列表(watch_list)中,若常驻,则在转到pending_list中的时候不删除
struct timeval timeout; //超时,在处理消息时,与当前的时间比较,以确定是否处理该消息
ril_event_cb func; //回调函数。此消息对应的处理函数
void *param; //回调函数的参数
}
eventLoop()
ril_event_init()
ril_event_set(struct ril_event *ev,int fd,bool persist,ril_event_cb func,void *param)
ril_event_add(struct ril_event *ev)
ril_timer_add(struct ril_event *ev,struct timeval *tv)
ril_event_del(struct ril_event *ev)
ril_event_loop(),每次循环主要做三件事:
1. 初始化。主要是获取要等待的描述符,以及获取超时信息,以便能够及时处理定时消息。
2. 等待。等待socket客户端,看是否有新消息,或间隔一定的时间,处理定时消息。
3. 依次处理三种类型的消息。
Memcpy(&rfds,&readFds,sizeof(fd_set));
获取要等待的描述符。(为什么每次循环都要做呢?)
if(-1 == calcNextTimeout(&tv){ //获取超时参数,若失败则返回-1
dlog(“~~~ no timers;blocking indefinitely ~~~”);
ptv = NULL;
}else{ //获取成功,则使用该值。
dlog(“~~~ blocking for %ds + %dus ~~~”,(int)tv.tvsec,(int)tv.tv_usec);
ptv = &tv; //ptv在select()中使用
}
//计算处理第一个消息的时间点与当前时间的间隔
static int calcNextTimeout(struct timeval *tv)
{
struct ril_event *tev = timer_list.next; //获取第一个定时消息的时间间隔。定时消息是排序的。
// Sorted list, so calc based on first node
if (tev == &timer_list){ // calcNextTimeout()函数先是判断超时消息列表是否为空,为空则返回-1
// no pending timers
return -1;
}
if (timercmp(&tev->timeout, &now, >)) { //处理消息的时间还没到(时间点)
timersub(&tev->timeout, &now, tv);
} else { //处理消息的时间已过
// timer already expired.
tv->tv_sec = tv->tv_usec = 0; //tv的最小值为0,此时会导致select()直接退出
}
}
n = select(nfds, &rfds, NULL, NULL, ptv);
实际上等待的描述符只有两种:socket描述符和用于发送定时消息的pipe描述符
最后,就是处理三种类型的消息:
//Check for timeouts
processTimeouts(); //处理定时消息
//Check for read-ready
processReadReadies(&rfds,n); //处理侦听消息
//Fire away
firePending(); //处理挂起消息
processTimeouts()是处理定时消息。它查找当前时间点以前的消息(因为是排序的,所以只要查找前面几个就可以)。然后,将这些消息放到pending_list中,暂时不处理。
static void processTimeouts()
{
dlog("~~~~ +processTimeouts ~~~~");
MUTEX_ACQUIRE();
struct timeval now;
struct ril_event * tev = timer_list.next; //获取第一个消息的时间
struct ril_event * next;
getNow(&now);
// walk list, see if now >= ev->timeout for any events
dlog("~~~~ Looking for timers <= %ds + %dus ~~~~", (int)now.tv_sec, (int)now.tv_usec);
while ((tev != &timer_list) && (timercmp(&now, &tev->timeout, >))) { //查找所有当前时间以前的消息
// Timer expired
dlog("~~~~ firing timer ~~~~");
next = tev->next;
removeFromList(tev); //从timer_list中移除
addToList(tev, &pending_list); //添加到pending_list中
tev = next;
}
MUTEX_RELEASE();
dlog("~~~~ -processTimeouts ~~~~");
}
接下来处理watch_list列表。processReadReadies(&rfds,n)的主要工作是从侦听列表中取出消息,移至pending_list中。如果ril_event的persist设置了,就不会从侦听列表中移除该消息。
static void processReadReadies(fd_set * rfds, int n)
{
dlog("~~~~ +processReadReadies (%d) ~~~~", n);
MUTEX_ACQUIRE();
for (int i = 0; (i < MAX_FD_EVENTS) && (n > 0); i++) {
struct ril_event * rev = watch_table[i];
if (rev != NULL && FD_ISSET(rev->fd, rfds)) { //FD_ISSET()判断描述符是否在当前循环的等待列表(rfds)中
addToList(rev, &pending_list); //添加到pending_list中
if (rev->persist == false) { //判断ril_event的persist是否设置了,如果没有设置,就调用removeWatch(rev,i)将该消息从watch_list中移除
removeWatch(rev, i);
}
n--;
}
}
MUTEX_RELEASE();
dlog("~~~~ -processReadReadies (%d) ~~~~", n);
}
最后处理pending_list。依次调用pending_list中的消息的回调函数。
static void firePending()
{
dlog("~~~~ +firePending ~~~~");
struct ril_event * ev = pending_list.next;
while (ev != &pending_list) { //pending_list不为空
struct ril_event * next = ev->next;
removeFromList(ev);
ev->func(ev->fd, 0, ev->param); //func是回调函数,fd为消息中的描述符,以此与ril_event联系
ev = next;
}
dlog("~~~~ -firePending ~~~~");
}
具体流程:ril_event_int()完成后,通过ril_event_set配置一个新的ril_event,并通过ril_event_add加到队列之中(什么队列?)(实际用的是rilEventAddWakeup),它会把队列里所有的ril_event的fd加入readFds集合中。在进入ril_event_loop之前,已经加入了一个s_wakeupfd_event,通过pipe机制(pipe()函数会创建一对文件描述符,一个用于读,一个用于写,都被记录在全局变量中。它实际是用来发送定时消息的)实现的,这个event为了可以在一些情况下,能内部唤醒ril_event_loop的多路复用阻塞。然后调用ril_event_loop(),其中的I/O多路复用机制(select)会等待这些fd状态的变化,如果任何一个fd有数据写入,则进入流程processTimeouts(),processReadies(&rfds,n),firePending()。
int filedes[2];
ret = pipe(filedes);
s_fdWakeupRead = filedes[0]; //消息循环中用于侦听
s_fdWakeupWrite = filedes[1]; //用于通知消息循环定义消息已发送
消息循环(ril_event_loop()函数内)位于单独的线程中,用来处理三种消息,分别对应三个不同的队列,面对的是三种不同的需求:
a. 定时列表(timer_list):此队列中的消息主要用来处理一些延时的操作。比如:从飞信模式切换到通信模式(实际是打开通信模块)时,若SIM卡未准备好,那么就需要延时一段时间再检查是否准备好,此时就需要将消息扔至此队列。
b. 侦听列表(watch_list):此队列中的消息一是作为socket服务端,用来侦听客户端的请求;另一个是作为本进程中的其他线程(如检查通信模块来电消息的线程)传递过来的消息。
c. 挂起列表(pending_list):实际上在处理上面两种消息的时候,并不真正的处理消息体,而是将符合条件的消息丢到本队列中。由于消息附带有消息处理函数,所以在处理本队列消息时,只要直接触发就可以。
2. 初始化libreference-ril.so,通过RIL_init完成。
RIL_Iint通过参数获取硬件接口的设备文件或模拟硬件接口的socket,然后新开一个mainLoop线程继续初始化。
mainLoop的主要任务是建立与硬件的通信,然后通过read方法阻塞等待硬件的主动上报或响应。
Const RIL_RadioFunctions *RIL_Init(const struct RIL_Env *env,int argc,char **argv)
{
int ret;
int fd = -1;
int opt;
pthread_attr_t attr;
s_rilenv = env;
while ( -1 != (opt = getopt(argc, argv, "p:d:s:"))) {
switch (opt) {
case 'p':
s_port = atoi(optarg);
if (s_port == 0) {
usage(argv[0]);
return NULL;
}
LOGI("Opening loopback port %d\n", s_port);
break;
case 'd':
s_device_path = optarg;
LOGI("Opening tty device %s\n", s_device_path);
break;
case 's':
s_device_path = optarg;
s_device_socket = 1;
LOGI("Opening socket %s\n", s_device_path);
break;
default:
usage(argv[0]);
return NULL;
}
}
if (s_port < 0 && s_device_path == NULL) {
usage(argv[0]);
return NULL;
}
pthread_attr_init (&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
ret = pthread_create(&s_tid_mainloop, &attr, mainLoop, NULL);
return &s_callbacks;
}
static void *
mainLoop(void *param)
{
for (;;) {
fd = -1;
while (fd < 0) {
if (s_port > 0) {
fd = socket_loopback_client(s_port, SOCK_STREAM);
} else if (s_device_socket) {
if (!strcmp(s_device_path, "/dev/socket/qemud")) {
/* Qemu-specific control socket */
fd = socket_local_client( "qemud",
ANDROID_SOCKET_NAMESPACE_RESERVED,
SOCK_STREAM );
if (fd >= 0 ) {
char answer[2];
if ( write(fd, "gsm", 3) != 3 ||
read(fd, answer, 2) != 2 ||
memcmp(answer, "OK", 2) != 0)
{
close(fd);
fd = -1;
}
}
}
else
fd = socket_local_client( s_device_path,
ANDROID_SOCKET_NAMESPACE_FILESYSTEM,
SOCK_STREAM );
} else if (s_device_path != NULL) {
fd = open (s_device_path, O_RDWR);
if ( fd >= 0 && !memcmp( s_device_path, "/dev/ttyS", 9 ) ) {
/* disable echo on serial ports */
struct termios ios;
tcgetattr( fd, &ios );
ios.c_lflag = 0; /* disable ECHO, ICANON, etc... */
tcsetattr( fd, TCSANOW, &ios );
}
}
if (fd < 0) {
perror ("opening AT interface. retrying...");
sleep(10);
/* never returns */
}
}
s_closed = 0;
ret = at_open(fd, onUnsolicited);
if (ret < 0) {
LOGE ("AT error %d on at_open\n", ret);
return 0;
}
RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_0);
// Give initializeCallback a chance to dispatched, since
// we don't presently have a cancellation mechanism
sleep(1);
waitForClose();
LOGI("Re-opening after close");
}
}
static UserCallbackInfo *
internalRequestTimedCallback (RIL_TimedCallback callback, void *param,
const struct timeval *relativeTime)
{ //用于发送定时消息。它包含了三个参数:callback回调函数、param参数、relativeTime时间间隔
struct timeval myRelativeTime;
UserCallbackInfo *p_info;
p_info = (UserCallbackInfo *) malloc (sizeof(UserCallbackInfo));
p_info->p_callback = callback;
p_info->userParam = param;
if (relativeTime == NULL) {
/* treat null parameter as a 0 relative time */
memset (&myRelativeTime, 0, sizeof(myRelativeTime));
} else {
/* FIXME I think event_add's tv param is really const anyway */
memcpy (&myRelativeTime, relativeTime, sizeof(myRelativeTime));
}
ril_event_set(&(p_info->event), -1, false, userTimerCallback, p_info); //对ril_event进行设置
ril_timer_add(&(p_info->event), &myRelativeTime); //将定时消息添加到timer_list中,myRelativeTime为多久后出发消息
triggerEvLoop(); //通知消息循环
return p_info;
}
// Add timer event
void ril_timer_add(struct ril_event * ev, struct timeval * tv)
{
dlog("~~~~ +ril_timer_add ~~~~");
MUTEX_ACQUIRE();
struct ril_event * list;
if (tv != NULL) {
// add to timer list
list = timer_list.next;
ev->fd = -1; // make sure fd is invalid
struct timeval now;
getNow(&now);
timeradd(&now, tv, &ev->timeout);
// keep list sorted
while (timercmp(&list->timeout, &ev->timeout, < ) //找到插入位置。插入的ril_event的位置之前的消息的timeout小于它的timeout,大于或等于它的timeout的消息都在它的后面
&& (list != &timer_list)) {
list = list->next;
}
// list now points to the first event older than ev
addToList(ev, list);
}
MUTEX_RELEASE();
dlog("~~~~ -ril_timer_add ~~~~");
}
static void triggerEvLoop() {
int ret;
if (!pthread_equal(pthread_self(), s_tid_dispatch)) {
/* trigger event loop to wakeup. No reason to do this,
* if we're in the event loop thread */
do {
ret = write (s_fdWakeupWrite, " ", 1); //向管道的写端写入数据
} while (ret < 0 && errno == EINTR);
}
}
为什么采用管道来通知定时消息已送至消息队列?这是为了和socket描述符保持一致,以便采用一致的方式来处理。
3. 通过RIL_init获取RIL_RadioFuntions函数指针,通过RIL_register完成注册,并打开与上层通信的socket通道。
RIL_RadioFunctions结构是RIL实现库需要实现的部分,通过RIL_Init返回rild。
至此,rild到RIL的实现库请求发送路径和反之的响应路径就已经搭建起来了。
RILD消息循环是一个通用的消息循环模型,也可以说是一种设计模式。
RIL_register()在ril.cpp中实现->ril_event_set(&s_listen_event,s_fdListen,false,listenCallback,NULL)->listenCallback(int fd,short flags,void *param)->ril_event_set(&s_commands_event,s_fdCommand,1,processCommandsCallback,p_rs)->processCommandsCallback