深入理解Android Telephony 之RILD机制分析
RILD负责modem和RILJ端的通信,信息分两种:unsolicited和solicited,前者是由modem主动上报的,诸如时区更新、通话状态、网络状态等消息,后者是RILJ端发请求并需要modem反馈的信息。RILJ与RILD之间的通信由主线程s_tid_dispatch负责监听,读取和分发,RILD与modem之间的通信由s_tid_mainloop和s_tid_reader负责写入和读取。
先看看目录,以android 7.0为例,RILD目录位于../hardware/ril,包括:
include —- 各种头文件定义,其中include/telephony/ril.h定义了135个RIL_REQUEST_XXX和45个RIL_UNSOL_XXX的宏定义,前者用于RILD向modem发送的请求消息id,后者用于由modem直接推送给RILD的消息id。
libril—-主要定义消息分发主线程和RILJ与RILD之间的socket监听
librilutils—-辅助类
reference-ril—-顾名思义,这个目录中的类有参考作用,因为整个RILD架构中,RILD与modem之间设计以库的形式加载,这个在RILD在初始化时能看到,这样就方便厂商定制
rild—-RILD的入口
在深入理解Android Telephony之RILD的启动 一文中看到,RILD通过rc加载,系统启动RILD的入口就到了rild.c中的main()函数,main主要做了三件事:
1、打开ril外部库,即厂商定制库;
2、启动事件分发主线程;
3、调用厂商库中的接口进行初始化:使用RIL_Init初始化RILD与modem的通信线程,使用RIL_register注册RILD与RILJ之间的socket,启动通信线程。
int main(int argc, char **argv) {
......
//打开libreference-ril.so
dlHandle = dlopen(rilLibPath, RTLD_NOW);
//创建主线程s_tid_dispatch,用来监听RILJ下发到socket的消息并分发。监听的原理是每个socket都注册上监听事件,然后把事件塞到s_tid_dispatch,一旦socket中有消息,s_tid_dispatch读到之后就触发事件的回调
RIL_startEventLoop();
//加载动态库,地址传给rilInit rilInit =(const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))dlsym(dlHandle, "RIL_Init");
...rilUimInit =(const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))dlsym(dlHandle, "RIL_SAP_Init");
//使用rilInit 初始化RIL,s_rilEnv是个静态回调数组
funcs = rilInit(&s_rilEnv, argc, rilArgv);
//
RIL_register(funcs);
}
看看主线程s_tid_dispatch,事件循环eventLoop–>ril_event_loop, watch_table,timer_list,pending_list三者为消息队列。
static struct ril_event * watch_table[MAX_FD_EVENTS];
static struct ril_event timer_list;
static struct ril_event pending_list;RIL_startEventLoop(void) {//标志s_tid_dispatch线程是否创建成功并启动s_started = 0;//创建线程,eventLoop是线程创建后的回调函数,其中会把s_started置为1int result = pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL);//如果线程尚未创建成功,进入死循环等待while (s_started == 0) {pthread_cond_wait(&s_startupCond, &s_startupMutex);}
......
}static void *
eventLoop(void *param) {
//s_tid_dispatch线程创建成功s_started = 1;
......
//定义匿名管道ret = pipe(filedes);
......s_fdWakeupRead = filedes[0];s_fdWakeupWrite = filedes[1];fcntl(s_fdWakeupRead, F_SETFL, O_NONBLOCK);ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true,processWakeupCallback, NULL);rilEventAddWakeup (&s_wakeupfd_event);ril_event_loop();
......
}
一、看看RILD是如何通过socket读取RILJ下发的消息
以s_wakeupfd_event为例:
1、首先封装一个事件,使用ril_event_set
void ril_event_set(struct ril_event * ev, int fd, bool persist, ril_event_cb func, void * param)
{memset(ev, 0, sizeof(struct ril_event));ev->fd = fd;ev->index = -1;ev->persist = persist;ev->func = func;ev->param = param;fcntl(fd, F_SETFL, O_NONBLOCK);
}
2、把事件添加到消息队列中,使用rilEventAddWakeup,其中分两小步
static void rilEventAddWakeup(struct ril_event *ev) {ril_event_add(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++) {if (watch_table[i] == NULL) {watch_table[i] = ev;ev->index = i;dump_event(ev);FD_SET(ev->fd, &readFds);if (ev->fd >= nfds) nfds = ev->fd+1;break;}}MUTEX_RELEASE();
}
ril_event_add就是把事件添加到watch_table中,最大可接纳8个事件,然后把事件文件描述符添加到readFds文件描述符集合中,nfds是当前readFds中的文件描述符总数。
triggerEvLoop就是往管道中写入空字符,来唤醒线程s_tid_dispatch,当当前线程不是s_tid_dispatch时,就需要唤醒s_tid_dispatch。
static void triggerEvLoop() {int ret;if (!pthread_equal(pthread_self(), s_tid_dispatch)) {do {ret = write (s_fdWakeupWrite, " ", 1);} while (ret < 0 && errno == EINTR);}
}
3、s_tid_dispatch被唤醒,循环体ril_event_loop肯定的执行了
void ril_event_loop()
{for (;;) {//把readFds文件描述符集合拷贝到本地rfds中memcpy(&rfds, &readFds, sizeof(fd_set));//看看timer_list中有没有定时事件,如果没有,ptv 赋值为空,传到select中,那么select将处于阻塞状态if (-1 == calcNextTimeout(&tv)) {ptv = NULL;} else {//如果timer_list中有定时事件,那么select将处于定时阻塞状态,时间到了,不管文件描述符集合rfds中是否有文件可读,select都要返回ptv = &tv;}
//关于select的介绍可以参考http://www.cnblogs.com/moonvan/archive/2012/05/26/2518881.htmln = select(nfds, &rfds, NULL, NULL, ptv);
......
//如果select处于非阻塞状态了,则继续往下走,那如果select一直处于阻塞状态呢?看这里是必须有定时事件,是否有可能一直没有定时事件,这样timer_list为空,而watch_table中的事件一直就处理不了?
//把定时事件从timer_list放到pending_listprocessTimeouts();//如过n大于0,表示rfds中有文件读状态发生改变了(通过FD_ISSET来识别),则把watch_table中的事件放到pending_listprocessReadReadies(&rfds, n);//timer_list和watch_table中的事件都放到pending_list之后,遍历pending_list中的事件并调用其回调。rilEventAddWakeup事件则是回调processWakeupCallback函数firePending();}
}static void firePending()
{struct ril_event * ev = pending_list.next;while (ev != &pending_list) {struct ril_event * next = ev->next;removeFromList(ev);
//回调事件处理方法ev->func(ev->fd, 0, ev->param);ev = next;}
}
接着看RIL_Init,主线程创建好之后,从库中动态加载RIL_Init函数,地址赋给rilInit。看看大体流程:
const RIL_RadioFunctions *RIL_Init(const struct RIL_Env *env, int argc, char **argv)
{
//回调方法数组s_rilenv = env;
......
//创建s_tid_mainloop线程,线程循环体为mainLoopret = pthread_create(&s_tid_mainloop, &attr, mainLoop, NULL);return &s_callbacks;
}static void *
mainLoop(void *param __unused)
{
......for (;;) {fd = -1;while (fd < 0) {......s_closed = 0;
//fd是读写文件描述符,如果读取到unsolicited类型命令,则回调onUnsolicited,后面再看at_open具体做什么ret = at_open(fd, onUnsolicited);RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_0);
...}
}
RIL_Init的第一个参数是指向RIL_Env 的指针,实参是定义在rild.c中的s_rilEnv。
struct RIL_Env {
//solicited类型命令请求完成的回调函数指针void (*OnRequestComplete)(RIL_Token t, RIL_Errno e,void *response, size_t responselen);
//unsolicited类型命令应答函数指针
#if defined(ANDROID_MULTI_SIM)void (*OnUnsolicitedResponse)(int unsolResponse, const void *data, size_t datalen, RIL_SOCKET_ID socket_id);
#elsevoid (*OnUnsolicitedResponse)(int unsolResponse, const void *data, size_t datalen);
#endif
//计时请求的超时回调函数指针void (*RequestTimedCallback) (RIL_TimedCallback callback,void *param, const struct timeval *relativeTime);
};static struct RIL_Env s_rilEnv = {RIL_onRequestComplete,RIL_onUnsolicitedResponse,RIL_requestTimedCallback
};
参数传入后,直接赋给reference-ril.c的静态变量s_rilenv ,只在下面的宏定义中使用到s_rilenv 。
#ifdef RIL_SHLIB
static const struct RIL_Env *s_rilenv;
#define RIL_onRequestComplete(t, e, response, responselen) s_rilenv->OnRequestComplete(t,e, response, responselen)
#define RIL_onUnsolicitedResponse(a,b,c) s_rilenv->OnUnsolicitedResponse(a,b,c)
#define RIL_requestTimedCallback(a,b,c) s_rilenv->RequestTimedCallback(a,b,c)
#endif
RIL_Init返回一个RIL_RadioFunctions类型结构体地址&s_callbacks
static const RIL_RadioFunctions s_callbacks = {RIL_VERSION,onRequest,currentState,onSupports,onCancel,getVersion
};
typedef struct {int version; //RIL的版本号RIL_RequestFunc onRequest; //请求的函数指针RIL_RadioStateRequest onStateRequest; //请求当前的radio状态RIL_Supports supports; //判断是否支持当前的请求RIL_Cancel onCancel; //取消当前请求RIL_GetVersion getVersion; //获取当前RIL版本号
} RIL_RadioFunctions;
RIL_Init的返回值作为参数传入RIL_register 中,然后拷贝到变量s_callbacks,s_callbacks主要在下面的宏定义用到。
#if defined(ANDROID_MULTI_SIM)
#define RIL_UNSOL_RESPONSE(a, b, c, d) RIL_onUnsolicitedResponse((a), (b), (c), (d))
#define CALL_ONREQUEST(a, b, c, d, e) s_callbacks.onRequest((a), (b), (c), (d), (e))
#define CALL_ONSTATEREQUEST(a) s_callbacks.onStateRequest(a)
#else
#define RIL_UNSOL_RESPONSE(a, b, c, d) RIL_onUnsolicitedResponse((a), (b), (c))
#define CALL_ONREQUEST(a, b, c, d, e) s_callbacks.onRequest((a), (b), (c), (d))
#define CALL_ONSTATEREQUEST(a) s_callbacks.onStateRequest()
#endif
RIL_register 是在RILJ和RILD之间定义socket,支持几张卡,就定义几个socket,最多四个。每个socket有个事件监听器fdListen,监听事件丢入到s_tid_dispatch线程中,事件的回调是listenCallback。一旦RILJ通过socket写入一个命令,s_tid_dispatch线程的循环处理ril_event_loop会调用注册的回调方法listenCallback对命令进行预处理,并把预处理结果又写入到s_tid_dispatch中,同时注册回调为processCommandsCallback, ril_event_loop又执行回调processCommandsCallback,processCommandsCallback中调用processCommandBuffer,而processCommandsCallback中最终调用dispatchFunction。
extern "C" void
RIL_register (const RIL_RadioFunctions *callbacks) {
......memcpy(&s_callbacks, callbacks, sizeof (RIL_RadioFunctions));//这里支持几张卡就定义几个socket param,并注册监听s_ril_param_socket = {RIL_SOCKET_1, /* socket_id */-1, /* fdListen */-1, /* fdCommand */PHONE_PROCESS, /* processName */&s_commands_event, /* commands_event */&s_listen_event, /* listen_event */processCommandsCallback, /* processCommandsCallback */NULL /* p_rs */};startListen(RIL_SOCKET_1, &s_ril_param_socket);
//类似地可定义RIL_SOCKET_2、RIL_SOCKET_3、RIL_SOCKET_4的参数并注册监听
......
}static void startListen(RIL_SOCKET_ID socket_id, SocketListenParam* socket_listen_p) {......
//在主线程中添加event,一旦RILJ通过socket写入事件,回调listenCallbackril_event_set (socket_listen_p->listen_event, fdListen, false,listenCallback, socket_listen_p);rilEventAddWakeup (socket_listen_p->listen_event);
}
了解了s_tid_dispatch线程的原理,这里可以直接看回调了。
static void listenCallback (int fd, short flags, void *param) {
...SocketListenParam *p_info = (SocketListenParam *)param;if(RIL_SAP_SOCKET == p_info->type) {listenParam = (MySocketListenParam *)param;sapSocket = listenParam->socket;}
......if(NULL == sapSocket) {processName = PHONE_PROCESS;} else {processName = BLUETOOTH_PROCESS;}
//从相应的socket读取命令fdCommand = accept(fd, (sockaddr *) &peeraddr, &socklen);
......err = getsockopt(fdCommand, SOL_SOCKET, SO_PEERCRED, &creds, &szCreds);
//如果不是radio socket,也不是bluetooth radio,则不处理命令if (!is_phone_socket) {RLOGE("RILD must accept socket from %s", processName);close(fdCommand);
......return;}ret = fcntl(fdCommand, F_SETFL, O_NONBLOCK);if(NULL == sapSocket) {p_info->fdCommand = fdCommand;p_rs = record_stream_new(p_info->fdCommand, MAX_COMMAND_BYTES);p_info->p_rs = p_rs;
//获取到socket中的信息之后,再把具体的命令事件add到s_tid_dispatch线程中处理,回调processCommandsCallbackril_event_set (p_info->commands_event, p_info->fdCommand, 1,p_info->processCommandsCallback, p_info);rilEventAddWakeup (p_info->commands_event);
//RIL_onUnsolicitedResponse发送ril connected和radio state changed等,推送ril connected消息会把ril的版本号带给RILJ,也就是说,每次读到socket中有信息从RILJ到RILD,则应答RIL_UNSOL_RIL_CONNECTED和RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED信息给RILJ,如果时区数据有更新,也推送上去onNewCommandConnect(p_info->socket_id);} else {
......}
}static void onNewCommandConnect(RIL_SOCKET_ID socket_id) {int rilVer = s_callbacks.version;RIL_UNSOL_RESPONSE(RIL_UNSOL_RIL_CONNECTED,&rilVer, sizeof(rilVer), socket_id);RIL_UNSOL_RESPONSE(RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED,NULL, 0, socket_id);if (s_lastNITZTimeData != NULL) {sendResponseRaw(s_lastNITZTimeData, s_lastNITZTimeDataSize, socket_id);free(s_lastNITZTimeData);s_lastNITZTimeData = NULL;}
......
}
接着看p_info->commands_event事件的回调processCommandsCallback
static void processCommandsCallback(int fd, short flags, void *param) {SocketListenParam *p_info = (SocketListenParam *)param;for (;;) {ret = record_stream_get_next(p_rs, &p_record, &recordlen);if (ret == 0 && p_record == NULL) {break;} else if (ret < 0) {break;} else if (ret == 0) { /* && p_record != NULL */
//处理命令processCommandBuffer(p_record, recordlen, p_info->socket_id);}}if (ret == 0 || !(errno == EAGAIN || errno == EINTR)) {
//处理完了则关闭对应的文件,并从watch_event中删除这个命令的事件close(fd);p_info->fdCommand = -1;ril_event_del(p_info->commands_event);record_stream_free(p_rs);rilEventAddWakeup(&s_listen_event);onCommandsSocketClosed(p_info->socket_id);}}
}static int
processCommandBuffer(void *buffer, size_t buflen, RIL_SOCKET_ID socket_id) {
......
//这里就调用ril_commands.h中诸如dispatchVoid的方法。在ril_commands.h中,定义了各种命令的dispatch方法和response方法,那么哪里调用response方法呢?pRI->pCI->dispatchFunction(p, pRI);return 0;
}//以{RIL_REQUEST_GET_SIM_STATUS, dispatchVoid, responseSimStatus}为例,
static void
dispatchVoid (Parcel& p, RequestInfo *pRI) {clearPrintBuf;printRequest(pRI->token, pRI->pCI->requestNumber);CALL_ONREQUEST(pRI->pCI->requestNumber, NULL, 0, pRI, pRI->socket_id);
}
CALL_ONREQUEST在ril.cpp中定义宏,实际调用s_callbacks中的onRequest
#define CALL_ONREQUEST(a, b, c, d, e) s_callbacks.onRequest((a), (b), (c), (d), (e))
s_callbacks在RIL_register 中由传入的参数赋值,这个参数值是RIL_Init方法的返回值,实质就是reference-ril.c中的
static const RIL_RadioFunctions s_callbacks = {RIL_VERSION,onRequest,currentState,onSupports,onCancel,getVersion
};
根据RIL_RadioFunctions的定义,s_callbacks.onRequest指向onRequest方法:
static void
onRequest (int request, void *data, size_t datalen, RIL_Token t)
{......switch (request) {case RIL_REQUEST_GET_SIM_STATUS: {RIL_CardStatus_v6 *p_card_status;char *p_buffer;int buffer_size;int result = getCardStatus(&p_card_status);if (result == RIL_E_SUCCESS) {p_buffer = (char *)p_card_status;buffer_size = sizeof(*p_card_status);} else {p_buffer = NULL;buffer_size = 0;}
//调用命令response方法,将结果上报给RILJRIL_onRequestComplete(t, result, p_buffer, buffer_size);freeCardStatus(p_card_status);break;}
......
}
二、看看RILD是如何把数据传给modem的
getCardStatus中调用getSIMStatus获取sim状态,getSIMStatus中调用at_send_command_singleline并传入具体的AT命令参数和处理结果的返回地址&p_response。
static SIM_Status
getSIMStatus()
{ATResponse *p_response = NULL;
......err = at_send_command_singleline("AT+CPIN?", "+CPIN:", &p_response);
}
接着的调用流程是:at_send_command_singleline–》at_send_command_full–》at_send_command_full_nolock–》writeline,也就是说,RILD最终通过writeline把数据写到了管道中,然后就等待管道的数据更新,更新了就把结果传给p_response
static int at_send_command_full_nolock (const char *command, ATCommandType type,const char *responsePrefix, const char *smspdu,long long timeoutMsec, ATResponse **pp_outResponse)
{
......err = writeline (command);
//开辟一个接受modem反馈的数据块sp_response = at_response_new();
//循环等待modem把数据写入到fd中,s_tid_reader线程把fd中的数据读出来之后,写入到sp_response->finalResponse,并发出条件信号s_commandcond,s_readerClosed被置为1,循环终止while (sp_response->finalResponse == NULL && s_readerClosed == 0) {if (timeoutMsec != 0) {
#ifdef USE_NPerr = pthread_cond_timeout_np(&s_commandcond, &s_commandmutex, timeoutMsec);
#elseerr = pthread_cond_timedwait(&s_commandcond, &s_commandmutex, &ts);
#endif} else {err = pthread_cond_wait(&s_commandcond, &s_commandmutex);}if (err == ETIMEDOUT) {err = AT_ERROR_TIMEOUT;goto error;}}
//把读取到的数据sp_response交给参数pp_outResponseif (pp_outResponse == NULL) {at_response_free(sp_response);} else {reverseIntermediates(sp_response);*pp_outResponse = sp_response;}
......return err;
}static int writeline (const char *s)
{
......while (cur < len) {do {
//s_fd在at_open时传入,整个写操作是在s_tid_mainloop线程中进行written = write (s_fd, s + cur, len - cur);} while (written < 0 && errno == EINTR);
......}
//写入结束符do {written = write (s_fd, "\r" , 1);} while ((written < 0 && errno == EINTR) || (written == 0));return 0;
}
三、看看RILD是如何从modem中读取数据的。
前文的RIL_Init中,定义了线程s_tid_mainloop,线程的循环体mainLoop调用at_open(fd, onUnsolicited)打开通道,在at_open中,又创建s_tid_reader线程,线程的循环体中调用readline从s_fd中读取。
int at_open(int fd, ATUnsolHandler h)
{
//传入的at命令读写通道保存到s_fd,unsolicited命令的回调处理保存到s_unsolHandlers_fd = fd;s_unsolHandler = h;s_readerClosed = 0;
......
//创建s_tid_reader读取fd的线程,创建成功调用readerLoopret = pthread_create(&s_tid_reader, &attr, readerLoop, &attr);return 0;
}static void *readerLoop(void *arg)
{for (;;) {const char * line;line = readline();if(isSMSUnsolicited(line)) {
......
//如果是短信,调用onUnsolicitedif (s_unsolHandler != NULL) {s_unsolHandler (line1, line2);}} else {processLine(line);}}
......return NULL;
}static void processLine(const char *line)
{pthread_mutex_lock(&s_commandmutex);if (sp_response == NULL) {//处理unsolicited消息handleUnsolicited(line);} else if (isFinalResponseSuccess(line)) {sp_response->success = 1;
//处理solicited消息handleFinalResponse(line);}
......
}static void handleFinalResponse(const char *line)
{
//把结果写入到sp_response->finalResponse,然后发出条件信号s_commandcond,这个条件是在at_send_command_full_nolock中会使用到sp_response->finalResponse = strdup(line);pthread_cond_signal(&s_commandcond);
}static const char *readline()
{
......do {
//writeline中,向s_fd写入数据,readline中读取数据count = read(s_fd, p_read,MAX_AT_RESPONSE - (p_read - s_ATBuffer));} while (count < 0 && errno == EINTR);return ret;
}static void onUnsolicited (const char *s, const char *sms_pdu)
{
......
//如果是时区命令if (strStartsWith(s, "%CTZV:")) {if (err != 0) {RLOGE("invalid NITZ line %s\n", s);} else {RIL_onUnsolicitedResponse (RIL_UNSOL_NITZ_TIME_RECEIVED,response, strlen(response));}
//来电等命令} else if (strStartsWith(s,"+CRING:")|| strStartsWith(s,"RING")|| strStartsWith(s,"NO CARRIER")|| strStartsWith(s,"+CCWA")) {RIL_onUnsolicitedResponse (RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED,NULL, 0);
#ifdef WORKAROUND_FAKE_CGEVRIL_requestTimedCallback (onDataCallListChanged, NULL, NULL); //TODO use new function
#endif /* WORKAROUND_FAKE_CGEV */}else if {
......
}
......
}
RIL_onUnsolicitedResponse是定义在reference-ril.c的宏,指向s_rilenv->OnUnsolicitedResponse,而s_rilenv是在RIL_Init中初始化的,实质指向rild.c中的s_rilEnv ,这样RIL_onUnsolicitedResponse最终调用RIL_onUnsolicitedResponse。
#define RIL_onUnsolicitedResponse(a,b,c) s_rilenv->OnUnsolicitedResponse(a,b,c)
四、接着要看看RILD如何把请求结果反馈给RILJ
RILD得到modem的数据后,就回调到了RIL_onRequestComplete
extern "C" void
RIL_onRequestComplete(RIL_Token t, RIL_Errno e, void *response, size_t responselen) {
//获取相应的socket通道int fd = s_ril_param_socket.fdCommand;size_t errorOffset;RIL_SOCKET_ID socket_id = RIL_SOCKET_1;pRI = (RequestInfo *)t;socket_id = pRI->socket_id;
......
#endif
......if (pRI->cancelled == 0) {......if (response != NULL) {ret = pRI->pCI->responseFunction(p, response, responselen);
......}
......
//sendResponse调用sendResponseRawsendResponse(p, socket_id);}done:free(pRI);
}static int
sendResponseRaw (const void *data, size_t dataSize, RIL_SOCKET_ID socket_id) {
//获取socket 1的文件描述符int fd = s_ril_param_socket.fdCommand;
......
//数据最大8Kif (dataSize > MAX_COMMAND_BYTES) {return -1;}
//写头,写数据ret = blockingWrite(fd, (void *)&header, sizeof(header));ret = blockingWrite(fd, data, dataSize);
}
//最终把数据写入到相应的socket中
static int
blockingWrite(int fd, const void *buffer, size_t len) {
......while (writeOffset < len) {ssize_t written;do {
//往相应的socket中写入数据written = write (fd, toWrite + writeOffset,len - writeOffset);} while (written < 0 && ((errno == EINTR) || (errno == EAGAIN)));
......return 0;
}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
