系统启动篇(三)[上]

进入main函数后,Linux内核执行硬件检测及初始化工作,即便在此之前BIOS已经对大部分硬件设备进行了相应的初始化,然而Linux并不依赖于BIOS,而是以特定的方式重新初始化相关设备,这样做的目的是为了增强可移植性及健壮性。需要强调的一点是,此时C语言仍旧运行在实模式状态下。

拷贝启动参数
进入arch\x86\boot\Main.c文件的main函数后,做的第一件事就是将从Kernel boot sector中偏移0x1f1处起始的hdr头变量拷贝至指定内存中。

  1. /* First, copy the boot header into the "zeropage" */  
  2. copy_boot_params();  
	/* First, copy the boot header into the "zeropage" */
	copy_boot_params();

可以看出该指定内存被称为“零页”,之所以这么称呼,是因为在保护模式下启用分页机制后,它是整个内存单元的第一个分页。注意启动头(boot header)与系统启动(二)中的安装头(setup header)均指的是hdr变量。下面是它的具体实现:

  1. /* 
  2.  * Copy the header into the boot parameter block.  Since this 
  3.  * screws up the old-style command line protocol, adjust by 
  4.  * filling in the new-style command line pointer instead. 
  5.  */  
  6.   
  7.     struct old_cmdline {  
  8.         u16 cl_magic;  
  9.         u16 cl_offset;  
  10.     };  
  11.     const struct old_cmdline * const oldcmd =  
  12.         (const struct old_cmdline *)OLD_CL_ADDRESS;  
/*
 * Copy the header into the boot parameter block.  Since this
 * screws up the old-style command line protocol, adjust by
 * filling in the new-style command line pointer instead.
 */

    struct old_cmdline {
        u16 cl_magic;
        u16 cl_offset;
    };
    const struct old_cmdline * const oldcmd =
        (const struct old_cmdline *)OLD_CL_ADDRESS;

首先解释下注释:拷贝boot header的过程无法和老式命令行协议谐调,所以需要通过填充新类型命令行指针调整。如上所示,拷贝过程中首先将指向常量的结构体指针常量赋值为OLD_CL_ADDRESS,其值在arch\x86\include\asm\Setup.h文件中定义如下:

  1. #define OLD_CL_ADDRESS      0x020   /* Relative to real mode data */  
#define OLD_CL_ADDRESS		0x020	/* Relative to real mode data */

从old_cmdline结构体的定义来看,我们就可大致猜测出这是针对老式命令行定义的变量类型,其中分别包含命令行魔数(cl_magic)及偏移(cl_offset)两个字段。以下是对命令行的简要介绍(见Documentation\x86中的boot.txt文件):

  • 内核命令行(THE KERNEL COMMAND LINE)
    内核命令行是bootloader与内核通信的一种重要的方式,该命令行由bootloader提供并且它的一些选项和bootloader相关。任何选项不应该从内核命令行中删除,即便它对于内核来说并不具有实际的意义。以下是内核命令行中选项的典型格式:
    ——vga=<mode>,这里的<mode>可以是一个整数(在C语言中的十进制、八进制或是十六进制格式),或者是字符串"normal"(使用0xFFFF表示)、"ext"(0xFFFE表示)以及"ask"(0xFFFD表示)之一。这个值应该被填入vid_mode域,并由内核所使用。更多有关命令行选项的详细信息参见boot.txt文件中的SPECIAL COMMAND LINE OPTIONS一节。

    如果协议版本不是2.02或者更高(注意现在所使用的协议版本为2.10),那么内核命令行将会遵守如下约定:
        1、在偏移0x0020处,字段"cmd_line_magic"被填入魔数0xA33F
        2、在偏移0x0022处,字段"cmd_line_offset"被填入内核命令行的起始地址,该地址相对实模式内核的起始地址而言。

根据以上描述,由于指针常量oldcmd被设置为OLD_CL_ADDRESS,因此在老版本的内核中,该指针所指向的结构体old_cmdline包含值为0xA33F的字段cl_magic。接着往下执行:

  1.     BUILD_BUG_ON(sizeof boot_params != 4096);  
  2.     memcpy(&boot_params.hdr, &hdr, sizeof hdr);  
    BUILD_BUG_ON(sizeof boot_params != 4096);
    memcpy(&boot_params.hdr, &hdr, sizeof hdr);

其中BUILD_BUG_ON在arch\x86\boot\Boot.h文件中定义如下:

  1. #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))  
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))

该宏表示当condition为真时,对其进行两次取反操作,因此将1-2*1=-1作为数组的维数,然而这是不合法的,换言之,若condition为真则将引发编译时错误。使用该宏的目的就是通过编译器来保证启动参数boot_params所占的字节数为4096。随后将Kernel boot sector中的hdr变量拷贝至结构体boot_params的hdr字段中,注意这里的boot_params是一个全局变量,且该变量同样被定义在arch\x86\boot\Main.c文件中。而结构体的类型定义则位于arch\x86\include\asm\Bootparam.h文件中,这里不再将其列出。在拷贝完成后执行如下判断:

  1. if (!boot_params.hdr.cmd_line_ptr &&  
  2.     oldcmd->cl_magic == OLD_CL_MAGIC) {  
  3.     /* Old-style command line protocol. */  
  4.    /*执行语句省略*/  
  5.    。。。。。。  
  6. }  
	if (!boot_params.hdr.cmd_line_ptr &&
	    oldcmd->cl_magic == OLD_CL_MAGIC) {
		/* Old-style command line protocol. */
    /*执行语句省略*/
    。。。。。。
	}

其中OLD_CL_MAGIC在arch\x86\include\asm\Setup.h文件中定义如下:

  1. #define OLD_CL_MAGIC        0xA33F  
#define OLD_CL_MAGIC		0xA33F

该判断语句测试已被拷贝至boot_params的hdr变量中的cmd_line_ptr字段是否为0,并且oldcmd中的cl_magic字段的值是否为0xA33F,若这两者都满足则执行括号内的语句,而由前文描述可知,只有在老版本的内核中cl_magic字段的值才为0xA33F,因此后续的语句实际上是针对老式的内核做相应的调整工作,具体细节不再剖析。

控制台初始化
在完成对启动参数的拷贝之后,接着调用如下函数初始化启动阶段的控制台。

  1.     /* Initialize the early-boot console */  
  2.     console_init();  
    /* Initialize the early-boot console */
    console_init();

在arch\x86\boot\Early_serial_console.c文件中定义如下:

  1. void console_init(void)  
  2. {  
  3.     parse_earlyprintk();  
  4.   
  5.     if (!early_serial_base)  
  6.         parse_console_uart8250();  
  7. }  
void console_init(void)
{
	parse_earlyprintk();

	if (!early_serial_base)
		parse_console_uart8250();
}

而以上两个函数又将对其他函数进行层层嵌套调用,可以在源文件中看到console_init函数调用的子过程整整占据了一个文件的大小,对其中用到的主要函数详细剖析将会花费大量的篇幅,以至深入到细节之中后可能会被弄得晕头转向,所以我在这里首先高屋建瓴地给出这些函数之间的调用关系,在对各个子函数分析完毕之后不妨再回顾一下它们之间的依赖关系,这样就会对控制台的初始化方式有更为深刻的理解。以下是函数之间依赖关系的示意图:


图1

首先从simple_strtoull入手。它位于arch\x86\boot\String.c文件中:

  1. /** 
  2.  * simple_strtoull - convert a string to an unsigned long long 
  3.  * @cp: The start of the string 
  4.  * @endp: A pointer to the end of the parsed string will be placed here 
  5.  * @base: The number base to use 
  6.  */  
  7.   
  8. unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int base)  
/**
 * simple_strtoull - convert a string to an unsigned long long
 * @cp: The start of the string
 * @endp: A pointer to the end of the parsed string will be placed here
 * @base: The number base to use
 */

unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int base)

从以上注释即可看出,该函数主要用于实现将一个字符串转换为无符号长整型。其中cp为待转换字符串的指针,endp则存放指向被解析的字符串的末尾的指针,base是被转换后的数所使用的基底。

  1. unsigned long long result = 0;  /* 存放转换后的结果 */  
  2.   
  3. if (!base)  
  4.     base = simple_guess_base(cp);  
	unsigned long long result = 0;  /* 存放转换后的结果 */

	if (!base)
		base = simple_guess_base(cp);

首先判断base的值是否为0,若是则调用simple_guess_base函数,该函数与simple_strtoull位于同一个文件中且定义如下:

  1. static unsigned int simple_guess_base(const char *cp)  
  2. {  
  3.     if (cp[0] == '0') {  /*待解析的字符串的第一个字符为0*/  
  4.         if (TOLOWER(cp[1]) == 'x' && isxdigit(cp[2]))  
  5.             return 16;  
  6.         else  
  7.             return 8;  
  8.     } else {  /*不为零*/  
  9.         return 10;  
  10.     }  
  11. }  
  12.   
  13. /*其中宏TOLOWER定义如下:*/  
  14. /* Works only for digits and letters, but small and fast */  
  15. #define TOLOWER(x) ((x) | 0x20)     
  16. /*26个英文字符中任意一个字符的大小写之差为32,也即十六进制的0x20*/  
  17. /*使用或运算的好处是无论字符x为大写还是小写,均将其转换为小写字符*/  
static unsigned int simple_guess_base(const char *cp)
{
	if (cp[0] == '0') {  /*待解析的字符串的第一个字符为0*/
		if (TOLOWER(cp[1]) == 'x' && isxdigit(cp[2]))
			return 16;
		else
			return 8;
	} else {  /*不为零*/
		return 10;
	}
}

/*其中宏TOLOWER定义如下:*/
/* Works only for digits and letters, but small and fast */
#define TOLOWER(x) ((x) | 0x20)  
/*26个英文字符中任意一个字符的大小写之差为32,也即十六进制的0x20*/
/*使用或运算的好处是无论字符x为大写还是小写,均将其转换为小写字符*/

函数首先检测待解析的字符串的第一个字符是否为0,为0则该字符串可能是十六进制或八进制,反之则为十进制。若cp[0]为0,那么将第二个字符转换为小写形式判断是否为字符'x',并调用isxdigit()函数对cp[2]进行检测,isxdigit()在文件arch\x86\boot\Ctype.h文件中定义如下:

  1. static inline int isxdigit(int ch)  
  2. {  
  3.     if (isdigit(ch))  
  4.         return true;  
  5.   
  6.     if ((ch >= 'a') && (ch <= 'f'))  
  7.         return true;  
  8.   
  9.     return (ch >= 'A') && (ch <= 'F');  
  10. }  
  11.   
  12. /*isdigit函数定义如下:*/  
  13. static inline int isdigit(int ch)  
  14. {  
  15.     return (ch >= '0') && (ch <= '9');  /*当条件 '0'≤ch≤'9' 成立时返回真*/  
  16. }  
static inline int isxdigit(int ch)
{
	if (isdigit(ch))
		return true;

	if ((ch >= 'a') && (ch <= 'f'))
		return true;

	return (ch >= 'A') && (ch <= 'F');
}

/*isdigit函数定义如下:*/
static inline int isdigit(int ch)
{
    return (ch >= '0') && (ch <= '9');  /*当条件 '0'≤ch≤'9' 成立时返回真*/
}

所以当被测试的参数ch满足条件 '0'≤ch≤'9','a'≤ch≤'f'或是'A'≤ch≤'F'时,isxdigit返回真,否则返回假。根据以上分析可知,simple_guess_base函数根据如下规则判断待解析的字符串的基底:当cp[0]='0'时,进一步判断cp[1]是否等于'x'以及cp[2]是否为'0'~'9'或'a'~'f'(其中'a'~'f'也可以为大写),若这两个条件满足则返回16,表明字符串按16进制进行转换,反之则返回8;若cp[0]≠'0'则返回10。在simple_strtoull函数中确保待转换的基底非零后,继续执行:

  1.     if (base == 16 && cp[0] == '0' && TOLOWER(cp[1]) == 'x')  
  2.         cp += 2;  /*当待转换的基底为16,且cp[0]为'0',cp[1]为'x'或是'X',那么将当前指针cp指向其后的第二个字符*/  
  3.   
  4.     while (isxdigit(*cp)) {  /*转换之前首先判断当前字符是否在'0'~'f'之间*/  
  5.         unsigned int value;  
  6.   
  7.       /*判断是否在'0'~'9'之间,条件成立则减去'0'得到对应数值 
  8.         若不成立则做相应修正——减去'a'并加上10*/  
  9.         value = isdigit(*cp) ? *cp - '0' : TOLOWER(*cp) - 'a' + 10;  
  10.         if (value >= base)  /*若得到的数值不小于基底则退出*/  
  11.             break;  
  12.         result = result * base + value;  /*将结果乘上基底并累加当前得到的数值*/  
  13.         cp++;  /*使字符指针自增*/  
  14.     }  
  15.     if (endp)  
  16.         *endp = (char *)cp;  /*将第一个未被转换的字符的指针存入*endp中*/  
  17.   
  18.     return result;  
    if (base == 16 && cp[0] == '0' && TOLOWER(cp[1]) == 'x')
        cp += 2;  /*当待转换的基底为16,且cp[0]为'0',cp[1]为'x'或是'X',那么将当前指针cp指向其后的第二个字符*/

    while (isxdigit(*cp)) {  /*转换之前首先判断当前字符是否在'0'~'f'之间*/
        unsigned int value;

      /*判断是否在'0'~'9'之间,条件成立则减去'0'得到对应数值
        若不成立则做相应修正——减去'a'并加上10*/
        value = isdigit(*cp) ? *cp - '0' : TOLOWER(*cp) - 'a' + 10;
        if (value >= base)  /*若得到的数值不小于基底则退出*/
            break;
        result = result * base + value;  /*将结果乘上基底并累加当前得到的数值*/
        cp++;  /*使字符指针自增*/
    }
    if (endp)
        *endp = (char *)cp;  /*将第一个未被转换的字符的指针存入*endp中*/

    return result;

根据上述分析,函数的作用正如其函数名所示,用于将字符串转换为相应的无符号长整型,不过待转换的进制不能超过16。根据图1所示,接着对cmdline_find_option具体分析,其实现位于arch\x86\boot\Boot.h文件中:

  1. static inline int cmdline_find_option(const char *option, char *buffer, int bufsize)  
  2. {  
  3.     return __cmdline_find_option(boot_params.hdr.cmd_line_ptr, option, buffer, bufsize);  
  4. }  
static inline int cmdline_find_option(const char *option, char *buffer, int bufsize)
{
	return __cmdline_find_option(boot_params.hdr.cmd_line_ptr, option, buffer, bufsize);
}

cmdline_find_option实际是对__cmdline_find_option的一层封装,后者在arch\x86\boot\Cmdline.c文件中定义如下:

  1. /* 
  2.  * Find a non-boolean option, that is, "option=argument".  In accordance 
  3.  * with standard Linux practice, if this option is repeated, this returns 
  4.  * the last instance on the command line. 
  5.  * 
  6.  * Returns the length of the argument (regardless of if it was 
  7.  * truncated to fit in the buffer), or -1 on not found. 
  8.  */  
  9. int __cmdline_find_option(u32 cmdline_ptr, const char *option, char *buffer, int bufsize)  
/*
 * Find a non-boolean option, that is, "option=argument".  In accordance
 * with standard Linux practice, if this option is repeated, this returns
 * the last instance on the command line.
 *
 * Returns the length of the argument (regardless of if it was
 * truncated to fit in the buffer), or -1 on not found.
 */
int __cmdline_find_option(u32 cmdline_ptr, const char *option, char *buffer, int bufsize)


由注释可知,这个函数的作用是查找一个非布尔类型的选项,选项的格式为"option=argument"。与标准Linux实践一致,如果这个选项重复出现,那么将返回命令行的最后一个实例。函数的返回值为argument的长度,而不管这个值在缓冲区中是否被截断,返回-1表示对应的选项未找到。如前所示,在cmdline_find_option中调用该函数时,填充的第一个参数是已被拷贝至boot_params中的hdr变量的cmd_line_ptr字段,而该字段确实存放指向内核命令行的指针,这可以在系统启动(二)的表格中看到其概要解释,并且选项的格式也和前文中所举的实例相同。下面是__cmdline_find_option的具体实现:

  1.     addr_t cptr;  /* unsigned int */  
  2.     char c;  
  3.     int len = -1;  
  4.     const char *opptr = NULL;  
  5.     char *bufptr = buffer;  /*bufptr指向存放argument的缓冲区*/  
  6.     enum {  
  7.         st_wordstart,    /* Start of word/after whitespace */  
  8.         st_wordcmp,    /* Comparing this word */  
  9.         st_wordskip,    /* Miscompare, skip */  
  10.         st_bufcpy    /* Copying this to buffer */  
  11.     } state = st_wordstart;  
  12.   
  13.     /*若函数的形参cmdline_ptr为0或超出第一个MB的内存空间,那么返回-1指示不存在命令行或不可访问*/  
  14.     if (!cmdline_ptr || cmdline_ptr >= 0x10 0000)    
  15.         return -1;    /* No command line, or inaccessible */   
  16.       
  17.     /*取cmdline_ptr中最右端的4个位*/  
  18.     cptr = cmdline_ptr & 0xf;  
  19.     set_fs(cmdline_ptr >> 4);  
    addr_t cptr;  /* unsigned int */
    char c;
    int len = -1;
    const char *opptr = NULL;
    char *bufptr = buffer;  /*bufptr指向存放argument的缓冲区*/
    enum {
        st_wordstart,    /* Start of word/after whitespace */
        st_wordcmp,    /* Comparing this word */
        st_wordskip,    /* Miscompare, skip */
        st_bufcpy    /* Copying this to buffer */
    } state = st_wordstart;

    /*若函数的形参cmdline_ptr为0或超出第一个MB的内存空间,那么返回-1指示不存在命令行或不可访问*/
    if (!cmdline_ptr || cmdline_ptr >= 0x10 0000)  
        return -1;    /* No command line, or inaccessible */ 
    
    /*取cmdline_ptr中最右端的4个位*/
    cptr = cmdline_ptr & 0xf;
    set_fs(cmdline_ptr >> 4);

上述定义的枚举类型在其后的“选项”查找中是一大亮点,而set_fs函数在arch\x86\boot\Boot.h文件中实现如下:

  1. static inline void set_fs(u16 seg)  
  2. {  
  3.     asm volatile("movw %0,%%fs" : : "rm" (seg));  
  4. }  
static inline void set_fs(u16 seg)
{
	asm volatile("movw %0,%%fs" : : "rm" (seg));
}

set_fs函数所实现的功能是将形参seg移入fs寄存器,注意fs寄存器为16位,所以set_fs(cmdline_ptr>>4)这条语句实际上只是将cmdline_ptr>>4所得结果的低端的16位存入fs寄存器中,之所以要这么做,是因为当前仍然处于实模式,对内存的寻址方式是通过seg*16+offset的方式实现的,其中的offset正是变量cptr中的值。设置好段寄存器及偏移量之后,紧接着便是读取命令行字符串并做适当的处理以返回所需的结果,处理的方式是通过一个while循环来实现的,循环头如下所示:

  1.     while (cptr < 0x10000 && (c = rdfs8(cptr++)))  
    while (cptr < 0x10000 && (c = rdfs8(cptr++)))

之所以在while循环体内需要判断cptr<0x10000是为了确保整个命令行在一个64KB的段内,其后的rdfs8()函数在arch\x86\boot\Boot.h文件中定义如下:

  1. static inline u8 rdfs8(addr_t addr)  
  2. {  
  3.     u8 v;  
  4.     /* 等价于 movb %fs:addr , v */  
  5.     asm volatile("movb %%fs:%1,%0" : "=q" (v) : "m" (*(u8 *)addr));  
  6.     return v;  
  7. }  
static inline u8 rdfs8(addr_t addr)
{
    u8 v;
    /* 等价于 movb %fs:addr , v */
    asm volatile("movb %%fs:%1,%0" : "=q" (v) : "m" (*(u8 *)addr));
    return v;
}

可以看到这个函数实际上就是读取fs寄存器所指向的段中偏移量为addr所在的内存字节,而根据前述分析,逻辑地址fs:cptr确实已被正确设置为指向命令行所在的内存。所以循环头每次都读取当前指向的内存字节并使偏移量自增,接着便将所得到的字节交由循环体进行处理,循环体中的语句定义如下:

  1.         switch (state) {  
  2.         case st_wordstart:  /* Start of word/after whitespace */  
  3.             if (myisspace(c))  /*判断当前字符是否 <= ' '*/  
  4.                 break;  /*若是则退出switch分支结构读取下一个内存字节*/  
  5.   
  6.             /* else */  
  7.             state = st_wordcmp;  
  8.             opptr = option;  /*option为函数形参,指向待查找的选项*/  
  9.             /* fall through */  
  10.   
  11.         case st_wordcmp:  /* Comparing this word */  
  12.             if (c == '=' && !*opptr) {  /*判断当前字符是否为'='以及opptr所指向的内存单元是否非空*/  
  13.                 len = 0;  /*若是则准备将选项所对应的参数拷贝至对应的缓冲区*/  
  14.                 bufptr = buffer;  
  15.                 state = st_bufcpy;  
  16.             } else if (myisspace(c)) {  /*判定当前字符是否<=' '*/  
  17.                 state = st_wordstart;  /*若是则设置相应的状态值,在st_wordstart中跳过一系列不相关字符*/  
  18.             } else if (c != *opptr++) {  /*将当前字符与opptr所指向的选项比较*/  
  19.                 state = st_wordskip;  /*若命令行中的当前选项与所要查找的选项不符,则设置state为st_wordskip*/  
  20.             }  /*若当前字符均不满足之前的条件,说明当前字符正确匹配,继续该过程*/  
  21.             break;  
  22.   
  23. static inline int myisspace(u8 c)  
  24. {  
  25.     return c <= ' ';    /* Close enough approximation */  
  26. }  
        switch (state) {
        case st_wordstart:  /* Start of word/after whitespace */
            if (myisspace(c))  /*判断当前字符是否 <= ' '*/
                break;  /*若是则退出switch分支结构读取下一个内存字节*/

            /* else */
            state = st_wordcmp;
            opptr = option;  /*option为函数形参,指向待查找的选项*/
            /* fall through */

        case st_wordcmp:  /* Comparing this word */
            if (c == '=' && !*opptr) {  /*判断当前字符是否为'='以及opptr所指向的内存单元是否非空*/
                len = 0;  /*若是则准备将选项所对应的参数拷贝至对应的缓冲区*/
                bufptr = buffer;
                state = st_bufcpy;
            } else if (myisspace(c)) {  /*判定当前字符是否<=' '*/
                state = st_wordstart;  /*若是则设置相应的状态值,在st_wordstart中跳过一系列不相关字符*/
            } else if (c != *opptr++) {  /*将当前字符与opptr所指向的选项比较*/
                state = st_wordskip;  /*若命令行中的当前选项与所要查找的选项不符,则设置state为st_wordskip*/
            }  /*若当前字符均不满足之前的条件,说明当前字符正确匹配,继续该过程*/
            break;

static inline int myisspace(u8 c)
{
    return c <= ' ';    /* Close enough approximation */
}

整个循环体使用一个switch分支语句来处理读取到的字节,总共分为4种情况,每种情况都由之前所定义的枚举值加以区分,上述代码段示出了前两种情况。

  • 在st_wordstart情形中,根据当前字符是否≤' '从而跳过一些列不相关字符——主要是空格符,其后将状态值state赋值为st_wordcmp并将opptr赋值为option,这里的option是该函数的形参,由调用者决定需要在命令行中查找的选项。由于此时当前字符可能与形参的第一个字符匹配,因此直接陷入st_wordcmp情况中。
  • 再次注意选项的格式为"option=argument",所以进入st_wordcmp情形后首先判断当前字符是否为'=',以及opptr指向的内存单元是否存放'\0'——表明已经匹配完成,如果这两者都满足那么可以进行其后的值拷贝工作——将状态state设置为st_bufcpy。可以看到在第三个条件分支中有如下判断语句"c!=*opptr++",若此条件成立,那么说明命令行中的当前选项并非所要查找的,因此将state设置为st_wordskip,并且由于此时opptr已经执行自增操作,所以当下次陷入此种情况时仍需指向option所在的首地址,这也就是为什么要在st_wordstart情况的最后执行"opptr=option"赋值语句的原因所在。

我们接着看下面的两种情形:

  1.         case st_wordskip:  /* Miscompare, skip */  
  2.             if (myisspace(c))  
  3.                 state = st_wordstart;  
  4.             break;  
  5.   
  6.         case st_bufcpy:  /* Copying this to buffer */  
  7.             if (myisspace(c)) {  
  8.                 state = st_wordstart;  
  9.             } else {  
  10.                 if (len < bufsize-1)  
  11.                     *bufptr++ = c;  /*将当前字符存入bufptr所指向的缓冲区,并将指针值自增*/  
  12.                 len++;  /*正确记录缓冲区中字符串的长度*/  
  13.             }  
  14.             break;  
  15.         }  
        case st_wordskip:  /* Miscompare, skip */
            if (myisspace(c))
                state = st_wordstart;
            break;

        case st_bufcpy:  /* Copying this to buffer */
            if (myisspace(c)) {
                state = st_wordstart;
            } else {
                if (len < bufsize-1)
                    *bufptr++ = c;  /*将当前字符存入bufptr所指向的缓冲区,并将指针值自增*/
                len++;  /*正确记录缓冲区中字符串的长度*/
            }
            break;
        }
  • 如枚举值的名称st_wordskip所示,这种情形主要用于跳过命令行中的其他选项。在该情形中仍需首先判断当前字符是否≤' ',以确保在跳过一个选项之后重新遇到不相关字符时将state设置为st_wordstart,因为选项的匹配是通过st_wordstart陷入第二种情形st_wordcmp所完成的。
  • 最后一种情形st_bufcpy主要用于将option所对应的值拷贝至用于存放结果的缓冲区中,注意拷贝之前首先判断字符的长度len<bufsize-1是否成立,若成立则不再存储当前字符,这意味着字符串的长度如果超出缓冲区的大小,那么将会发生截断,然而这种情形下仍能确保len正确记录字符串的长度。

然而在完成拷贝之后仍将执行循环,除非循环头中的判断条件cptr < 0x10000 && (c = rdfs8(cptr++))不成立,这意味着所寻址的内存已超出64KB大小的段,或是当前读到的字符为'\0'——标识命令行的结束。这种处理方式正如该函数的注释所示,如果一个选项在命令行中重复出现,那么将返回最后一个选项所对应的值。退出循环之后,这个函数如下做一些适当的善后工作:

  1.     if (bufsize)  
  2.         *bufptr = '\0';  
  3.   
  4.     return len;  
    if (bufsize)
        *bufptr = '\0';

    return len;

在缓冲区中作为结果的字符串后存入'\0'字符,并返回整个字符串的大小。

以上是对simple_strtoull以及cmdline_find_option函数的简要剖析。接下来在对early_serial_init,parse_earlyprintk,probe_baud,parse_console_uart8250这几个函数剖析之前,首先需要明白串行端口通信的基本概念。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>