Android開機動畫

Android開機動畫

一 前言

標題3

標題4

Android的開機動畫分爲以下三個畫面:php

  • 內核啓動過程出現的靜態畫面
  • Init進程啓動過程出現的靜態畫面
  • 系統服務啓動過程當中出現的動態畫面 
    下文咱們就從源代碼中一步一步的分析幀緩衝區(frame buffer)的硬件設備上進行渲染的過程。

二 內核啓動過程出現的靜態畫面

Android開機的第一關畫面實際上是Linux內核的啓動畫面。在默認狀況下,這個畫面不會出現(咱們可使用make menuconfig查看)。 
若是咱們想要在啓動的過程當中看到這個畫面,能夠在編譯內核的時候,啓用如下兩個編譯選項:node

  • CONFIG_FRAMEBUFFER_CONSOLE 
    Device Drivers —> Graphics support —> Support for frame buffer devices
  • CONFIG_LOGO 
    Device Drivers —> Graphics support —> Bootup logo

初始化幀緩衝區

幀緩衝區硬件設備在kernel中對於的驅動程序模塊爲fbmem,它的初始化代碼以下: 
kernel/shamu/drivers/video/fbmem.clinux

/**
* 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)
{
/**
* 調用函數proc_create在/proc目錄下建立了一個fb文件
*/
proc_create("fb", 0, NULL, &fb_proc_fops);

/**
* 註冊fd字符設備
*/
if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
printk("unable to get major %d for fb devs\n", FB_MAJOR);

/**
* 在/sys/class目錄下建立了一個graphics目錄,主要是描述內核的圖形系統
*/
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;
}

從fbmem_init函數中咱們能夠知道,在函數初始化工程中主要作了以下工做:android

  • 調用函數proc_create(「fb」, 0, NULL, &fb_proc_fops);在/proc目錄下建立fb文件
  • 接着又調用函數register_chrdev(FB_MAJOR,」fb」,&fb_fops);註冊字符設備fb
  • 調用函數class_create(THIS_MODULE, 「graphics」);在/sys/class目錄下建立了一個graphics目錄,主要是描述內核的圖形系統

驅動模塊fbmem初始化工做完成後,就會導出一個函數register_framebuffer(此函數在內核的啓動過程會被調用)。數組

EXPORT_SYMBOL(register_framebuffer);

註冊幀緩衝區

kernel/shamu/drivers/video/fbmem.ccookie

/**
* 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.
*
*/
int
register_framebuffer(struct fb_info *fb_info)
{
int ret;

mutex_lock(&registration_lock);
ret = do_register_framebuffer(fb_info);/*每個幀緩衝區硬件都是使用一個結構體fb_info來描述*/
mutex_unlock(&registration_lock);

return ret;
}
static int do_register_framebuffer(struct fb_info *fb_info)
{
int i;
struct fb_event event;
struct fb_videomode mode;

if (fb_check_foreignness(fb_info))
return -ENOSYS;

do_remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id,
fb_is_primary_device(fb_info));

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;
atomic_set(&fb_info->count, 1);
mutex_init(&fb_info->lock);
mutex_init(&fb_info->mm_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);

if (fb_info->pixmap.addr == NULL) {
fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
if (fb_info->pixmap.addr) {
fb_info->pixmap.size = FBPIXMAPSIZE;
fb_info->pixmap.buf_align = 1;
fb_info->pixmap.scan_align = 1;
fb_info->pixmap.access_align = 32;
fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
}
}
fb_info->pixmap.offset = 0;

if (!fb_info->pixmap.blit_x)
fb_info->pixmap.blit_x = ~(u32)0;

if (!fb_info->pixmap.blit_y)
fb_info->pixmap.blit_y = ~(u32)0;

if (!fb_info->modelist.prev || !fb_info->modelist.next)
INIT_LIST_HEAD(&fb_info->modelist);

if (fb_info->skip_vt_switch)
pm_vt_switch_required(fb_info->dev, false);
else
pm_vt_switch_required(fb_info->dev, true);

fb_var_to_videomode(&mode, &fb_info->var);
fb_add_videomode(&mode, &fb_info->modelist);
registered_fb[i] = fb_info;/*數組registered_fb保存全部已經註冊幀緩衝區的硬件設備*/

event.info = fb_info;
if (!lock_fb_info(fb_info))
return -ENODEV;
console_lock();
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);/*通知幀緩衝區控制檯,有一個新的幀緩衝區設備被註冊到內核中了*/
console_unlock();
unlock_fb_info(fb_info);
return 0;
}

幀緩衝設備爲標準的字符型設備,在Linux中主設備號29,定義在/include/linux/major.h中的FB_MAJOR(29),次設備號定義幀緩衝的個數,最大容許有32個FrameBuffer,定義在/include/linux/fb.h中的FB_MAX,對應於文件系統下/dev /fb%d設備文件。用戶空間的應用程序經過這個設備文件就能夠操做幀緩衝區硬件設備了,即將要顯示的畫面渲染到幀緩衝區硬件設備上去。 
正由於在系統中可能會存在多個幀緩衝區硬件設備,因此fbmem模塊使用一個數組registered_fb保存全部已經註冊了的幀緩衝區硬件設備,其中,每個幀緩衝區硬件都是使用一個結構體fb_info來描述的。session

幀緩衝區控制檯

幀緩衝區控制檯在內核中的驅動程序模塊爲fbcon, 其初始化以下: 
kernel/shamu/drivers/video/console/fbcon.capp

static int __init fb_console_init(void)
{
int i;

console_lock();
/**
* 監聽幀緩衝區硬件設備的註冊事件
*/
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;
}

監聽幀緩衝區硬件設備的註冊事件, 函數fbcon_event_notify來實現:composer

static struct notifier_block fbcon_event_notifier = {
.notifier_call = 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;
struct fb_videomode *mode;
struct fb_con2fbmap *con2fb;
struct fb_blit_caps *caps;
int idx, ret = 0;

/*
* ignore all events except driver registration and deregistration
* if fbcon is not active
*/
if (fbcon_has_exited && !(action == FB_EVENT_FB_REGISTERED ||
action == FB_EVENT_FB_UNREGISTERED))
goto done;

switch(action) {
case FB_EVENT_SUSPEND:
fbcon_suspended(info);
break;
case FB_EVENT_RESUME:
fbcon_resumed(info);
break;
case FB_EVENT_MODE_CHANGE:
fbcon_modechanged(info);
break;
case FB_EVENT_MODE_CHANGE_ALL:
fbcon_set_all_vcs(info);
break;
case FB_EVENT_MODE_DELETE:
mode = event->data;
ret = fbcon_mode_deleted(info, mode);
break;
case FB_EVENT_FB_UNBIND:
idx = info->node;
ret = fbcon_fb_unbind(idx);
break;
case FB_EVENT_FB_REGISTERED:/*幀緩衝區硬件設備的註冊事件最終是函數fbcon_fb_registere完成我*/
ret = fbcon_fb_registered(info);
break;
case FB_EVENT_FB_UNREGISTERED:
ret = fbcon_fb_unregistered(info);
break;
case FB_EVENT_SET_CONSOLE_MAP:
/* called with console lock held */
con2fb = event->data;
ret = set_con2fb_map(con2fb->console - 1,
con2fb->framebuffer, 1);
break;
case FB_EVENT_GET_CONSOLE_MAP:
con2fb = event->data;
con2fb->framebuffer = con2fb_map[con2fb->console - 1];
break;
case FB_EVENT_BLANK:
fbcon_fb_blanked(info, *(int *)event->data);
break;
case FB_EVENT_NEW_MODELIST:
fbcon_new_modelist(info);
break;
case FB_EVENT_GET_REQ:
caps = event->data;
fbcon_get_requirement(info, caps);
break;
case FB_EVENT_REMAP_ALL_CONSOLE:
idx = info->node;
fbcon_remap_all(idx);
break;
}
done:
return ret;
}
/* called with console_lock held */
static int fbcon_fb_registered(struct fb_info *info)
{
int ret = 0, i, idx;

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 = 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;
}

在函數fbcon_fb_registered中主要完成了以下工做:dom

  • 調用函數fbcon_select_primary檢查當前註冊的幀緩衝區硬件設備是不是一個主幀緩衝區硬件設備 
    • 是—–>將信息保存
    • 否—–>
  • first_fb_vc和last_fb_vc(是數組con2fb_map_boot和con2fb_map的索引值): 用來指定系統當前可用的控制檯編號範圍,它們也是能夠經過設置內核啓動參數來初始化
  • info_idx: 表示系統當前所使用的幀緩衝區硬件的編號 
    • info_idx = -1—–>系統尚未設置好當前所使用的幀緩衝區硬件設備。 
      • 在數組con2fb_map_boot中檢查是否存在一個控制檯編號與當前所註冊的幀緩衝區硬件設備的編號idx對應
      • 調用函數fbcon_takeover來初始化系統所使用的控制檯(函數fbcon_takeove傳進去的參數爲1,表示要顯示第一個開機畫面。)
    • info_idx != -1 —–> 一樣會在數組con2fb_map_boot中檢查是否存在一個控制檯編號與當前所註冊的幀緩衝區硬件設備的編號idx對應。若存在,則調用函數set_con2fb_map來調整當前所註冊的幀緩衝區硬件設備與控制檯的映射關係,即調整數組con2fb_map_boot和con2fb_map的值。

假設info_idx = -1, 也就是系統只有一個幀緩衝區硬件設備,因此在數組con2fb_map_boot中發現有一個控制檯的編號與這個幀緩衝區硬件設備的編號idx對應,下來就會調用函數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;

/**
* 將當前可用的控制檯的編號都映射到當前正在註冊的幀緩衝區硬件設備的編號info_idx中去,表示當前可用的控制檯與緩衝區硬件設備的實際映射關係。
*/
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);
/**
* 若是它的返回值不等於0,那麼就表示初始化失敗。
* 因此將數組con2fb_map的各個元素的值設置爲-1,表示系統當前可用的控制檯尚未映射到實際的幀緩衝區硬件設備中。
* 這時候全局變量info_idx的值也會被從新設置爲-1。
*/
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;
}
  • logo_shown是一個全局變量,它的初始值爲FBCON_LOGO_CANSHOW,表示能夠顯示第一個開機畫面。
  • 若是傳進來的show_logo的值爲0,全局變量logo_shown的值會被從新設置爲FBCON_LOGO_DONTSHOW,表示不能夠顯示第一個開機畫面。
  • 把當前能夠用的控制檯編號映射到正在註冊的幀緩衝區硬件設備的編號info_idx中
  • take_over_console: 初始化系統當前所使用的控制檯
  • 函數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,
.con_debug_enter = fbcon_debug_enter,
.con_debug_leave = fbcon_debug_leave,
};

初始化控制檯

在這一過程當中咱們只需關注函數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, ret;

if (info_idx == -1 || info == NULL)
return;

cap = info->flags;

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

if (var_to_display(p, &info->var, info))
return;

if (!info->fbcon_par)
con2fb_acquire_newinfo(vc, info, vc->vc_num, -1);

/* If we are not the first console on this
fb, copy the font from that console */
t = &fb_display[fg_console];
if (!p->fontdata) {
if (t->fontdata) {
struct vc_data *fvc = vc_cons[fg_console].d;

vc->vc_font.data = (void *)(p->fontdata =
fvc->vc_font.data);
vc->vc_font.width = fvc->vc_font.width;
vc->vc_font.height = fvc->vc_font.height;
p->userfont = t->userfont;

if (p->userfont)
REFCOUNT(p->fontdata)++;
} else {
const struct font_desc *font = NULL;

if (!fontname[0] || !(font = find_font(fontname)))
font = get_default_font(info->var.xres,
info->var.yres,
info->pixmap.blit_x,
info->pixmap.blit_y);
vc->vc_font.width = font->width;
vc->vc_font.height = font->height;
vc->vc_font.data = (void *)(p->fontdata = font->data);
vc->vc_font.charcount = 256; /* FIXME Need to
support more fonts */
}
}

if (p->userfont)
charcnt = FNTCHARCNT(p->fontdata);

vc->vc_panic_force_write = !!(info->flags & FBINFO_CAN_FORCE_OUTPUT);
vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix)!=1);
vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;
if (charcnt == 256) {
vc->vc_hi_font_mask = 0;
} else {
vc->vc_hi_font_mask = 0x100;
if (vc->vc_can_do_color)
vc->vc_complement_mask <<= 1;
}

if (!*svc->vc_uni_pagedir_loc)
con_set_default_unimap(svc);
if (!*vc->vc_uni_pagedir_loc)
con_copy_unimap(vc, svc);

ops = info->fbcon_par;
p->con_rotate = initial_rotation;
set_blitting_type(vc, info);

cols = vc->vc_cols;
rows = vc->vc_rows;
new_cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres);
new_rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres);
new_cols /= vc->vc_font.width;
new_rows /= vc->vc_font.height;

/*
* We must always set the mode. The mode of the previous console
* driver could be in the same resolution but we are using different
* hardware so we have to initialize the hardware.
*
* We need to do it in fbcon_init() to prevent screen corruption.
*/
if (CON_IS_VISIBLE(vc) && vc->vc_mode == KD_TEXT) {
if (info->fbops->fb_set_par &&
!(ops->flags & FBCON_FLAGS_INIT)) {
ret = info->fbops->fb_set_par(info);

if (ret)
printk(KERN_ERR "fbcon_init: detected "
"unhandled fb_set_par error, "
"error code %d\n", ret);
}

ops->flags |= FBCON_FLAGS_INIT;
}

ops->graphics = 0;

if ((cap & FBINFO_HWACCEL_COPYAREA) &&
!(cap & FBINFO_HWACCEL_DISABLED))
p->scrollmode = SCROLL_MOVE;
else /* default to something safe */
p->scrollmode = SCROLL_REDRAW;

/*
* ++guenther: console.c:vc_allocate() relies on initializing
* vc_{cols,rows}, but we must not set those if we are only
* resizing the console.
*/
if (init) {
vc->vc_cols = new_cols;
vc->vc_rows = new_rows;
} else
vc_resize(vc, new_cols, new_rows);

if (logo)
fbcon_prepare_logo(vc, info, cols, rows, new_cols, new_rows);

if (vc == svc && softback_buf)
fbcon_update_softback(vc);

if (ops->rotate_font && ops->rotate_font(info, vc)) {
ops->rotate = FB_ROTATE_UR;
set_blitting_type(vc, info);
}

ops->p = &fb_display[fg_console];
}
  • vc: 當前正在初始化的控制檯 
    • vc_num: 當前正在初始化的控制檯的編號,能夠在數組con2fb_map中經過這個編號找到對應的幀緩衝區硬件設備編號。有幀緩衝區硬件設備編號後,就能夠在另一個數組registered_fb中找到一個fb_info結構體info,用來描述與當前正在初始化的控制檯所對應的幀緩衝區硬件設備。
    • vc_display_fg: 系統當前可見的控制檯,它是一個類型爲vc_data**的指針。從這裏就能夠看出,最終獲得的vc_data結構體svc就是用來描述系統當前可見的控制檯的。
  • 變量logo開始的時候被設置爲1,表示須要顯示第一個開機畫面。可是如下三種狀況它的值會被設置爲0,表示不須要顯示開機畫面: 
    • 參數vc和變量svc指向的不是同一個vc_data結構體,即當前正在初始化的控制檯不是系統當前可見的控制檯。
    • 全局變量logo_shown的值等於FBCON_LOGO_DONTSHOW,即系統不須要顯示第一個開機畫面。
    • 與當前正在初始化的控制檯所對應的幀緩衝區硬件設備的顯示方式被設置爲文本方式,即info->fix.type的值等於FB_TYPE_TEXT。

當最終獲得的變量logo的值等於1的時候,接下來就會調用函數fbcon_prepare_logo來準備要顯示的第一個開機畫面的內容。

#ifdef MODULE
static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info,
int cols, int rows, int new_cols, int new_rows)
{
logo_shown = FBCON_LOGO_DONTSHOW;
}
#else
static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info,
int cols, int rows, int new_cols, int new_rows)
{
/* Need to make room for the logo */
struct fbcon_ops *ops = info->fbcon_par;
int cnt, erase = vc->vc_video_erase_char, step;
unsigned short *save = NULL, *r, *q;
int logo_height;

if (info->flags & FBINFO_MODULE) {
logo_shown = FBCON_LOGO_DONTSHOW;
return;
}

/*
* remove underline attribute from erase character
* if black and white framebuffer.
*/
if (fb_get_color_depth(&info->var, &info->fix) == 1)
erase &= ~0x400;
logo_height = fb_prepare_logo(info, ops->rotate);
logo_lines = DIV_ROUND_UP(logo_height, vc->vc_font.height);
q = (unsigned short *) (vc->vc_origin +
vc->vc_size_row * rows);
step = logo_lines * cols;
for (r = q - logo_lines * cols; r < q; r++)
if (scr_readw(r) != vc->vc_video_erase_char)
break;
if (r != q && new_rows >= rows + logo_lines) {
save = kmalloc(logo_lines * new_cols * 2, GFP_KERNEL);
if (save) {
int i = cols < new_cols ? cols : new_cols;
scr_memsetw(save, erase, logo_lines * new_cols * 2);
r = q - step;
for (cnt = 0; cnt < logo_lines; cnt++, r += i)
scr_memcpyw(save + cnt * new_cols, r, 2 * i);
r = q;
}
}
if (r == q) {
/* We can scroll screen down */
r = q - step - cols;
for (cnt = rows - logo_lines; cnt > 0; cnt--) {
scr_memcpyw(r + step, r, vc->vc_size_row);
r -= cols;
}
if (!save) {
int lines;
if (vc->vc_y + logo_lines >= rows)
lines = rows - vc->vc_y - 1;
else
lines = logo_lines;
vc->vc_y += lines;
vc->vc_pos += lines * vc->vc_size_row;
}
}
scr_memsetw((unsigned short *) vc->vc_origin,
erase,
vc->vc_size_row * logo_lines);

if (CON_IS_VISIBLE(vc) && vc->vc_mode == KD_TEXT) {
fbcon_clear_margins(vc, 0);
update_screen(vc);
}

if (save) {
q = (unsigned short *) (vc->vc_origin +
vc->vc_size_row *
rows);
scr_memcpyw(q, save, logo_lines * new_cols * 2);
vc->vc_y += logo_lines;
vc->vc_pos += logo_lines * vc->vc_size_row;
kfree(save);
}

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_shown = FBCON_LOGO_DRAW;
vc->vc_top = logo_lines;
}
}
#endif /* MODULE */

在fbcon_prepare_logo函數中,第一個開機畫面的內容是經過調用fb_prepare_logo函數來準備的。 
從函數fb_prepare_logo返回來以後,若是要顯示的第一個開機畫面所佔用的控制檯行數小於等於參數vc所描述的控制檯的最大行數,而且全局變量logo_show的值不等於FBCON_LOGO_DONTSHOW,那麼就說明前面所提到的第一個開機畫面能夠顯示在控制檯中。這時候全局變量logo_show的值就會被設置爲FBCON_LOGO_DRAW,表示第一個開機畫面處於等待渲染的狀態。

kernel/shamu/drivers/video/fbmem.c

int fb_prepare_logo(struct fb_info *info, int rotate)
{
int depth = fb_get_color_depth(&info->var, &info->fix);
unsigned int yres;

memset(&fb_logo, 0, sizeof(struct logo_data));

if (info->flags & FBINFO_MISC_TILEBLITTING ||
info->flags & FBINFO_MODULE)
return 0;

if (info->fix.visual == FB_VISUAL_DIRECTCOLOR) {
depth = info->var.blue.length;
if (info->var.red.length < depth)
depth = info->var.red.length;
if (info->var.green.length < depth)
depth = info->var.green.length;
}

if (info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR && depth > 4) {
/* assume console colormap */
depth = 4;
}

/* Return if no suitable logo was found */
fb_logo.logo = fb_find_logo(depth);

if (!fb_logo.logo) {
return 0;
}

if (rotate == FB_ROTATE_UR || rotate == FB_ROTATE_UD)
yres = info->var.yres;
else
yres = info->var.xres;

if (fb_logo.logo->height > yres) {
fb_logo.logo = NULL;
return 0;
}

/* What depth we asked for might be different from what we get */
if (fb_logo.logo->type == LINUX_LOGO_CLUT224)
fb_logo.depth = 8;
else if (fb_logo.logo->type == LINUX_LOGO_VGA16)
fb_logo.depth = 4;
else
fb_logo.depth = 1;


if (fb_logo.depth > 4 && depth > 4) {
switch (info->fix.visual) {
case FB_VISUAL_TRUECOLOR:
fb_logo.needs_truepalette = 1;
break;
case FB_VISUAL_DIRECTCOLOR:
fb_logo.needs_directpalette = 1;
fb_logo.needs_cmapreset = 1;
break;
case FB_VISUAL_PSEUDOCOLOR:
fb_logo.needs_cmapreset = 1;
break;
}
}

return fb_prepare_extra_logos(info, fb_logo.logo->height, yres);
}
  • 獲得參數info所描述的幀緩衝區硬件設備的顏色深度depth
  • 調用函數fb_find_logo來得到要顯示的第一個開機畫面的內容,而且保存在全局變量fb_logo的變量logo中

kernel/shamu/drivers/video/logo/logo.c

#include <linux/linux_logo.h>
#include <linux/stddef.h>
#include <linux/module.h>

#ifdef CONFIG_M68K
#include <asm/setup.h>
#endif

#ifdef CONFIG_MIPS
#include <asm/bootinfo.h>
#endif

static bool nologo;
module_param(nologo, bool, 0);
MODULE_PARM_DESC(nologo, "Disables startup logo");

/* logo's are marked __initdata. Use __init_refok to tell
* modpost that it is intended that this function uses data
* marked __initdata.
*/
const struct linux_logo * __init_refok fb_find_logo(int depth)
{
const struct linux_logo *logo = NULL;

if (nologo)
return NULL;

if (depth >= 1) {
#ifdef CONFIG_LOGO_LINUX_MONO
/* Generic Linux logo */
logo = &logo_linux_mono;
#endif
#ifdef CONFIG_LOGO_SUPERH_MONO
/* SuperH Linux logo */
logo = &logo_superh_mono;
#endif
}

if (depth >= 4) {
#ifdef CONFIG_LOGO_LINUX_VGA16
/* Generic Linux logo */
logo = &logo_linux_vga16;
#endif
#ifdef CONFIG_LOGO_BLACKFIN_VGA16
/* Blackfin processor logo */
logo = &logo_blackfin_vga16;
#endif
#ifdef CONFIG_LOGO_SUPERH_VGA16
/* SuperH Linux logo */
logo = &logo_superh_vga16;
#endif
}

if (depth >= 8) {
#ifdef CONFIG_LOGO_LINUX_CLUT224
/* Generic Linux logo */
logo = &logo_linux_clut224;
#endif
#ifdef CONFIG_LOGO_BLACKFIN_CLUT224
/* Blackfin Linux logo */
logo = &logo_blackfin_clut224;
#endif
#ifdef CONFIG_LOGO_DEC_CLUT224
/* DEC Linux logo on MIPS/MIPS64 or ALPHA */
logo = &logo_dec_clut224;
#endif
#ifdef CONFIG_LOGO_MAC_CLUT224
/* Macintosh Linux logo on m68k */
if (MACH_IS_MAC)
logo = &logo_mac_clut224;
#endif
#ifdef CONFIG_LOGO_PARISC_CLUT224
/* PA-RISC Linux logo */
logo = &logo_parisc_clut224;
#endif
#ifdef CONFIG_LOGO_SGI_CLUT224
/* SGI Linux logo on MIPS/MIPS64 and VISWS */
logo = &logo_sgi_clut224;
#endif
#ifdef CONFIG_LOGO_SUN_CLUT224
/* Sun Linux logo */
logo = &logo_sun_clut224;
#endif
#ifdef CONFIG_LOGO_SUPERH_CLUT224
/* SuperH Linux logo */
logo = &logo_superh_clut224;
#endif
#ifdef CONFIG_LOGO_M32R_CLUT224
/* M32R Linux logo */
logo = &logo_m32r_clut224;
#endif
}
return logo;
}
EXPORT_SYMBOL_GPL(fb_find_logo);
  • 聲明一系列linux_logo結構體變量,用來保存kernel/shamu/drivers/video/logo目錄下的一系列ppm或者pbm文件的內容。這些ppm或者pbm文件都是用來描述第一個開機畫面的。
  • 全局變量nologo是一個類型爲布爾變量的模塊參數,它的默認值等於0,表示要顯示第一個開機畫面。在這種狀況下,函數fb_find_logo就會根據參數depth的值以及不一樣的編譯選項來選擇第一個開機畫面的內容,而且保存在變量logo中返回給調用者。

完成這個過程以後,第一個開機畫面的內容就保存在模塊fbmem的全局變量fb_logo的變量logo中。這時候控制檯的初始化過程也就結束了,接下來系統就會執行切換控制檯的操做。前面提到,當系統執行切換控制檯的操做的時候,模塊fbcon中的函數fbcon_switch就會被調用。在調用的過程當中,就會執行顯示第一個開機畫面的操做。

顯示第一個開機界面

kernel/shamu/drivers/video/console/fbcon.c 
顯示第一個開機畫面的過程以下:

``` cppstatic int fbcon_switch(struct vc_data *vc) 

struct fb_info *info, *old_info = NULL; 
struct fbcon_ops *ops; 
struct display *p = &fb_display[vc->vc_num]; 
struct fb_var_screeninfo var; 
int i, ret, prev_console, charcnt = 256;

info = registered_fb[con2fb_map[vc->vc_num]];
ops = info->fbcon_par;

if (softback_top) {
if (softback_lines)
fbcon_set_origin(vc);
softback_top = softback_curr = softback_in = softback_buf;
softback_lines = 0;
fbcon_update_softback(vc);
}

if (logo_shown >= 0) {
struct vc_data *conp2 = vc_cons[logo_shown].d;

if (conp2->vc_top == logo_lines
&& conp2->vc_bottom == conp2->vc_rows)
conp2->vc_top = 0;
logo_shown = FBCON_LOGO_CANSHOW;
}

prev_console = ops->currcon;
if (prev_console != -1)
old_info = registered_fb[con2fb_map[prev_console]];
/*
* FIXME: If we have multiple fbdev's loaded, we need to
* update all info->currcon. Perhaps, we can place this
* in a centralized structure, but this might break some
* drivers.
*
* info->currcon = vc->vc_num;
*/
for (i = 0; i < FB_MAX; i++) {
if (registered_fb[i] != NULL && registered_fb[i]->fbcon_par) {
struct fbcon_ops *o = registered_fb[i]->fbcon_par;

o->currcon = vc->vc_num;
}
}
memset(&var, 0, sizeof(struct fb_var_screeninfo));
display_to_var(&var, p);
var.activate = FB_ACTIVATE_NOW;

/*
* make sure we don't unnecessarily trip the memcmp()
* in fb_set_var()
*/
info->var.activate = var.activate;
var.vmode |= info->var.vmode & ~FB_VMODE_MASK;
fb_set_var(info, &var);
ops->var = info->var;

if (old_info != NULL && (old_info != info ||
info->flags & FBINFO_MISC_ALWAYS_SETPAR)) {
if (info->fbops->fb_set_par) {
ret = info->fbops->fb_set_par(info);

if (ret)
printk(KERN_ERR "fbcon_switch: detected "
"unhandled fb_set_par error, "
"error code %d\n", ret);
}

if (old_info != info)
fbcon_del_cursor_timer(old_info);
}

if (fbcon_is_inactive(vc, info) ||
ops->blank_state != FB_BLANK_UNBLANK)
fbcon_del_cursor_timer(info);
else
fbcon_add_cursor_timer(info);

set_blitting_type(vc, info);
ops->cursor_reset = 1;

if (ops->rotate_font && ops->rotate_font(info, vc)) {
ops->rotate = FB_ROTATE_UR;
set_blitting_type(vc, info);
}

vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix)!=1);
vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;

if (p->userfont)
charcnt = FNTCHARCNT(vc->vc_font.data);

if (charcnt > 256)
vc->vc_complement_mask <<= 1;

updatescrollmode(p, info, vc);

switch (p->scrollmode) {
case SCROLL_WRAP_MOVE:
scrollback_phys_max = p->vrows - vc->vc_rows;
break;
case SCROLL_PAN_MOVE:
case SCROLL_PAN_REDRAW:
scrollback_phys_max = p->vrows - 2 * vc->vc_rows;
if (scrollback_phys_max < 0)
scrollback_phys_max = 0;
break;
default:
scrollback_phys_max = 0;
break;
}

scrollback_max = 0;
scrollback_current = 0;

if (!fbcon_is_inactive(vc, info)) {
ops->var.xoffset = ops->var.yoffset = p->yscroll = 0;
ops->update_start(info);
}

fbcon_set_palette(vc, color_table);
fbcon_clear_margins(vc, 0);

if (logo_shown == FBCON_LOGO_DRAW) {

logo_shown = fg_console;
/* This is protected above by initmem_freed */
fb_show_logo(info, ops->rotate);
update_region(vc,
vc->vc_origin + vc->vc_size_row * vc->vc_top,
vc->vc_size_row * (vc->vc_bottom -
vc->vc_top) / 2);
return 0;
}
return 1;

}

由於前文咱們假設logo_show==FBCON_LOGO_DRAW, 因此程序會走到fb_show_logo函數來顯示第一個開機畫面。在顯示以前,這個函數會將全局變量logo_shown的值設置爲fg_console,後者表示系統當前可見的控制檯的編號。

<font color=#0099ff >kernel/shamu/drivers/video/fbmem.c</font>




<div class="se-preview-section-delimiter"></div>

``` cpp
int fb_show_logo(struct fb_info *info, int rotate)
{
int y;

y = fb_show_logo_line(info, rotate, fb_logo.logo, 0,
num_online_cpus());
y = fb_show_extra_logos(info, y, rotate);

return y;
}

調用fb_show_logo_line函數來進一步執行渲染第一個開機畫面的操做。

static int fb_show_logo_line(struct fb_info *info, int rotate,
const struct linux_logo *logo, int y,
unsigned int n)
{
u32 *palette = NULL, *saved_pseudo_palette = NULL;
unsigned char *logo_new = NULL, *logo_rotate = NULL;
struct fb_image image;

/* Return if the frame buffer is not mapped or suspended */
if (logo == NULL || info->state != FBINFO_STATE_RUNNING ||
info->flags & FBINFO_MODULE)
return 0;

image.depth = 8;
image.data = logo->data;

if (fb_logo.needs_cmapreset)
fb_set_logocmap(info, logo);

if (fb_logo.needs_truepalette ||
fb_logo.needs_directpalette) {
palette = kmalloc(256 * 4, GFP_KERNEL);
if (palette == NULL)
return 0;

if (fb_logo.needs_truepalette)
fb_set_logo_truepalette(info, logo, palette);
else
fb_set_logo_directpalette(info, logo, palette);

saved_pseudo_palette = info->pseudo_palette;
info->pseudo_palette = palette;
}

if (fb_logo.depth <= 4) {
logo_new = kmalloc(logo->width * logo->height, GFP_KERNEL);
if (logo_new == NULL) {
kfree(palette);
if (saved_pseudo_palette)
info->pseudo_palette = saved_pseudo_palette;
return 0;
}
image.data = logo_new;
fb_set_logo(info, logo, logo_new, fb_logo.depth);
}

image.dx = 0;
image.dy = y;
image.width = logo->width;
image.height = logo->height;

if (rotate) {
logo_rotate = kmalloc(logo->width *
logo->height, GFP_KERNEL);
if (logo_rotate)
fb_rotate_logo(info, logo_rotate, &image, rotate);
}

fb_do_show_logo(info, &image, rotate, n);

kfree(palette);
if (saved_pseudo_palette != NULL)
info->pseudo_palette = saved_pseudo_palette;
kfree(logo_new);
kfree(logo_rotate);
return logo->height;
}

參數logo指向了前面所準備的第一個開機畫面的內容。這個函數首先根據參數logo的內容來構造一個fb_image結構體image,用來描述最終要顯示的第一個開機畫面。最後就調用函數fb_do_show_logo來真正執行渲染第一個開機畫面的操做。

static void fb_do_show_logo(struct fb_info *info, struct fb_image *image,
int rotate, unsigned int num)
{
unsigned int x;

if (rotate == FB_ROTATE_UR) {
for (x = 0;
x < num && image->dx + image->width <= info->var.xres;
x++) {
info->fbops->fb_imageblit(info, image);
image->dx += image->width + 8;
}
} else if (rotate == FB_ROTATE_UD) {
for (x = 0; x < num && image->dx >= 0; x++) {
info->fbops->fb_imageblit(info, image);
image->dx -= image->width + 8;
}
} else if (rotate == FB_ROTATE_CW) {
for (x = 0;
x < num && image->dy + image->height <= info->var.yres;
x++) {
info->fbops->fb_imageblit(info, image);
image->dy += image->height + 8;
}
} else if (rotate == FB_ROTATE_CCW) {
for (x = 0; x < num && image->dy >= 0; x++) {
info->fbops->fb_imageblit(info, image);
image->dy -= image->height + 8;
}
}
}
  • rotate:描述屏幕的當前旋轉方向。屏幕旋轉方向不一樣,第一個開機畫面的渲染方式也有所不一樣。例如,當屏幕上下顛倒時(FB_ROTATE_UD),第一個開機畫面的左右順序就恰好調換過來,這時候就須要從右到左來渲染。其它三個方向FB_ROTATE_UR、FB_ROTATE_CW和FB_ROTATE_CCW分別表示沒有旋轉、順時針旋轉90度和逆時針旋轉90度。
  • info: 描述要渲染的幀緩衝區硬件設備,它的變量fbops指向了一系列回調函數,用來操做幀緩衝區硬件設備,其中,回調函數fb_imageblit就是用來在指定的幀緩衝區硬件設備渲染指定的圖像的。

三 Init進程啓動過程出現的靜態畫面

init進程的入口函數main以下所示: 
system/core/init/init.cpp

int main(int argc, char** argv) {
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}

if (!strcmp(basename(argv[0]), "watchdogd")) {
return watchdogd_main(argc, argv);
}

// Clear the umask.
umask(0);

add_environment("PATH", _PATH_DEFPATH);

bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);

// Get the basic filesystem setup we need put together in the initramdisk
// on / and then we'll let the rc file figure out the rest.
if (is_first_stage) {
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);
}

// We must have some place other than / to create the device nodes for
// kmsg and null, otherwise we won't be able to remount / read-only
// later on. Now that tmpfs is mounted on /dev, we can actually talk
// to the outside world.
open_devnull_stdio();
klog_init();
klog_set_level(KLOG_NOTICE_LEVEL);

NOTICE("init%s started!\n", is_first_stage ? "" : " second stage");

if (!is_first_stage) {
// Indicate that booting is in progress to background fw loaders, etc.
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));

property_init();

// If arguments are passed both on the command line and in DT,
// properties set in DT always have priority over the command-line ones.
process_kernel_dt();
process_kernel_cmdline();

// Propogate the kernel variables to internal variables
// used by init as well as the current required properties.
export_kernel_boot_props();
}

// Set up SELinux, including loading the SELinux policy if we're in the kernel domain.
selinux_initialize(is_first_stage);

// If we're in the kernel domain, re-exec init to transition to the init domain now
// that the SELinux policy has been loaded.
if (is_first_stage) {
if (restorecon("/init") == -1) {
ERROR("restorecon failed: %s\n", strerror(errno));
security_failure();
}
char* path = argv[0];
char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };
if (execv(path, args) == -1) {
ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
security_failure();
}
}

// These directories were necessarily created before initial policy load
// and therefore need their security context restored to the proper value.
// This must happen before /dev is populated by ueventd.
INFO("Running restorecon...\n");
restorecon("/dev");
restorecon("/dev/socket");
restorecon("/dev/__properties__");
restorecon_recursive("/sys");

epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (epoll_fd == -1) {
ERROR("epoll_create1 failed: %s\n", strerror(errno));
exit(1);
}

signal_handler_init();

property_load_boot_defaults();
start_property_service();

init_parse_config_file("/init.rc");

action_for_each_trigger("early-init", action_add_queue_tail);

// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
// ... so that we can start queuing up actions that require stuff from /dev.
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init");

// Trigger all the boot actions to get us started.
action_for_each_trigger("init", action_add_queue_tail);

// Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
// wasn't ready immediately after wait_for_coldboot_done
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");

// Don't mount filesystems or start core system services in charger mode.
char bootmode[PROP_VALUE_MAX];
if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) {
action_for_each_trigger("charger", action_add_queue_tail);
} else {
action_for_each_trigger("late-init", action_add_queue_tail);
}

// Run all property triggers based on current state of the properties.
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");

while (true) {
if (!waiting_for_exec) {
execute_one_command();
restart_processes();
}

int timeout = -1;
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}

if (!action_queue_empty() || cur_action) {
timeout = 0;
}

bootchart_sample(&timeout);

epoll_event ev;
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
if (nr == -1) {
ERROR("epoll_wait failed: %s\n", strerror(errno));
} else if (nr == 1) {
((void (*)()) ev.data.ptr)();
}
}

return 0;
}
  • 判斷參數argv[0]的值是否等於「ueventd」,即當前正在啓動的進程名稱是否等於「ueventd」
  • 調用函數queue_builtin_action來向init進程中的一個待執行action隊列增長了一個名稱等於「console_init」的action。這個action對應的執行函數爲console_init_action,它就是用來顯示第二個開機畫面的。queue_builtin_action(console_init_action, 「console_init」);

在while (true)死循環中,完成以下工做:

  • 調用execute_one_command函數,檢查action_queue列表是否爲空 
    • 不爲空—–> init進程就會將保存在列表頭中的action移除,而且執行這個被移除的action。因爲前面咱們將一個名稱爲「console_init」的action添加到了action_queue列表中,所以,在這個無限循環中,這個action就會被執行,即函數console_init_action會被調用。
  • 調用restart_processes函數,檢查系統中是否有進程須要重啓。在啓動腳本/init.rc中,咱們能夠指定一個進程在退出以後會自動從新啓動。在這種狀況下,restart_processes函數就會檢查是否存在須要從新啓動的進程,若是存在的話,那麼就會將它從新啓動起來。
  • 處理系統屬性變化事件。當咱們調用函數property_set來改變一個系統屬性值時,系統就會經過一個socket(經過調用函數get_property_set_fd能夠得到它的文件描述符)來向init進程發送一個屬性值改變事件通知。init進程接收到這個屬性值改變事件以後,就會調用函數handle_property_set_fd來進行相應的處理。後面在分析第三個開機畫面的顯示過程時,咱們就會看到,SurfaceFlinger服務就是經過修改「ctl.start」和「ctl.stop」屬性值來啓動和中止第三個開機畫面的。
  • 處理一種稱爲「chorded keyboard」的鍵盤輸入事件。這種類型爲chorded keyboard」的鍵盤設備經過不一樣的銨鍵組合來描述不一樣的命令或者操做,它對應的設備文件爲/dev/keychord。咱們能夠經過調用函數get_keychord_fd來得到這個設備的文件描述符,以即可以監控它的輸入事件,而且調用函數handle_keychord來對這些輸入事件進行處理。
  • 回收殭屍進程。咱們知道,在Linux內核中,若是父進程不等待子進程結束就退出,那麼當子進程結束的時候,就會變成一個殭屍進程,從而佔用系統的資源。爲了回收這些殭屍進程,init進程會安裝一個SIGCHLD信號接收器。當那些父進程已經退出了的子進程退出的時候,內核就會發出一個SIGCHLD信號給init進程。init進程能夠經過一個socket(經過調用函數get_signal_fd能夠得到它的文件描述符)來將接收到的SIGCHLD信號讀取回來,而且調用函數handle_signal來對接收到的SIGCHLD信號進行處理,即回收那些已經變成了殭屍的子進程。

注意,因爲後面三個事件都是能夠經過文件描述符來描述的,所以,init進程的入口函數main使用poll機制來同時輪詢它們,以即可以提升效率。

system/core/init/init_parser.cpp

void queue_builtin_action(int (*func)(int nargs, char **args), const char *name)
{
action* act = (action*) calloc(1, sizeof(*act));
trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
cur_trigger->name = name;
list_init(&act->triggers);
list_add_tail(&act->triggers, &cur_trigger->nlist);
list_init(&act->commands);
list_init(&act->qlist);

command* cmd = (command*) calloc(1, sizeof(*cmd));
cmd->func = func;
cmd->args[0] = const_cast<char*>(name);
cmd->nargs = 1;
list_add_tail(&act->commands, &cmd->clist);

list_add_tail(&action_list, &act->alist);
action_add_queue_tail(act);
}

action_list列表用來保存從啓動腳本/init.rc解析獲得的一系列action,以及一系列內建的action。當這些action須要執行的時候,它們就會被添加到action_queue列表中去,以便init進程能夠執行它們

console_init_action

static int console_init_action(int nargs, char **args)
{
char console[PROP_VALUE_MAX];
if (property_get("ro.boot.console", console) > 0) {
snprintf(console_name, sizeof(console_name), "/dev/%s", console);
}

int fd = open(console_name, O_RDWR | O_CLOEXEC);
if (fd >= 0)
have_console = 1;
close(fd);

fd = open("/dev/tty0", O_WRONLY | O_CLOEXEC);
if (fd >= 0) {
const char *msg;
msg = "\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n" // console is 40 cols x 30 lines
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
" A N D R O I D ";
write(fd, msg, strlen(msg));
close(fd);
}

return 0;
}
  • 初始化控制檯。init進程在啓動的時候,會解析內核的啓動參數(保存在文件/proc/cmdline中)。若是發現內核的啓動參數中包含有了一個名稱爲「androidboot.console」的屬性,那麼就會將這個屬性的值保存在字符數組console中。這樣咱們就能夠經過設備文件/dev/來訪問系統的控制檯。若是內核的啓動參數沒有包含名稱爲「androidboot.console」的屬性,那麼默認就經過設備文件/dev/console來訪問系統的控制檯。若是可以成功地打開設備文件/dev/或者/dev/console,那麼就說明系統支持訪問控制檯,所以,全局變量have_console的就會被設置爲1。
  • 顯示第二個開機畫面。顯示第二個開機畫面是經過調用函數load_565rle_image來實現的。在調用函數load_565rle_image的時候,指定的開機畫面文件爲INIT_IMAGE_FILE。

四 系統服務啓動過程當中出現的動態畫面

第三個開機畫面是由應用程序bootanimation來負責顯示的。應用程序bootanimation在啓動腳本init.rc中被配置成了一個服務,以下所示: 
system/core/rootdir/init.rc

service bootanim /system/bin/bootanimation
class core
user graphics
group graphics audio
disabled
oneshot

應用程序bootanimation的用戶和用戶組名稱分別被設置爲graphics。用來啓動應用程序bootanimation的服務是disable的,即init進程在啓動的時候,不會主動將應用程序bootanimation啓動起來。當SurfaceFlinger服務啓動的時候,它會經過修改系統屬性ctl.start的值來通知init進程啓動應用程序bootanimation,以即可以顯示第三個開機畫面,而當System進程將系統中的關鍵服務都啓動起來以後,ActivityManagerService服務就會通知SurfaceFlinger服務來修改系統屬性ctl.stop的值,以即可以通知init進程中止執行應用程序bootanimation,即中止顯示第三個開機畫面。下文咱們就分別分析第三個開機畫面的顯示過程和中止過程。

Zygote —–> SystemServer —–> SurfaceFlinger 
frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

void SurfaceFlinger::init() {
ALOGI( "SurfaceFlinger's main thread ready to run. "
"Initializing graphics H/W...");

Mutex::Autolock _l(mStateLock);

// initialize EGL for the default display
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(mEGLDisplay, NULL, NULL);

// start the EventThread
sp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync,
vsyncPhaseOffsetNs, true, "app");
mEventThread = new EventThread(vsyncSrc);
sp<VSyncSource> sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync,
sfVsyncPhaseOffsetNs, true, "sf");
mSFEventThread = new EventThread(sfVsyncSrc);
mEventQueue.setEventThread(mSFEventThread);

// Initialize the H/W composer object. There may or may not be an
// actual hardware composer underneath.
mHwc = new HWComposer(this,
*static_cast<HWComposer::EventHandler *>(this));

// get a RenderEngine for the given display / config (can't fail)
mRenderEngine = RenderEngine::create(mEGLDisplay, mHwc->getVisualID());

// retrieve the EGL context that was selected/created
mEGLContext = mRenderEngine->getEGLContext();

LOG_ALWAYS_FATAL_IF(mEGLContext == EGL_NO_CONTEXT,
"couldn't create EGLContext");

// initialize our non-virtual displays
for (size_t i=0 ; i<DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES ; i++) {
DisplayDevice::DisplayType type((DisplayDevice::DisplayType)i);
// set-up the displays that are already connected
if (mHwc->isConnected(i) || type==DisplayDevice::DISPLAY_PRIMARY) {
// All non-virtual displays are currently considered secure.
bool isSecure = true;
createBuiltinDisplayLocked(type);
wp<IBinder> token = mBuiltinDisplays[i];

sp<IGraphicBufferProducer> producer;
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&producer, &consumer,
new GraphicBufferAlloc());

sp<FramebufferSurface> fbs = new FramebufferSurface(*mHwc, i,
consumer);
int32_t hwcId = allocateHwcDisplayId(type);
sp<DisplayDevice> hw = new DisplayDevice(this,
type, hwcId, mHwc->getFormat(hwcId), isSecure, token,
fbs, producer,
mRenderEngine->getEGLConfig());
if (i > DisplayDevice::DISPLAY_PRIMARY) {
// FIXME: currently we don't get blank/unblank requests
// for displays other than the main display, so we always
// assume a connected display is unblanked.
ALOGD("marking display %zu as acquired/unblanked", i);
hw->setPowerMode(HWC_POWER_MODE_NORMAL);
}
mDisplays.add(token, hw);
}
}

// make the GLContext current so that we can create textures when creating Layers
// (which may happens before we render something)
getDefaultDisplayDevice()->makeCurrent(mEGLDisplay, mEGLContext);

mEventControlThread = new EventControlThread(this);
mEventControlThread->run("EventControl", PRIORITY_URGENT_DISPLAY);

// set a fake vsync period if there is no HWComposer
if (mHwc->initCheck() != NO_ERROR) {
mPrimaryDispSync.setPeriod(16666667);
}

// initialize our drawing state
mDrawingState = mCurrentState;

// set initial conditions (e.g. unblank default device)
initializeDisplays();

// start boot animation
startBootAnim();
}

啓動SurfaceFlinger的主線程,對設備主屏幕以及OpenGL庫進行初始化。初始化完成以後,調用startBootAnim()

void SurfaceFlinger::startBootAnim() {
// start boot animation
property_set("service.bootanim.exit", "0");
property_set("ctl.start", "bootanim");
}

調用函數property_set來將系統屬性「ctl.start」的值設置爲「bootanim」 
當系統屬性發生改變時,init進程就會接收到一個系統屬性變化通知,這個通知最終是由在init進程中的函數handle_property_set_fd來處理的。

system/core/init/property_service.cpp

static void handle_property_set_fd()
{
prop_msg msg;
int s;
int r;
struct ucred cr;
struct sockaddr_un addr;
socklen_t addr_size = sizeof(addr);
socklen_t cr_size = sizeof(cr);
char * source_ctx = NULL;
struct pollfd ufds[1];
const int timeout_ms = 2 * 1000; /* Default 2 sec timeout for caller to send property. */
int nr;

if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
return;
}

/* Check socket options here */
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
close(s);
ERROR("Unable to receive socket options\n");
return;
}

ufds[0].fd = s;
ufds[0].events = POLLIN;
ufds[0].revents = 0;
nr = TEMP_FAILURE_RETRY(poll(ufds, 1, timeout_ms));
if (nr == 0) {
ERROR("sys_prop: timeout waiting for uid=%d to send property message.\n", cr.uid);
close(s);
return;
} else if (nr < 0) {
ERROR("sys_prop: error waiting for uid=%d to send property message: %s\n", cr.uid, strerror(errno));
close(s);
return;
}

r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT));
if(r != sizeof(prop_msg)) {
ERROR("sys_prop: mis-match msg size received: %d expected: %zu: %s\n",
r, sizeof(prop_msg), strerror(errno));
close(s);
return;
}

switch(msg.cmd) {
case PROP_MSG_SETPROP:
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;

if (!is_legal_property_name(msg.name, strlen(msg.name))) {
ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name);
close(s);
return;
}

getpeercon(s, &source_ctx);

if(memcmp(msg.name,"ctl.",4) == 0) {
// Keep the old close-socket-early behavior when handling
// ctl.* properties.
close(s);
if (check_control_mac_perms(msg.value, source_ctx)) {
handle_control_message((char*) msg.name + 4, (char*) msg.value);
} else {
ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
}
} else {
if (check_perms(msg.name, source_ctx)) {
property_set((char*) msg.name, (char*) msg.value);
} else {
ERROR("sys_prop: permission denied uid:%d name:%s\n",
cr.uid, msg.name);
}

// Note: bionic's property client code assumes that the
// property server will not close the socket until *AFTER*
// the property is written to memory.
close(s);
}
freecon(source_ctx);
break;

default:
close(s);
break;
}
}
  • init進程是經過一個socket來接收系統屬性變化事件的。每個系統屬性變化事件的內容都是經過一個prop_msg對象來描述的。
  • 在prop_msg對象中: 
    • 變量name用來描述發生變化的系統屬性的名稱
    • 變量value用來描述發生變化的系統屬性的值。
  • 系統屬性分爲兩種類型: 
    • 控制類型的系統屬性(屬性名稱以「ctl.」開頭)。 
      • 控制類型的系統屬性在發生變化時,會觸發init進程執行一個命令
    • 普通類型的系統屬性 
      • 而普通類型的系統屬性就不具備這個特性。 

        注意,改變系統屬性是須要權限,所以,函數handle_property_set_fd在處理一個系統屬性變化事件以前,首先會檢查修改系統屬性的進程是否具備相應的權限,這是經過調用函數check_control_perms或者check_perms來實現的。


從前面的調用過程能夠知道,當前發生變化的系統屬性的名稱爲「ctl.start」,它的值被設置爲「bootanim」。因爲這是一個控制類型的系統屬性,所以,在經過了權限檢查以後,另一個函數handle_control_message就會被調用,以即可以執行一個名稱爲「bootanim」的命令。 handle_control_message函數實現以下: system/core/init/init.cpp
void handle_control_message(const char *msg, const char *arg)
{
if (!strcmp(msg,"start")) {
msg_start(arg);
} else if (!strcmp(msg,"stop")) {
msg_stop(arg);
} else if (!strcmp(msg,"restart")) {
msg_restart(arg);
} else {
ERROR("unknown control msg '%s'\n", msg);
}
}
控制類型的系統屬性的名稱是以」ctl.」開頭,而且是以「start」或者「stop」結尾的。
  • start:啓動某一個服務,經過msg_start函數實現
  • stop:中止某一個服務,經過msg_stop來函數實現
  • restart:重啓某一個服務,經過msg_restart函數實現 
    因爲當前發生變化的系統屬性是以「start」來結尾的,所以,接下來就會調用函數msg_start來啓動一個名稱爲「bootanim」的服務。
static void msg_start(const char *name)
{
struct service *svc = NULL;
char *tmp = NULL;
char *args = NULL;

if (!strchr(name, ':'))
svc = service_find_by_name(name);
else {
tmp = strdup(name);
if (tmp) {
args = strchr(tmp, ':');
*args = '\0';
args++;

svc = service_find_by_name(tmp);
}
}

if (svc) {
service_start(svc, args);
} else {
ERROR("no such service '%s'\n", name);
}
if (tmp)
free(tmp);
}
  • 參數name的值等於「bootanim」,它用來描述一個服務名稱
  • 調用函數service_find_by_name來找到名稱等於「bootanim」的服務的信息,這些信息保存在一個service結構體svc中
  • 調用函數service_start來將對應的應用程序啓動起來。

從前面的內容能夠知道,名稱等於「bootanim」的服務所對應的應用程序爲/system/bin/bootanimation 
這個應用程序實如今frameworks/base/cmds/bootanimation目錄中,其中,應用程序入口函數main在bootanimation_main.cpp中 
sframeworks/base/cmds/bootanimation/bootanimation_main.cpp

int main()
{
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);

char value[PROPERTY_VALUE_MAX];
property_get("debug.sf.nobootanimation", value, "0");
int noBootAnimation = atoi(value);
ALOGI_IF(noBootAnimation, "boot animation disabled");
if (!noBootAnimation) {

sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();

// create the boot animation object
sp<BootAnimation> boot = new BootAnimation();

IPCThreadState::self()->joinThreadPool();

}
return 0;
}
  • 檢查系統屬性「debug.sf.nobootnimaition」的值是否等於0。 
    • 若是不等於的話,那麼接下來就會啓動一個Binder線程池,而且建立一個BootAnimation對象(由於BootAnimation對象在顯示第三個開機畫面的過程當中,須要與SurfaceFlinger服務通訊,所以,應用程序bootanimation就須要啓動一個Binder線程池。)
  • BootAnimation類間接地繼承了RefBase類,而且重寫了RefBase類的成員函數onFirstRef,所以,當一個BootAnimation對象第一次被智能指針引用的時,這個BootAnimation對象的成員函數onFirstRef就會被調用。

BootAnimation類的函數onFirstRef實現以下: 
sframeworks/base/cmds/bootanimation/BootAnimation.cpp

void BootAnimation::onFirstRef() {
status_t err = mSession->linkToComposerDeath(this);
ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
if (err == NO_ERROR) {
run("BootAnimation", PRIORITY_DISPLAY);
}
}
  • mSession是BootAnimation類的一個變量,它的類型爲SurfaceComposerClient,是用來和SurfaceFlinger執行Binder進程間通訊的
  • 因爲BootAnimation類引用了SurfaceFlinger服務,所以,當SurfaceFlinger服務意外死亡時,BootAnimation類就須要獲得通知,這是經過調用成員變量mSession的成員函數linkToComposerDeath來註冊SurfaceFlinger服務的死亡接收通知來實現的。
  • BootAnimation類繼承了Thread類,所以,當BootAnimation類的成員函數onFirstRef調用了父類Thread的成員函數run以後,系統就會建立一個線程,這個線程在第一次運行以前,會調用BootAnimation類的成員函數readyToRun來執行一些初始化工做,後面再調用BootAnimation類的成員函數htreadLoop來顯示第三個開機畫面。

mSession在BootAnimation類的構造函數中建立的,以下所示:

BootAnimation::BootAnimation() : Thread(false), mZip(NULL)
{
mSession = new SurfaceComposerClient();
}

SurfaceComposerClient類內部有一個實現了ISurfaceComposerClient接口的Binder代理對象mClient,這個Binder代理對象引用了SurfaceFlinger服務,SurfaceComposerClient類就是經過它來和SurfaceFlinger服務通訊的。

readyToRun執行一些初始化工做

#define OEM_BOOTANIMATION_FILE "/oem/media/bootanimation.zip"
#define SYSTEM_BOOTANIMATION_FILE "/system/media/bootanimation.zip"

status_t BootAnimation::readyToRun() {
mAssets.addDefaultAssets();

sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
ISurfaceComposer::eDisplayIdMain));
DisplayInfo dinfo;
status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
if (status)
return -1;

/**
* 得到一個SurfaceControl對象control
*/
// create the native surface
sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);

SurfaceComposerClient::openGlobalTransaction();
control->setLayer(0x40000000);
SurfaceComposerClient::closeGlobalTransaction();

sp<Surface> s = control->getSurface();

// initialize opengl and egl
const EGLint attribs[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_DEPTH_SIZE, 0,
EGL_NONE
};
EGLint w, h;
EGLint numConfigs;
EGLConfig config;
EGLSurface surface;
EGLContext context;

EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

eglInitialize(display, 0, 0);
eglChooseConfig(display, attribs, &config, 1, &numConfigs);
surface = eglCreateWindowSurface(display, config, s.get(), NULL);
context = eglCreateContext(display, config, NULL, NULL);
eglQuerySurface(display, surface, EGL_WIDTH, &w);
eglQuerySurface(display, surface, EGL_HEIGHT, &h);

if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
return NO_INIT;

mDisplay = display;
mContext = context;
mSurface = surface;
mWidth = w;
mHeight = h;
mFlingerSurfaceControl = control;
mFlingerSurface = s;

// If the device has encryption turned on or is in process
// of being encrypted we show the encrypted boot animation.
char decrypt[PROPERTY_VALUE_MAX];
property_get("vold.decrypt", decrypt, "");

bool encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt);

ZipFileRO* zipFile = NULL;
if ((encryptedAnimation &&
(access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0) &&
((zipFile = ZipFileRO::open(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE)) != NULL)) ||

((access(OEM_BOOTANIMATION_FILE, R_OK) == 0) &&
((zipFile = ZipFileRO::open(OEM_BOOTANIMATION_FILE)) != NULL)) ||

((access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) &&
((zipFile = ZipFileRO::open(SYSTEM_BOOTANIMATION_FILE)) != NULL))) {
mZip = zipFile;
}

return NO_ERROR;
}
bool BootAnimation::threadLoop()
{
bool r;
// We have no bootanimation file, so we use the stock android logo
// animation.
if (mZip == NULL) {
r = android();
} else {
r = movie();
}

eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(mDisplay, mContext);
eglDestroySurface(mDisplay, mSurface);
mFlingerSurface.clear();
mFlingerSurfaceControl.clear();
eglTerminate(mDisplay);
IPCThreadState::self()->stopProcess();
return r;
}

若是BootAnimation類的成員變量mAndroidAnimation的值等於true,那麼接下來就會調用BootAnimation類的成員函數android來顯示系統默認的開機動畫,不然的話,就會調用BootAnimation類的成員函數movie來顯示用戶自定義的開機動畫。顯示完成以後,就會銷燬前面所建立的EGLContext對象mContext、EGLSurface對象mSurface,以及EGLDisplay對象mDisplay等。

接下來,咱們就分別分析BootAnimation類的成員函數android和movie的實現。 
BootAnimation類的函數android的實現以下所示:

bool BootAnimation::android()
{
initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");

// clear screen
glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(mDisplay, mSurface);

glEnable(GL_TEXTURE_2D);
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

const GLint xc = (mWidth - mAndroid[0].w) / 2;
const GLint yc = (mHeight - mAndroid[0].h) / 2;
const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);

glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),
updateRect.height());

// Blend state
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

const nsecs_t startTime = systemTime();
do {
nsecs_t now = systemTime();
double time = now - startTime;
float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w;
GLint offset = (1 - (t - floorf(t))) * mAndroid[1].w;
GLint x = xc - offset;

glDisable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT);

glEnable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
glDrawTexiOES(x, yc, 0, mAndroid[1].w, mAndroid[1].h);
glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);

glEnable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h);

EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
if (res == EGL_FALSE)
break;

// 12fps: don't animate too fast to preserve CPU
const nsecs_t sleepTime = 83333 - ns2us(systemTime() - now);
if (sleepTime > 0)
usleep(sleepTime);

checkExit();
} while (!exitPending());

glDeleteTextures(1, &mAndroid[0].name);
glDeleteTextures(1, &mAndroid[1].name);
return false;
}
  • Android系統默認的開機動畫是由兩張圖片android-logo-mask.png和android-logo-shine.png中。這兩張圖片保存在frameworks/base/core/res/assets/images目錄中,它們最終會被編譯在framework-res模塊(frameworks/base/core/res)中,即編譯在framework-res.apk文件中。編譯在framework-res模塊中的資源文件能夠經過AssetManager類來訪問。
  • BootAnimation類的成員函數android首先調用另一個成員函數initTexture來將根據圖片android-logo-mask.png和android-logo-shine.png的內容來分別建立兩個紋理對象,這兩個紋理對象就分別保存在BootAnimation類的成員變量mAndroid所描述的一個數組中。經過混合渲染這兩個紋理對象,咱們就能夠獲得一個開機動畫,這是經過中間的while循環語句來實現的。
  • 圖片android-logo-mask.png用做動畫前景,它是一個鏤空的「ANDROID」圖像。圖片android-logo-shine.png用做動畫背景,它的中間包含有一個高亮的呈45度角的條紋。在每一次循環中,圖片android-logo-shine.png被劃分紅左右兩部份內容來顯示。左右兩個部分的圖像寬度隨着時間的推移而此消彼長,這樣就可使得圖片android-logo-shine.png中間高亮的條紋好像在移動同樣。另外一方面,在每一次循環中,圖片android-logo-shine.png都做爲一個總體來渲染,並且它的位置是恆定不變的。因爲它是一個鏤空的「ANDROID」圖像,所以,咱們就能夠經過它的鏤空來看到它背後的圖片android-logo-shine.png的條紋一閃一閃地劃過。

BootAnimation類的函數movie的實現以下所示:

bool BootAnimation::movie()
{
String8 desString;

if (!readFile("desc.txt", desString)) {
return false;
}
char const* s = desString.string();

// Create and initialize an AudioPlayer if we have an audio_conf.txt file
String8 audioConf;
if (readFile("audio_conf.txt", audioConf)) {
mAudioPlayer = new AudioPlayer;
if (!mAudioPlayer->init(audioConf.string())) {
ALOGE("mAudioPlayer.init failed");
mAudioPlayer = NULL;
}
}

Animation animation;

/**
* 從readyToRun函數實現能夠知道,若是目標設備上存在壓縮文件/data/local/bootanimation.zip,
* 那麼變量mZip就會指向它,不然的話,就會指向目標設備上的壓縮文件/system/media/bootanimation.zip。
* 不管變量mZip指向的是哪個壓縮文件,這個壓縮文件都必須包含有一個名稱爲「desc.txt」的文件,
* 用來描述用戶自定義的開機動畫是如何顯示的。
* 就獲得了開機動畫的顯示大小、速度以及片段信息。這些信息都保存在Animation對象animation中,
* 其中,每個動畫片段都使用一個Animation::Part對象來描述,
* 而且保存在Animation對象animation的成員變量parts所描述的一個片段列表中。
*/
// Parse the description file
for (;;) {
const char* endl = strstr(s, "\n");
if (endl == NULL) break;
String8 line(s, endl - s);
const char* l = line.string();
int fps, width, height, count, pause;
char path[ANIM_ENTRY_NAME_MAX];
char color[7] = "000000"; // default to black if unspecified

char pathType;
if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {
// ALOGD("> w=%d, h=%d, fps=%d", width, height, fps);
animation.width = width;
animation.height = height;
animation.fps = fps;
}
else if (sscanf(l, " %c %d %d %s #%6s", &pathType, &count, &pause, path, color) >= 4) {
// ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s", pathType, count, pause, path, color);
Animation::Part part;
part.playUntilComplete = pathType == 'c';
part.count = count;
part.pause = pause;
part.path = path;
part.audioFile = NULL;
if (!parseColor(color, part.backgroundColor)) {
ALOGE("> invalid color '#%s'", color);
part.backgroundColor[0] = 0.0f;
part.backgroundColor[1] = 0.0f;
part.backgroundColor[2] = 0.0f;
}
animation.parts.add(part);
}

s = ++endl;
}

// read all the data structures
const size_t pcount = animation.parts.size();
void *cookie = NULL;
if (!mZip->startIteration(&cookie)) {
return false;
}

ZipEntryRO entry;
char name[ANIM_ENTRY_NAME_MAX];
while ((entry = mZip->nextEntry(cookie)) != NULL) {
const int foundEntryName = mZip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX);
if (foundEntryName > ANIM_ENTRY_NAME_MAX || foundEntryName == -1) {
ALOGE("Error fetching entry file name");
continue;
}
/**
* 接下來,BootAnimation類的成員函數movie再斷續將每個片段所對應的png圖片讀取出來。
* 每個png圖片都表示一個動畫幀,使用一個Animation::Frame對象來描述,
* 而且保存在對應的Animation::Part對象的成員變量frames所描述的一個幀列表中
*/
const String8 entryName(name);
const String8 path(entryName.getPathDir());
const String8 leaf(entryName.getPathLeaf());
if (leaf.size() > 0) {
for (size_t j=0 ; j<pcount ; j++) {
if (path == animation.parts[j].path) {
uint16_t method;
// supports only stored png files
if (mZip->getEntryInfo(entry, &method, NULL, NULL, NULL, NULL, NULL)) {
if (method == ZipFileRO::kCompressStored) {
FileMap* map = mZip->createEntryFileMap(entry);
if (map) {
Animation::Part& part(animation.parts.editItemAt(j));
if (leaf == "audio.wav") {
// a part may have at most one audio file
part.audioFile = map;
} else {
Animation::Frame frame;
frame.name = leaf;
frame.map = map;
part.frames.add(frame);
}
}
}
}
}
}
}
}

mZip->endIteration(cookie);
/**
* 一系列gl函數首先用來清理屏幕,接下來的一系列gl函數用來設置OpenGL的紋理顯示方式。
*/
glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);

glBindTexture(GL_TEXTURE_2D, 0);
glEnable(GL_TEXTURE_2D);
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

/**
* 變量xc和yc的值用來描述開機動畫的顯示位置,即須要在屏幕中間顯示開機動畫,
* 另一個變量frameDuration的值用來描述每一幀的顯示時間,它是以納秒爲單位的。
* Region對象clearReg用來描述屏幕中除了開機動畫以外的其它區域,\
* 它是用整個屏幕區域減去開機動畫所點據的區域來獲得的。
*/
const int xc = (mWidth - animation.width) / 2;
const int yc = ((mHeight - animation.height) / 2);
nsecs_t frameDuration = s2ns(1) / animation.fps;

Region clearReg(Rect(mWidth, mHeight));
clearReg.subtractSelf(Rect(xc, yc, xc+animation.width, yc+animation.height));

/**
* 準備好開機動畫的顯示參數以後,最後就能夠執行顯示開機動畫的操做了。
*/
for (size_t i=0 ; i<pcount ; i++) {
const Animation::Part& part(animation.parts[i]);
const size_t fcount = part.frames.size();
glBindTexture(GL_TEXTURE_2D, 0);

for (int r=0 ; !part.count || r<part.count ; r++) {
// Exit any non playuntil complete parts immediately
if(exitPending() && !part.playUntilComplete)
break;

// only play audio file the first time we animate the part
if (r == 0 && mAudioPlayer != NULL && part.audioFile) {
mAudioPlayer->playFile(part.audioFile);
}

glClearColor(
part.backgroundColor[0],
part.backgroundColor[1],
part.backgroundColor[2],
1.0f);

for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
const Animation::Frame& frame(part.frames[j]);
nsecs_t lastFrame = systemTime();

if (r > 0) {
glBindTexture(GL_TEXTURE_2D, frame.tid);
} else {
if (part.count != 1) {
glGenTextures(1, &frame.tid);
glBindTexture(GL_TEXTURE_2D, frame.tid);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
initTexture(frame);
}

if (!clearReg.isEmpty()) {
Region::const_iterator head(clearReg.begin());
Region::const_iterator tail(clearReg.end());
glEnable(GL_SCISSOR_TEST);
while (head != tail) {
const Rect& r2(*head++);
glScissor(r2.left, mHeight - r2.bottom,
r2.width(), r2.height());
glClear(GL_COLOR_BUFFER_BIT);
}
glDisable(GL_SCISSOR_TEST);
}
// specify the y center as ceiling((mHeight - animation.height) / 2)
// which is equivalent to mHeight - (yc + animation.height)
glDrawTexiOES(xc, mHeight - (yc + animation.height),
0, animation.width, animation.height);
eglSwapBuffers(mDisplay, mSurface);

nsecs_t now = systemTime();
nsecs_t delay = frameDuration - (now - lastFrame);
//ALOGD("%lld, %lld", ns2ms(now - lastFrame), ns2ms(delay));
lastFrame = now;

if (delay > 0) {
struct timespec spec;
spec.tv_sec = (now + delay) / 1000000000;
spec.tv_nsec = (now + delay) % 1000000000;
int err;
do {
err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL);
} while (err<0 && errno == EINTR);
}

checkExit();
}

usleep(part.pause * ns2us(frameDuration));

// For infinite parts, we've now played them at least once, so perhaps exit
if(exitPending() && !part.count)
break;
}

// free the textures for this part
if (part.count != 1) {
for (size_t j=0 ; j<fcount ; j++) {
const Animation::Frame& frame(part.frames[j]);
glDeleteTextures(1, &frame.tid);
}
}
}

return false;
}
  • 顯示每個動畫片段
  • 循環顯示每個動畫片段
  • 顯示每個動畫片段所對應的png圖片。這些png圖片以紋理的方式來顯示在屏幕中。
  • 若是Region對象clearReg所包含的區域不爲空,那麼在調用函數glDrawTexiOES和eglSwapBuffers來顯示每個png圖片以前,首先要將它所包含的區域裁剪掉,避免開機動畫能夠顯示在指定的位置以及大小中。
  • 每當顯示完成一個png圖片以後,都要將變量frameDuration的值從納秒轉換爲毫秒。若是轉換後的值大小於,那麼就須要調用函數usleep函數來讓線程睡眠一下,以保證每個png圖片,即每一幀動畫都按照預先指定好的速度來顯示。注意,函數usleep指定的睡眠時間只能精確到毫秒,所以,若是預先指定的幀顯示時間小於1毫秒,那麼BootAnimation類的成員函數movie是沒法精確地控制地每一幀的顯示時間的。
  • 判斷一個動畫片段是不是循環顯示的,即循環次數不等於1。若是是的話,那麼就說明前面爲它所對應的每個png圖片都建立過一個紋理對象。如今既然這個片段的顯示過程已經結束了,所以,就須要釋放前面爲它所建立的紋理對象。
  • 注意,若是一個動畫片段的循環顯示次數不等於1,那麼就說明這個動畫片段中的png圖片須要重複地顯示在屏幕中。因爲每個png圖片都須要轉換爲一個紋理對象以後才能顯示在屏幕中,所以,爲了不重複地爲同一個png圖片建立紋理對象,第三層的for循環在第一次顯示一個png圖片的時候,會調用函數glGenTextures來爲這個png圖片建立一個紋理對象,而且將這個紋理對象的名稱保存在對應的Animation::Frame對象的成員變量tid中,這樣,下次再顯示相同的圖片時,就可使用前面已經建立好了的紋理對象,即調用函數glBindTexture來指定當前要操做的紋理對象。
  • 還有另一個地方須要注意的是,每當循環顯示完成一個片段時,須要調用usleep函數來使得線程睡眠part.pause * ns2us(frameDuration)毫秒,以即可以按照預先設定的節奏來顯示開機動畫。
相關文章
相關標籤/搜索