Android系统的开机画面显示过程分析

本文深入剖析了Android系统启动过程中出现的三个开机画面显示过程。首先,第一个开机画面是Linux内核启动画面,依赖于帧缓冲区硬件设备fbmem。接着,第二个开机画面在init进程启动时显示,通过加载565rle图像文件到帧缓冲区实现。最后,第三个开机画面由bootanimation应用负责,通过SurfaceFlinger服务和BootAnimation类显示动态动画。整个过程涉及系统启动、帧缓冲区、控制台初始化和图形渲染等关键环节。
摘要由CSDN通过智能技术生成
               

        好几个月都没有更新过博客了,从今天开始,老罗将尝试对Android系统的UI实现作一个系统的分析,也算是落实之前所作出的承诺。提到Android系统的UI,我们最先接触到的便是系统在启动过程中所出现的画面了。Android系统在启动的过程中,最多可以出现三个画面,每一个画面都用来描述一个不同的启动阶段。本文将详细分析这三个开机画面的显示过程,以便可以开启我们对Android系统UI实现的分析之路。

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

        第一个开机画面是在内核启动的过程中出现的,它是一个静态的画面。第二个开机画面是在init进程启动的过程中出现的,它也是一个静态的画面。第三个开机画面是在系统服务启动的过程中出现的,它是一个动态的画面。无论是哪一个画面,它们都是在一个称为帧缓冲区(frame buffer,简称fb)的硬件设备上进行渲染的。接下来,我们就分别分析这三个画面是如何在fb上显示的。

        1. 第一个开机画面的显示过程

        Android系统的第一个开机画面其实是Linux内核的启动画面。在默认情况下,这个画面是不会出现的,除非我们在编译内核的时候,启用以下两个编译选项:

        CONFIG_FRAMEBUFFER_CONSOLE

        CONFIG_LOGO

        第一个编译选项表示内核支持帧缓冲区控制台,它对应的配置菜单项为:Device Drivers ---> Graphics support ---> Console display driver support ---> Framebuffer Console support。第二个编译选项表示内核在启动的过程中,需要显示LOGO,它对应的配置菜单项为:Device Drivers ---> Graphics support ---> Bootup logo。配置Android内核编译选项可以参考在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)一文。

        帧缓冲区硬件设备在内核中有一个对应的驱动程序模块fbmem,它实现在文件kernel/goldfish/drivers/video/fbmem.c中,它的初始化函数如下所示:

/** *      fbmem_init - init frame buffer subsystem * *      Initialize the frame buffer subsystem. * *      NOTE: This function is _only_ to be called by drivers/char/mem.c. * */static int __initfbmem_init(void){        proc_create("fb", 0, NULL, &fb_proc_fops);        if (register_chrdev(FB_MAJOR,"fb",&fb_fops))                printk("unable to get major %d for fb devs\n", FB_MAJOR);        fb_class = class_create(THIS_MODULE, "graphics");        if (IS_ERR(fb_class)) {                printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));                fb_class = NULL;        }        return 0;}
        这个函数首先调用函数proc_create在/proc目录下创建了一个fb文件,接着又调用函数register_chrdev来注册了一个名称为fb的字符设备,最后调用函数class_create在/sys/class目录下创建了一个graphics目录,用来描述内核的图形系统。

        模块fbmem除了会执行上述初始化工作之外,还会导出一个函数register_framebuffer:

EXPORT_SYMBOL(register_framebuffer);
        这个函数在内核的启动过程会被调用,以便用来执行注册帧缓冲区硬件设备的操作,它的实现如下所示:

/** *      register_framebuffer - registers a frame buffer device *      @fb_info: frame buffer info structure * *      Registers a frame buffer device @fb_info. * *      Returns negative errno on error, or zero for success. * */intregister_framebuffer(struct fb_info *fb_info){        int i;        struct fb_event event;        ......        if (num_registered_fb == FB_MAX)                return -ENXIO;        ......        num_registered_fb++;        for (i = 0 ; i < FB_MAX; i++)                if (!registered_fb[i])                        break;        fb_info->node = i;        mutex_init(&fb_info->lock);        fb_info->dev = device_create(fb_class, fb_info->device,                                     MKDEV(FB_MAJOR, i), NULL, "fb%d", i);        if (IS_ERR(fb_info->dev)) {                /* Not fatal */                printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));                fb_info->dev = NULL;        } else                fb_init_device(fb_info);        ......        registered_fb[i] = fb_info;        event.info = fb_info;        fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);        return 0;}
        由于系统中可能会存在多个帧缓冲区硬件设备,因此,fbmem模块使用一个数组registered_fb保存所有已经注册了的帧缓冲区硬件设备,其中,每一个帧缓冲区硬件都是使用一个结构体fb_info来描述的。

        我们知道,在Linux内核中,每一个硬件设备都有一个主设备号和一个从设备号,它们用来唯一地标识一个硬件设备。对于帧缓冲区硬件设备来说,它们的主设备号定义为FB_MAJOR(29),而从设备号则与注册的顺序有关,它们的值依次等于0,1,2等。

        每一个被注册的帧缓冲区硬件设备在/dev/graphics目录下都有一个对应的设备文件fb<minor>,其中,<minor>表示一个从设备号。例如,第一个被注册的帧缓冲区硬件设备在/dev/graphics目录下都有一个对应的设备文件fb0。用户空间的应用程序通过这个设备文件就可以操作帧缓冲区硬件设备了,即将要显示的画面渲染到帧缓冲区硬件设备上去。

        这个函数最后会通过调用函数fb_notifier_call_chain来通知帧缓冲区控制台,有一个新的帧缓冲区设备被注册到内核中来了。

        帧缓冲区控制台在内核中对应的驱动程序模块为fbcon,它实现在文件kernel/goldfish/drivers/video/console/fbcon.c中,它的初始化函数如下所示:

static struct notifier_block fbcon_event_notifier = {
            .notifier_call  = fbcon_event_notify,};......static int __init fb_console_init(void){        int i;        acquire_console_sem();        fb_register_client(&fbcon_event_notifier);        fbcon_device = device_create(fb_class, NULL, MKDEV(0, 0), NULL,                                     "fbcon");        if (IS_ERR(fbcon_device)) {                printk(KERN_WARNING "Unable to create device "                       "for fbcon; errno = %ld\n",                       PTR_ERR(fbcon_device));                fbcon_device = NULL;        } else                fbcon_init_device();        for (i = 0; i < MAX_NR_CONSOLES; i++)                con2fb_map[i] = -1;        release_console_sem();        fbcon_start();        return 0;}

        这个函数除了会调用函数device_create来创建一个类别为graphics的设备fbcon之外,还会调用函数fb_register_client来监听帧缓冲区硬件设备的注册事件,这是由函数fbcon_event_notify来实现的,如下所示:

static int fbcon_event_notify(struct notifier_block *self,                              unsigned long action, void *data){        struct fb_event *event = data;        struct fb_info *info = event->info;        ......        int ret = 0;        ......        switch(action) {        ......        case FB_EVENT_FB_REGISTERED:                ret = fbcon_fb_registered(info);                break;        ......        }done:        return ret;}

        帧缓冲区硬件设备的注册事件最终是由函数fbcon_fb_registered来处理的,它的实现如下所示:

static int fbcon_fb_registered(struct fb_info *info){        int ret = 0, i, idx = info->node;        fbcon_select_primary(info);        if (info_idx == -1) {                for (i = first_fb_vc; i <= last_fb_vc; i++) {                        if (con2fb_map_boot[i] == idx) {                                info_idx = idx;                                break;                        }                }                if (info_idx != -1)                        ret = fbcon_takeover(1);        } else {                for (i = first_fb_vc; i <= last_fb_vc; i++) {                        if (con2fb_map_boot[i] == idx)                                set_con2fb_map(i, idx, 0);                }        }        return ret;}
        函数fbcon_select_primary用来检查当前注册的帧缓冲区硬件设备是否是一个主帧缓冲区硬件设备。如果是的话,那么就将它的信息记录下来。这个函数只有当指定了CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY编译选项时才有效,否则的话,它是一个空函数。

        在Linux内核中,每一个控制台和每一个帧缓冲区硬件设备都有一个从0开始的编号,它们的初始对应关系保存在全局数组con2fb_map_boot中。控制台和帧缓冲区硬件设备的初始对应关系是可以通过设置内核启动参数来初始化的。在模块fbcon中,还有另外一个全局数组con2fb_map,也是用来映射控制台和帧缓冲区硬件设备的对应关系,不过它映射的是控制台和帧缓冲区硬件设备的实际对应关系。

        全局变量first_fb_vc和last_fb_vc是全局数组con2fb_map_boot和con2fb_map的索引值,用来指定系统当前可用的控制台编号范围,它们也是可以通过设置内核启动参数来初始化的。全局变量first_fb_vc的默认值等于0,而全局变量last_fb_vc的默认值等于MAX_NR_CONSOLES - 1。

        全局变量info_idx表示系统当前所使用的帧缓冲区硬件的编号。如果它的值等于-1,那么就说明系统当前还没有设置好当前所使用的帧缓冲区硬件设备。在这种情况下,函数fbcon_fb_registered就会在全局数组con2fb_map_boot中检查是否存在一个控制台编号与当前所注册的帧缓冲区硬件设备的编号idx对应。如果存在的话,那么就会将当前所注册的帧缓冲区硬件设备编号idx保存在全局变量info_idx中。接下来还会调用函数fbcon_takeover来初始化系统所使用的控制台。在调用函数fbcon_takeover的时候,传进去的参数为1,表示要显示第一个开机画面。

        如果全局变量info_idx的值不等于-1,那么函数fbcon_fb_registered同样会在全局数组con2fb_map_boot中检查是否存在一个控制台编号与当前所注册的帧缓冲区硬件设备的编号idx对应。如果存在的话,那么就会调用函数set_con2fb_map来调整当前所注册的帧缓冲区硬件设备与控制台的映射关系,即调整数组con2fb_map_boot和con2fb_map的值。

        为了简单起见,我们假设系统只有一个帧缓冲区硬件设备,这样当它被注册的时候,全局变量info_idx的值就会等于-1。当函数fbcon_fb_registered在全局数组con2fb_map_boot中发现有一个控制台的编号与这个帧缓冲区硬件设备的编号idx对应时,接下来就会调用函数fbcon_takeover来设置系统所使用的控制台。

        函数fbcon_takeover的实现如下所示:

static int fbcon_takeover(int show_logo){        int err, i;        if (!num_registered_fb)                return -ENODEV;        if (!show_logo)                logo_shown = FBCON_LOGO_DONTSHOW;        for (i = first_fb_vc; i <= last_fb_vc; i++)                con2fb_map[i] = info_idx;        err = take_over_console(&fb_con, first_fb_vc, last_fb_vc,                                fbcon_is_default);        if (err) {                for (i = first_fb_vc; i <= last_fb_vc; i++) {                        con2fb_map[i] = -1;                }                info_idx = -1;        }        return err;}
        全局变量logo_shown的初始值为FBCON_LOGO_CANSHOW,表示可以显示第一个开机画面。但是当参数show_logo的值等于0的时候,全局变量logo_shown的值会被重新设置为FBCON_LOGO_DONTSHOW,表示不可以显示第一个开机画面。

        中间的for循环将当前可用的控制台的编号都映射到当前正在注册的帧缓冲区硬件设备的编号info_idx中去,表示当前可用的控制台与缓冲区硬件设备的实际映射关系。

        函数take_over_console用来初始化系统当前所使用的控制台。如果它的返回值不等于0,那么就表示初始化失败。在这种情况下,最后的for循环就会将全局数组con2fb_map的各个元素的值设置为-1,表示系统当前可用的控制台还没有映射到实际的帧缓冲区硬件设备中去。这时候全局变量info_idx的值也会被重新设置为-1。

       调用函数take_over_console来初始化系统当前所使用的控制台,实际上就是向系统注册一系列回调函数,以便系统可以通过这些回调函数来操作当前所使用的控制台。这些回调函数使用结构体consw来描述。这里所注册的结构体consw是由全局变量fb_con来指定的,它的定义如下所示:

/* *  The console `switch' structure for the frame buffer based console */static const struct consw fb_con = {
            .owner                  = THIS_MODULE,        .con_startup            = fbcon_startup,        .con_init               = fbcon_init,        .con_deinit             = fbcon_deinit,        .con_clear              = fbcon_clear,        .con_putc               = fbcon_putc,        .con_putcs              = fbcon_putcs,        .con_cursor             = fbcon_cursor,        .con_scroll             = fbcon_scroll,        .con_bmove              = fbcon_bmove,        .con_switch             = fbcon_switch,        .con_blank              = fbcon_blank,        .con_font_set           = fbcon_set_font,        .con_font_get           = fbcon_get_font,        .con_font_default       = fbcon_set_def_font,        .con_font_copy          = fbcon_copy_font,        .con_set_palette        = fbcon_set_palette,        .con_scrolldelta        = fbcon_scrolldelta,        .con_set_origin         = fbcon_set_origin,        .con_invert_region      = fbcon_invert_region,        .con_screen_pos         = fbcon_screen_pos,        .con_getxy              = fbcon_getxy,        .con_resize             = fbcon_resize,};

       接下来我们主要关注函数fbcon_init和fbcon_switch的实现,系统就是通过它来初始化和切换控制台的。在初始化的过程中,会决定是否需要准备第一个开机画面的内容,而在切换控制台的过程中,会决定是否需要显示第一个开机画面的内容。

       函数fbcon_init的实现如下所示:

static void fbcon_init(struct vc_data *vc, int init){        struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];        struct fbcon_ops *ops;        struct vc_data **default_mode = vc->vc_display_fg;        struct vc_data *svc = *default_mode;        struct display *t, *p = &fb_display[vc->vc_num];        int logo = 1, new_rows, new_cols, rows, cols, charcnt = 256;        int cap;        if (info_idx == -1 || info == NULL)            return;        ......        if (vc != svc || logo_shown == FBCON_LOGO_DONTSHOW ||            (info->fix.type == FB_TYPE_TEXT))                logo = 0;        ......        if (logo)                fbcon_prepare_logo(vc, info, cols, rows, new_cols, new_rows);        ......}

        当前正在初始化的控制台使用参数vc来描述,而它的成员变量vc_num用来描述当前正在初始化的控制台的编号。通过这个编号之后,就可以在全局数组con2fb_map中找到对应的帧缓冲区硬件设备编号。有了帧缓冲区硬件设备编号之后,就可以在另外一个全局数组中registered_fb中找到一个fb_info结构体info,用来描述与当前正在初始化的控制台所对应的帧缓冲区硬件设备。

        参数vc的成员变量vc_display_fg用来描述系统当前可见的控制台,它是一个类型为vc_data**的指针。从这里就可以看出,最终得到的vc_data结构体svc就是用来描述系统当前可见的控制台的。

        变量logo开始的时候被设置为1,表示需要显示第一个开机画面,但是在以下三种情况下,它的值会被设置为0,表示不需要显示开机画面:

        A. 参数vc和变量svc指向的不是同一个vc_data结构体,即当前正在初始化的控制台不是系统当前可见的控制台。

        B. 全局变量logo_shown的值等于FBCON_LOGO_DONTSHOW,即系统不需要显示第一个开机画面。

        C. 与当前正在初始化的控制台所对应的帧缓冲区硬件设备的显示方式被设置为文本方式,即info->fix.type的值等于FB_TYPE_TEXT。

        当最终得到的变量logo的值等于1的时候,接下来就会调用函数fbcon_prepare_logo来准备要显示的第一个开机画面的内容。

        在函数fbcon_prepare_logo中,第一个开机画面的内容是通过调用函数fb_prepare_logo来准备的,如下所示:

static void fbcon_prepare_lo
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值