1. 程式人生 > >Android開機動畫的顯示(一)

Android開機動畫的顯示(一)

參考部落格

主要是跟著老羅的部落格,深入研究學習下Android的UI架構,徹底搞清楚繪製流程。鑑於老羅的部落格寫成時間有點久,分析的Andorid版本也有點低。現在從Android P的原始碼開始分析。

同樣的,從Android啟動顯示的畫面順序依次分析。

alt

Android系統在啟動的過程中,最多可以出現四個畫面,每一個畫面都用來描述一個不同的啟動階段。

  1. Linux uboot顯示(靜態)
  2. Linux 核心的啟動畫面(靜態)
  3. init 程序的啟動畫面(靜態)
  4. BootAnimation 啟動動畫(動態) uboot 其實就是 Bootloader ,用來啟動、部署作業系統以及操作flash等硬體驅動,並提供了一個供人操作的介面,如下: alt

進入bootloader的方式:adb reboot bootloader 或者通過各個廠商自定義的按鍵方式進入。關於 uboot ,可以觀看博文: http://www.bubuko.com/infodetail-2302475.html。 一般來說 前三個啟動畫面都會做成相同的。uboot 的開機logo會影響開機速度,這個暫不深入分析,直接從核心logo開始看。 眾所周知,Android也是linux派生出來的,因此Android的顯示機制用的是Linux一樣的機制:Framebuffer。所以上述的這些畫面都是在幀緩衝區(frame buffer,簡稱fb)上進行渲染的。幀緩衝裝置對應的裝置檔案為/dev/fb*,如果系統有多個顯示卡,Linux下還可支援多個幀緩衝裝置,最多可達32個,分別為/dev/fb0到/dev/fb31。

一、 Linux核心的啟動畫面

Android系統的核心開機畫面其實是Linux核心的啟動畫面,需要在編譯的時候開啟以下兩個編譯選項:

  • CONFIG_FRAMEBUFFER_CONSOLE :核心支援幀緩衝區控制檯
  • CONFIG_LOGO :核心在啟動的過程中,需要顯示LOGO

1.1 frame buffer初始化

幀緩衝區硬體裝置在核心中有一個對應的驅動程式模組fbmem,它實現在檔案android/kernel/msm-4.9/drivers/video/fbdev/core/fbmem.c中。

幀緩衝裝置在Linux中也可以看做是一個完整的子系統,大體由fbmem.c和xxxfb.c組成。向上給應用程式提供完善的裝置檔案操作介面(即對FrameBuffer裝置進行read、write、ioctl等操作),介面在Linux提供的fbmem.c檔案中實現;向下提供了硬體操作的介面,只是這些介面Linux並沒有提供實現,因為這要根據具體的LCD控制器硬體進行設定,即xxxfb.c部分的實現。

alt

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 __init
fbmem_init(void)
{
    int ret;
    // 在/proc目錄下建立了一個fb檔案
    if (!proc_create("fb", 0, NULL, &fb_proc_fops))
        return -ENOMEM;

    // 註冊一個名稱為 fb 的字元裝置
    ret = register_chrdev(FB_MAJOR, "fb", &fb_fops);
    if (ret) {
        printk("unable to get major %d for fb devs\n", FB_MAJOR);
        goto err_chrdev;
    }

    // 在/sys/class目錄下建立了一個graphics目錄,用來描述核心的圖形系統。
    fb_class = class_create(THIS_MODULE, "graphics");
    if (IS_ERR(fb_class)) {
        ret = PTR_ERR(fb_class);
        pr_warn("Unable to create fb class; errno = %d\n", ret);
        fb_class = NULL;
        goto err_class;
    }
    return 0;
err_class:
    unregister_chrdev(FB_MAJOR, "fb");
err_chrdev:
    remove_proc_entry("fb", NULL);
    return ret;
}

fbmem.c中除了做初始化,還會匯出一個重要的函式:register_framebuffer

1.2 fbmem.c:register_framebuffer(struct fb_info*)

在Linux核心中,每一個幀緩衝區硬體都是使用一個結構體fb_info來描述的,該結構體記錄了Framebuffer裝置的全部資訊,包括裝置的設定引數、狀態以及對底層硬體操作的函式指標。

在Linux核心中,每一個硬體裝置都有一個主裝置號和一個從裝置號,它們用來唯一地標識一個硬體裝置。對於幀緩衝區硬體裝置來說,它們的主裝置號定義為FB_MAJOR(29),而從裝置號則與註冊的順序有關,它們的值依次等於0,1,2等。


int
register_framebuffer(struct fb_info *fb_info)
{
    int ret;
    mutex_lock(&registration_lock);
    ret = do_register_framebuffer(fb_info);
    mutex_unlock(&registration_lock);
    return ret;
}
EXPORT_SYMBOL(register_framebuffer);



static int do_register_framebuffer(struct fb_info *fb_info)
{

    // 註冊 fb

    ......
    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);

        ......
    }

    ......

    // 由於frame buffer可能不止1個(最多32個),用陣列registered_fb儲存註冊的fb

    registered_fb[i] = fb_info;

    ......

    // 通知幀緩衝區控制檯,有新的 fb 被註冊進核心
    fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
    unlock_fb_info(fb_info);
    if (!lockless_register_fb)
        console_unlock();
    return 0;
}

1.2.1 fb_notify.c:fb_notifier_call_chain(unsigned long,void*)


/**
 * fb_notifier_call_chain - notify clients of fb_events
 */
int fb_notifier_call_chain(unsigned long val, void *v)
{
    return blocking_notifier_call_chain(&fb_notifier_list, val, v);
}
EXPORT_SYMBOL_GPL(fb_notifier_call_chain);

1.2.2 notifier.c:blocking_notifier_call_chain(struct blocking_notifier_head*,unsigned long,void*)


int blocking_notifier_call_chain(struct blocking_notifier_head *nh,
        unsigned long val, void *v)
{
    return __blocking_notifier_call_chain(nh, val, v, -1, NULL);
}
EXPORT_SYMBOL_GPL(blocking_notifier_call_chain);



int __blocking_notifier_call_chain(struct blocking_notifier_head *nh,
                   unsigned long val, void *v,
                   int nr_to_call, int *nr_calls)
{
    int ret = NOTIFY_DONE;
    if (rcu_access_pointer(nh->head)) {
        down_read(&nh->rwsem);
        ret = notifier_call_chain(&nh->head, val, v, nr_to_call,
                    nr_calls);
        up_read(&nh->rwsem);
    }
    return ret;
}
EXPORT_SYMBOL_GPL(__blocking_notifier_call_chain);



static int notifier_call_chain(struct notifier_block **nl,
                   unsigned long val, void *v,
                   int nr_to_call, int *nr_calls)
{
    int ret = NOTIFY_DONE;
    struct notifier_block *nb, *next_nb;
    nb = rcu_dereference_raw(*nl);
    while (nb && nr_to_call) {
        next_nb = rcu_dereference_raw(nb->next);
#ifdef CONFIG_DEBUG_NOTIFIERS
        if (unlikely(!func_ptr_is_kernel_text(nb->notifier_call))) {
            WARN(1, "Invalid notifier called!");
            nb = next_nb;
            continue;
        }
#endif
        ret = nb->notifier_call(nb, val, v);
        if (nr_calls)
            (*nr_calls)++;
        if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)
            break;
        nb = next_nb;
        nr_to_call--;
    }
    return ret;
}
NOKPROBE_SYMBOL(notifier_call_chain);

如上,最終呼叫到 notifier_block->notifier_call(notifier_block*,unsigned long,void*),我們看一下這個結構體的構造:


struct notifier_block;
typedef    int (*notifier_fn_t)(struct notifier_block *nb,
            unsigned long action, void *data);
struct notifier_block {
    notifier_fn_t notifier_call;
    struct notifier_block __rcu *next;
    int priority;
};

這裡設計Linux核心的RCU同步機制,不太瞭解。不過每一個被註冊的幀緩衝區硬體裝置在/dev/graphics目錄下都有一個對應的裝置檔案fb%d,其中%d表示一個從裝置號。例如,第一個被註冊的幀緩衝區硬體裝置在/dev/graphics目錄下都有一個對應的裝置檔案fb0。使用者空間的應用程式通過這個裝置檔案就可以操作幀緩衝區硬體裝置了,即將要顯示的畫面渲染到幀緩衝區硬體裝置上去。

這裡最後會通知幀緩衝區控制檯,有一個新的幀緩衝區裝置被註冊到核心中來了。回過頭看看幀緩衝區控制檯的實現。

1.3 幀緩衝區控制檯 fbcon.c

幀緩衝區控制檯在核心中對應的驅動程式模組為fbcon.c,其實現位於檔案:android/kernel/msm-4.9/drivers/video/console/fbcon.c

1.3.1 初始化


static struct notifier_block fbcon_event_notifier = {
    .notifier_call    = fbcon_event_notify,
};

static int __init fb_console_init(void)
{
    int i;
    console_lock();

    // 監聽幀緩衝區硬體裝置的註冊事件,其實現是 fbcon_event_notify
    fb_register_client(&fbcon_event_notifier);

    // 建立一個類別為graphics的裝置fbcon
    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;
    console_unlock();
    fbcon_start();
    return 0;
}
fs_initcall(fb_console_init);

1.3.2 監聽幀緩衝硬體裝置的註冊事件


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;
    ......
    switch(action) {
    ......
    case FB_EVENT_FB_REGISTERED: //幀緩衝區硬體設備註冊監聽
        ret = fbcon_fb_registered(info);
        break;
    ......
    }
done:
    return ret;
}



// 監聽實現
static int fbcon_fb_registered(struct fb_info *info)
{
    int ret = 0, i, idx;
    idx = info->node;

    // 檢查當前註冊的幀緩衝區硬體裝置是否是一個主幀緩衝區硬體裝置

    // 如果是的話,那麼就將它的資訊記錄下來
    fbcon_select_primary(info);

    // 全域性變數info_idx表示系統當前所使用的幀緩衝區硬體的編號。

    // 如果它的值等於-1,那麼就說明系統當前還沒有設定好當前所使用的幀緩衝區硬體裝置。
    if (info_idx == -1) {
        for (i = first_fb_vc; i <= last_fb_vc; i++) {

            // 在全域性陣列con2fb_map_boot中檢查是否存在一個控制檯編號與當前所註冊的幀緩衝區硬體裝置的編號idx對應。
            if (con2fb_map_boot[i] == idx) {

                // 將當前所註冊的幀緩衝區硬體裝置編號idx儲存在全域性變數info_idx中
                info_idx = idx;
                break;
            }
        }

        // con2fb_map_boot中存在當前註冊的幀緩衝區硬體裝置編號
        if (info_idx != -1)

            // 設定系統所使用的控制檯

            // 這裡的引數1代表要顯示核心開機動畫
            ret = do_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;
}

顯然開機的時候,正常就會呼叫函式do_fbcon_takeover(1).

1.3.3 設定系統所使用的控制檯 do_fbcon_takeover(int)


static int do_fbcon_takeover(int show_logo)
{
    int err, i;
    if (!num_registered_fb)
        return -ENODEV;

    // 全域性變數logo_shown的初始值為FBCON_LOGO_CANSHOW,表示可以顯示核心開機畫面
    // 顯然當傳入的show_logo為0時,代表不顯示核心開機畫面
    if (!show_logo)

        logo_shown = FBCON_LOGO_DONTSHOW;

    // 將當前可用的控制檯的編號都對映到當前正在註冊的幀緩衝區硬體裝置的編號info_idx中

    // 去,表示當前可用的控制檯與緩衝區硬體裝置的實際對映關係。
    for (i = first_fb_vc; i <= last_fb_vc; i++)
        con2fb_map[i] = info_idx;
    //  初始化系統當前所使用的控制檯。如果它的返回值不等於0,那麼就表示初始化失敗。

    err = do_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;
    } else {
        fbcon_has_console_bind = 1;
    }
    return err;
}

1.3.4 do_take_over_console


int do_take_over_console(const struct consw *csw, int first, int last, int deflt)
{
    int err;
    err = do_register_con_driver(csw, first, last);
    if (err == -EBUSY)
        err = 0;
    if (!err)
        do_bind_con_driver(csw, first, last, deflt);
    return err;
}
EXPORT_SYMBOL_GPL(do_take_over_console);

實際上這裡就是向系統註冊一系列回撥函式,以便系統可以通過這些回撥函式來操作當前所使用的控制檯。這些回撥函式使用結構體consw來描述。

初始化系統控制檯時所註冊的結構體consw是由全域性變數fb_con來指定的,它的定義如下所示:


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_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,
    .con_debug_enter    = fbcon_debug_enter,
    .con_debug_leave    = fbcon_debug_leave,
};

這裡我們只關注初始化和切換控制檯兩個方法。

1.3.5 初始化系統控制檯 fbcon.c:fbcon_init(vc_data*,int)


// vc_data 用來描述正在初始化的控制檯

static void fbcon_init(struct vc_data *vc, int init)
{

    // 通過控制檯編號(vc_num)找到對應的幀緩衝區硬體裝置編號
    struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
    struct fbcon_ops *ops;

    // vc_display_fg 描述當前可見的控制檯
    struct vc_data **default_mode = vc->vc_display_fg;
    struct vc_data *svc = *default_mode;
    struct display *t, *p = &fb_display[vc->vc_num];

    // logo 為1代表需要顯示開機畫面,反之不顯示
    int logo = 1, new_rows, new_cols, rows, cols, charcnt = 256;
    ......

    // 以下三個條件滿足其一就不顯示開機畫面

    // 1. 初始化的控制檯非當前可見的控制檯
    // 2. logo_shown 為 FBCON_LOGO_DONTSHOW,即不顯示開機畫面

    // 3. 當前正在初始化的控制檯對應的幀緩衝區硬體裝置的顯示方式被設定為文字顯示(FB_TYPE_TEXT)

    if (vc != svc || logo_shown == FBCON_LOGO_DONTSHOW ||
        (info->fix.type == FB_TYPE_TEXT))
        logo = 0;

    ......
if (logo)

        // 1.3.5.1 準備開機logo
        fbcon_prepare_logo(vc, info, cols, rows, new_cols, new_rows);
    ......

}

1.3.5.1 準備開機畫面 fbcon.c:fbcon_prepare_logo(vc_data*,fb_info*,int,int,int,int)


static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info,
                   int cols, int rows, int new_cols, int new_rows)
{
    ......
    int logo_height;
    ......

    // 1.3.5.2 獲取logo內容並儲存至 fb_info中

    logo_height = fb_prepare_logo(info, ops->rotate);

    // 計算顯示logo所需的控制檯行數,DIV_ROUND_UP是向上取整的意思
    logo_lines = DIV_ROUND_UP(logo_height, vc->vc_font.height);
    ......

    // 若所需行數大於控制檯最大行數,則不顯示logo
    if (logo_lines > vc->vc_bottom) {
        logo_shown = FBCON_LOGO_CANSHOW;
        printk(KERN_INFO
               "fbcon_init: disable boot-logo (boot-logo bigger than screen).\n");
    } else if (logo_shown != FBCON_LOGO_DONTSHOW) {

        // 表示開機logo處於等待渲染的狀態
        logo_shown = FBCON_LOGO_DRAW;
        vc->vc_top = logo_lines;
    }
}

1.3.5.2 獲取logo內容 fbmem.c:fb_prepare_logo(fb_info*,int)


int fb_prepare_logo(struct fb_info *info, int rotate)
{

    // 獲取幀緩衝區硬體裝置的顏色深度depth
    int depth = fb_get_color_depth(&info->var, &info->fix);
    unsigned int yres;
    memset(&fb_logo, 0, sizeof(struct logo_data));
    ......

    // 尋找開機logo,並將其儲存在全域性變數fb_logo.logo中

    fb_logo.logo = fb_find_logo(depth);

    ......

    return fb_prepare_extra_logos(info, fb_logo.logo->