接下来我们就重点分析函数console_init_action的实现,以便可以了解第二个开机画面的显示过程:
-
static int console_init_action(int nargs, char **args)
-
{
-
int fd;
-
char tmp[PROP_VALUE_MAX];
-
-
if (console[0]) {
-
snprintf(tmp, sizeof(tmp), "/dev/%s", console);
-
console_name = strdup(tmp);
-
}
-
-
fd = open(console_name, O_RDWR);
-
if (fd >= 0)
-
have_console = 1;
-
close(fd);
-
-
if( load_565rle_image(INIT_IMAGE_FILE) ) {
-
fd = open("/dev/tty0", O_WRONLY);
-
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;
-
}
这个函数主要做了两件事件:
A. 初始化控制台。init进程在启动的时候,会解析内核的启动参数(保存在文件/proc/cmdline中)。如果发现内核的启动参数中包含有了一个名称为“androidboot.console”的属性,那么就会将这个属性的值保存在字符数组console中。这样我们就可以通过设备文件/dev/<console>来访问系统的控制台。如果内核的启动参数没有包含名称为“androidboot.console”的属性,那么默认就通过设备文件/dev/console来访问系统的控制台。如果能够成功地打开设备文件/dev/<console>或者/dev/console,那么就说明系统支持访问控制台,因此,全局变量have_console的就会被设置为1。
B. 显示第二个开机画面。显示第二个开机画面是通过调用函数load_565rle_image来实现的。在调用函数load_565rle_image的时候,指定的开机画面文件为INIT_IMAGE_FILE。INIT_IMAGE_FILE是一个宏,定义在system/core/init/init.h文件中,如下所示:
-
#define INIT_IMAGE_FILE "/initlogo.rle"
即第二个开机画面的内容是由文件/initlogo.rle来指定的。如果文件/initlogo.rle不存在,或者在显示它的过程中出现异常,那么函数load_565rle_image的返回值就会等于-1,这时候函数console_init_action就以文本的方式来显示第二个开机画面,即向编号为0的控制台(/dev/tty0)输出“ANDROID”这7个字符。
函数load_565rle_image实现在文件system/core/init/logo.c中,如下所示:
-
/* 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))
-
return -1;
-
-
fd = open(fn, O_RDONLY);
-
if (fd < 0) {
-
ERROR("cannot open '%s'\n", fn);
-
goto fail_restore_text;
-
}
-
-
if (fstat(fd, &s) < 0) {
-
goto fail_close_file;
-
}
-
-
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;
-
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);
-
return 0;
-
-
fail_unmap_data:
-
munmap(data, s.st_size);
-
fail_close_file:
-
close(fd);
-
fail_restore_text:
-
vt_set_mode(0);
-
return -1;
-
}
函数首先将控制台的显示方式设置为图形方式,这是通过调用函数vt_set_mode来实现的,如下所示:
-
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;
-
}
函数vt_set_mode首先打开控制台设备文件/dev/tty0,接着再通过IO控制命令KDSETMODE来将控制台的显示方式设置为文本方式或者图形方式,取决于参数graphics的值。从前面的调用过程可以知道,参数graphics的值等于1,因此,这里是将控制台的显示方式设备为图形方式。
回到函数load_565rle_image中,从前面的调用过程可以知道,参数fn的值等于“/initlogo.rle”,即指向目标设备上的initlogo.rle文件。函数load_565rle_image首先调用函数open打开这个文件,并且将获得的文件描述符保存在变量fd中,接着再调用函数fstat来获得这个文件的大小。有了这些信息之后,函数load_565rle_image就可以调用函数mmap来把文件/initlogo.rle映射到init进程的地址空间来了,以便可以读取它的内容。
将文件/initlogo.rle映射到init进程的地址空间之后,接下来再调用函数fb_open来打开设备文件/dev/graphics/fb0。前面在介绍第一个开机画面的显示过程中提到,设备文件/dev/graphics/fb0是用来访问系统的帧缓冲区硬件设备的,因此,打开了设备文件/dev/graphics/fb0之后,我们就可以将文件/initlogo.rle的内容输出到帧缓冲区硬件设备中去了。