【转载】Android进程间通信机制-管道

Android进程间通信机制-管道

作者:凯玲之恋
链接:https://www.jianshu.com/p/115cf0e519c2
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

PIPE和FIFO的使用及原理

PIPE和FIFO都是指管道,只是PIPE独指匿名管道,FIFO独指有名管道,我们先看一下管道的数据结构以及他们的使用方式:

  1. 匿名管道(PIPE) —— Linux下进程通信 匿名管道PIPE
  2. 命名管道(FIFO) —— Linux下进程通信 命名管道FIFO
//匿名管道(PIPE)
#include <unistd.h>
int pipe (int fd[2]); //创建pipe
ssize_t write(int fd, const void *buf, size_t count);   //写数据
ssize_t read(int fd, void *buf, size_t count);     //读数据//有名管道(FIFO)
#include<sys/stat.h>        
#include <unistd.h>
int mkfifo(const char *path, mode_t mode);  //创建fifo文件
int open(const char *pathname, int flags);  //打开fifo文件
ssize_t write(int fd, const void *buf, size_t count);   //写数据
ssize_t read(int fd, void *buf, size_t count);     //读数据

匿名管道

可以看到,匿名管道通过pipe函数创建,pipe函数通过在内核的虚拟文件系统中创建两个pipefs虚拟文件,并返回这两个虚拟文件描述符,有了这两个文件描述,我们就能进行跨进
在这里插入图片描述

匿名管道是单向半双工的通信方式,单向即意味着只能一端读另一端写,半双工意味着不能同时读和写,其中文件描述符fd[1]只能用来写,文件描述符f[0]只能用来读,pipe创建好后,我们就可以用Linux标准的文件读取函数read和写入函数write来对pipe进行读写了。
为什么pipe是匿名管道呢?因为pipefs文件是特殊的虚拟文件系统,并不会显示在VFS的目录中,所以用户不可见,既然用户不可见,那么又怎么能进行进程间通信呢?

因为pipe是匿名的,所以它只支持父子和兄弟进程之间的通信。通过fork创建父子进程或者通过clone创建兄弟进程的时候,会共享内存拷贝,

这时,子进程或兄弟进程就能在共享拷贝中拿到pipe的文件描述符进行通信了。我们通过下图看一下通信的流程。

FIFO

FIFO,FIFO是半双工双向通信,所以通过FIFO创建的管道既能读也能写,但是不能同时读和写,FIFO本质上是一个先进先出的队列数据结构,最早放入的数据被最先读出来,这样的数据结构能保证信息交流的顺序。

FIFO使用也很简单,通过mkfifo函数创建管道,它同样会在内核的虚拟文件系统中创建一个FIFO虚拟文件,FIFO文件在在VFS目录中可见,所以他是有名管道,FIFO创建后,我们需要调用open函数打开这个fifo文件,然后才能通过write和read函数进行读写

pipe和fifo的异同点

相同点

  • IPC的本质都是通过在内核创建虚拟文件,并且调用文件读写函数来进行数据通信
  • 都只能接收字节流数据
  • 都是半双工通信

不同点

  • pipe是单向通信,fifo可以双向通信
  • pipe只能在父子,兄弟进程间通信,fifo没有这个限制

管道的使用场景

匿名管道只能用在亲属进程之间通信,而且传输的数量小,一般只支持4K,不适合大数据的交换数据和不同进程间的通信,但是使用简单方便,因为是单向通信,所以不存在并发问题。

虽然FIFO能在任意两个进程间进行通信,但是因为FIFO是可以双向通信的,这样也不可避免的带来了并发的问题,我们需要花费比较大的精力用来控制并发问题。

管道在Android系统中的使用场景
在Android 6.0 以下版本中,主线程Looper的唤醒就使用到了管道。

//文件-->/system/core/libutils/Looper.cpp
Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    int wakeFds[2];
    int result = pipe(wakeFds);  //创建pipe
​
    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];
​
    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",
            errno);
​
    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",
            errno);
​
    mIdling = false;// Allocate the epoll instance and register the wake pipe.
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);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;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",
            errno);
}

从第上面代码可以看到,native层的Looper的构造函数就使用了pipe来创建管道,通过mWakeReadPipeFd,mWakeWritePipeFd这两个文件描述符的命名也看出,它是用来做唤醒的,我们就来看一下具体的唤醒的实现吧。

//文件-->/system/core/libutils/Looper.cpp
void Looper::wake() {
    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);
        }
    }
}

可以看到,唤醒函数其实就是往管道mWakeWritePipeFd里写入一个字母“W”,mWakeReadPipeFd接收到数据后,就会唤醒Looper。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值