Android telephony整体结构简要介绍(RILJ、RILD详解)

一、前言

本文主要简要介绍Android整个通信模块结构信息,并详细介绍RIL(modem和上层之间的运作方式)。

1.1、基础概念

1、通信整体构成
首先从硬件层面上来说,手机的设计都是手机芯片+信号处理模块,信号处理模块可以处理modem(调制解调器)+RF(射频)+UICC(通用集成电路卡,即电话卡) 。

2、AP和BP
其中Android系统、UI和应用程序运行在手机芯片上,我们称之为AP端
手机视频通讯控制运行在信号处理模块上的芯片上,我们称之为BP端
这么设计核心优势主要是不管操作系统怎么变,通信模块都是相同的。数据都不会出错,增加稳定性

1.2、相关代码

1、大部分源码aosp都有,由于各个厂商定制的不同,在芯片厂商提供给手机厂商的原始代码和aosp的路径还是有部分差异和定制。以下代码路径以MTK方案举例。MTK部分型号芯片也在vendor路径下单独提供了实现的代码。但这不影响我们学习通信模块。这部分表格中我就没有列出来了。
2、本文讲解时只列举核心代码行,需要读者自行结合源码阅读

层级 路径 文件
Apps /packages/apps/Dialer Dialer.apk
/packages/apps/Messaging Messaging.apk
/packages/apps/Contacts Contacts.apk
Providers /packages/providers/TelephonyProvider TelephonyProvider.apk
/packages/providers/ContactsProvider ContactsProvider.apk
Service /packages/services/Telecomm Telecomm.apk
/packages/services/Telephony TeleService.apk
/packages/services/Mms MmsService.apk
vendor/mediatek/proprietary/packages/services/Ims ImsService.apk
Framework frameworks/base/telecomm framework.jar
frameworks/base/telephony framework.jar
frameworks/opt/telephony framework.jar
frameworks/opt/net/ims ims-common.jar
vendor/mediatek/proprietary/frameworks/opt/telecomm mediatek-telephony-common.jar
vendor/mediatek/proprietary/frameworks/opt/telephony mediatek-telephony-common.jar
vendor/mediatek/proprietary/frameworks/opt/telephonybase mediatek-telephony-base.jar
HAL vendor/mediatek/proprietary//hardware/ril/fusion librilfusion.so、libmtk-ril.so、mtkfusionrild.bin
vendor/mediatek/proprietary/hardware/c2kril/fusion libvia-ril.so
vendor/mediatek/proprietary/hardware/gsm0710muxd gsm0710muxd.bin
vendor/mediatek/proprietary/external/ccci_mdinit_src ccci_mdinit.bin
vendor/mediatek/proprietary/external/ccci_fsd ccci_fsd.bin
Drivers kernel-4.9/drivers/misc/mediatek/eccci kernel img

1.3、软件架构

1、功能说明
由于BP的设计都是这样,于是不管你是ios还是安卓或是塞班还是老人机,通信模块提供给我们的功能就这么五个部分:UICC、ServiceState、DataConnect、Call、SMS

功能 简单介绍
UICC SIM卡:存储号码、短信、PIN、PUK、驻网鉴权、STK工具包、2G、3G、4G
ServiceState 网络服务:网络制式、运营商名字、信号格数、时区、漫游、注册情况
DataConnect 上网服务:2G/3G/4G/5G
Call 通话:拨号、接听、挂断、保持、恢复、多方通话
SMS 短信:普通短信、长短信

2、简单的画了一张Android通信模块的架构图,功能设计完全按照Android分层而来

通信模块架构图.png

软件架构图中其他几个功能点简单介绍

功能 简单介绍
应用内 Dialer拨号、Contacts联系人、Mms短信、settings设置、browser浏览器
TeleService Telephony应用框架:数据连接、MMS业务逻辑、Call控制、RILD通信
Telecom 管理通话、和TeleService交互对应用层提供接口
通话 GSMCdmaPhone:2G、3G通话 ImsPhone:4G通话(Volte)
RILD RILD是RILJ和Modem中间层:1、RILJ下发请求->RILD将Request转换为Modem的AT指令发送2、Modem上报或者返回的消息->RILD处理传给RILJ
Gsm0710muxd 1、Gsm0710muxd是AT指令通道进行复用的守护进程2、Gsm0710是开源多路复用协议,提高Modem和AP间AT指令的通信效率
CCCI_FSD 1、Modem不能直接操作文件系统、CCCI_FSD是AP提供给Modem文件守护进程2、通过FSDmodem就可以操作文件系统
CCCI_MDINIT Modem状态守护进程:启动、停止、重启、飞行模式、reset重置
ECCCI driver 驱动框架:复用不同modem驱动、减少中断内存开支、AT指令转换modem数据、网卡驱动、文件系统、Audio通话数据

二、RIL详细介绍

2.1、phone进程

packages\services\Telephony
phone进程是各个功能详细模块framework开始的地方
1、自启动

 <application android:name="PhoneApp"
            android:persistent="true"

在elephony#AndroidManifest.xml中有persistent标记。有此标记AMS会持续保证进程(com.android.phone)存活,意外挂掉也会自动重启。所以PhoneApp(telephony的Application)就是最初的代码入口

public class PhoneApp extends Application {
    public void onCreate() {
        mPhoneGlobals = new PhoneGlobals(this);
        mPhoneGlobals.onCreate();
        mTelephonyGlobals = new TelephonyGlobals(this);
        mTelephonyGlobals.onCreate();

2、PhoneGlobals#onCreate
在PhoneGlobals的构造函数里,开始了Phone对象的创建(makeDefaultPhones).

public void onCreate() {
        PhoneFactory.makeDefaultPhones(this);
    }
public static void makeDefaultPhone(Context context) {
    //有几张卡创建几个Phone对象
    sPhones = new Phone[numPhones];
    //创建RILJ对象
    sCommandsInterfaces = new RIL[numPhones];

2.2、RIL

名字 介绍
RILJ java层,运行在telephony的phone进程,它为上层提供访问modem的入口和消息接收
RILD HAL层,系统守护进程。RILJ和Modem中间层:1、RILJ下发请求->RILD将Request转换为Modem的AT指令发送2、Modem上报或者返回的消息->RILD处理传给RILJ

在Android8.0之前,RILJ和modem通信时通过socket连接发送AT指令来进行通信。现在的版本已经改用HIDL通信的方式。HIDL和AIDL很类似。可以简单的理解为绑定底层的服务来作用到对应的功能上。

2.2.1、RILJ

基本代码结构

public class RIL extends BaseCommands implements CommandsInterface
public abstract class BaseCommands implements CommandsInterface 
public interface CommandsInterface 

这里的RIL类就是我们口中的RILJ,采用依赖倒转原则,定义功能。
<1>、 CommandsInterface接口中定义一些常量表示状态,再抽象一些方法。
<2>、抽象类实现方法,观察者模式 register、unregister注册监听。留空一些方法具体实现在RIL类中实现
<3>、RIL类则是具体的功能实现。
<4>、在BaseCommands中定义了大量RegistrantList和Registrant对象,这两个对象是对handler消息的封装,结合register注册的监听,就能把从RILD发送上来的消息传递给监听的对象
<5>、RILJ消息的下发和接收都是通过HIDL拿到radio service,间接通过hidl service来操作和modem之间消息的上传和下发。
获得radio hidl service
RIL.java中RIL#getRadioProxy

public IRadio getRadioProxy(Message result) {
        ...
        try {
            mRadioProxy = IRadio.getService(HIDL_SERVICE_NAME[mPhoneId == null ? 0 : mPhoneId],true);   // mPhoneId sim卡id,一张卡一个service
             if (mRadioProxy != null) {
                mRadioProxy.linkToDeath(mRadioProxyDeathRecipient,
                        mRadioProxyCookie.incrementAndGet());
                mRadioProxy.setResponseFunctions(mRadioResponse, mRadioIndication);
            } else {
                riljLoge("getRadioProxy: mRadioProxy == null");
            }
        ...
        return mRadioProxy;
    }

下发

@Override
    public void getIccCardStatus(Message result) {
        IRadio radioProxy = getRadioProxy(result);
        if (radioProxy != null) {
             // 封装要发送的数据
            RILRequest rr = obtainRequest(RIL_REQUEST_GET_SIM_STATUS, result,
                    mRILDefaultWorkSource);
      
            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));

            try {
                // 调用到HIDL 提供的radioservice
                radioProxy.getIccCardStatus(rr.mSerial);
            } catch (RemoteException | RuntimeException e) {
                handleRadioProxyExceptionForRR(rr, "getIccCardStatus", e);
            }
        }
    }

上传
在获得hw aodio service的时候,setResponseFunctions(mRadioResponse, mRadioIndication)传入了两个对象,他们负责消息的接收和监听。
这里随便复制了一个状态改变的回调:RadioIndication#radioStateChanged

public void radioStateChanged(int indicationType, int radioState) {
        mRil.processIndication(indicationType);

        CommandsInterface.RadioState newState = getRadioStateFromInt(radioState);
        if (RIL.RILJ_LOGD) {
            mRil.unsljLogMore(RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, "radioStateChanged: " +
                    newState);
        }

        mRil.setRadioState(newState);
    }

2.2.2 RILD

temp.png

RILD程序架构图清晰的表示了RILD的一个初始化过程。归纳为如下四点
1、ril.cpp来统管功能,具体实现在ril_event,reference_ril,atchanel,ril_service里
2、第一个流程:startEventLoop开启loop循环
3、第二个流程:rilInit初始化,打开AT通道流程
4、第三个流程: 注册回调函数,ril管理

2.2.2.1、loop循环简要说明

第一个流程开启loop循环后,按流程图走到ril_event#ril_event_loop方法,这里和handler原理中loop循环类似,无限循环查找处理三个链表中的event,下面截取最后循环的代码和链表结构体

static struct ril_event * watch_table[MAX_FD_EVENTS];
static struct ril_event timer_list;
static struct ril_event pending_list;

struct ril_event {
    struct ril_event *next;
    struct ril_event *prev;

    int fd;
    int index;
    bool persist;
    struct timeval timeout;
    ril_event_cb func;
    void *param;
};

void ril_event_loop()
{
    for (;;) {
        // Check for timeouts
        processTimeouts();
        // Check for read-ready
        processReadReadies(&rfds, n);
        // Fire away
        firePending();
    }
}

2.2.2.2、打开AT通道简要说明

打开AT通道,按代码流程rilInit走到at_open时,主要执行了readerLoop,在readerLoop中,此时AT通道有消息就会被处理。分别由s_unsolHandler 处理主动上报的消息(比如有短消息),processLine经过上层请求过后的需要回复到上层的消息,比如请求sim卡信息

static void *readerLoop(void *arg __unused)
{
    for (;;) {
            line1 = strdup(line);
            line2 = readline();
            if (s_unsolHandler != NULL) {
                s_unsolHandler (line1, line2);
            }
        } else {
            processLine(line);
        }
}

s_unsolHandler上报到RILJ

// ril.cpp
void RIL_onUnsolicitedResponse(int unsolResponse, const void *data,
                                size_t datalen) {
    ret = s_unsolResponses[unsolResponseIndex].responseFunction(
            (int) soc_id, responseType, 0, RIL_E_SUCCESS, const_cast<void*>(data),
            datalen);

static UnsolResponseInfo s_unsolResponses[] = {
#include "ril_unsol_commands.h"
};

//ril_unsol_commands.h
    {RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, radio::radioStateChangedInd, WAKE_PARTIAL},
    {RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, radio::callStateChangedInd, WAKE_PARTIAL},
    {RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED, radio::networkStateChangedInd, WAKE_PARTIAL},
    ...

// ril_service.cpp
int radio::radioStateChangedInd(int slotId,
        Return<void> retStatus = radioService[slotId]->mRadioIndication->radioStateChanged(
                convertIntToRadioIndicationType(indicationType), radioState);
        radioService[slotId]->checkReturnStatus(retStatus);
    }

    return 0;
}

sp<RadioImpl> radioService[SIM_COUNT];

struct RadioImpl : public V1_1::IRadio {
    int32_t mSlotId;
    sp<IRadioResponse> mRadioResponse;
    sp<IRadioIndication> mRadioIndication;

<1>、处理先也是流程化传递,先是ril.cpp的RIL_onUnsolicitedResponse然后通过结构体传递到ril_unsol_commands.h里声明的radio::radioStateChangedInd等方法
<2>、radio::radioStateChangedInd等方法的具体实现在ril_service.cpp中
<3>、ril_service在实现的时候,上报就交给了ril_service中RadioImpl结构体里的mRadioResponse和mRadioIndication。这两个对象就是2.2.1、RILJ小结获得hw radio service的时候传进来的两个对象。
<4>、总结一下就是rild初始化打开AT通道后,从AT通道中读到要上报的消息时,交给mRadioResponse和mRadioIndication来上报。RILJ拿到的hw radio service 就是ril_service
<5>、RadioImpl 这个将在第三部分讲,可以简单理解它为ril_service的代理。
processLine上报
这种上报也有几种类型

static void processLine(const char *line)
{
 if (sp_response == NULL) {
        /* no command pending */  
        handleUnsolicited(line); //  1、主动上报
    } else if (isFinalResponseSuccess(line)) {
        sp_response->success = 1;
        handleFinalResponse(line); // 2、成功,标准响应
    } else if (isFinalResponseError(line)) {
        sp_response->success = 0;
        handleFinalResponse(line);  // 3、失败,标准响应
    }  else if (s_smsPDU != NULL && 0 == strcmp(line, "> ")) {
        // See eg. TS 27.005 4.3
        // Commands like AT+CMGS have a "> " prompt
        writeCtrlZ(s_smsPDU);   // 4、收到>符号,发送sms数据再继续等待响应
        s_smsPDU = NULL;
    }  else switch (s_type) {   // 5、命令有具体的响应信息需要对应分析
        // case中省略了处理方法
        case NO_RESULT:
        case NUMERIC:
        case SINGLELINE:
        case MULTILINE:
        break;

// 这里以handleFinalResponse举例
static void handleFinalResponse(const char *line)
{
    // 把消息返回RILJ
    sp_response->finalResponse = strdup(line);
    //s_commandcond脱离阻塞状态 
    pthread_cond_signal(&s_commandcond);
}

static int at_send_command_full_nolock (const char *command, ATCommandType type,
                    const char *responsePrefix, const char *smspdu,
                    long long timeoutMsec, ATResponse **pp_outResponse)
{
    while (sp_response->finalResponse == NULL && s_readerClosed == 0) {
        if (timeoutMsec != 0) {
            err = pthread_cond_timedwait(&s_commandcond, &s_commandmutex, &ts);
        } else {
            err = pthread_cond_wait(&s_commandcond, &s_commandmutex);
        }

    if (pp_outResponse == NULL) {
        at_response_free(sp_response);
    } else {
        /* line reader stores intermediate responses in reverse order */
        reverseIntermediates(sp_response);
        *pp_outResponse = sp_response;
    }

int at_send_command (const char *command, ATResponse **pp_outResponse) {
    err = at_send_command_full (command, NO_RESULT, NULL,
                                    NULL, 0, pp_outResponse);
}

//ril_service.cpp
static void onRequest (int request, void *data, size_t datalen, RIL_Token t) {
            err = at_send_command_numeric("AT+CIMI", &p_response);

<1>、processLine处理也有5种类型,流程上都类似。以handleFinalResponse普通流程举例
<2>、从handleFinalResponse线程锁跟踪到at_send_command_full_nolock,线程解锁被再次唤醒过后把结果传给了函数参数*pp_outResponse。
<3>再跟踪at_send_command_full_nolock方法的调用流程,来自at_send_command 方法。而这个pp_outResponse就是这样传过来的。
<4>最后截取了ril_service.cpp中一次包含response的请求代码片段
<5>所以AT请求下发也是ril_service.cpp中众多方法间接调用at_send_command传递即可。

2.2.2.3、注册回调函数,ril管理说明

RIL_register (const RIL_RadioFunctions *callbacks) {
    radio::registerService(&s_callbacks, s_commands);
}
//ril_service.cpp
void radio::registerService(RIL_RadioFunctions *callbacks, CommandInfo *commands) {
    s_vendorFunctions = callbacks;
    s_commands = commands;
    for (int i = 0; i < simCount; i++) {
        radioService[i] = new RadioImpl;
}

<1>、注册函数就简单了,注册时把callback传进来还为每一张SIM卡创建了一个RadioImpl对象
<2>、radioService这里就对RIL进行收发管理。

2.2.2.4、RILD小结

<1>、RILJ层通过HIDL拿到reference-ril.c提供的radio service,通过service来下发和上传回调
<2>ril.cpp通过三大流程调用,来实现rild的功能。
<3>radio service下发是调用atchannel.c中at_send_command来给modem发送at指令
<4>radio service上传是通过set的两个回调接口向RILJ传递信息。或者通过at_send_command传递的response回传消息。

三、写在最后

对于Android通信模块来说,主要学习的是流程跟踪。从调用栈来讲,不管是读取SIM卡信息or状态,还是上网、拨号、发短信。都是通过RIL层来转发AT指令。通过对RIL的了解以达到了解整个通信模块咋个工作的目的。如果要具体的去修改或者维护telephony模块,还需要对具体功能的流程进行分析掌握,才能游刃有余。

read the fucking source code!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容