RILRadio Interface Layer的缩写,是介于电话服务和硬件设备之间的抽象层。它一方面负责应用层命令的发送,另一方面把Modem对命令响应内容返还给应用。可以认为它是应用和Modem之间的信息管道。

  RIL相关的代码主要分布在 /hardware/ril 目录下面,会编译生产几个重要的库文件或可执行文件,分别为 rild, libril.so,librefrence_ril.so, 但是librefrence_ril.so的源代码,不同的厂商会根据自己的Modem特性做相应的修改,其中高通平台,librefrence_ril.so文件被libril-qc-qmi-1.so文件取代,该库的源代码路径如下 : /vendor/qcom/proprietary/qcril/qcril_qmi

上面提到的文件中,rild由/hardware/ril/rild编译生成,libril.so由/hardware/ril/libril编译生成

        在介绍Android RIL之前,我先给大家回顾一下应用和底层代码的几种常用的通信方式。

1Android JNI. 其中Android JNI又有两种实现方式,分别为静态编译和动态加载。例如:

{ "native_setup","(Ljava/lang/Object;ILjava/lang/String;)V", (void*)android_hardware_Camera_native_setup}

上面是一个典型的JNI函数声明,定义了Java层的代码和底层代码函数接口的对应关系。也就是说在java里面调用native_setup 函数其实会转换成调用底层的 android_hardware_Camera_native_setup函数。对于静态编译来说,它会把这种函数声明和函数注册相关的代码编译成一个库文件,应用代码想要使用这个接口函数时,需要首先修改*.mk文件,引入该库文件,然后才能引入该native方法。但是动态加载就不一样了,它是在系统运行的时候动态的注册函数。例如:

REG_JNI(register_android_net_wifi_WifiNative),

REG_JNI(register_android_hardware_Camera),

上面的代码摘自/frameworks/base/core/jni/AndroidRuntime.cpp

对应很多系统服务,像这种函数对应关系,并不是将其分别编译成静态库,而是在系统启动运行的时候动态的加载并注册。这种JNI的通信方式就是动态加载。

2Socket通信方式。Socket是用于网络通信的套接字,但这里我们用的是本地Socket,也就是Local Socket.我们先来看一下这种通信方式的关键代码:

Framework层的java代码:

LocalSocket s = null;

LocalSocketAddress l;

s = new LocalSocket();

l = newLocalSocketAddress(rilSocket, LocalSocketAddress.Namespace.RESERVED);

s.connect(l);

其中rilSocket是一个字符串变量,是要连接的Socket服务的名字。

底层的代码:

intfdListen = -1;

fdListen = android_get_control_socket(socket_name);

ret = listen(fdListen, 4);

s_fdCommand = accept(s_fdListen, (sockaddr*) &peeraddr, &socklen);

这个是Socket服务端的代码。有一点需要注意的是,这里的变量  socket_name要和要和前面提到的字符串变量rilSocket相同,这样连接之后,服务端和客户端就可以按照Socket的编程方式进行read, write通信了。

有一点需要注意的是,我们利用函数android_get_control_socket(socket_name);获取的socket服务端必须在init.rc的文件中申明定义,如下:

service logd /system/bin/logd

class core

socket logd stream 0666 logd logd

socket logdr seqpacket 0666 logd logd

socket logdw dgram 0222 logd logd

seclabel u:r:logd:s0

其中socket关键字,表示给进程 logd 分配一个sokcet资源。上面的代码摘自init.rc文件。

framework层和底层交互的java代码主要是RIL.java,路径如下:

frameworks/opt/telephony/src/java/com/android/

 internal/telephony/RIL.java

 系统通过下面的流程创建RIL对象:

framework层和底层交互的java代码主要是RIL.java,路径如下:

frameworks/opt/telephony/src/java/com/android/

 internal/telephony/RIL.java

 系统通过下面的流程创建RIL对象:

系统通过上面的调用最终进入RIL的构造函数,在该构造函数中,主要创建了两个线程,分别为mSenderThread(用于向modem发送命令)和mReceiverThread(用于读取modem的响应或者主动上报的信息)。

创建mSenderThread

mSenderThread = newHandlerThread("RILSender" + mInstanceId);

mSenderThread.start();

创建mReceiverThread:

mReceiver = newRILReceiver();

mReceiverThread =newThread(mReceiver,"RILReceiver"+mInstanceId);

mReceiverThread.start();

下面,我们来看看这个两个线程都做了哪些事情。对于mReceiverThread线程,如下:

s = new LocalSocket();

l = newLocalSocketAddress(rilSocket,

LocalSocketAddress.Namespace.RESERVED);

s.connect(l);

for (;;) {

…….

length= readRilMessage(is, buffer);

…….

}

从上面的代码可以看出,mReceiverThread线程其实就是创建了一个Socket,并和服务端进行连接,然后不断循环读取Socket套接字里面的消息。

对于mSenderThread

handleMessage(Messagemsg){

……

dataLength[0] =dataLength[1] = 0;

dataLength[2] = (byte)((data.length >> 8) &0xff);

dataLength[3] = (byte)((data.length) & 0xff);

s.getOutputStream().write(dataLength);

s.getOutputStream().write(data);

……}

从上面的代码可以看出, mSenderThread 有一个监听消息的handler处理函数,当有别的地方发送消息过来时,该线程就会把数据经过处理后发送给Socket套接字。

现在关于RIL,我们已经知道客户端是如何创建并操作套接字的,但是我们还不知道服务端是在哪里创建的。继续往下看。

service ril-daemon /system/bin/rild

class main

socket rild stream 660 rootradio

socket rild-debug stream 660radio system

user root

group radio cache inet misc audio log qcom_diag

上面的一段代码摘自init.rc,定义了一个服务ril-daemon,对应的可执行文件为/system/bin/rild,该服务是由Init进程启动的。而可执行文件rild又是由/hardware/ril/rild目录里面的代码编译生成。

Rild服务,进入main主函数后,执行以下流程:

上面是main函数的几个主要调用过程。

进入RIL_startEventLoop

int result =pthread_create(&s_tid_dispatch, &attr,  eventLoop, NULL);

创建一个eventLoop的线程。

进入eventLoop:

ret = pipe(filedes);

s_fdWakeupRead =filedes[0];

上面是main函数的几个主要调用过程。

进入RIL_startEventLoop

int result =pthread_create(&s_tid_dispatch, &attr,  eventLoop, NULL);

创建一个eventLoop的线程。

进入eventLoop:

ret = pipe(filedes);

s_fdWakeupRead =filedes[0];

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

firePending();}

从代码分析知道,RIL_startEventLoop函数,其实就是创建消息队列机制,当有进程往无名管道的读节点s_fdWakeupWrite里面写数据时,将会触发select函数,而后调用firePending函数进行处理。在firePending函数中继而调用ev->func(ev->fd, 0, ev->param);函数进行具体的处理。

对于这种消息机制使用方法,做一个总结:

*staticstructril_events_wakeupfd_event;

*ril_event_set(&s_wakeupfd_event,s_fdWakeupRead,     true,processWakeupCallback, NULL);

*rilEventAddWakeup (&s_wakeupfd_event);

接下来看看rilInit函数,在这里我只介绍几个关键的函数调用:

qcril_response_api[QCRIL_DEFAULT_INSTANCE_ID ](struct RIL_Env *) env;

qcril_event_init();

qcril_init();

进入qcril_event_init:

ret = utf_pthread_create_handler(&qcril_event.tid,&attr,qcril_event_main, NULL);

同样,一开始创建了一个qcril_event_main线程,

qcril_event_main线程中:

ret = pipe(filedes);

qcril_event.fdWakeupRead =filedes[0];

qcril_event.fdWakeupWrite =filedes[1];

FD_SET(qcril_event.fdWakeupRead,&qcril_event.readFds);

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

for (;;){

n = select(qcril_event.fdWakeupRead + 1, &rfds, NULL, NULL, NULL);

……

ret = read(qcril_event.fdWakeupRead, &buff,sizeof(buff));

err_no = qcril_process_event(ev->instance_id,ev->modem_id,ev->event_id,ev->data, ev->datalen,ev->t );

}

从上面代码分析看出, qcril_event_main线程和RIL_startEventLoop函数一样,也是创建了一个事件监听机制,首先创建一个无名管道,获取该无名管道的读写端分别赋值给qcril_event.fdWakeupRead和qcril_event.fdWakeupWrite,然后将qcril_event.fdWakeupRead 加入到监听文件组,一旦有进程往qcril_event.fdWakeupWrite写数据,将会触发select函数,然后读取管道里面的数据,再调用qcril_process_event函数进行处理。

当Modem有消息要主动上报给上层时,都会往qcril_event.fdWakeupWrite里面写数据,从而触发qcril_process_event函数处理数据,这个待会再讲。

进入qcril_init:

里面有一个重要的函数, qcril_init_hash_table:

uint32 reg_index, hash_index;

qcril_dispatch_table_entry_type *temp_entry_ptr;

for (reg_index = 0; reg_index <  QCRIL_ARR_SIZE( qcril_event_table ); reg_index++)

{

{

hash_index = qcril_hash( qcril_event_table[reg_index].event_id,QCRIL_HT_ENTRIES_MAX, 0 );

if(hash_index <QCRIL_HT_ENTRIES_MAX)

{

if (qcril_hash_table[hash_index] == NULL)

{

qcril_hash_table[hash_index]=                             &qcril_event_table[reg_index];

else

{

/* Link the entry at the end of the collision list */

temp_entry_ptr = qcril_hash_table[hash_index];

while (temp_entry_ptr->next_ptr != NULL)

{

temp_entry_ptr = temp_entry_ptr->next_ptr;

}

temp_entry_ptr->next_ptr =&qcril_event_table[reg_index];

}

}

}

}

上面的代码就是将  qcril_event_table 各个元素用一个hash表进行连接,方便后续查找。

{ QCRIL_REG_ALL_ACTIVE_STATES( RIL_REQUEST_GET_SIM_STATUS, qcril_uim_request_get_sim_status ) },

{ QCRIL_REG_ALL_ACTIVE_STATES( RIL_REQUEST_ENTER_SIM_PIN, qcril_uim_request_enter_pin ) },

当上层代码下发命令或者Modem有数据上报的时候,系统会根据传递的ID号,通过这个hash列表找到相应的操作函数,而后做相应的处理。RIL_Init函数最终把qcril_request_api这个回调函数返回给rild,并将其拷贝给 s_callbacks变量。

再来看看RIL_register:

该函数其实最主要的事情就是创建了一个Socket,监听系统应用发起的Socket连接请求,而后做相应的处理:

fdListen = android_get_control_socket(socket_name);

ret = listen(fdListen, 4);

ril_event_set (socket_listen_p->listen_event, fdListen,   false,  listenCallback,  socket_listen_p);

自此,rild这个可执行文件进入main函数后所做的事情已经基本了解,总结如下:

1、通过函数RIL_startEventLoop /hardware层创建了一个线程,监听事情的触发并响应。而后通过函数RIL_register创建Socket服务端,监听上层客户端发起的连接请求,这种Socket的监听就是在RIL_startEventLoop 创建的线程里面做的。

2、通过函数rilInit初始化高通平台的 ril接口,创建了以函数qcril_event_main为入口的线程,监听hardware层发送下来的命令同时上报Modem响应的数据。

Modem向上层发送数据分两种情况,一种是主动发事件,另一种是发送上层请求的事件,下面以一个例子

来看看事件发送流程。

发送数据前,会先发起Socket连接请求,触发listenCallback函数,在该函数里面通过fdCommand = accept(fd, (sockaddr *) &peeraddr, &socklen)语句获取Socket的操作节点,而后将该节点加入监听队列,一旦监听到上层有命令发送,会触发processCommandsCallback函数。例如当我们发送命令RIL_REQUEST_GET_SIM_STATUS获取sim卡状态时,其调用流程如下:

我们现在来对这些东西做个总结:

RIL架构中,维护三个数组,分别为 s_commands, s_unsolResponses  和 qcril_event_table。

其中s_commands部分成员如下:

s_commands[] = {

……

{RIL_REQUEST_GET_SIM_STATUS, dispatchVoid, responseSimStatus},

{RIL_REQUEST_DIAL, dispatchDial,responseVoid},

{RIL_REQUEST_SHUTDOWN, dispatchVoid, responseVoid},

……

}

s_commands数组里面定义的函数是处理上层的主动请求的方法,

其中红色的函数是发送命令时的响应函数,通过该函数发送命令给modem, 紫色部分的函数是处理Modem反馈信息的函数,通过该函数将Modem返还的数据简单处理后再发送给上层。

s_unsolResponses 是处理Modem主动上报信息的函数数组,比如信号强度,STK信息等,其部分成员如下:

s_unsolResponses[] = {

……

{RIL_UNSOL_SIGNAL_STRENGTH, responseRilSignalStrength, DONT_WAKE},

{RIL_UNSOL_STK_PROACTIVE_COMMAND, responseString, WAKE_PARTIAL},

……

}

其中红色部分是处理Modem主动上报的信息的函数。

qcril_event_table[] =

{

……

{ QCRIL_REG_ALL_ACTIVE_STATES(RIL_REQUEST_GET_SIM_STATUS,           qcril_uim_request_get_sim_status ) },

……

}

该函数是直接接受上层的命令和Modem进行交互的函数。

发送命令和接受Modem主动上报的信息时,数据流程图如下:

Android RIL相关推荐

  1. android ril.java_Android RIL的java框架

    Android RIL的Java部分也被分为了两个模块,RIL模块与Phone模块.其中RIL模块负责进行请求以及相应的处理,它将直接与RIL的原声代码进行通信.而Phone模块则向应用程序开发者提供 ...

  2. Android RIL

    Android RIL结构分析与移植 介绍 本文档对Android RIL部分的内容进行了介绍,其重点放在了Android RIL的原生代码部分. 包括四个主题: 1.Android RIL框架介绍 ...

  3. Android RIL 架构学习总结

    1.Android RIL 概念     (转自http://newfaction.net/2011/03/08/android-ril-structure-learning-summary.html ...

  4. Android——RIL 机制源码分析

    Android 电话系统框架介绍 在android系统中rild运行在AP上,AP上的应用通过rild发送AT指令给BP,BP接收到信息后又通过rild传送给AP.AP与BP之间有两种通信方式: 1. ...

  5. Android RIL学习

    Android RIL学习 tomorrow.cyz@gmail.com 1.Android RIL概念 Android RIL是基于telephony 服务和raido 硬件层的抽象层.Androi ...

  6. Android RIL框架分析

    1.RIL框架 RIL,Radio Interface Layer.本层为一个协议转换层,提供Android Telephony与无线通信设备之间的抽象层. Android RIL位于Telephon ...

  7. 冰枫论坛android,Android Ril 分析

    引言: 这段时间手中的工作,正好好调试一款3g modem,于是乎就分析了一下Android Ril的代码,做了些总结归纳,阅读时可以先看前后两段以及流程图,这样可能更容易把握: 知识在于分享,文档中 ...

  8. Android RIL架构分析——(1)本地库实现[c/cpp部分]

    田海立 2012-03-24 本文介绍了AndroidRIL的总体架构,具体分析了本地库的实现[c/cpp部分] 一.总体架构 Android RIL (Radio Interface Layer)提 ...

  9. Android RIL 调试分析全记录 No APN found for carrier

    Android RIL 调试分析全记录 以前没有调试过RIL的东西, 从零开始, 花了两周时间,终于拨号成功,这里发表出来与大家共享经验,少走弯路. 上一篇文章是一个成功拨号的Android RIL ...

  10. android ril.java_Android RIL学习

    1.Android RIL概念 Android RIL是基于telephony服务和raido硬件层的抽象层.Android的rild库是介于HAL接口与baseband modem之间,它提供了语音 ...

最新文章

  1. Python到底有多强大?只需 15 行代码即可进行人脸检测
  2. spark on yarn任务提交及运行完整流程图
  3. linux实现自己的write函数,Linux 内核源码阅读 - write 系统调用的实现
  4. youcans 的 OpenCV 学习课—1.安装与环境配置
  5. (89)FPGA三分频设计,面试必问(十三)(第18天)
  6. Ghost 2.18.3 发布,基于 Markdown 的在线写作平台
  7. L1-045 宇宙无敌大招呼-PAT团体程序设计天梯赛GPLT
  8. 再见, VS Code !你好,GitHub!
  9. 2018想要薪资翻倍?你需要掌握这个技能
  10. linux下录制视频流,Ubuntu Linux 下的PSP视频输出以及录制
  11. Ubuntu18.04 (WSL) 编译RK3399 Android8.1源码
  12. 关于开会了一点点想法
  13. OpenStack实践(十一):Instance Live Migrate and Evacuate
  14. 如何同时或者按顺序间隔启动多个程序
  15. Java评论点赞推送系统_推送(评论,点赞,关注)
  16. 在 UltraEdit 或 UEStudio 中执行 DOS 或 Windows 命令
  17. Gitlab: 常用设置
  18. DICOM:开源DICOM服务框架DCM4CHE 安装
  19. 【计算机视觉】图像形成与颜色
  20. 十六进制数据原样转换成字符串

热门文章

  1. windows中安卓子系统安卓
  2. 宅米网技术变迁——初创互联网公司的技术发展之路
  3. 暴走:从核弹级技术Deepfake与区块链攻防战开始说起
  4. 《那些年啊,那些事——一个程序员的奋斗史》——127
  5. 认识Vue.js+Vue.js的优缺点+和与其他前端框架的区别
  6. 国家统计局公布,平均工资最高的行业是......
  7. TeamCity自动部署操作手册
  8. MATLAB工具箱路径缓存
  9. 【安全归约】第一讲 | Definitions定义(算法和安全模型)
  10. favourite和favorite一样吗