Wednesday, December 28, 2011

Android開機啟動分析(一)logo的顯示


Android開機啟動的時候會有一個logo出現,它對應的源代碼位於/system/core/init/目錄下的logo.c中:
下面是我註釋過的代碼:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
  
#include <linux/fb.h>
#include <linux/kd.h>
  
#include "init.h"  
  
#ifdef ANDROID  
#include <cutils/memory.h>
#else  
void android_memset16(void *_ptr, unsigned short val, unsigned count)  
{  
    unsigned short *ptr = _ptr;  
    count >>= 1;  
    while(count--)  
        *ptr++ = val;  
}  
#endif  
  
struct FB {  
    unsigned short *bits;  
    unsigned size;  
    int fd;  
    struct fb_fix_screeninfo fi;  
    struct fb_var_screeninfo vi;  
};  
  
#define fb_width(fb) ((fb)->vi.xres)  
#define fb_height(fb) ((fb)->vi.yres)  
#define fb_size(fb) ((fb)->vi.xres * (fb)->vi.yres * 2)  
  
static int fb_open(struct FB *fb)//打開framebuffer設備  
{  
//fd對應設備文件為 /dev/graphics/fb0  
    fb->fd = open("/dev/graphics/fb0", O_RDWR);  
    if (fb->fd < 0)  
        return -1;  
  
    if (ioctl(fb->fd, FBIOGET_FSCREENINFO, &fb->fi) < 0)  
        goto fail;  
    if (ioctl(fb->fd, FBIOGET_VSCREENINFO, &fb->vi) < 0)  
        goto fail;  
//對fd對應的Framebuffer大小進行mmap  
    fb->bits = mmap(0, fb_size(fb), PROT_READ | PROT_WRITE,   
                    MAP_SHARED, fb->fd, 0);  
    if (fb->bits == MAP_FAILED)  
        goto fail;  
  
    return 0;  
  
fail:  
    close(fb->fd);  
    return -1;  
}  
  
static void fb_close(struct FB *fb)//關閉framebuffer  
{  
    munmap(fb->bits, fb_size(fb));  
    close(fb->fd);  
}  
  
/* there's got to be a more portable way to do this ... */  
static void fb_update(struct FB *fb)  
{  
    fb->vi.yoffset = 1;  
    ioctl(fb->fd, FBIOPUT_VSCREENINFO, &fb->vi);  
    fb->vi.yoffset = 0;  
    ioctl(fb->fd, FBIOPUT_VSCREENINFO, &fb->vi);  
}  
//設置終端顯示模式:Graphics or Text?  
static int vt_set_mode(int graphics)  
{  
    int fd, r;  
    fd = open("/dev/tty0", O_RDWR | O_SYNC);  
    if (fd < 0)  
        return -1;  
//設置圖片還是文字  
    r = ioctl(fd, KDSETMODE, (void*) (graphics ? KD_GRAPHICS : KD_TEXT));  
    close(fd);  
    return r;  
}  
  
/* 565RLE image format: [count(2 bytes), rle(2 bytes)] */  
  
int load_565rle_image(char *fn)  
{  
    struct FB fb;  
    struct stat s;  
    unsigned short *data, *bits, *ptr;  
    unsigned count, max;  
    int fd;  
  
    if (vt_set_mode(1)) //打開Graphics顯示模式  
        return -1;  
  
//判斷是否能夠打開INIT_IMAGE_FILE  
    fd = open(fn, O_RDONLY);  
    if (fd < 0) {  
        ERROR("cannot open '%s'/n", fn);  
//如果不行就恢復Text顯示模式  
        goto fail_restore_text;  
    }  
  
    if (fstat(fd, &s) < 0) {  
        goto fail_close_file;  
    }  
/*在用戶空間調用mmap,相當於在進程中開闢了文件的存儲IO映射 
  data為返回的映射地址,把圖片文件內容映射到存儲區 
*/  
    data = mmap(0, s.st_size, PROT_READ, MAP_SHARED, fd, 0);  
    if (data == MAP_FAILED)  
        goto fail_close_file;  
  
    if (fb_open(&fb))  
        goto fail_unmap_data;  
  
    max = fb_width(&fb) * fb_height(&fb);  
    ptr = data;  
    count = s.st_size;  
    bits = fb.bits;  
    while (count > 3) {  
        unsigned n = ptr[0];  
        if (n > max)  
            break;  
//將ptr對應的圖片寫到bits對應的framebuffer中,細節不追究了  
        android_memset16(bits, ptr[1], n << 1);  
        bits += n;  
        max -= n;  
        ptr += 2;  
        count -= 4;  
    }  
  
    munmap(data, s.st_size);  
    fb_update(&fb);  
    fb_close(&fb);  
    close(fd);  
    unlink(fn); //刪除fn ?  
    return 0;  
  
fail_unmap_data:  
//munmap釋放申請的內存映射IO  
    munmap(data, s.st_size);      
fail_close_file:  
    close(fd);  
fail_restore_text:  
    vt_set_mode(0);  
//返回-1,於是就直接顯示"Android"字樣!  
    return -1;  
}

分析如下:
Android開機的第一個進程為init,在它的實現文件,即init.c中,會通過調用函數load_565rle_image(INIT_IMAGE_FILE)來實現開機啟動顯示logo的。而上述這個函數是在同目錄下的logo.c中實現的。
在load_565rle_image()這個主函數中先調用vt_set_mode()來設定終端的顯示方式,默認是使用終端的graphics顯示方式,通過對開/dev/tty0設備文件調用ioctl系統調用來設定,但是如果不能打開load_565rle_image(INIT_IMAGE_FILE)中指定的INIT_IMAGE_FILE圖片文件時,就直接將/dev/tty0設置為Text顯示模式,然後直接返回,執行init.c中後面的代碼(直接向/dev/tty0設備寫入"Android"字樣的logo,這也就是默認的啟動logo,很沒有創意,哈哈!)
但是如果存在INIT_IMAGE_FILE這個圖片文件的話,就會在load_565rle_image中通過調用mmap,將圖片文件映射到當前進程的存儲空間中,接著就又調用fb_open()函數來打開linux framebuufer對應的設備文件 /dev/graphics/fb0 ,並把framebuffer也通過mmap映射到存儲空間,於是就在後面調用android_memset16()來寫入圖片,從android_memset16()這樣函數的參數就可以看出,程序的目的就是要把ptr對應的圖片存儲空間地址所對應的字節,寫到bits對應的framebuffer對應的存儲空間中。寫Framebuffer具體細節就不追究了,可能牽涉到Framebuffer原理的一些知識。
而在init.h文件中:
#define INIT_IMAGE_FILE "/initlogo.rle"
說明了圖片文件的位置和默認的文件名,這樣就可以自定義開機顯示畫面了,哈哈!!!
總結:顯示開機logo,如果使用圖片的話就是通過mmap,將圖片內容寫到Framebuffer存儲映射空間來完成的;而如果只是顯示Text的話,就是直接和/dev/tty0打交道了。分析完畢!!

No comments: