­

Android 12(S) 圖像顯示系統 – 基礎知識之 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;
}

 

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:

//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等等

至於具體的使用過程,在接下來的文章中我們會再詳細介紹,,本篇就僅先講解必要的基礎知識。