進入main函數後,Linux內核執行硬件檢測及初始化工作,即便在此之前BIOS已經對大部分硬件設備進行了相應的初始化,然而Linux並不依賴於BIOS,而是以特定的方式重新初始化相關設備,這樣做的目的是爲了增強可移植性及健壯性。需要強調的一點是,此時C語言仍舊運行在實模式狀態下。
拷貝啓動參數
進入arch\x86\boot\Main.c文件的main函數後,做的第一件事就是將從Kernel boot sector中偏移0x1f1處起始的hdr頭變量拷貝至指定內存中。
- /* First, copy the boot header into the "zeropage" */
- copy_boot_params();
/* First, copy the boot header into the "zeropage" */
copy_boot_params();
可以看出該指定內存被稱爲“零頁”,之所以這麼稱呼,是因爲在保護模式下啓用分頁機制後,它是整個內存單元的第一個分頁。注意啓動頭(boot header)與系統啓動(二)中的安裝頭(setup header)均指的是hdr變量。下面是它的具體實現:
- /*
- * 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;
/*
* 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文件中定義如下:
- #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。接着往下執行:
- BUILD_BUG_ON(sizeof boot_params != 4096);
- 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文件中定義如下:
- #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文件中,這裏不再將其列出。在拷貝完成後執行如下判斷:
- if (!boot_params.hdr.cmd_line_ptr &&
- oldcmd->cl_magic == OLD_CL_MAGIC) {
- /* Old-style command line protocol. */
- /*執行語句省略*/
- 。。。。。。
- }
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文件中定義如下:
- #define OLD_CL_MAGIC 0xA33F
#define OLD_CL_MAGIC 0xA33F
該判斷語句測試已被拷貝至boot_params的hdr變量中的cmd_line_ptr字段是否爲0,並且oldcmd中的cl_magic字段的值是否爲0xA33F,若這兩者都滿足則執行括號內的語句,而由前文描述可知,只有在老版本的內核中cl_magic字段的值才爲0xA33F,因此後續的語句實際上是針對老式的內核做相應的調整工作,具體細節不再剖析。
控制檯初始化
在完成對啓動參數的拷貝之後,接着調用如下函數初始化啓動階段的控制檯。
- /* Initialize the early-boot console */
- console_init();
/* Initialize the early-boot console */
console_init();
在arch\x86\boot\Early_serial_console.c文件中定義如下:
- void console_init(void)
- {
- parse_earlyprintk();
- if (!early_serial_base)
- parse_console_uart8250();
- }
void console_init(void)
{
parse_earlyprintk();
if (!early_serial_base)
parse_console_uart8250();
}
而以上兩個函數又將對其他函數進行層層嵌套調用,可以在源文件中看到console_init函數調用的子過程整整佔據了一個文件的大小,對其中用到的主要函數詳細剖析將會花費大量的篇幅,以至深入到細節之中後可能會被弄得暈頭轉向,所以我在這裏首先高屋建瓴地給出這些函數之間的調用關係,在對各個子函數分析完畢之後不妨再回顧一下它們之間的依賴關係,這樣就會對控制檯的初始化方式有更爲深刻的理解。以下是函數之間依賴關係的示意圖:
圖1
首先從simple_strtoull入手。它位於arch\x86\boot\String.c文件中:
- /**
- * 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)
/**
* 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是被轉換後的數所使用的基底。
- unsigned long long result = 0; /* 存放轉換後的結果 */
- if (!base)
- base = simple_guess_base(cp);
unsigned long long result = 0; /* 存放轉換後的結果 */
if (!base)
base = simple_guess_base(cp);
首先判斷base的值是否爲0,若是則調用simple_guess_base函數,該函數與simple_strtoull位於同一個文件中且定義如下:
- 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爲大寫還是小寫,均將其轉換爲小寫字符*/
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文件中定義如下:
- 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' 成立時返回真*/
- }
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函數中確保待轉換的基底非零後,繼續執行:
- 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;
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文件中:
- 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);
- }
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文件中定義如下:
- /*
- * 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)
/*
* 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的具體實現:
- 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);
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文件中實現如下:
- static inline void set_fs(u16 seg)
- {
- asm volatile("movw %0,%%fs" : : "rm" (seg));
- }
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循環來實現的,循環頭如下所示:
- while (cptr < 0x10000 && (c = rdfs8(cptr++)))
while (cptr < 0x10000 && (c = rdfs8(cptr++)))
之所以在while循環體內需要判斷cptr<0x10000是爲了確保整個命令行在一個64KB的段內,其後的rdfs8()函數在arch\x86\boot\Boot.h文件中定義如下:
- 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;
- }
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確實已被正確設置爲指向命令行所在的內存。所以循環頭每次都讀取當前指向的內存字節並使偏移量自增,接着便將所得到的字節交由循環體進行處理,循環體中的語句定義如下:
- 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 (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"賦值語句的原因所在。
我們接着看下面的兩種情形:
- 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;
- }
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'——標識命令行的結束。這種處理方式正如該函數的註釋所示,如果一個選項在命令行中重複出現,那麼將返回最後一個選項所對應的值。退出循環之後,這個函數如下做一些適當的善後工作:
- if (bufsize)
- *bufptr = '\0';
- return len;
if (bufsize)
*bufptr = '\0';
return len;
在緩衝區中作爲結果的字符串後存入'\0'字符,並返回整個字符串的大小。
以上是對simple_strtoull以及cmdline_find_option函數的簡要剖析。接下來在對early_serial_init,parse_earlyprintk,probe_baud,parse_console_uart8250這幾個函數剖析之前,首先需要明白串行端口通信的基本概念。