必讀:
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等等
至於具體的使用過程,在接下來的文章中我們會再詳細介紹,,本篇就僅先講解必要的基礎知識。