• android Framework 中用到了哪些跨进程通信方式?


    Linux 有哪些跨进程的通信方式?

    • Binder 机制是Android基于Linux的一种独特的IPC机制。我们常用的AMS,PMS 等都是通过Binder机制来完成跨进程通信的,那么除了Binder ,Linux 还有其他跨进程通信的方式可以选择。在Android Framework中主要用到以下方式

        1. 管道
        2. Socket
        3. 共享内存
        4. 信号
      
      • 1
      • 2
      • 3
      • 4

    管道

    • 管道的特点:半双工的,单向

      管道描述符数据只能往一个方向,要么read要么write。如果向既可以读又可以写,则需要两个描述符才可以。Linux 基于这种情况提供了 pipe(fds) api,这个api可以生成一对描述符,一个用来写,一个用来读。

    • 一般是在父子进程之间使用

      无名管道一般是在父子进程之间使用。如果是有名管道,只要两个进程之间都知道名字就可以直接通信了。

    • 管道的使用方式
      在这里插入图片描述

    #include
    #include
     
     
    int main()
    {
    int n,fd[2];                         // 这里的fd是文件描述符的数组,用于创建管道做准备的
    pid_t pid;
    char line[100];
    if(pipe(fd)<0)                     //   创建管道,生成描述符 fd[1] 是用来写的 fd[0] 是用来读的
       printf("pipe create error/n");
     
    if((pid=fork())<0)              //利用fork()创建新进程
        printf("fork error/n");
     
    else if(pid>0){                   //这里是父进程,先关闭管道的读出端,然后在管道的写端写入“hello world"
        close(fd[0]);
        write(fd[1],"hello word/n",11);
    }
    else{
        close(fd[1]);                 //这里是子进程,先关闭管道的写入端,然后在管道的读出端读出数据
       n= read(fd[0],line,100);
        write(STDOUT_FILENO,line,n);
    }
    exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • Framework 中 在Android 4.4 中 Looper 中使用到了管道,高版本更换了 eventfd 的方式。当有线程拿到写的描述符,往里写内容,那么读端就可以收到通知了。
    Looper::Looper(bool allowNonCallbacks) :
            mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
            mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
        int wakeFds[2];
        // 通过pipe 生成两个描述符
        int result = pipe(wakeFds);
        LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);
    	// 0 元素对应的是 读
        mWakeReadPipeFd = wakeFds[0];
        // 1 对应的是写 
        mWakeWritePipeFd = wakeFds[1];
        result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
        result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
        mIdling = false;
        // Allocate the epoll instance and register the wake pipe.
        mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    
        struct epoll_event eventItem;
        memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
        eventItem.events = EPOLLIN;
        eventItem.data.fd = mWakeReadPipeFd;
        // 通过 epoll_ctl 注册事件监听
        result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • epoll 是怎么监听读端事件的
    int Looper::pollInner(int timeoutMillis) {
    	// 。。。
    	struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    	// epoll_wait 阻塞在这,当返回的时候 eventCount 代表有几个事件被触发了
        int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    	// 然后依次处理
    	
    	   for (int i = 0; i < eventCount; i++) {
            int fd = eventItems[i].data.fd;
            uint32_t epollEvents = eventItems[i].events;
            // 判断描述符 mWakeReadPipeFd ,如果是读描述符
            if (fd == mWakeReadPipeFd) {
                if (epollEvents & EPOLLIN) {
                	// 从管道中读取数据
                    awoken();
                } 
            } else {
                // ... 
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 往管道中写数据,通过 Looper 的 wake() 函数写
    void Looper::wake() {
    #if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ wake", this);
    #endif
    
        ssize_t nWrite;
        do {
        	// 通过写描述符写
            nWrite = write(mWakeWritePipeFd, "W", 1);
        } while (nWrite == -1 && errno == EINTR);
    
        if (nWrite != 1) {
            if (errno != EAGAIN) {
                ALOGW("Could not write wake signal, errno=%d", errno);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    管道在进程内可以用,跨进程也可以用,可以和 epoll 相结合监听读写事件,一般用在数据量不大的跨进程通信中使用。

    本地 Socket

    • Socket 特点

        全双工,既可以读也可以写
        两个进程之间可以无亲缘关系
      
      • 1
      • 2
    • Android Framework 中在 Zygote 中,通过 Socket 来接收 AMS 请求,启动应用进程。在 ZygoteInit 的入口函数中

    public static void main(String argv[]) {
                // 注册 Zygote 的 socket 监听接口,用来接收启动应用程序的消息
                zygoteServer.registerServerSocketFromEnv(socketName);
                // 通过调用 runSelectLoop 进入监听和接收消息的环节 里面有一个 while (true) 
                caller = zygoteServer.runSelectLoop(abiList);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • Runnable runSelectLoop(String abiList)  处理和返回Socket数据
      
      • 1
    Runnable runSelectLoop(String abiList) {
    
    	while (true) {
                StructPollfd[] pollFds = new StructPollfd[fds.size()];
                // 。。。
                try {
                //	 用来检测有没有事件发生
                    Os.poll(pollFds, -1);
                } catch (ErrnoException ex) {
                    throw new RuntimeException("poll failed", ex);
                }
    			if (i == 0) {
                        // 处理新来的连接
               } else {
    					// 处理发过来的数据
    			}
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    共享内存

    • 共享内存的特点

        1. 很快,不需要多次拷贝。上面提到的管道和Socket数据量太大会很糟,因为会拷贝两次数据。
        共享内存拿到文件描述符后,把它同时交给两个进程,就可以进行通信了。
        2. 进程之间也不用存在亲缘关系
      
      • 1
      • 2
      • 3
    • 具体匿名共享内存看之前的文章

    1. 匿名共享内存 ashmem
    2. 跨进程通信–共享内存(ashmem)实例

    信号

    • 特点

        1. 单向发送:不关心发出去之后的事
        2. 只能带一个信号,不能带别的参数
        3. 知道进程 pid 就可以发信号了,而且可以群发信号
      
      • 1
      • 2
      • 3
    • 哪里用到了信号?看下面的代码,大多数人都见过,一般我们安装或者重启应用的时候可能先kill掉自己。

    android.os.Process.killProcess(android.os.Process.myPid())
    
    • 1

    killProcess 中就发送了一个信号

    	public static final native void sendSignal(int pid, int signal);
    	
        public static final void killProcess(int pid) {
            sendSignal(pid, SIGNAL_KILL);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    static void SetSigChldHandler() {
      struct sigaction sa;
      memset(&sa, 0, sizeof(sa)); //对sa地址内容进行清零操作
      sa.sa_handler = SigChldHandler;
      // zygote 关注的SIGCHLD信号,如果进程杀死了好及时回收资源
      int err = sigaction(SIGCHLD, &sa, NULL);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    Redis 做签到统计
    opencv鼠标事件函数setMouseCallback()详解
    2023 年和 2024 年人工智能和机器学习会议清单
    使用 AI 学习 Python web 的 django 代码(1/30天)
    【递归】什么是递归-C语言为例
    【游戏建模全流程】Maya制作绿色小屋模型
    大厂面试题-索引的底层实现,为什么选择B+Tree而不是红黑树?
    详解 Spark 编程之 RDD 依赖关系
    创建型:工厂模式-简单工厂
    人力资源团队怎样利用智能科技提升工作效率
  • 原文地址:https://blog.csdn.net/ldxlz224/article/details/128107698