android gralloc 流程分析

来源:互联网 发布:hf线切割软件 编辑:程序博客网 时间:2024/05/16 12:46

Android 中 lcd 是一个帧缓冲设备,驱动程序通过处理器的 lcd 控制器将物理内存的一段区域设置为显存,如果向这段内存区域写入数据就会马上在 lcd 上显示出来。Android 在 HAL 中提供了gralloc 模块,封装了用户层对帧缓冲设备的所有操作接口,并通过 SurfaceFlinger 服务向应用提供显示支持。在启动过程中系统会加载 gralloc 模块,然后打开帧缓冲设备,获取设备的各种参数并完成 gralloc 模块的初始化。当应用程序需要把内容显示到 lcd 上时,需要通过 gralloc 模块申请一块图形缓冲区,然后将这块图形缓冲区映射到自己的地址空间并写入内容即可。当应用程序不再需要这块图形缓冲区时需要通过 gralloc 模块释放掉,然后解除对缓冲区的映射。

1、基础数据结构

gralloc 模块通过 struct private_module_t 来描述,该结构定义如下:

struct private_module_t {    gralloc_module_t base;    private_handle_t* framebuffer;  /* 指向图形缓冲区的句柄 */    uint32_t flags;                 /* 用来标志系统帧缓冲区是否支持双缓冲 */    uint32_t numBuffers;            /* 表示系统帧缓冲的个数 */    uint32_t bufferMask;            /* 记录系统帧缓冲的使用情况 */    pthread_mutex_t lock;           /* 保护结构体private_module_t的并行访问 */    buffer_handle_t currentBuffer;  /* 描述当前正在被渲染的图形缓冲区 */    int pmem_master;                /* pmem设备节点的描述符 */    void* pmem_master_base;         /* pmem的起始虚拟地址 */    struct fb_var_screeninfo info;  /* lcd的可变参数 */    struct fb_fix_screeninfo finfo; /* lcd的固定参数 */    float xdpi;                     /* x方向上每英寸的像素数量 */    float ydpi;                     /* y方向上每英寸的像素数量 */    float fps;                      /* lcd的刷新率 */        int orientation;                /* 显示方向 */    enum {        PRIV_USAGE_LOCKED_FOR_POST = 0x80000000  /* flag to indicate we'll post this buffer */    };};
该结构的成员记录了 gralloc 模块的各种参数,主要为模块自己使用,应用程序操作的图形缓冲区的数据结构是struct private_handle_t,定义如下:
#ifdef __cplusplusstruct private_handle_t : public native_handle {#elsestruct private_handle_t {    struct native_handle nativeHandle;  /* 用来描述一个本地句柄值 */#endif        enum {        PRIV_FLAGS_FRAMEBUFFER    = 0x00000001,        PRIV_FLAGS_USES_PMEM      = 0x00000002,        PRIV_FLAGS_USES_MMEM      = 0x00000004,        PRIV_FLAGS_NEEDS_FLUSH    = 0x00000008,    };    enum {        LOCK_STATE_WRITE     =   1<<31,        LOCK_STATE_MAPPED    =   1<<30,        LOCK_STATE_READ_MASK =   0x3FFFFFFF    };    /* 指向一个文件描述符,这个文件描述符要么指向帧缓冲区设备,要么指向一块匿名共享内存     * 取决于private_handle_t描述的图形缓冲区是在帧缓冲区分配的,还是在内存中分配的 */    int     fd;    /* 指向一个魔数,它的值由静态成员变量sMagic来指定,用来标识一个private_handle_t结构体 */    int     magic;    /* 用来描述一个图形缓冲区的标志,它的值要么等于0,要么等于PRIV_FLAGS_FRAMEBUFFER     * 当一个图形缓冲区的标志值等于PRIV_FLAGS_FRAMEBUFFER的时候,就表示它是在帧缓冲区中分配的 */    int     flags;    int     size;   /* 描述一个图形缓冲区的大小 */    int     offset; /* 描述一个图形缓冲区的偏移地址 */    int     phys;   /* 图形缓冲区或帧缓冲的起始物理地址 */    int     base;   /* 图形缓冲区或帧缓冲的起始虚拟地址 */    int     lockState;    int     writeOwner;    int     pid;    /* 描述一个图形缓冲区的创建者的PID */#ifdef __cplusplus    static const int sNumInts = 9;  /* 有9个整数变量 */    static const int sNumFds = 1;   /* 有1个文件描述符 */    static const int sMagic = 0x3141592;    private_handle_t(int fd, int size, int flags) :        fd(fd), magic(sMagic), flags(flags), size(size), offset(0),        phys(0), base(0), lockState(0), writeOwner(0), pid(getpid())    {        version = sizeof(native_handle);        numInts = sNumInts;        numFds = sNumFds;    }    ~private_handle_t() {        magic = 0;    }    bool usesPhysicallyContiguousMemory() {        return (flags & PRIV_FLAGS_USES_PMEM) != 0;    }    /* 用来验证一个native_handle_t指针是否指向了一个private_handle_t结构体 */    static int validate(const native_handle* h) {        const private_handle_t* hnd = (const private_handle_t*)h;        if (!h || h->version != sizeof(native_handle) ||                h->numInts != sNumInts || h->numFds != sNumFds ||                hnd->magic != sMagic)         {            LOGE("invalid gralloc handle (at %p)", h);            return -EINVAL;        }        return 0;    }    static private_handle_t* dynamicCast(const native_handle* in) {        if (validate(in) == 0) {            return (private_handle_t*) in;        }        return NULL;    }#endif};

图形缓冲区的操作接口由结构 struct gralloc_module_t 定义:

typedef struct gralloc_module_t {    struct hw_module_t common;    /* 注册一个图形缓冲区,这个指定的图形缓冲区使用一个buffer_handle_t句柄来描述 */    int (*registerBuffer)(struct gralloc_module_t const* module,            buffer_handle_t handle);    /* 注销一个图形缓冲区 */    int (*unregisterBuffer)(struct gralloc_module_t const* module,            buffer_handle_t handle);    /* 用来锁定一个图形缓冲区并将缓冲区映射到用户进程     * 在锁定一块图形缓冲区的时候,可以指定要锁定的图形绘冲区的位置以及大小     * 这是通过参数l、t、w和h来指定的,其中,参数l和t指定的是要访问的图形缓冲区的左上角位置     * 而参数w和h指定的是要访问的图形缓冲区的宽度和长度     * 锁定之后,就可以获得由参数参数l、t、w和h所圈定的一块缓冲区的起始地址,保存在输出参数vaddr中     * 另一方面,在访问完成一块图形缓冲区之后,需要解除这块图形缓冲区的锁定 */    int (*lock)(struct gralloc_module_t const* module,            buffer_handle_t handle, int usage,            int l, int t, int w, int h,            void** vaddr);    int (*unlock)(struct gralloc_module_t const* module,            buffer_handle_t handle);    int (*perform)(struct gralloc_module_t const* module,            int operation, ... );    /* reserved for future use */    void* reserved_proc[7];} gralloc_module_t;

gralloc 设备则用结构 struct alloc_device_t 来描述,其定义如下:

typedef struct alloc_device_t {    struct hw_device_t common;    /* 申请图形缓冲区的内存空间 */        int (*alloc)(struct alloc_device_t* dev,int w, int h, int format, int usage,buffer_handle_t* handle, int* stride);    /* 释放图形缓冲区的内存空间 */    int (*free)(struct alloc_device_t* dev,buffer_handle_t handle);} alloc_device_t;
帧缓冲设备则采用结构 struct framebuffer_device_t 描述:

typedef struct framebuffer_device_t {    struct hw_device_t common;    const uint32_t  flags;  /* 用来记录系统帧缓冲区的标志 */    const uint32_t  width;  /* lcd显示区域的像素点数 */    const uint32_t  height;    const int       stride; /* 描述设备显示屏的一行有多少个像素点 */    /* 描述系统帧缓冲区的像素格式,主要有HAL_PIXEL_FORMAT_RGBX_8888和HAL_PIXEL_FORMAT_RGB_565两种 */    const int       format;    const float     xdpi;    const float     ydpi;    const float     fps;              /* lcd刷新率 */    const int       minSwapInterval;  /* 交换两帧图像的最小间隔时间 */    const int       maxSwapInterval;  /* 交换两帧图像的最大间隔时间 */    int reserved[8];    /* 设置帧交换间隔 */    int (*setSwapInterval)(struct framebuffer_device_t* window,int interval);    /* 设置帧缓冲区的更新区域 */    int (*setUpdateRect)(struct framebuffer_device_t* window,int left, int top, int width, int height);    /* 用来将图形缓冲区buffer的内容渲染到帧缓冲区中去,即显示在设备的显示屏中去 */    int (*post)(struct framebuffer_device_t* dev, buffer_handle_t buffer);    /* 用来通知fb设备device,图形缓冲区的组合工作已经完成 */    int (*compositionComplete)(struct framebuffer_device_t* dev);    void* reserved_proc[8];} framebuffer_device_t;
其中成员函数 post 对应用程序来说是最重要的接口,它将完成数据写入显存的工作。

2、gralloc 模块

HAL 中通过 hw_get_module 接口加载指定 id 的模块,并获得一个 hw_module_t 结构来打开设备,流程如下:

#define HAL_LIBRARY_PATH1 "/system/lib/hw"#define HAL_LIBRARY_PATH2 "/vendor/lib/hw"static const char *variant_keys[] = {    "ro.hardware",  /* This goes first so that it can pick up a different file on the emulator. */    "ro.product.board",    "ro.board.platform",    "ro.arch"};static const int HAL_VARIANT_KEYS_COUNT =    (sizeof(variant_keys)/sizeof(variant_keys[0]));int hw_get_module(const char *id, const struct hw_module_t **module) {    int status;    int i;    const struct hw_module_t *hmi = NULL;    char prop[PATH_MAX];    char path[PATH_MAX];    /*     * Here we rely on the fact that calling dlopen multiple times on     * the same .so will simply increment a refcount (and not load     * a new copy of the library).     * We also assume that dlopen() is thread-safe.     */    /* Loop through the configuration variants looking for a module */    for (i=0 ; i<HAL_VARIANT_KEYS_COUNT+1 ; i++) {        if (i < HAL_VARIANT_KEYS_COUNT) {            if (property_get(variant_keys[i], prop, NULL) == 0) {  /* 读取variant_keys数组指定的属性值 */                continue;            }            snprintf(path, sizeof(path), "%s/%s.%s.so",  /* 格式化模块名和路径,如:/system/lib/hw/gralloc.xxx.so */                    HAL_LIBRARY_PATH1, id, prop);            if (access(path, R_OK) == 0) break;            snprintf(path, sizeof(path), "%s/%s.%s.so",                     HAL_LIBRARY_PATH2, id, prop);            if (access(path, R_OK) == 0) break;        } else {            snprintf(path, sizeof(path), "%s/%s.default.so",                     HAL_LIBRARY_PATH1, id);            if (access(path, R_OK) == 0) break;        }    }    status = -ENOENT;    if (i < HAL_VARIANT_KEYS_COUNT+1) {        /* load the module, if this fails, we're doomed, and we should not try to load a different variant. */        status = load(id, path, module);                 /* 加载模块 */    }    return status;}

函数会在 /system/lib/hw 或者 /vendor/lib/hw 目录中去寻找gralloc.xxx.so 文件,如果找到了就调用load接口完成加载。最终会调用 gralloc_device_open完成 gralloc 设备成员的初始化:

int gralloc_device_open(const hw_module_t* module, const char* name,        hw_device_t** device){    int status = -EINVAL;    if (!strcmp(name, GRALLOC_HARDWARE_GPU0)) {        gralloc_context_t *dev;        dev = (gralloc_context_t*)malloc(sizeof(*dev));        /* initialize our state here */        memset(dev, 0, sizeof(*dev));        /* initialize the procs */        dev->device.common.tag = HARDWARE_DEVICE_TAG;        dev->device.common.version = 0;        dev->device.common.module = const_cast<hw_module_t*>(module);        dev->device.common.close = gralloc_close;        dev->device.alloc   = gralloc_alloc;        dev->device.free    = gralloc_free;        *device = &dev->device.common;        status = 0;    } else {        status = fb_device_open(module, name, device);    }    return status;}
在 android 系统中,所有的图形缓冲区都是由 SurfaceFlinger 服务分配的,在系统帧缓冲区中分配的图形缓冲区只在 SurfaceFlinger 服务中使用,而在内存中分配的图形缓冲区既可以在 SurfaceFlinger 服务中使用,也可以在其它的应用程序中使用,当应用程序请求 SurfaceFlinger 服务分配图形缓冲区时会发生两次映射:服务所在的进程首先会将申请到的缓冲区映射至服务的地址空间,然后应用程序使用这个图形缓冲时再将其映射至应用程序的地址空间。分配函数的实现如下:

static int gralloc_alloc_framebuffer(alloc_device_t* dev,size_t size, int usage, buffer_handle_t* pHandle){    private_module_t* m = reinterpret_cast<private_module_t*>(            dev->common.module);    pthread_mutex_lock(&m->lock);    int err = gralloc_alloc_framebuffer_locked(dev, size, usage, pHandle);    pthread_mutex_unlock(&m->lock);    return err;}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);        fd = ashmem_create_region("gralloc-buffer", size);    if (fd < 0) {        LOGE("couldn't create ashmem (%s)", strerror(-errno));        err = -errno;    }    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);        err = mapBuffer(module, hnd);        if (err == 0) {            *pHandle = hnd;        }    }        LOGE_IF(err, "gralloc failed err=%s", strerror(-err));        return err;}/*****************************************************************************/static int gralloc_alloc(alloc_device_t* dev,int w, int h, int format, int usage,                            buffer_handle_t* pHandle, int* pStride){    if (!pHandle || !pStride)        return -EINVAL;    size_t size, stride;    int align = 4;    int bpp = 0;    switch (format) {  /* 一个像素点占用的字节数 */        case HAL_PIXEL_FORMAT_RGBA_8888:        case HAL_PIXEL_FORMAT_RGBX_8888:        case HAL_PIXEL_FORMAT_BGRA_8888:            bpp = 4;            break;        case HAL_PIXEL_FORMAT_RGB_888:            bpp = 3;            break;        case HAL_PIXEL_FORMAT_RGB_565:        case HAL_PIXEL_FORMAT_RGBA_5551:        case HAL_PIXEL_FORMAT_RGBA_4444:            bpp = 2;            break;        default:            return -EINVAL;    }    size_t bpr = (w*bpp + (align-1)) & ~(align-1);    size = bpr * h;    stride = bpr / bpp;    int err;    if (usage & GRALLOC_USAGE_HW_FB) {        err = gralloc_alloc_framebuffer(dev, size, usage, pHandle);  /* 在系统帧缓冲中分配图形缓冲区 */    } else {        err = gralloc_alloc_buffer(dev, size, usage, pHandle);       /* 在内存中分配图形缓冲区 */    }    if (err < 0) {        return err;    }    *pStride = stride;    return 0;}
3、fb 模块

在 gralloc_device_open 中会根据传递的参数分别初始化两个设备,定义如下:

#define GRALLOC_HARDWARE_FB0 "fb0"#define GRALLOC_HARDWARE_GPU0 "gpu0"
如果参数不是 "gpu0" 则会调用 fb_device_open 初始化 fb 设备,主要流程和打开 gralloc 基本一致,在函数中会通过调用 mapFrameBuffer->mapFrameBufferLocked 获取帧缓存设备的参数并将其设备节点映射到用户空间,流程如下:

int mapFrameBufferLocked(struct private_module_t* module){    if (module->framebuffer) {        return 0;    }            char const * const device_template[] = {            "/dev/graphics/fb%u",            "/dev/fb%u",            0 };    int fd = -1;    int i=0;    char name[64];    while ((fd==-1) && device_template[i]) {        snprintf(name, 64, device_template[i], 0);        fd = open(name, O_RDWR, 0);        i++;    }    if (fd < 0)        return -errno;    struct fb_fix_screeninfo finfo;    if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1)  /* 获取帧缓冲的固定参数 */        return -errno;    struct fb_var_screeninfo info;    if (ioctl(fd, FBIOGET_VSCREENINFO, &info) == -1)   /* 获取帧缓冲的可变参数 */        return -errno;    info.reserved[0] = 0;    info.reserved[1] = 0;    info.reserved[2] = 0;    info.xoffset = 0;    info.yoffset = 0;    info.activate = FB_ACTIVATE_NOW;    info.bits_per_pixel = 32;    info.red.offset     = 16;    info.red.length     = 8;    info.green.offset   = 8;    info.green.length   = 8;    info.blue.offset    = 0;    info.blue.length    = 8;    info.transp.offset  = 24;    info.transp.length  = 8;    /*     * Request NUM_BUFFERS screens (at lest 2 for page flipping)     */    info.yres_virtual = info.yres * NUM_BUFFERS;  /* 帧缓冲总长度 */    uint32_t flags = PAGE_FLIP;  /* 支持缓冲交换 */    if (ioctl(fd, FBIOPAN_DISPLAY, &info) == -1) {        info.yres_virtual = info.yres;        flags &= ~PAGE_FLIP;        LOGW("FBIOPAN_DISPLAY failed, page flipping not supported");    }    if (info.yres_virtual < info.yres * 2) {        /* we need at least 2 for page-flipping */        info.yres_virtual = info.yres;        flags &= ~PAGE_FLIP;        LOGW("page flipping not supported (yres_virtual=%d, requested=%d)",                info.yres_virtual, info.yres*2);    }    if (ioctl(fd, FBIOGET_VSCREENINFO, &info) == -1)        return -errno;    int refreshRate = 1000000000000000LLU /    (            uint64_t( info.upper_margin + info.lower_margin + info.yres )            * ( info.left_margin  + info.right_margin + info.xres )            * info.pixclock    );  /* 计算lcd刷新率 */    if (refreshRate == 0) {        /* bleagh, bad info from the driver */        refreshRate = 60*1000;  // 60 Hz    }    if (int(info.width) <= 0 || int(info.height) <= 0) {        /* the driver doesn't return that information, default to 160 dpi */        info.width  = ((info.xres * 25.4f)/160.0f + 0.5f);        info.height = ((info.yres * 25.4f)/160.0f + 0.5f);    }    float xdpi = (info.xres * 25.4f) / info.width;    float ydpi = (info.yres * 25.4f) / info.height;    float fps  = refreshRate / 1000.0f;    LOGI(   "using (fd=%d)\n"            "id           = %s\n"            "xres         = %d px\n"            "yres         = %d px\n"            "xres_virtual = %d px\n"            "yres_virtual = %d px\n"            "bpp          = %d\n"            "r            = %2u:%u\n"            "g            = %2u:%u\n"            "b            = %2u:%u\n",            fd,            finfo.id,            info.xres,            info.yres,            info.xres_virtual,            info.yres_virtual,            info.bits_per_pixel,            info.red.offset, info.red.length,            info.green.offset, info.green.length,            info.blue.offset, info.blue.length    );    LOGI(   "width        = %d mm (%f dpi)\n"            "height       = %d mm (%f dpi)\n"            "refresh rate = %.2f Hz\n",            info.width,  xdpi,            info.height, ydpi,            fps    );    if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1)        return -errno;    if (finfo.smem_len <= 0)        return -errno;    module->flags = flags;    module->info = info;    module->finfo = finfo;    module->xdpi = xdpi;    module->ydpi = ydpi;    module->fps = fps;    /*     * map the framebuffer     */    int err;    size_t fbSize = roundUpToPageSize(finfo.line_length * info.yres_virtual);  /* 帧缓冲大小 */    module->framebuffer = new private_handle_t(dup(fd), fbSize,            private_handle_t::PRIV_FLAGS_USES_PMEM);    module->numBuffers = info.yres_virtual / info.yres;  /* 计算系统帧缓冲的个数 */    module->bufferMask = 0;    void* vaddr = mmap(0, fbSize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);    /* 将fb映射到用户空间 */    if (vaddr == MAP_FAILED) {        LOGE("Error mapping the framebuffer (%s)", strerror(errno));        return -errno;    }    module->framebuffer->base = intptr_t(vaddr);         /* 帧缓冲的起始虚拟地址 */    memset(vaddr, 0, fbSize);    return 0;}

fb 模块最重要的工作就是将应用程序指定的内容写入显存中,是通过函数 fb_post 完成的,流程如下:

/* 将图形缓冲区buffer的内容渲染到帧缓冲区中去 */static int fb_post(struct framebuffer_device_t* dev, buffer_handle_t buffer){unsigned int phys;void* virt;int pitch;int format;    /* 首先验证参数handle指向的一块图形缓冲区的确是由Gralloc模块分配的 */    if (private_handle_t::validate(buffer) < 0)        return -EINVAL;    fb_context_t* ctx = (fb_context_t*)dev;    private_handle_t const* hnd = reinterpret_cast<private_handle_t const*>(buffer);  /* 图形缓冲区 */    private_module_t* m = reinterpret_cast<private_module_t*>(dev->common.module);    /* 帧缓冲区 */    if (m->currentBuffer) {  /* 当前正在渲染的图形缓冲区 */        m->base.unlock(&m->base, m->currentBuffer);        m->currentBuffer = 0;    }    if (hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER) {  /* 如果图形缓冲区是在系统帧缓冲中分配的 */        m->base.lock(&m->base, buffer,  /* 锁定图像缓冲区 */                private_module_t::PRIV_USAGE_LOCKED_FOR_POST,                0, 0, m->info.xres, m->info.yres, NULL);        const size_t offset = hnd->base - m->framebuffer->base; /* 计算图形缓冲区与帧缓冲的偏移 */        /* 将作为参数的fb_var_screeninfo结构体的成员变量activate的值设置FB_ACTIVATE_VBL         * 表示要等到下一个垂直同步事件出现时,再将当前要渲染的图形缓冲区的内容绘制出来         * 这样做的目的是避免出现屏幕闪烁,即避免前后两个图形缓冲区的内容各有一部分同时出现屏幕中 */        m->info.activate = FB_ACTIVATE_VBL;        m->info.yoffset = offset / m->finfo.line_length;  /* 得到偏移的起始行 */        if (ioctl(m->framebuffer->fd, FBIOPAN_DISPLAY, &m->info) == -1) {  /* 刷新显示内容 */            LOGE("FBIOPAN_DISPLAY failed");            m->base.unlock(&m->base, buffer);             return -errno;        }        if (UNLIKELY(mDebugFps)) {            debugShowFPS();        }#ifdef SNAPSHOT        dumpfile((void*)hnd->base, m->info.xres, m->info.yres);  /* dump帧缓冲 */#endif        m->currentBuffer = buffer;  /* 设置当前图形缓冲区 */    } else {  /* 如果图形缓冲区是在内存中分配的 */        /* If we can't do the page_flip, just copy the buffer to the front          * FIXME: use copybit HAL instead of memcpy */        LOGD("copy bit.\n");        void* fb_vaddr;        void* buffer_vaddr;        /* 将帧缓冲整个锁定 */        m->base.lock(&m->base, m->framebuffer,                 GRALLOC_USAGE_SW_WRITE_RARELY,                 0, 0, m->info.xres, m->info.yres,                &fb_vaddr);        /* 将图形缓冲整个锁定 */        m->base.lock(&m->base, buffer,                 GRALLOC_USAGE_SW_READ_RARELY,                 0, 0, m->info.xres, m->info.yres,                &buffer_vaddr);        /* 将图形缓冲的内容拷贝到帧缓冲中即显示到lcd */        memcpy(fb_vaddr, buffer_vaddr, m->finfo.line_length * m->info.yres);        m->base.unlock(&m->base, buffer);   /* 解锁 */        m->base.unlock(&m->base, m->framebuffer);     }    return 0;}

原创粉丝点击