前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Framework笔记 | Android Framework用到了哪些IPC方式,分别在哪里用到

Framework笔记 | Android Framework用到了哪些IPC方式,分别在哪里用到

作者头像
凌川江雪
发布2019-08-27 11:06:37
1K0
发布2019-08-27 11:06:37
举报
  • 是否了解Linux常用的跨进程通信方式 android很多底层实现, 都依赖于Linux的操作系统调用;
  • 是否研究过Android Framework并了解一些实现原理
  • 是否了解Framework各组件之间的通信原理 很多组件基本都是要跨进程的, 跨进程通讯并不全是用Binder机制;

主要关注三个层面

  • 列举用到哪些IPC方式
  • 各个IPC方式的特点
  • Framework中是怎么用到的

Android中主要用到的Linux IPC方式

  • 管道
  • Socket
  • 共享内存
  • 信号

管道通信

  • 半双工的,单向的 管道的描述符数据只能往一个方向流,要么读要么写, 如果需要既能读又能写,则需要给管道有两个描述符; 不过Linux给了我们一个APIpipe(fds), 这个API可以生成一对描述符, 一个用来写一个用来读;
  • 一般无名管道是在父子进程之间使用的; 有名管道只要两个进程都知道这个管道的名字就可以通信了;
看一个例子
  • 通过pipe调用,生成管道的一对描述符; fd[1]是用来写的; fd[0]是用来读的; 通过fork()调用创建一个子进程; 子进程会继承这对描述符;
  • 现在我们要父进程往子进程里面写东西, 首先,把子进程写描述符关闭,把子进程写描述符关闭 接着,父进程往写描述符里边写一个字符串 然后,子进程就可以从读描述符里边把这个字符串读出来
if(pid == 0){
    
    close(fd[1]);//把子进程写描述符关闭
    //子进程就可以从读描述符里边把这个字符串给读出来
    read(fd[0], buf, SIZE);


}else if(pid > 0){

    close(fd[0]);//把子进程写描述符关闭
    write(fd[1], "Hello", 5);//父进程往写描述符里边写一个字符串

}

概念图如下, 我们可以看到数据流的方向是 父进程写描述符fd[1]--管道--子进程读描述符fd[0] 即,我们刚刚所说的半双工设计

Framework中哪儿用到了管道
  • Android 4.4中的MQ机制中的重要元素Looper,用到了管道 (更高的版本如Android 6.0用的就不是管道了):
  • 代码大概实现: 前四行是为管道配置一对读写描述符 后半部分是注册一个监听事件 监听读描述符的读事件,即eventItem.data.fd = mWakeReadPipeFd; 这个时候如果有另一个线程拿到写描述符并往里面写东西的话, 读端就能收到通知了;

相关阅读

epoll机制有一套函数,共三个,如下 创建epoll句柄: 1. int epfd =epoll_create(intsize); 创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。 2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) 将被监听的描述符(对应例程中mWakeReadPipeFd添加epoll句柄(对应例程中mEpollFd) 或从epool句柄中删除或者对监听事件进行修改 (添加、删除和修改通过op位参数进行控制)

3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)

注意最后一句话, 该函数返回需要处理的事件数目,即几个事件被触发了, 第二个参数events列表用来接收存入触发的事件;

接着看epoll是怎么监听读端事件的
  • 通过epoll_wait得到触发的事件列表及其数量;
  • for循环中遍历触发事件列表, 遍历到事件的fd是刚刚设置的读描述符的(mWakeReadPipeFd), 及其事件是刚刚设定的读事件的(EPOLLIN), 则调用awoken(),把管道中的东西读出来, 管道满了就写不进去了,所以二话不说先读出来;
  • 不管管道里面写了什么东西, 只要写了东西,这个线程 就能被唤醒,就能去处理消息;
  • 当别的线程 要往这个Looper线程里面写东西的时候, 就通过wake()函数往管道里边写东西:

小结 管道使用起来还是比较方便的, 它可以跟epoll相结合监听读写事件; 管道在进程自身中可以用, 跨进程也可以用; 在数据量不怎么大的跨进程通信的时候还是比较有用的;

Socket通信

  • 这里说的不是网络上的socket,而是本地的;
  • 全双工,既可读又可写;
  • 可以用在两个无亲缘关系的进程之间的通信
  • socket在创建的时候需要指定一个路径, 只要把路径公开给别的进程, 别的进程就可以过来通信;
Framework中哪里用到
  • 在Zygote模块, 通过socket接收请求,然后启动应用进程;
  • main函数是Zygote的logo函数, registerZygoteSocket()创建了一个本地的Socket, socketName通过argv接收Socket的名字, 接着进入runSelectLoop(),一个循环, 检测这个socket有没有新过来的连接或者是数据;
我们看runSelectLoop()
  • 这里是一个循环,其中, poll()用来监测有没有我们关注的事件发生, 如果有的话,可能会有两种情况, 第一种,是可能会有新的连接; 第二种,就是有新的数据发过来, 这时候可以调用runOnce()来处理数据(先从Socket中把参数出来, 根据参数去执行响应的指令,主要是创建应用进程, 应用进程启动之后,通过Socket把pid给对方) 所以我们可以看到Socket其实很方便, 它即可以读又可以写;

共享内存

  • 很快,不需要多次拷贝; 拿到一个文件描述符(文件引用句柄),即把它映射到两个进程的内存空间, 这样,一个进程往里边写,另一个进程就能读到,运行速度非常快; 相对的,管道和Socket传输的数据量都不能太大, 如果太大的话性能会很糟,因为里边涉及到至少两次拷贝;
  • 进程之间不用存在亲缘关系; 只需要能拿到文件描述符就好了; 文件描述符可以跨进程传递;

Android中哪里用到

  • Android中涉及到进程之间大数据量传输的主要就是图像相关的传输
  • 这里主要以MemoryFile为例, 这是Android的一个工具类, 封装了内存共享机制Ashmem,也就是匿名共享内存;

MemoryFile是android在最开始就引入的一套框架,其内部实际上是封装了android特有的内存共享机制Ashmem匿名共享内存,简单来说,Ashmem在Android内核中是被注册成一个特殊的字符设备,Ashmem驱动通过在内核的一个自定义slab缓冲区中初始化一段内存区域,然后通过mmap把申请的内存映射到用户的进程空间中(通过tmpfs),这样子就可以在用户进程中使用这里申请的内存了。

参考文章

  • MemoryFile的实现, 首先是调用native_open方法, 这个方法在native层其实是 通过下面这个方法首先创建了一块匿名共享内存:

native_open方法会返回一个描述符(句柄);

  • 接下来调用native_mmap函数, 这个函数在native层调用下面这个方法

把描述符mFD映射到当前进程的内存空间, 内存空间的地址则返回过来,即例程中的mAddress;

  • 下面是MemoryFile的读和写
  • 读函数就是把数据共享内存 应用层的buff中, SetByteArrayRegion()就是把nativebuff数据拷到java数据流的;
  • 写函数则与读函数相反, 就是把数据应用层的buff拷到共享内存 GetByteArrayRegion()就是把java数据流中的数据拷到nativebuff的;

信号

  • 单向的,只负责发出去,不接受回复的, 怎么处理,处理没有,处理结果怎么样都不管,是别人的事
  • 只能带个信号,不能带别的参数
  • 知道进程pid就能发信号, 可以一次给一群进程发信号
  • 必须是root权限才能发信号, 或者本进程跟另一进程的Userid相同, 本进程才能发信号;

Android中哪里用到

例1
  • 有时候要kill应用进程, 就会调用Proces的killProces函数, 这个函数中其实就是给进程发送了一个SIGNAL_KILL信号, pid参数位就是要杀掉的进程pid; 当然不是想杀就能杀,同样这里是有权限控制的, 比如说本进程跟另一进程的Userid相同, 本进程才能发信号,杀掉另一个进程;

《开发艺术探索》中有一段类似的描述

  • 虽然我们的应用进程都是从Zygote那fork出来的, UID都是默认和Zygote相同的, 但是进程启动之后, 就会马上重新设置自己的UID的, 所以基于任意进程是不可以随便给别的进程发信号的;

zygote翻译成中文是受精卵的意思,名字比较奇怪、但是很有意思, zygote在android中主要有两个作用: 建立运行时环境并启动虚拟机,为应用程序创建DVM进程。 执行com.android.internal.os.ZygoteInit的main函数, 从而fork SystemService。 类似参考文章

例2
  • 以上这个函数是Zygote用来关注信号的; 启动子进程之后,它需要关注子进程退出了没有, 如果子进程退出了,Zygote就要及时把子进程的资源给回收掉;
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.08.26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Android中主要用到的Linux IPC方式
  • 管道通信
  • Socket通信
  • 共享内存
    • Android中哪里用到
    • 信号
      • Android中哪里用到
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档