一、引入
我们知道,在 Android 的系统设计中,应用通过 dequeueBuffer 申请图像缓冲,再通过 queueBuffer 对图像缓冲进行绘制操作,最后交给屏幕控制器进行合成等显示操作。其中,不论是 GPU 的绘制,还是 GPU/HWC 的合成显示,都是一个耗时操作。所以,现代 Android 系统不会串行地等待 GPU/HWC 完成工作,而是把工作提交给 GPU/HWC 并行执行,使用 Fence 确认图像缓冲的同步,这样系统无需等待 GPU/HWC 的工作完成就能进行后续流程。只有在真正需要使用图像缓冲时,通过等待 Fence 的释放确认前一个流程已经使用完图像缓冲,再写入新的数据。Android 使用的这种 Fence 同步框架,既能提升图形显示系统的效率,又能保证图像缓冲的同步性。
从 Android trace 中可以清晰地看到这一流程。我们以 Android example trace 为例,对于进程 com.google.android.GoogleCamera,它有「GPU completion」和「HWC release」两个线程。从名称不难推测,这两个线程分别负责追踪 GPU 绘制和 HWC 合成的完成情况。应用在每次 queueBuffer 的时候会去追踪一个 GPU Fence,通过 Fence 的标识可以在「GPU completion」线程中确认 GPU 绘制的完成情况。类似地,应用在每次 dequeueBuffer 时会去追踪一个 HWC Fence,并在「HWC release」线程中确认 HWC 合成的完成情况。
然而,Android 系统如何实现 Fence,又如何实现对 GPU/HWC 完成情况的追踪?这正是本文要介绍的内容。
二、Android framework 中的 Fence 实现
从第一节的分析中我们知道 Android framework 中应该定义了一个 Fence
类,用于保证图像数据的同步。我们先来看看这个类的具体实现。
2.1 Fence
的定义
C++
// [/frameworks/native//libs/ui/include/ui/Fence.h]
class Fence
: public LightRefBase<Fence>, public Flattenable<Fence>
{
public:
//..
private:
// Only allow instantiation using ref counting.
friend class LightRefBase<Fence>;
virtual ~Fence() = default;
// Allow mocking for unit testing
friend class mock::MockFence;
base::unique_fd mFenceFd;
};
Fence
类的定义很简单,它只有一个成员变量 mFenceFd
,显然它表示 Fence 的文件描述符。
2.2 Fence
的成员函数
由于 Fence
类只有 mFenceFd
这一个成员变量,我们不妨再看看 Fence
中有哪些成员函数。
C++
class Fence
: public LightRefBase<Fence>, public Flattenable<Fence>
{
// ...
// Check whether the Fence has an open fence file descriptor. Most Fence
// methods treat an invalid file descriptor just like a valid fence that
// is already signalled, so using this is usually not necessary.
bool isValid() const { return mFenceFd != -1; }
// wait waits for up to timeout milliseconds for the fence to signal. If
// the fence signals then NO_ERROR is returned. If the timeout expires
// before the fence signals then -ETIME is returned. A timeout of
// TIMEOUT_NEVER may be used to indicate that the call should wait
// indefinitely for the fence to signal.
status_t wait(int timeout);
// waitForever is a convenience function for waiting forever for a fence to
// signal (just like wait(TIMEOUT_NEVER)), but issuing an error to the
// system log and fence state to the kernel log if the wait lasts longer
// than a warning timeout.
// The logname argument should be a string identifying
// the caller and will be included in the log message.
status_t waitForever(const char* logname);
// merge combines two Fence objects, creating a new Fence object that
// becomes signaled when both f1 and f2 are signaled (even if f1 or f2 is
// destroyed before it becomes signaled). The name argument specifies the
// human-readable name to associated with the new Fence object.
static sp<Fence> merge(const char* name, const sp<Fence>& f1,
const sp<Fence>& f2);
static sp<Fence> merge(const String8& name, const sp<Fence>& f1,
const sp<Fence>& f2);
// Return a duplicate of the fence file descriptor. The caller is
// responsible for closing the returned file descriptor. On error, -1 will
// be returned and errno will indicate the problem.
int dup() const;
// Return the underlying file descriptor without giving up ownership. The
// returned file descriptor is only valid for as long as the owning Fence
// object lives. (If the situation is unclear, dup() is always a safer
// option.)
int get() const { return mFenceFd.get(); }
// getSignalTime returns the system monotonic clock time at which the
// fence transitioned to the signaled state. If the fence is not signaled
// then SIGNAL_TIME_PENDING is returned. If the fence is invalid or if an
// error occurs then SIGNAL_TIME_INVALID is returned.
virtual nsecs_t getSignalTime() const;
// ...
};
我们可以从注释了解每个函数的大体作用,本文不会对 Fence
类的每个成员函数都做详细分析。不过,细心的读者可能已经发现,Fence
的成员函数中出现了与第一节 trace 中相同的 waitForever
函数!这并不是巧合,trace 中出现的 waitForever
就是 Fence::waitForever
函数。
2.3 Fence::waitForever
函数分析
我们重点分析下 Fence::waitForever
函数的逻辑:
C++
status_t Fence::waitForever(const char* logname) {
ATRACE_CALL();
if (mFenceFd == -1) {
return NO_ERROR;
}
int warningTimeout = 3000;
int err = sync_wait(mFenceFd, warningTimeout);
if (err < 0 && errno == ETIME) {
ALOGE("waitForever: %s: fence %d didn't signal in %u ms", logname, mFenceFd.get(),
warningTimeout);
struct sync_file_info* finfo = sync_file_info(mFenceFd);
if (finfo) {
// status: active(0) signaled(1) error(<0)
ALOGI("waitForever: fence(%s) status(%d)", finfo->name, finfo->status);
struct sync_fence_info* pinfo = sync_get_fence_info(finfo);
for (uint32_t i = 0; i < finfo->num_fences; i++) {
uint64_t ts_sec = pinfo[i].timestamp_ns / 1000000000LL;
uint64_t ts_usec = (pinfo[i].timestamp_ns % 1000000000LL) / 1000LL;
ALOGI("waitForever: sync point: timeline(%s) drv(%s) status(%d) timestamp(%" PRIu64
".%06" PRIu64 ")",
pinfo[i].obj_name, pinfo[i].driver_name, pinfo[i].status, ts_sec, ts_usec);
}
sync_file_info_free(finfo);
}
err = sync_wait(mFenceFd, TIMEOUT_NEVER);
}
return err < 0 ? -errno : status_t(NO_ERROR);
}
waitForever
函数并不长,实际上它只有一个核心步骤------调用 sync_wait
函数。sync_wait
函数的细节本文不做分析,笔者这里只列结论:sync_wait
函数通过 Linux poll 系统调用阻塞整个线程,直至对应的设备唤醒线程。回到 waitForever
函数,它的核心逻辑有两步:
- 调用
sync_wait
函数等待一个固定的warningTimeout
时长,如果阻塞时长不满warningTimeout
就被唤醒,waitForever
直接返回,否则进入第二步 - 调用
sync_wait
函数直至被唤醒
三、Android framework 对 Fence 的直接追踪------FenceMonitor
顺着第一节 trace 中 waitForever
函数的调用链向上排查,不难发现调用它的地方是 FenceMonitor::threadLoop
,这里引出了一个新的类 FenceMonitor
,它是 Android framework 直接追踪 Fence 的核心。
3.1 FenceMonitor
的定义
C++
// [/frameworks/native/libs/gui/include/gui/FenceMonitor.h]
class FenceMonitor {
public:
explicit FenceMonitor(const char* name);
void queueFence(const sp<Fence>& fence);
private:
void loop();
void threadLoop();
const char* mName;
uint32_t mFencesQueued;
uint32_t mFencesSignaled;
std::deque<sp<Fence>> mQueue;
std::condition_variable mCondition;
std::mutex mMutex;
};
FenceMonitor
的定义比较简单,其成员变量中与 Fence
直接相关的是 mQueue
,它是一个 sp<Fence>
类型的 队列。
PS:不知道读者是否已经发现,Fence
定义在 libui 库中,而 FenceMonitor
定义在 libgui 库中,不妨思考思考 Google 为什么这样设计代码结构。
3.2 FenceMonitor
的构造函数
FenceMonitor
只有下面这个构造函数:
C++
FenceMonitor::FenceMonitor(const char* name) : mName(name), mFencesQueued(0), mFencesSignaled(0) {
std::thread thread(&FenceMonitor::loop, this);
pthread_setname_np(thread.native_handle(), mName);
thread.detach();
}
FenceMonitor
的构造函数会初始化一些成员变量和 创建一个线程 。这个线程的名字是创建对象时传入的 name
,可调用对象是 FenceMonitor::loop
。事实上,第一节 trace 中的「GPU completion」和「HWC release」两个线程就是在 FenceMonitor
的构造函数中创建的!
C++
void FenceMonitor::loop() {
while (true) {
threadLoop();
}
}
FenceMonitor::loop
函数的逻辑是循环执行 threadLoop
。还记得本节开始时提到过的,调用waitForever
函数的地方是 FenceMonitor::threadLoop
函数吗?通过在线程内无限循环调用 threadLoop
,FenceMonitor
实现了对 Fence 的持续追踪。
3.3 mQueue
出队分析
3.1 节提到 FenceMonitor
存储 Fence 的数据结构是 队列 ,所以必然会有入队和出队两种操作。我们先来分析 mQueue
出队的逻辑,因为唯一一处进行出队操作的地方正是 FenceMonitor::threadLoop
函数:
C++
void FenceMonitor::threadLoop() {
sp<Fence> fence;
uint32_t fenceNum;
{
std::unique_lock<std::mutex> lock(mMutex);
while (mQueue.empty()) {
mCondition.wait(lock);
}
fence = mQueue[0];
fenceNum = mFencesSignaled;
}
{
char message[64];
snprintf(message, sizeof(message), "waiting for %s %u", mName, fenceNum);
ATRACE_NAME(message);
status_t result = fence->waitForever(message);
if (result != OK) {
ALOGE("Error waiting for fence: %d", result);
}
}
{
std::lock_guard<std::mutex> lock(mMutex);
mQueue.pop_front();
mFencesSignaled++;
ATRACE_INT(mName, int32_t(mQueue.size()));
}
}
threadLoop
函数会从 mQueue
队首取一个 Fence
指针,并调用其 waitForever
函数,最后从 mQueue
出队。
3.4 mQueue
入队分析
接着分析 mQueue
入队的逻辑。在 Fencewonitor
中,也只有 FenceMonitor::queueFence
这一处函数会给 mQueue
添加元素。
C++
void FenceMonitor::queueFence(const sp<Fence>& fence) {
char message[64];
std::lock_guard<std::mutex> lock(mMutex);
if (fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) {
snprintf(message, sizeof(message), "%s fence %u has signaled", mName, mFencesQueued);
ATRACE_NAME(message);
// Need an increment on both to make the trace number correct.
mFencesQueued++;
mFencesSignaled++;
return;
}
snprintf(message, sizeof(message), "Trace %s fence %u", mName, mFencesQueued);
ATRACE_NAME(message);
mQueue.push_back(fence);
mCondition.notify_one();
mFencesQueued++;
ATRACE_INT(mName, int32_t(mQueue.size()));
}
queueFence
的入参会接收一个 Fence
指针 fence
,并添加到 mQueue
队尾。又因为 threadLoop
会被无限循环调用,所以这个入队的 fence
很快就会被 FenceMonitor
追踪到。
四、Android framework 追踪 Fence 的完整链路
在有了 Fence
和 FenceMonitor
这两个类的基础后,我们可以正式开始分析 Android framework 追踪 Fence 的完整链路。根据第三节的分析,我们已经清楚 FenceMonitor
如何追踪 Fence
的释放情况------通过子线程中循环调用 threadLoop
函数。但是,我们对于 FenceMonitor
如何追踪 Fence
的创建情况的分析尚不充分,因为queueFence
是从入参中拿到的 Fence
,FenceMonitor
中并没有创建 Fence
的逻辑。所以,接下来我们要分析 Android framework 在哪里创建的 Fence
?
继续顺着 FenceMonitor::queueFence
的调用链向上排查,我们可以在 Surface
中找到调用它的函数:dequeueBuffer
和 onBufferQueuedLocked
(实际上就是 queueBuffer
)。Amazing!我们的分析终于实现了对第一节中整个图形显示流程的闭环。dequeueBuffer
和 queueBuffer
各有两种实现,但核心逻辑是一致的,所以我们仅详细分析其中一个 dequeueBuffer
函数的逻辑。
C++
// [/frameworks/native/libs/gui/Surface.cpp]
int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {
// ...
int buf = -1;
sp<Fence> fence;
nsecs_t startTime = systemTime();
FrameEventHistoryDelta frameTimestamps;
status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, dqInput.width,
dqInput.height, dqInput.format,
dqInput.usage, &mBufferAge,
dqInput.getTimestamps ?
&frameTimestamps : nullptr);
// ...
if (CC_UNLIKELY(atrace_is_tag_enabled(ATRACE_TAG_GRAPHICS))) {
static gui::FenceMonitor hwcReleaseThread("HWC release");
hwcReleaseThread.queueFence(fence);
}
// ...
}
可以看到,Surface::dequeueBuffer
函数中会创建一个 fence
指针,但没有赋值。这个 fence
指针会作为入参传给 mGraphicBufferProducer>dequeueBuffer
,并在该函数内给 fence
指针赋值。在 mGraphicBufferProducer->dequeueBuffer
流程中,会完成申请图形缓冲的操作,这是一个异步过程,mGraphicBufferProducer->dequeueBuffer
函数本身很快就会返回。mGraphicBufferProducer->dequeuesuffer
调用返回后,Surface::dequeueBuffer
就拿到了真正的 Fence,再对静态变量 hwcReleaseThread
调用 queueFence
,实现对这个 fence
指针创建到释放整个过程的追踪。
同理,Surface
中还有一个静态变量 gpuCompletionThread
,在 Surface::queueBuffer
的时候实现对写图形缓存的追踪。不过,gpuCompletion
追踪的 fence
对象的来源与 hwcReleaseThread
不同,它是从 queueBuffer
的入参中拿到的,实际上和前面 dequeueBuffer
函数中申请到的 fence
是同一个指针。
综上所述,Android framework 追踪 Fence 的完整链路如下图所示。
五、Android 同步框架概述
Fence 机制是 Android 用于保证图形系统中不同异步操作之间的依赖关系的框架,该机制提供了一个(或一组)API,实现了组件在缓冲区被释放时进行提示。同时,它还允许在驱动程序之间(从内核驱动程序到用户空间驱动程序)以及在用户空间进程本身之间传递同步基元。
本文介绍的流程基本都发生在用户空间,对内核空间的原理并没有进行深入分析。Android 在内核中同样定义了 Fence 机制的组件和规范,以让生产厂商实现。关于内核空间部分的内容,感兴趣的读者可以自行阅读参考文档和 AOSP 源码,点赞够多的话,笔者也会出一篇内核空间的分析文章。
注
本文中所有 AOSP 代码的版本均为 android15-qpr1-release。