函數fb_open的實現以下所示:
-
static int fb_open(struct FB *fb)
-
{
-
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;
-
-
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;
-
}
打開了設備文件/dev/graphics/fb0以後,接着再分別經過IO控制命令FBIOGET_FSCREENINFO和FBIOGET_VSCREENINFO來得到幀緩衝硬件設備的固定信息和可變信息。固定信息使用一個fb_fix_screeninfo結構體來描述,它保存的是幀緩衝區硬件設備固有的特性,這些特性在幀緩衝區硬件設備被初始化了以後,就不會發生改變,例如屏幕大小以及物理地址等信息。可變信息使用一個fb_var_screeninfo結構體來描述,它保存的是幀緩衝區硬件設備可變的特性,這些特性在系統運行的期間是能夠改變的,例如屏幕所使用的分辨率、顏色深度以及顏色格式等。
除了得到幀緩衝區硬件設備的固定信息和可變信息以外,函數fb_open還會將設備文件/dev/graphics/fb0的內容映射到init進程的地址空間來,這樣init進程就能夠經過映射獲得的虛擬地址來訪問幀緩衝區硬件設備的內容了。
回到函數load_565rle_p_w_picpath中,接下來分別使用宏fb_width和fb_height來得到屏幕所使用的的分辨率,即屏幕的寬度和高度。宏fb_width和fb_height的定義以下所示:
-
#define fb_width(fb) ((fb)->vi.xres)
-
#define fb_height(fb) ((fb)->vi.yres)
屏幕的所使用的分辨率使用結構體fb_var_screeninfo的成員變量xres和yres來描述,其中,成員變量xres用來描述屏幕的寬度,而成員變量成員變量yres用來描述屏幕的高度。獲得了屏幕的分辨率以後,就能夠知道最多能夠向幀緩衝區硬件設備寫入的字節數的大小了,這個大小就等於屏幕的寬度乘以高度,保存在變量max中。
如今咱們分別獲得了文件initlogo.rle和幀緩衝區硬件設備在init進程中的虛擬訪問地址以及大小,這樣咱們就能夠將文件initlogo.rle的內容寫入到幀緩衝區硬件設備中去,以即可以將第二個開機畫面顯示出來,這是經過函數load_565rle_p_w_picpath中的while循環來實現的。
文件initlogo.rle保存的第二個開機畫面的圖像格式是565rle的。rle的全稱是run-length encoding,翻譯爲遊程編碼或者行程長度編碼,它可使用4個字節來描述一個連續的具備相同顏色值的序列。在rle565格式,前面2個字節中用來描述序列的個數,然後面2個字節用來描述一個具體的顏色,其中,顏色的RGB值分別佔5位、6位和7位。理解了565rle圖像格式以後,咱們就能夠理解函數load_565rle_p_w_picpath中的while循環的實現邏輯了。在每一次循環中,都會依次從文件initlogo.rle中讀出4個字節,其中,前兩個字節的內容保存在變量n中,然後面2個字節的內容用來寫入到幀緩衝區硬件設備中去。因爲2個字節恰好就可使用一個無符號短整數來描述,所以,函數load_565rle_p_w_picpath經過調用函數android_memset16來將從文件initlogo.rle中讀取出來的顏色值寫入到幀緩衝區硬件設備中去,
函數android_memset16的實現以下所示:
-
void android_memset16(void *_ptr, unsigned short val, unsigned count)
-
{
-
unsigned short *ptr = _ptr;
-
count >>= 1;
-
while(count--)
-
*ptr++ = val;
-
}
參數ptr指向被寫入的地址,在咱們這個場景中,這個地址即爲幀緩衝區硬件設備映射到init進程中的虛擬地址值。
參數val用來描述被寫入的值,在咱們這個場景中,這個值即爲從文件initlogo.rle中讀取出來的顏色值。
參數count用來描述被寫入的地址的長度,它是以字節爲單位的。因爲在將參數val的值寫入到參數ptr所描述的地址中去時,是以無符號短整數爲單位的,便是以2個字節爲單位的,所以,函數android_memset16在將參數val寫入到地址ptr中去以前,首先會將參數count的值除以2。相應的地,在函數load_565rle_p_w_picpath中,須要將具備相同顏色值的序列的個數乘以2以後,再調用函數android_memset16。
回到函數load_565rle_p_w_picpath中,將文件/initlogo.rle的內容寫入到幀緩衝區硬件設備去以後,第二個開機畫面就能夠顯示出來了。接下來函數load_565rle_p_w_picpath就會調用函數munmap來註銷文件/initlogo.rle在init進程中的映射,而且調用函數close來關閉文件/initlogo.rle。關閉了文件/initlogo.rle以後,還會調用函數unlink來刪除目標設備上的/initlogo.rle文件。注意,這只是刪除了目標設備上的/initlogo.rle文件,而不是刪除ramdisk映像中的initlogo.rle文件,所以,每次關機啓動以後,系統都會從新將ramdisk映像中的initlogo.rle文件安裝到目標設備上的根目錄來,這樣就能夠在每次開機的時候都能將它顯示出來。
除了須要註銷文件/initlogo.rle在init進程中的映射和關閉文件/initlogo.rle以外,還須要註銷文件/dev/graphics/fb0在init進程中的映射以及關閉文件/dev/graphics/fb0,這是經過調用fb_close函數來實現的,以下所示:
-
static void fb_close(struct FB *fb)
-
{
-
munmap(fb->bits, fb_size(fb));
-
close(fb->fd);
-
}
在調用fb_close函數以前,函數load_565rle_p_w_picpath還會調用另一個函數fb_update來更新屏幕上的第二個開機畫面,它的實現以下所示:
-
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);
-
}
在結構體fb_var_screeninfo中,除了使用成員變量xres和yres來描述屏幕所使用的分辨率以外,還使用成員變量xres_virtual和yres_virtual來描述屏幕所使用的虛擬分辨率。成員變量xres和yres所描述屏幕的分辨率稱爲可視分辨率。可視分辨率和虛擬分辨率有什麼關係呢?可視分辨率是屏幕實際上使用的分辨率,即用戶所看到的分辨率,而虛擬分辨率是在系統內部使用的,它是不可見的,而且能夠大於可視分辨率。例如,假設可視分辨率是800 x 600,那麼虛擬分辨率能夠設置爲1600 x 600。因爲屏幕最多隻能夠顯示800 x 600個像素,所以,在系統內部,就須要決定從1600 x 600中取出800 x 600個像素來顯示,這是經過結構體fb_var_screeninfo的成員變量xoffset和yoffset的值來描述的。成員變量xoffset和yoffset的默認值等於0,即默認從虛擬分辨率的左上角取出與可視分辨率大小相等的像素出來顯示,不然的話,就會根據成員變量xoffset和yoffset的值來從虛擬分辨率的中間位置取出與可視分辨率大小相等的像素出來顯示。
幀緩衝區的大小是由虛擬分辨率決定的,所以,咱們就能夠在幀緩衝中寫入比屏幕大小還要多的像素值,多出來的這個部分像素值就能夠用做雙緩衝。咱們仍然假設可視分辨率和虛擬分辨率分別是800 x 600和1600 x 600,那麼咱們就能夠先將前一個圖像的內容寫入到幀緩衝區的前面800 x 600個像素中去,接着再將後一個圖像的內容寫入到幀緩衝區的後面800 x 600個像素中。經過分別將用來描述幀緩衝區硬件設備的fb_var_screeninfo結構體的成員變量yoffset的值設置爲0和800,就能夠平滑地顯示兩個圖像。
理解了幀緩衝區硬件設備的可視分辨性和虛擬分辨性以後,函數fb_update的實現邏輯就能夠很好地理解了。
至此,第二個開機畫面的顯示過程就分析完成了。