函数fb_open的实现如下所示:
  1. static int fb_open(struct FB *fb)   
  2. {   
  3.     fb->fd = open("/dev/graphics/fb0", O_RDWR);   
  4.     if (fb->fd < 0)   
  5.         return -1;   
  6.    
  7.     if (ioctl(fb->fd, FBIOGET_FSCREENINFO, &fb->fi) < 0)   
  8.         goto fail;   
  9.     if (ioctl(fb->fd, FBIOGET_VSCREENINFO, &fb->vi) < 0)   
  10.         goto fail;   
  11.    
  12.     fb->bits = mmap(0, fb_size(fb), PROT_READ | PROT_WRITE,   
  13.                     MAP_SHARED, fb->fd, 0);   
  14.     if (fb->bits == MAP_FAILED)   
  15.         goto fail;   
  16.    
  17.     return 0;   
  18.    
  19. fail:   
  20.     close(fb->fd);   
  21.     return -1;   
  22. }   
       打开了设备文件/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的定义如下所示:
  1. #define fb_width(fb) ((fb)->vi.xres)  
  2. #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的实现如下所示:
  1. void android_memset16(void *_ptr, unsigned short val, unsigned count)  
  2. {  
  3.     unsigned short *ptr = _ptr;  
  4.     count >>= 1;  
  5.     while(count--)  
  6.         *ptr++ = val;  
  7. }  
       参数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函数来实现的,如下所示:
  1. static void fb_close(struct FB *fb)  
  2. {  
  3.     munmap(fb->bits, fb_size(fb));  
  4.     close(fb->fd);  
  5. }  
       在调用fb_close函数之前,函数load_565rle_p_w_picpath还会调用另外一个函数fb_update来更新屏幕上的第二个开机画面,它的实现如下所示: 
  1. static void fb_update(struct FB *fb)  
  2. {  
  3.     fb->vi.yoffset = 1;  
  4.     ioctl(fb->fd, FBIOPUT_VSCREENINFO, &fb->vi);  
  5.     fb->vi.yoffset = 0;  
  6.     ioctl(fb->fd, FBIOPUT_VSCREENINFO, &fb->vi);  
  7. }  
        在结构体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的实现逻辑就可以很好地理解了。
        至此,第二个开机画面的显示过程就分析完成了。