系統啓動篇(三)[上]

進入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這幾個函數剖析之前,首先需要明白串行端口通信的基本概念。

發佈了6 篇原創文章 · 獲贊 3 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章