• Android 图像显示系统 - 基础知识之 BitTube



    必读:

    Android 12(S) 图像显示系统 - 开篇


     

     

    一、基本概念


    在Android显示子系统中,我们会看到有使用BitTube来进行跨进程数据传递。BitTube的实现很简洁,就是一对“parcel-able”模式的socket,用Linux/Unix中的专业术语就是socketpair。socketpair是Linux/Unix系统中用于进程间通信的一种机制,和pipe十分类似。

    socketpair利用socket为双方建立了全双工的通信管道(communication pipe)。通过文件描述符的复用(dup/dup2),可以传递socket handle到另一个进程,复用它并开启通信。

    BitTube使用了Linux/Unix socket中的顺序数据包(sequenced packets,SOCK_SEQPACKET),像SOCK_DGRAM,它只传送整包数据;又像SOCK_STREAM,面向连接且提供有序的数据报传送。

    尽管socketpair是一个全双工的管道,但BitTube是按照单向方式使用它的:一端写入数据,另一端读出数据。收、发缓存默认限制为4KB大小。在BitTube中,提供了收发序列化对象的方法(sendObjects, recvObjects)。

     

    二、源码解读


    BitTube代码量很少,在(frameworks\native\libs\gui\BitTube.cpp)中,我们直接看它的几个重要的接口。

    2.1 构造函数

    [/frameworks/native/libs/gui/include/private/gui/BitTube.h]
    BitTube() = default; // 默认构造函数,未初始化
    explicit BitTube(size_t bufsize); // 创建指定发送、接收缓存大小的BitTube对象,creates a BitTube with a a specified send and receive buffer size
    explicit BitTube(DefaultSizeType); // 默认缓存大小4KB,creates a BitTube with a default (4KB) send buffer
    explicit BitTube(const Parcel& data); // 从Parcel中解析创建对象,用于跨进程传递该对象

    BitTube提供了四个构造函数,用于不同的场景

    [/frameworks/native/libs/gui/BitTube.cpp]
    BitTube::BitTube(size_t bufsize) {
        init(bufsize, bufsize); // 根据指定的buffer size,进行初始化
    }
    
    BitTube::BitTube(DefaultSizeType) : BitTube(DEFAULT_SOCKET_BUFFER_SIZE) {}
    
    BitTube::BitTube(const Parcel& data) {
        readFromParcel(&data);
    }

    构造函数中最主要的还是调用了init方法进行初始化。

    2.2 init初始化

    init方法中就是去创建/配置sockect pair

    [/frameworks/native/libs/gui/BitTube.cpp]
    void BitTube::init(size_t rcvbuf, size_t sndbuf) {
        int sockets[2];
        if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets) == 0) { // 创建socket pair
            size_t size = DEFAULT_SOCKET_BUFFER_SIZE;
            setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)); // //对socketfd进行配置
            setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
            // since we don't use the "return channel", we keep it small...
            setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));
            setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
            fcntl(sockets[0], F_SETFL, O_NONBLOCK); //设置为非阻塞
            fcntl(sockets[1], F_SETFL, O_NONBLOCK); //设置为非阻塞
            mReceiveFd.reset(sockets[0]); //用于数据接收的socket handle
            mSendFd.reset(sockets[1]);    //用于数据发送的socket handle
        } else {
            mReceiveFd.reset();
            ALOGE("BitTube: pipe creation failed (%s)", strerror(errno));
        }
    }

    成员变量mReceiveFd,看起来是一个接收端,实际上这个fd也可以用来发送,同样mSendFd也可以用来接收,只是BitTube是按照单向方式使用它的:一端写入数据,另一端读出数据。

     

    2.3 sendObjects 发送数据

    先看其定义,sendObject实现为一个模板函数,sendObjects里调用的是write成员函数,write中调用send接口将数据写入mSendFd中。

    [/frameworks/native/libs/gui/include/private/gui/BitTube.h]
    // send objects (sized blobs). All objects are guaranteed to be written or the call fails.
    template <typename T>
    static ssize_t sendObjects(BitTube* tube, T const* events, size_t count) {
            return sendObjects(tube, events, count, sizeof(T));
    }

    发送成功则返回:发送的对象的个数

    发送失败则返回:负数

    [ /frameworks/native/libs/gui/BitTube.cpp]
    ssize_t BitTube::sendObjects(BitTube* tube, void const* events, size_t count, size_t objSize) {
        const char* vaddr = reinterpret_cast<const char*>(events);
        ssize_t size = tube->write(vaddr, count * objSize);
        ...
        return size < 0 ? size : size / static_cast<ssize_t>(objSize);
    }
    
    ssize_t BitTube::write(void const* vaddr, size_t size) {
        ssize_t err, len;
        do {
            len = ::send(mSendFd, vaddr, size, MSG_DONTWAIT | MSG_NOSIGNAL); // 通过mSendFd,发送数据
            // cannot return less than size, since we're using SOCK_SEQPACKET
            err = len < 0 ? errno : 0;
        } while (err == EINTR);
        return err == 0 ? len : -err;
    }

     

    2.4 recvObjects 接收数据

    先看其定义,recvObject实现为一个模板函数,recvObjects里调用的是read成员函数,read中调用rev接口将数据从mReceiveFd中读出。

    接收成功则返回:接收的对象的个数

    接收失败则返回:负数

    [ /frameworks/native/libs/gui/include/private/gui/BitTube.h]
    template <typename T>
    static ssize_t recvObjects(BitTube* tube, T* events, size_t count) {
        return recvObjects(tube, events, count, sizeof(T));
    }
    [/frameworks/native/libs/gui/BitTube.cpp]
    ssize_t BitTube::recvObjects(BitTube* tube, void* events, size_t count, size_t objSize) {
        char* vaddr = reinterpret_cast<char*>(events);
        ssize_t size = tube->read(vaddr, count * objSize);
        ...
        return size < 0 ? size : size / static_cast<ssize_t>(objSize);
    }
    
    ssize_t BitTube::read(void* vaddr, size_t size) {
        ssize_t err, len;
        do {
            len = ::recv(mReceiveFd, vaddr, size, MSG_DONTWAIT);
            err = len < 0 ? errno : 0;
        } while (err == EINTR);
        if (err == EAGAIN || err == EWOULDBLOCK) {
            // EAGAIN means that we have non-blocking I/O but there was no data to be read. Nothing the
            // client should care about.
            return 0;
        }
        return err == 0 ? len : -err;
    }

    本文作者@二的次方  2022-04-18 发布于博客园

    2.5 writeToParcel & readFromParcel

    writeToParcel & readFromParcel用于跨进程传递BitTube对象,进行序列化和反序列化,主要是传递mReceivedFd 和 mSendFd。

    
    status_t BitTube::writeToParcel(Parcel* reply) const {
        if (mReceiveFd < 0) return -EINVAL;
    
        status_t result = reply->writeDupFileDescriptor(mReceiveFd); // mReceiveFd写入Parcel
        mReceiveFd.reset();
        if (result != NO_ERROR) {
            return result;
        }
        result = reply->writeDupFileDescriptor(mSendFd);// mSendFd写入Parcel
        mSendFd.reset();
        return result;
    }
    
    status_t BitTube::readFromParcel(const Parcel* parcel) {
        mReceiveFd.reset(dup(parcel->readFileDescriptor())); // 获取 mReceiveFd
        if (mReceiveFd < 0) {
            mReceiveFd.reset();
            int error = errno;
            ALOGE("BitTube::readFromParcel: can't dup file descriptor (%s)", strerror(error));
            return -error;
        }
        mSendFd.reset(dup(parcel->readFileDescriptor())); // 获取 mSendFd
        if (mSendFd < 0) {
            mSendFd.reset();
            int error = errno;
            ALOGE("BitTube::readFromParcel: can't dup file descriptor (%s)", strerror(error));
            return -error;
        }
        return NO_ERROR;
    }

     

    三、使用

    关于如何使用BitTube实现跨进程的数据通信,提供一个简单的测试Demo:

    https://github.com/yrzroger/BitTubeTest

    在测试demo中,创建了一个BitTube对象,这样就建立了通信的 socketpair。

    然后使用fork系统调用创建新的进程,来模拟跨进的通信中的不同进程(一个父进程,一个子进程)

    父进程和子进程就可以使用BitTube对象的sendObjects方法发送数据,或使用recvObjects方法接收数据

    Demo的主要代码如下:

    struct Event {
        int id;
        int message;
    };
    
    int main()
    {
        gui::BitTube* dataChannel = new gui::BitTube(gui::BitTube::DefaultSize);
        
        printf("\033[0mBitTube info: mReceiveFd=%d, mSendFd=%d\n", dataChannel->getFd(), dataChannel->getSendFd());
    
        if(fork()) {
            // 父进程发送数据
            Event events[] = { {0, 888}, {1, 999} };
            ssize_t size = gui::BitTube::sendObjects(dataChannel, events, 2);
            if(size < 0)
                printf("\033[32mprocess(%d) send failed, in parent process", getpid());
            else
                printf("\033[32mprocess(%d) send success, object size = %d\n", getpid(), size);
            sleep(1);
            // 父进程接收数据
            size = gui::BitTube::recvObjects(dataChannel, events, 2);
            if(size < 0) {
                printf("\033[32mprocess(%d) receive failed, in child process", getpid());
            }
            else {
                printf("\033[32mprocess(%d) receive success, object size = %d\n", getpid(), size);
                for(int i = 0; i < size; ++i) {
                    printf("\033[32mprocess(%d): id=%d, message=%d\n", getpid(), events[i].id, events[i].message);
                }
            }
            sleep(1);
            
        } else {
            // 子进程接收数据
            Event events[2];
            ssize_t size = gui::BitTube::recvObjects(dataChannel, events, 2);
            if(size < 0) {
                printf("\033[31mprocess(%d) receive failed, in child process", getpid());
            }
            else {
                printf("\033[31mprocess(%d) receive success, object size = %d\n", getpid(), size);
                for(int i = 0; i < size; ++i) {
                    printf("\033[31mprocess(%d): id=%d, message=%d\n", getpid(), events[i].id, events[i].message);
                }
            }
            // 子进程发送数据
            events[0].message+=1; events[1].message+=1;
            size = gui::BitTube::sendObjects(dataChannel, events, 2);
            if(size < 0)
                printf("\033[31mprocess(%d) send failed, in parent process", getpid());
            else
                printf("\033[31mprocess(%d) send success, object size = %d\n", getpid(), size);
            sleep(2);
        }
        delete dataChannel;
    
        return 0;
    }

     

    放到Android源码下,执行mm,编译得到可执行档BitTubeTest,push到测试板/system/bin/目录下 执行BitTubeTest可以查看打印的结果:

    绿色字体是父进程的打印(PID=12374),红色字体是子进程的打印(PID=12375)

     

     

    在Android图像显示子系统中, /frameworks/native/libs/gui/DisplayEventReceiver.cpp 及  /frameworks/native/services/surfaceflinger/Scheduler/EventThread.cpp 中可以看到使用BitTube的身影。

    BitTube用于建立跨进程传递数据的通道,主要是display evnets, 比如hotplug events , vsync events等等

    至于具体的使用过程,在接下来的文章中我们会再详细介绍,,本篇就仅先讲解必要的基础知识。

     

  • 相关阅读:
    docker 命令
    出差学小白知识No5:|Ubuntu上关联GitLab账号并下载项目(ssh key配置)
    【css】关于css样式中border-radius和border-image不兼容问题
    使用curl命令访问openstack api
    k8s集群pod和node状态监控
    青少年python系列 28.turtle库绘一个圆点
    C++读写Excel有许多开源库
    【Docker】Docker安装
    MEMM最大熵模型
    【任务调度框架】「分析技术指南」带你一同盘点一下常用的任务调度框架的方案和原理开发指南
  • 原文地址:https://www.cnblogs.com/roger-yu/p/16158539.html