資料來源: 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:
Post a Comment