在完成控制台初始化之后,可以看到在arch\x86\boot\Main.c文件的main主函数中接着执行if (cmdline_find_option_bool("debug")),这条if判断语句首先调用cmdline_find_option_bool函数在内核命令行中查找"debug"选项,该函数的实现和在系统启动篇(三)[上]一文中剖析过的cmdline_find_option函数非常相似,但前者只需要判断在命令行中是否存在要找的选项,并不需要取出对应的值,因而实现过程较后者更为简单,在这里不再进行详细剖析。若if判断语句中的函数调用返回真,则执行puts("early console in setup code\n"); 语句,反之则跳过继续执行后续代码。因此如果在内核命令行中找到"debug"选项那么将打印出"early console in setup code\n"字符串,而联系到查找的选项名称则不难发现这条信息主要是用来调试内核的。
在裸机状态下向屏幕输出字符
许多初学者在学习编程的时候都是从最经典的"Hello World!"开始的,而如果学习的第一门语言是C的话,那么打印上述字符串的程序最主要的实现语句就是printf("Hello World!"); 其实printf的实现过程无非是首先进行格式解析,然后再将解析后的结果打印至屏幕。对于学习编程已经有一段时间的人来说,格式解析模块的实现只需逻辑上的一些细微处理,但对于如何操作硬件以实现字符的输出却相当不解,而这部分功能的实现其实与puts函数如出一辙,这也正是为什么我在这里详细剖析puts的原因。该函数位于arch\x86\boot\Tty.c文件中:
void __attribute__((section(".inittext"))) puts(const char *str) /*位于.inittext节区*/
{
while (*str)
putchar(*str++);
}
函数首先检测字符串str中当前所指向的字符是否为'\0',若是则直接退出循环结束整个串的输出,否则调用putchar函数输出当前指向的字符,并将指针值进行自增,而putchar函数的实现位于同样位于arch\x86\boot\Tty.c文件中:
void __attribute__((section(".inittext"))) putchar(int ch)
{
if (ch == '\n')
putchar('\r'); /* \n -> \r\n */
bios_putchar(ch);
if (early_serial_base != 0)
serial_putchar(ch);
}
首先判断是否为'\n'字符,若是则先输出'\r',之后再输出'\n'。其中'\r'表示return——指回到当前行的行首,而'\n'则表示next——指移动到下一行,所以其实\r\n连用才表示真正的回车换行。但通常写程序时都只用'\n'表示即可,之所以在这里按照这种方式执行,一种可能的猜测是此时处于文本模式下,对于回车换行必须严格按照相关的协议,正如HTTP中使用\r\n表示一行的结束。此后接着调用bios_putchar(ch)函数输出,顾名思义就是调用BIOS中断输出该字符,这个函数同样被定义在arch\x86\boot\Tty.c文件中:
static void __attribute__((section(".inittext"))) bios_putchar(int ch)
{
struct biosregs ireg;
initregs(&ireg);
ireg.bx = 0x0007;
ireg.cx = 0x0001;
ireg.ah = 0x0e;
ireg.al = ch;
intcall(0x10, &ireg, NULL);
}
在函数内部首先使用结构体类型biosregs定义了一个变量ireg,其中结构体类型biosregs被定义在arch\x86\boot\Boot.h文件中,这里不再将其列出,需要强调的一点是该结构体之所以如此定义,是因为x86采用的是小端法,对于某些可以单独设置低位的寄存器(如eax等通用寄存器),若将其保存在内存中时,其中的低位被放置在内存的低地址空间中,所以对于比如说u32 eax;之类的定义,只有u16 ax, hax才是与其等价的。之后调用ini