前文Android匿名共享記憶體(Ashmem)原理分析了匿名共享記憶體,它最主要的作用就是View檢視繪製,Android檢視是按照一幀一幀顯示到螢幕的,而每一幀都會佔用一定的儲存空間,通過Ashmem機制APP與SurfaceFlinger共享繪圖資料,提高圖形處理效能,本文就看Android是怎麼利用Ashmem分配及繪製的:
View檢視記憶體的分配
前文Window新增流程中描述了:在新增視窗的時候,WMS會為APP分配一個WindowState,以標識當前視窗並用於視窗管理,同時向SurfaceFlinger端請求分配Layer抽象圖層,在SurfaceFlinger分配Layer的時候建立了兩個比較關鍵的Binder物件,用於填充WMS端Surface,一個是sp handle:是每個視窗標識的控制程式碼,將來WMS同SurfaceFlinger通訊的時候方便找到對應的圖層。另一個是sp gbp :共享記憶體分配的關鍵物件,同時兼具Binder通訊的功能,用來傳遞指令及共享記憶體的控制程式碼,注意,這裡只是抽象建立了物件,並未真正分配每一幀的記憶體,記憶體的分配要等到真正繪製的時候才會申請,首先看一下分配流程:
- 分配的時機:什麼時候分配
- 分配的手段:如何分配
- 傳遞的方式:如何跨程式傳遞
Surface被抽象成一塊畫布,只要擁有Surface就可以繪圖,其根本原理就是Surface握有可以繪圖的一塊記憶體,這塊記憶體是APP端在需要的時候,通過sp gbp向SurfaceFlinger申請的,那麼首先看一下APP端如何獲得sp gbp這個服務代理的,之後再看如何利用它申請記憶體,在WMS利用向SurfaceFlinger申請填充Surface的時候,會請求SurfaceFlinger分配這把劍,並將其控制程式碼交給自己
sp<SurfaceControl> SurfaceComposerClient::createSurface(
const String8& name, uint32_t w, uint32_t h, PixelFormat format, uint32_t flags){
sp<SurfaceControl> sur;
...
if (mStatus == NO_ERROR) {
sp<IBinder> handle;
sp<IGraphicBufferProducer> gbp;
<!--關鍵點1 獲取圖層的關鍵資訊handle, gbp-->
status_t err = mClient->createSurface(name, w, h, format, flags,
&handle, &gbp);
<!--關鍵點2 根據返回的圖層關鍵資訊 建立SurfaceControl物件-->
if (err == NO_ERROR) {
sur = new SurfaceControl(this, handle, gbp);
}
}
return sur;
}複製程式碼
看關鍵點1,這裡其實就是建立了一個sp gbp容器,並請求SurfaceFlinger分配填充內容,SurfaceFlinger收到請求後會為WMS建立與APP端對應的Layer,同時為其分配sp gbp,並填充到Surface中返回給APP,
status_t SurfaceFlinger::createNormalLayer(const sp<Client>& client,
const String8& name, uint32_t w, uint32_t h, uint32_t flags, PixelFormat& format,
sp<IBinder>* handle, sp<IGraphicBufferProducer>* gbp, sp<Layer>* outLayer){
...
<!--關鍵點 1 -->
*outLayer = new Layer(this, client, name, w, h, flags);
status_t err = (*outLayer)->setBuffers(w, h, format, flags);
<!--關鍵點 2-->
if (err == NO_ERROR) {
*handle = (*outLayer)->getHandle();
*gbp = (*outLayer)->getProducer();
}
return err;
}
void Layer::onFirstRef() {
sp<IGraphicBufferProducer> producer;
sp<IGraphicBufferConsumer> consumer;
<!--建立producer與consumer-->
BufferQueue::createBufferQueue(&producer, &consumer);
mProducer = new MonitoredProducer(producer, mFlinger);
mSurfaceFlingerConsumer = new SurfaceFlingerConsumer(consumer, mTextureName,
this);
...
}
void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
sp<IGraphicBufferConsumer>* outConsumer,
const sp<IGraphicBufferAlloc>& allocator) {
sp<BufferQueueCore> core(new BufferQueueCore(allocator));
sp<IGraphicBufferProducer> producer(new BufferQueueProducer(core));
sp<IGraphicBufferConsumer> consumer(new BufferQueueConsumer(core));
*outProducer = producer;
*outConsumer = consumer;
}複製程式碼
從上面兩個函式可以很清楚的看到Producer/Consumer的模型原樣,也就說每個圖層Layer都有自己的producer/ consumer,sp gbp對應的其實是BufferQueueProducer,而BufferQueueProducer是一個Binder通訊物件,在服務端是:
class BufferQueueProducer : public BnGraphicBufferProducer,
private IBinder::DeathRecipient {}複製程式碼
在APP端是
class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>{}複製程式碼
IGraphicBufferProducer Binder實體在SurfaceFlinger中建立後,打包到Surface物件,並通過binder通訊傳遞給APP端,APP段通過反序列化將其恢復出來,如下:
status_t Surface::readFromParcel(const Parcel* parcel, bool nameAlreadyRead) {
if (parcel == nullptr) return BAD_VALUE;
status_t res = OK;
if (!nameAlreadyRead) {
name = readMaybeEmptyString16(parcel);
// Discard this for now
int isSingleBuffered;
res = parcel->readInt32(&isSingleBuffered);
if (res != OK) {
return res;
}
}
sp<IBinder> binder;
res = parcel->readStrongBinder(&binder);
if (res != OK) return res;
<!--interface_cast會將其轉換成BpGraphicBufferProducer-->
graphicBufferProducer = interface_cast<IGraphicBufferProducer>(binder);
return OK;
}複製程式碼
自此,APP端就獲得了申請記憶體的控制程式碼BpGraphicBufferProducer,它真正發揮作用是在第一次繪圖時,看一下ViewRootImpl中的draw
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
final Canvas canvas;
try {
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
<!--關鍵點1 獲取繪圖記憶體-->
canvas = mSurface.lockCanvas(dirty);
try {
try {
<!--關鍵點2 繪圖-->
mView.draw(canvas);
}
} finally {
try {
<!--關鍵點 3 繪圖結束 ,通知surfacefling混排,更新顯示介面-->
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {}複製程式碼
先看關鍵點1,記憶體的分配時機其實就在這裡,直接進入到native層
static jlong nativeLockCanvas(JNIEnv* env, jclass clazz,
jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {
sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
...
status_t err = surface->lock(&outBuffer, dirtyRectPtr);
...
sp<Surface> lockedSurface(surface);
lockedSurface->incStrong(&sRefBaseOwner);
return (jlong) lockedSurface.get();
}複製程式碼
surface.cpp的lock會進一步呼叫dequeueBuffer函式來請求分配記憶體:
int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {
...
int buf = -1;
sp<Fence> fence;
nsecs_t now = systemTime();
<!--申請buffer,並獲得識別符號-->
status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence,
reqWidth, reqHeight, reqFormat, reqUsage);
...
if ((result & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) || gbuf == 0) {
<!--申請的記憶體是在surfaceflinger程式中,Surface通過呼叫requestBuffer將圖形緩衝區對映到Surface所在程式-->
result = mGraphicBufferProducer->requestBuffer(buf, &gbuf);
...
}複製程式碼
最終會呼叫BpGraphicBufferProducer的dequeueBuffer向服務端請求分配記憶體,這裡用到了匿名共享記憶體的知識,在Linux中一切都是檔案,共享記憶體也看成一個檔案。分配成功之後,需要跨程式傳遞tmpfs臨時檔案的描述符fd。先看下申請的邏輯:
class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>{
virtual status_t dequeueBuffer(int *buf, sp<Fence>* fence, bool async,
uint32_t w, uint32_t h, uint32_t format, uint32_t usage) {
Parcel data, reply;
data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
data.writeInt32(async);
data.writeInt32(w);
data.writeInt32(h);
data.writeInt32(format);
data.writeInt32(usage);
//通過BpBinder將要什麼的buffer的相關引數儲存到data,傳送給BBinder
status_t result = remote()->transact(DEQUEUE_BUFFER, data, &reply);
if (result != NO_ERROR) {
return result;
}
//BBinder給BpBinder返回了一個int,並不是緩衝區的記憶體
*buf = reply.readInt32();
bool nonNull = reply.readInt32();
if (nonNull) {
*fence = new Fence();
reply.read(**fence);
}
result = reply.readInt32();
return result;
}
}複製程式碼
在client側,也就是BpGraphicBufferProducer側,通過DEQUEUE_BUFFER後核心只返回了一個*buf = reply.readInt32();其實是陣列mSlots的下標,在BufferQueue中有個和mSlots對應的陣列,也是32個,一一對應,
status_t BnGraphicBufferProducer::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
case DEQUEUE_BUFFER: {
CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
bool async = data.readInt32();
uint32_t w = data.readInt32();
uint32_t h = data.readInt32();
uint32_t format = data.readInt32();
uint32_t usage = data.readInt32();
int buf;
sp<Fence> fence;
//呼叫BufferQueue的dequeueBuffer
//也返回一個int的buf
int result = dequeueBuffer(&buf, &fence, async, w, h, format, usage);
//將buf和fence寫入parcel,通過binder傳給client
reply->writeInt32(buf);
reply->writeInt32(fence != NULL);
if (fence != NULL) {
reply->write(*fence);
}
reply->writeInt32(result);
return NO_ERROR;
}複製程式碼
可以看到BnGraphicBufferProducer端獲取到長寬及格式,之後利用BufferQueueProducer的dequeueBuffer來申請記憶體,記憶體可能已經申請,也可能未申請,未申請,則直接申請新記憶體,每個surface可以對應32塊記憶體:
status_t BufferQueueProducer::dequeueBuffer(int *outSlot,
sp<android::Fence> *outFence, uint32_t width, uint32_t height,
PixelFormat format, uint32_t usage) {
...
sp<GraphicBuffer> graphicBuffer(mCore->mAllocator->createGraphicBuffer(
width, height, format, usage,
{mConsumerName.string(), mConsumerName.size()}, &error));複製程式碼
mCore其實就是上面的BufferQueueCore,mCore->mAllocator = new GraphicBufferAlloc(),最終會利用GraphicBufferAlloc物件分配共享記憶體:
sp<GraphicBuffer> GraphicBufferAlloc::createGraphicBuffer(uint32_t width,
uint32_t height, PixelFormat format, uint32_t usage,
std::string requestorName, status_t* error) {
<!--直接new新建-->
sp<GraphicBuffer> graphicBuffer(new GraphicBuffer(
width, height, format, usage, std::move(requestorName)));
status_t err = graphicBuffer->initCheck();
return graphicBuffer;
}複製程式碼
從上面看到,直接new GraphicBuffer新建影象記憶體,
GraphicBuffer::GraphicBuffer(uint32_t inWidth, uint32_t inHeight,
PixelFormat inFormat, uint32_t inUsage, std::string requestorName)
: BASE(), mOwner(ownData), mBufferMapper(GraphicBufferMapper::get()),
mInitCheck(NO_ERROR), mId(getUniqueId()), mGenerationNumber(0){
...
handle = NULL;
mInitCheck = initSize(inWidth, inHeight, inFormat, inUsage,
std::move(requestorName));
}
status_t GraphicBuffer::initSize(uint32_t inWidth, uint32_t inHeight,
PixelFormat inFormat, uint32_t inUsage, std::string requestorName)
{
GraphicBufferAllocator& allocator = GraphicBufferAllocator::get();
uint32_t outStride = 0;
<!--請求分配記憶體-->
status_t err = allocator.allocate(inWidth, inHeight, inFormat, inUsage,
&handle, &outStride, mId, std::move(requestorName));
if (err == NO_ERROR) {
width = static_cast<int>(inWidth);
height = static_cast<int>(inHeight);
format = inFormat;
usage = static_cast<int>(inUsage);
stride = static_cast<int>(outStride);
}
return err;
}
status_t GraphicBufferAllocator::allocate(uint32_t width, uint32_t height,
PixelFormat format, uint32_t usage, buffer_handle_t* handle,
uint32_t* stride, uint64_t graphicBufferId, std::string requestorName)
{
...
auto descriptor = mDevice->createDescriptor();
auto error = descriptor->setDimensions(width, height);
error = descriptor->setFormat(static_cast<android_pixel_format_t>(format));
error = descriptor->setProducerUsage(
static_cast<gralloc1_producer_usage_t>(usage));
error = descriptor->setConsumerUsage(
static_cast<gralloc1_consumer_usage_t>(usage));
<!--這裡的device就是抽象的硬體裝置-->
error = mDevice->allocate(descriptor, graphicBufferId, handle);
error = mDevice->getStride(*handle, stride);
...
return NO_ERROR;
}複製程式碼
上面程式碼的mDevice就是利用hw_get_module及gralloc1_open獲取到的硬體抽象層device,hw_get_module裝載HAL模組,會載入相應的.so檔案gralloc.default.so,它實現位於 hardware/libhardware/modules/gralloc.cpp中,最後將device對映的函式操作載入進來。這裡我們關心的是allocate函式,先分析普通圖形緩衝區的分配,它最終會呼叫gralloc_alloc_buffer()利用匿名共享記憶體進行分配,之前的文章Android匿名共享記憶體(Ashmem)原理分析了Android是如何通過匿名共享記憶體進行通訊的,這裡就直接用了:
static int gralloc_alloc_buffer(alloc_device_t* dev,
size_t size, int usage, buffer_handle_t* pHandle)
{
int err = 0;
int fd = -1;
size = roundUpToPageSize(size);
// 建立共享記憶體,並且設定名字跟size
fd = ashmem_create_region("gralloc-buffer", size);
if (err == 0) {
private_handle_t* hnd = new private_handle_t(fd, size, 0);
gralloc_module_t* module = reinterpret_cast<gralloc_module_t*>(
dev->common.module);
// 執行mmap,將記憶體對映到自己的程式
err = mapBuffer(module, hnd);
if (err == 0) {
*pHandle = hnd;
}
}
return err;
}複製程式碼
mapBuffer會進一步呼叫ashmem的驅動,在tmpfs新建檔案,同時開闢虛擬記憶體,
int mapBuffer(gralloc_module_t const* module,
private_handle_t* hnd)
{
void* vaddr;
// vaddr有個毛用?
return gralloc_map(module, hnd, &vaddr);
}
static int gralloc_map(gralloc_module_t const* module,
buffer_handle_t handle,
void** vaddr)
{
private_handle_t* hnd = (private_handle_t*)handle;
if (!(hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER)) {
size_t size = hnd->size;
void* mappedAddress = mmap(0, size,
PROT_READ|PROT_WRITE, MAP_SHARED, hnd->fd, 0);
if (mappedAddress == MAP_FAILED) {
return -errno;
}
hnd->base = intptr_t(mappedAddress) + hnd->offset;
}
*vaddr = (void*)hnd->base;
return 0;
}複製程式碼
View繪製記憶體的傳遞
分配之後,會繼續利用BpGraphicBufferProducer的requestBuffer,申請將共享記憶體給對映到當前程式:
virtual status_t requestBuffer(int bufferIdx, sp<GraphicBuffer>* buf) {
Parcel data, reply;
data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
data.writeInt32(bufferIdx);
status_t result =remote()->transact(REQUEST_BUFFER, data, &reply);
if (result != NO_ERROR) {
return result;
}
bool nonNull = reply.readInt32();
if (nonNull) {
*buf = new GraphicBuffer();
reply.read(**buf);
}
result = reply.readInt32();
return result;
}複製程式碼
private_handle_t物件用來抽象圖形緩衝區,其中儲存著與共享記憶體對應tmpfs檔案的fd,GraphicBuffer物件會通過序列化,將這個fd會利用Binder通訊傳遞給App程式,APP端獲取到fd之後,便可以同mmap將共享記憶體對映到自己的程式空間,進而進行圖形繪製。等到APP端對GraphicBuffer的反序列化的時候,會將共享記憶體mmap到當前程式空間:
status_t Parcel::read(Flattenable& val) const
{
// size
const size_t len = this->readInt32();
const size_t fd_count = this->readInt32();
// payload
void const* buf = this->readInplace(PAD_SIZE(len));
if (buf == NULL)
return BAD_VALUE;
int* fds = NULL;
if (fd_count) {
fds = new int[fd_count];
}
status_t err = NO_ERROR;
for (size_t i=0 ; i<fd_count && err==NO_ERROR ; i++) {
fds[i] = dup(this->readFileDescriptor());
if (fds[i] < 0) err = BAD_VALUE;
}
if (err == NO_ERROR) {
err = val.unflatten(buf, len, fds, fd_count);
}
if (fd_count) {
delete [] fds;
}
return err;
} 複製程式碼
進而呼叫GraphicBuffer::unflatten:
status_t GraphicBuffer::unflatten(void const* buffer, size_t size,
int fds[], size_t count)
{
...
mOwner = ownHandle;
<!--將共享記憶體對映當前記憶體空間-->
if (handle != 0) {
status_t err = mBufferMapper.registerBuffer(handle);
}
return NO_ERROR;
}複製程式碼
mBufferMapper.registerBuffer函式對應gralloc_register_buffer
struct private_module_t HAL_MODULE_INFO_SYM = {
.base = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = GRALLOC_HARDWARE_MODULE_ID,
.name = "Graphics Memory Allocator Module",
.author = "The Android Open Source Project",
.methods = &gralloc_module_methods
},
.registerBuffer = gralloc_register_buffer,
.unregisterBuffer = gralloc_unregister_buffer,
.lock = gralloc_lock,
.unlock = gralloc_unlock,
},
.framebuffer = 0,
.flags = 0,
.numBuffers = 0,
.bufferMask = 0,
.lock = PTHREAD_MUTEX_INITIALIZER,
.currentBuffer = 0,
};複製程式碼
最後會呼叫gralloc_register_buffer,通過mmap真正將tmpfs檔案對映到程式空間:
static int gralloc_register_buffer(gralloc_module_t const* module,
buffer_handle_t handle)
{
...
if (cb->ashmemSize > 0 && cb->mappedPid != getpid()) {
void *vaddr;
<!--mmap-->
int err = map_buffer(cb, &vaddr);
cb->mappedPid = getpid();
}
return 0;
}複製程式碼
終於我們用到tmpfs中檔案對應的描述符fd0->cb->fd
static int map_buffer(cb_handle_t *cb, void **vaddr)
{
if (cb->fd < 0 || cb->ashmemSize <= 0) {
return -EINVAL;
}
void *addr = mmap(0, cb->ashmemSize, PROT_READ | PROT_WRITE,
MAP_SHARED, cb->fd, 0);
cb->ashmemBase = intptr_t(addr);
cb->ashmemBasePid = getpid();
*vaddr = addr;
return 0;
}複製程式碼
到這裡記憶體傳遞成功,App端就可以應用這塊記憶體進行圖形繪製了。
View繪製記憶體的使用
關於記憶體的使用,我們回到之前的Surface lock函式,記憶體經過反序列化,拿到記憶體地址後,會封裝一個ANativeWindow_Buffer返回給上層呼叫:
status_t Surface::lock(
ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds)
{
...
void* vaddr;
<!--lock獲取地址-->
status_t res = backBuffer->lock(
GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
newDirtyRegion.bounds(), &vaddr);
if (res != 0) {
err = INVALID_OPERATION;
} else {
mLockedBuffer = backBuffer;
outBuffer->width = backBuffer->width;
outBuffer->height = backBuffer->height;
outBuffer->stride = backBuffer->stride;
outBuffer->format = backBuffer->format;
<!--關鍵點 設定虛擬記憶體的地址-->
outBuffer->bits = vaddr;
}
}
return err;
}複製程式碼
ANativeWindow_Buffer的資料結構如下,其中bits欄位與虛擬記憶體地址對應,
typedef struct ANativeWindow_Buffer {
// The number of pixels that are show horizontally.
int32_t width;
// The number of pixels that are shown vertically.
int32_t height;
// The number of *pixels* that a line in the buffer takes in
// memory. This may be >= width.
int32_t stride;
// The format of the buffer. One of WINDOW_FORMAT_*
int32_t format;
// The actual bits.
void* bits;
// Do not touch.
uint32_t reserved[6];
} ANativeWindow_Buffer;複製程式碼
如何使用,看下Canvas的draw
static void nativeLockCanvas(JNIEnv* env, jclass clazz,
jint nativeObject, jobject canvasObj, jobject dirtyRectObj) {
sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
...
status_t err = surface->lock(&outBuffer, &dirtyBounds);
...
<!--SkBitmap-->
SkBitmap bitmap;
ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
<!--為SkBitmap填充配置-->
bitmap.setConfig(convertPixelFormat(outBuffer.format), outBuffer.width, outBuffer.height, bpr);
<!--為SkBitmap填充格式-->
if (outBuffer.format == PIXEL_FORMAT_RGBX_8888) {
bitmap.setIsOpaque(true);
}
<!--為SkBitmap填充記憶體-->
if (outBuffer.width > 0 && outBuffer.height > 0) {
bitmap.setPixels(outBuffer.bits);
} else {
// be safe with an empty bitmap.
bitmap.setPixels(NULL);
}
<!--建立native SkCanvas-->
SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (bitmap));
swapCanvasPtr(env, canvasObj, nativeCanvas);
...
}複製程式碼
對於2D繪圖,會用skia庫會填充Bitmap對應的共享記憶體,如此即可完成繪製,本文不深入Skia庫,有興趣自行分析。繪製完成後,通過unlock直接通知SurfaceFlinger服務進行圖層合成。
總結
Android View的繪製建立匿名共享記憶體的基礎上,APP端與SurfaceFlinger通過共享記憶體的方式避免了View檢視資料的拷貝,提高了系統同的檢視處理能力。
作者:看書的小蝸牛
原文連結:Android視窗管理分析(4):Android View繪製記憶體的分配、傳遞、使用
僅供參考,歡迎指正