v77.01 鸿蒙内核源码分析(消息封装篇) | 剖析LiteIpc(上)进程通讯内容 | 新的一年祝大家生龙活虎 虎虎生威
百篇博客分析|本篇为:(消息封装篇) | 剖析LiteIpc进程通讯内容
进程通讯相关篇为:
- v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志
- v27.05 鸿蒙内核源码分析(互斥锁) | 同样是锁它确更丰满
- v28.04 鸿蒙内核源码分析(进程通讯) | 九种进程间通讯方式速揽
- v29.05 鸿蒙内核源码分析(信号量) | 谁在解决任务间的同步
- v30.07 鸿蒙内核源码分析(事件控制) | 多对多任务如何同步
- v33.03 鸿蒙内核源码分析(消息队列) | 进程间如何异步传递大数据
- v76.01 鸿蒙内核源码分析(共享内存) | 进程间最快通讯方式
- v77.01 鸿蒙内核源码分析(消息封装) | 剖析LiteIpc进程通讯内容
基本概念
LiteIPC
是OpenHarmony LiteOS-A
内核提供的一种新型IPC
(Inter-Process Communication,即进程间通信)机制,为轻量级进程间通信组件,为面向服务的系统服务框架提供进程间通信能力,分为内核实现和用户态实现两部分,其中内核实现完成进程间消息收发、IPC内存管理、超时通知和死亡通知等功能;用户态提供序列化和反序列化能力,并完成IPC
回调消息和死亡消息的分发。
我们主要讲解内核态实现部分,本想一篇说完,但发现它远比想象中的复杂和重要,所以分两篇说,通讯内容和通讯机制。通讯的内容就是消息,围绕着消息展开的结构体多达10
几个,捋不清它们之间的关系肯定是搞不懂通讯的机制,所以咱们得先搞清楚关系再说流程。下图是笔者读完LiteIPC
模块后绘制的消息封装图,可以说LiteIPC
是内核涉及结构体最多的模块,请消化理解,本篇将围绕它展开。
系列篇多次提过,内核的每个模块都至少围绕着一个重要结构体展开,抓住了它顺瓜摸藤就能把细节抹的清清楚楚,于LiteIPC
,这个结构体就是IpcMsg
。
运行机制
typedef struct {//IPC 消息结构体
MsgType type; /**< cmd type, decide the data structure below | 命令类型,决定下面的数据结构*/
SvcIdentity target; /**< serviceHandle or targetTaskId, depending on type | 因命令类型不同而异*/
UINT32 code; /**< service function code | 服务功能代码*/
UINT32 flag; ///< 标签
#if (USE_TIMESTAMP == 1)
UINT64 timestamp; ///< 时间戳,用于验证
#endif
UINT32 dataSz; /**< size of data | 消息内容大小*/
VOID *data; ///< 消息的内容,真正要传递的消息,这个数据内容是指spObjNum个数据的内容,定位就靠offsets
UINT32 spObjNum; ///< 对象数量, 例如 spObjNum = 3时,offsets = [0,35,79],代表从data中读取 0 - 35给第一个对象,依次类推
VOID *offsets; ///< 偏移量,注意这里有多少个spObjNum就会有多少个偏移量,详见 CopyDataFromUser 来理解
UINT32 processID; /**< filled by kernel, processId of sender/reciever | 由内核提供,发送/接收消息的进程ID*/
UINT32 taskID; /**< filled by kernel, taskId of sender/reciever | 由内核提供,发送/接收消息的任务ID*/
#ifdef LOSCFG_SECURITY_CAPABILITY
UINT32 userID; ///< 用户ID
UINT32 gid; ///< 组ID
#endif
} IpcMsg;
解读
- 第一个
type
,通讯的本质就是你来我往,异常当然也要考虑typedef enum {
MT_REQUEST, ///< 请求
MT_REPLY, ///< 回复
MT_FAILED_REPLY,///< 回复失败
MT_DEATH_NOTIFY,///< 通知死亡
MT_NUM
} MsgType;
- 第二个
target
,LiteIPC
中有两个主要概念,一个是ServiceManager
,另一个是Service
。整个系统只能有一个ServiceManager
,而Service
可以有多个。ServiceManager
有两个主要功能:一是负责Service
的注册和注销,二是负责管理Service
的访问权限(只有有权限的任务Task
可以向对应的Service
发送IPC
消息)。首先将需要接收IPC
消息的任务通过ServiceManager
注册成为一个Service
,然后通过ServiceManager
为该Service
任务配置访问权限,即指定哪些任务可以向该Service
任务发送IPC
消息。LiteIPC
的核心思想就是在内核态为每个Service
任务维护一个IPC
消息队列,该消息队列通过LiteIPC
设备文件向上层用户态程序分别提供代表收取IPC
消息的读操作和代表发送IPC
消息的写操作。/// SVC(service)服务身份证
typedef struct {
UINT32 handle; //service 服务ID, 范围[0,最大任务ID]
UINT32 token; //由应用层带入
UINT32 cookie; //由应用层带入
} SvcIdentity;
code
,timestamp
由应用层设定,用于确保回复正确有效,详见CheckRecievedMsg
dataSz
,data
,spObjNum
,offsets
这四个需连在一起理解,是重中之重。其实消息又分成三种类型(对象)typedef enum {
OBJ_FD, ///< 文件句柄
OBJ_PTR, ///< 指针
OBJ_SVC ///< 服务,用于设置权限
} ObjType;
typedef union {
UINT32 fd; ///< 文件描述符
BuffPtr ptr; ///< 缓存的开始地址,即:指针,消息从用户空间来时,要将内容拷贝到内核空间
SvcIdentity svc; ///< 服务,用于设置访问权限
} ObjContent;
typedef struct { // IpcMsg->data 包含三种子消息,也要将它们读到内核空间
ObjType type; ///< 类型
ObjContent content;///< 内容
} SpecialObj;
这三种对象都打包在
data
中,总长度是dataSz
,spObjNum
表示个数,offsets
是个整型数组,标记了对应第几个对象在data
中的位置,这样就很容易从data
读到对象的数据。
UINT32 fd
类型对象通讯的实现是通过两个进程间共享同一个fd
来实现通讯,具体实现函数为HandleFd
。/// 按句柄方式处理, 参数 processID 往往不是当前进程
LITE_OS_SEC_TEXT STATIC UINT32 HandleFd(UINT32 processID, SpecialObj *obj, BOOL isRollback)
{
int ret;
if (isRollback == FALSE) { // 不回滚
ret = CopyFdToProc(obj->content.fd, processID);//目的是将两个不同进程fd都指向同一个系统fd,共享FD的感觉
if (ret < 0) {//返回 processID 的 新 fd
return ret;
}
obj->content.fd = ret; // 记录 processID 的新FD, 可用于回滚
} else {// 回滚时关闭进程FD
ret = CloseProcFd(obj->content.fd, processID);
if (ret < 0) {
return ret;
}
}
SvcIdentity svc
用于设置进程<->任务之间彼此访问权限,具体实现函数为HandleSvc
。/// 按服务的方式处理,此处推断 Svc 应该是 service 的简写 @note_thinking
LITE_OS_SEC_TEXT STATIC UINT32 HandleSvc(UINT32 dstTid, const SpecialObj *obj, BOOL isRollback)
{
UINT32 taskID = 0;
if (isRollback == FALSE) {
if (IsTaskAlive(obj->content.svc.handle) == FALSE) {
PRINT_ERR("Liteipc HandleSvc wrong svctid\n");
return -EINVAL;
}
if (HasServiceAccess(obj->content.svc.handle) == FALSE) {
PRINT_ERR("Liteipc %s, %d\n", __FUNCTION__, __LINE__);
return -EACCES;
}
if (GetTid(obj->content.svc.handle, &taskID) == 0) {//获取参数消息服务ID所属任务
if (taskID == OS_PCB_FROM_PID(OS_TCB_FROM_TID(taskID)->processID)->ipcInfo->ipcTaskID) {//如果任务ID一样,即任务ID为ServiceManager
AddServiceAccess(dstTid, obj->content.svc.handle);
}
}
}
return LOS_OK;
}
BuffPtr ptr
是通过指针传值,具体实现函数为HandlePtr
,对应结构体为BuffPtr
。typedef struct {
UINT32 buffSz; ///< 大小
VOID *buff; ///< 内容 内核需要将内容从用户空间拷贝到内核空间的动作
} BuffPtr;
/// 按指针方式处理
LITE_OS_SEC_TEXT STATIC UINT32 HandlePtr(UINT32 processID, SpecialObj *obj, BOOL isRollback)
{
VOID *buf = NULL;
UINT32 ret;
if ((obj->content.ptr.buff == NULL) || (obj->content.ptr.buffSz == 0)) {
return -EINVAL;
}
if (isRollback == FALSE) {
if (LOS_IsUserAddress((vaddr_t)(UINTPTR)(obj->content.ptr.buff)) == FALSE) { // 判断是否为用户空间地址
PRINT_ERR("Liteipc Bad ptr address\n"); //不在用户空间时
return -EINVAL;
}
buf = LiteIpcNodeAlloc(processID, obj->content.ptr.buffSz);//在内核空间分配内存接受来自用户空间的数据
if (buf == NULL) {
PRINT_ERR("Liteipc DealPtr alloc mem failed\n");
return -EINVAL;
}
ret = copy_from_user(buf, obj->content.ptr.buff, obj->content.ptr.buffSz);//从用户空间拷贝数据到内核空间
if (ret != LOS_OK) {
LiteIpcNodeFree(processID, buf);
return ret;
}//这里要说明下 obj->content.ptr.buff的变化,虽然都是用户空间的地址,但第二次已经意义变了,虽然数据一样,但指向的是申请经过拷贝后的内核空间
obj->content.ptr.buff = (VOID *)GetIpcUserAddr(processID, (INTPTR)buf);//获取进程 processID的用户空间地址,如此用户空间操作buf其实操作的是内核空间
EnableIpcNodeFreeByUser(processID, (VOID *)buf);//创建一个IPC节点,挂到可使用链表上,供读取
} else {
(VOID)LiteIpcNodeFree(processID, (VOID *)GetIpcKernelAddr(processID, (INTPTR)obj->content.ptr.buff));//在内核空间释放IPC节点
}
return LOS_OK;
}
processID
和taskID
则由内核填充,应用层是感知不到进程和任务的,暴露给它是服务ID,SvcIdentity.handle
,上层使用时只需向服务发送/读取消息,而服务是由内核创建,绑定在任务和进程上。所以只要有服务ID就能查询到对应的进程和任务ID。userID
和gid
涉及用户和组安全模块,请查看系列相关篇。
进程和任务
再说两个结构体 ProcIpcInfo
,IpcTaskInfo
LiteIPC
实现的是进程间的通讯,所以在进程控制块中肯定有它的位置存在,即:ProcIpcInfo
。
typedef struct {
IpcPool pool; ///< ipc内存池,IPC操作所有涉及内核空间分配的内存均有此池提供
UINT32 ipcTaskID; ///< 指定能ServiceManager的任务ID
LOS_DL_LIST ipcUsedNodelist;///< 已使用节点链表,上面挂 IpcUsedNode 节点, 申请IpcUsedNode的内存来自内核堆空间
UINT32 access[LOSCFG_BASE_CORE_TSK_LIMIT]; ///< 允许进程通过IPC访问哪些任务
} ProcIpcInfo;
而进程只是管家,真正让内核忙飞的是任务,在任务控制块中也应有LiteIPC
一席之地,即:IpcTaskInfo
。
typedef struct {
LOS_DL_LIST msgListHead;///< 上面挂的是一个个的 ipc节点 上面挂 IpcListNode,申请IpcListNode的内存来自进程IPC内存池
BOOL accessMap[LOSCFG_BASE_CORE_TSK_LIMIT]; ///< 此处是不是应该用 LOSCFG_BASE_CORE_PROCESS_LIMIT ? @note_thinking
///< 任务是否可以给其他进程发送IPC消息
} IpcTaskInfo;
两个结构体不复杂,把发送/回复的消息挂到对应的链表上,并提供进程<->任务间彼此访问权限功能access
,accessMap
,由谁来设置权限呢 ? 上面已经说过了是 HandleSvc
。
IPC内存池
还有最后一个结构体IpcPool
,
typedef struct {//用户空间和内核空间的消息传递通过偏移量计算
VOID *uvaddr; ///< 用户空间地址,由kvaddr映射而来的地址,这两个地址的关系一定要搞清楚,否则无法理解IPC的核心思想
VOID *kvaddr; ///< 内核空间地址,IPC申请的是内核空间,但是会通过 DoIpcMmap 将这个地址映射到用户空间
UINT32 poolSize; ///< ipc池大小
} IpcPool;
它是LiteIPC
实现通讯机制的基础,是内核设计很巧妙的地方,实现了在用户态读取内核态数据的功能。请想想它是如何做到的 ?
百文说内核 | 抓住主脉络
- 百文相当于摸出内核的肌肉和器官系统,让人开始丰满有立体感,因是直接从注释源码起步,在加注释过程中,每每有心得处就整理,慢慢形成了以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切。
- 与代码需不断
debug
一样,文章内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx
代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。 - 百文在 < 鸿蒙研究站 | 开源中国 | 博客园 | 51cto | csdn | 知乎 | 掘金 > 站点发布,公众号回复 百文 可方便阅读。
按功能模块:
百万注源码 | 处处扣细节
百万汉字注解内核目的是要看清楚其毛细血管,细胞结构,等于在拿放大镜看内核。内核并不神秘,带着问题去源码中找答案是很容易上瘾的,你会发现很多文章对一些问题的解读是错误的,或者说不深刻难以自圆其说,你会慢慢形成自己新的解读,而新的解读又会碰到新的问题,如此层层递进,滚滚向前,拿着放大镜根本不愿意放手。
< gitee | github | coding | codechina > 四大码仓推送 | 同步官方源码,公众号中回复 百万 可方便阅读。
关注不迷路 | 代码即人生
v77.01 鸿蒙内核源码分析(消息封装篇) | 剖析LiteIpc(上)进程通讯内容 | 新的一年祝大家生龙活虎 虎虎生威的更多相关文章
- v78.01 鸿蒙内核源码分析(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 | 百篇博客分析OpenHarmony源码
百篇博客分析|本篇为:(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析( ...
- v74.01 鸿蒙内核源码分析(编码方式篇) | 机器指令是如何编码的 | 百篇博客分析OpenHarmony源码
本篇关键词:指令格式.条件域.类型域.操作域.数据指令.访存指令.跳转指令.SVC(软件中断) 内核汇编相关篇为: v74.01 鸿蒙内核源码分析(编码方式) | 机器指令是如何编码的 v75.03 ...
- 鸿蒙内核源码分析(消息队列篇) | 进程间如何异步传递大数据 | 百篇博客分析OpenHarmony源码 | v33.02
百篇博客系列篇.本篇为: v33.xx 鸿蒙内核源码分析(消息队列篇) | 进程间如何异步传递大数据 | 51.c.h .o 进程通讯相关篇为: v26.xx 鸿蒙内核源码分析(自旋锁篇) | 自旋锁 ...
- v75.01 鸿蒙内核源码分析(远程登录篇) | 内核如何接待远方的客人 | 百篇博客分析OpenHarmony源码
子曰:"不学礼,无以立 ; 不学诗,无以言 " <论语>:季氏篇 百篇博客分析.本篇为: (远程登录篇) | 内核如何接待远方的客人 设备驱动相关篇为: v67.03 ...
- v86.01 鸿蒙内核源码分析 (静态分配篇) | 很简单的一位小朋友 | 百篇博客分析 OpenHarmony 源码
本篇关键词:池头.池体.节头.节块 内存管理相关篇为: v31.02 鸿蒙内核源码分析(内存规则) | 内存管理到底在管什么 v32.04 鸿蒙内核源码分析(物理内存) | 真实的可不一定精彩 v33 ...
- v76.01 鸿蒙内核源码分析(共享内存) | 进程间最快通讯方式 | 百篇博客分析OpenHarmony源码
百篇博客分析|本篇为:(共享内存篇) | 进程间最快通讯方式 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) | 同样 ...
- v79.01 鸿蒙内核源码分析(用户态锁篇) | 如何使用快锁Futex(上) | 百篇博客分析OpenHarmony源码
百篇博客分析|本篇为:(用户态锁篇) | 如何使用快锁Futex(上) 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) ...
- v80.01 鸿蒙内核源码分析(内核态锁篇) | 如何实现快锁Futex(下) | 百篇博客分析OpenHarmony源码
百篇博客分析|本篇为:(内核态锁篇) | 如何实现快锁Futex(下) 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) ...
- v87.01 鸿蒙内核源码分析 (内核启动篇) | 从汇编到 main () | 百篇博客分析 OpenHarmony 源码
本篇关键词:内核重定位.MMU.SVC栈.热启动.内核映射表 内核汇编相关篇为: v74.01 鸿蒙内核源码分析(编码方式) | 机器指令是如何编码的 v75.03 鸿蒙内核源码分析(汇编基础) | ...
随机推荐
- 微服务架构攀登之路(二)之RPC
1. RPC 简介 远程过程调用(Remote Procedure Call,RPC)是一个计算机通信协议 该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编 ...
- 构造注入链:POP
1.POP链原理简介: 在反序列化中,我们能控制的数据就是对象中的属性值,所以在PHP反序列化中有一种 漏洞利用方法叫"面向属性编程",即POP( Property Oriente ...
- 【爬虫】从零开始使用 Scrapy
一. 概述 最近有一个爬虫相关的需求,需要使用 scrapy 框架来爬取数据,所以学习了一下这个非常强大的爬虫框架,这里将自己的学习过程记录下来,希望对有同样需求的小伙伴提供一些帮助. 本文主要从下面 ...
- Flink SQL任务自动生成与提交
目录 起因 思路 实现 1.配置 2.界面如下 3.环境 问题 起因 事情的起因,是看到一篇公众号文章Apache Flink 在汽车之家的应用与实践,里面提到了"基于 SQL 的开发流程& ...
- Redis 事务支持 ACID 么?
腾讯面试官:「数据库事务机制了解么?」 「内心独白:小意思,不就 ACID 嘛,转眼一想,我面试的可是技术专家,不会这么简单的问题吧」 程许远:「balabala-- 极其自信且从容淡定的说了一通.」 ...
- [开发笔记usbTOcan]软件需求分析和软件架构设计
前面文章进行了系统分析和系统架构设计,手工焊接了一个板子,集合EK-TMC123GXL开发板(请忽略焊接技术) SWE.1 | 软件需求分析 软件需求分析过程的目的是将系统需求的软件相关部分转化为一组 ...
- 【刷题-LeetCode】224. Basic Calculator
Basic Calculator Implement a basic calculator to evaluate a simple expression string. The expression ...
- 集合框架-工具类-JDK5.0特性-ForEach循环
1 package cn.itcast.p4.news.demo; 2 3 import java.util.ArrayList; 4 import java.util.HashMap; 5 impo ...
- Maven作用及安装
与传统开发项目相比使用Maven: 1)Maven可以管理jar文件 2)自动下载jar和它的文档,源代码 3)管理jar直接的依赖,a.jar需要b.jar,maven会自动下载b.jar 4)管理 ...
- 不难懂------git开发过程中流程
001.创建仓库 002.新建项目 003.初始化仓库 这一步不需要做 git init : 文件夹中会多出一个隐藏的.git文件 004.克隆项目 git clone <项目地址> 0 ...