KERNEL DECOMPRESS

1.1 解壓縮程序

LINUX 核心在構建過程中會被壓縮,在執行之前必須先解壓縮。 核心解壓縮程序的程式碼位於 linux/arch/x86/boot/compressed。

核心壓縮映像檔的生成規則。

linux/arch/x86/boot/compressed/Makefile
01 $(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/head_$(BITS).o $(obj)/misc.o $(obj)/string.o $(obj)/cmdline.o $(obj)/early_serial_console.o $(obj)/piggy.o FORCE
02     $(call if_changed,ld)
03     @:

核心壓縮映像檔的入口點位於 linux/arch/x86/boot/compressed/head_32.S 的 startup_32。

linux/arch/x86/boot/compressed/vmlinux.lds
01 ENTRY(startup_32)

核心映像解壓縮程式是由六個檔案組成。其中,piggy.c 是一個包含核心壓縮檔的映像程式。 其他的檔案則是用來解開核心壓縮檔。

檔案名稱說明
head_32.S解壓縮起頭程式。
misc.c解壓縮相關函式庫。misc.c 會再將 linux/lib/decompress_inflate.c 包含進來
string.c字串處理函式庫。
cmdline.c命令列執行程式。
early_serial_console.c早期串列埠初始化程式。
piggy.c資料壓縮程式,負責將 vmlinux.bin.gz 製作成一個 ELF 格式的壓縮資料映像檔。
linux/lib/decompress_inflate.c這是解壓縮函式庫。
linux/lib/zlib_inflate/inftrees.c這是解壓縮函式庫。
linux/lib/zlib_inflate/inffast.c這是解壓縮函式庫。
linux/lib/zlib_inflate/inflate.c這是解壓縮函式庫。

1.2 解壓縮起頭程式

解壓縮起頭程式將壓縮核心重定址,並呼叫解壓縮函式,將核心解壓縮。 最後跳到解壓縮後的核心起頭處,運行 LINUX。


1.2.1 核心映像重定址

計算重定址位址,將整個映像複製到重定址位址。 重定址的位址是由 mkpiggy 計算,並寫入 piggy.S,並由連結器將 piggy.o 連結成自我解壓縮核心映像。 mkpiggy 會從壓縮檔取得所需要的解壓縮後的體積值,並將重定址位址設定到解壓縮後的記憶體後面,以便將核心映像解壓縮到前面的記憶體。

linux/arch/x86/boot/compressed/head_32.S
01     .text
02 ENTRY(startup_32)
03     cld
04     testb    $(1<<6), BP_loadflags(%esi)
05     jnz    1f
06     cli
07     movl    $__BOOT_DS, %eax
08     movl    %eax, %ds
09     movl    %eax, %es
10     movl    %eax, %fs
11     movl    %eax, %gs
12     movl    %eax, %ss
13 1:  leal    (BP_scratch+4)(%esi), %esp
14     call    1f
15 1:  popl    %ebp
16     subl    $1b, %ebp
17     movl    %ebp, %ebx
18     movl    BP_kernel_alignment(%esi), %eax
19     decl    %eax
20     addl    %eax, %ebx
21     notl    %eax
22     andl    %eax, %ebx
23     addl    $z_extract_offset, %ebx
24     leal    boot_stack_end(%ebx), %esp
25     pushl    $0
26     popfl
27     pushl    %esi
28     leal    (_bss-4)(%ebp), %esi
29     leal    (_bss-4)(%ebx), %edi
30     movl    $(_bss - startup_32), %ecx
31     shrl    $2, %ecx
32     std
33     rep    movsl
34     cld
35     popl    %esi
36     leal    relocated(%ebx), %eax
37     jmp    *%eax
38 ENDPROC(startup_32)

行號說明
01宣告區段為程式碼區段,。
02宣告此區段的入口點,其符號名稱為 startup_32。
03設定 CPU 資料複製方向,cld 表示資料拷貝時索引暫存器是遞增。
04當節區值保持旗號為一,表示要保持目前所有節區暫存器值,程式跳往下一個標籤 1:,f 表示 forward。
05關閉外部中斷,因為後面的動作不能被中斷打岔。
06取得資料節區的描述子索引值,__BOOT_DS 的值是 24,即全域描述子表的第四個描述子。此常數定義在 linux/arch/include/asm/segment.h。
08設定資料節區暫存器, ds 值為 24。
09設定額外節區暫存器, es 值為 24。
10設定旗號節區暫存器, fs 值為 24。
11設定圖形節區暫存器, gs 值為 24。
12設定堆疊節區暫存器, ss 值為 24。
13將 BP_scratch+4 位址的值存入堆疊指標,即 使用 BP_scratch+4+%esi 的值為堆疊頂端。BP_scratch 是 boot_params 結構第 484 位元組的位址。
14呼叫 1f,這是藉由呼叫取得下一個標籤 1: 的位址值。
15第二個標籤 1: 是一個特別功能的標籤,用來計算重定址的偏移位址值。
從堆疊取得 1: 的位址值,這是前一行呼叫指令存入對碟的指令位址,即標籤 1: 的位址值。
16將標籤 1: 的位址值(ebp)減去標籤 1: 的編譯位址值($1b)。這兩個值雖然同樣指向標籤 1:,前一個是程式執行時的標籤 1: 位址值,後一個是編譯器安排的位址值。
17~22計算核心載入的實際位址。算法是 address=(address+(align-1))&(~align),align=0x10。
17將標籤 1: 的執行位址與標籤位址差異值存入 ebx。
18取得 boot_params 的 BP_kernel_alignment 核心對齊值,存入 eax。可能是 0x10。
19將核心對齊值減一。可能是 0x0F。
20ebx 加上 eax,即核心對齊值加上標籤 1: 的執行位址與標籤位址差異值。這是為了將值延伸出對齊記憶體外,以便用和邏輯去除尾數。
21取 eax 的 1 的補數。可能是 0xFFFFFFF0。
22取 eax 和 ebx 的和邏輯,使重定址位址值對齊核心對齊記憶體 0x10。
23計算解壓縮的重定址位址,ebx 加上解壓縮位址偏移值 z_extract_offset,這是由 mkpiggy 計算出來,並寫入 piggy.S 中的參數值。
24將堆疊指標設定到 boot_stack_end 加重定址位址,即新的 boot_stack_end 的位址。
25~26設定 flags 暫存器值 0。
27~35將壓縮核心複製到緩衝區尾部。
27將 esi 存入堆疊,因為後面的程序會用到 esi 暫存器。 方法是取得原始映像最尾端的位址,即未初始化記憶體的最前端。再取得重定址映像的最尾端位址。 再計算未初始化記憶體到映像開始的記憶體長度,做為資料複製的計數器。再以遞減的方式複製資料。
28將 (_bss-4)(%ebp) 的值存入 esi,這是原始映像位址的最尾端。
29將 (_bss-4)(%ebx) 的值存入 edi,這是重定址的位址的最尾端。
30計算 _bss 到 startup_32 的長度,即映像的長度,單位是位元組。
31將映像長度轉換成以雙字組為單位,。
32設定資料複製方向為遞減,。
33複製資料指令,將資料從 ds:esi 複製到 es:edi,每次複製資料長度為雙字組(long)。
34回復資料複製方向為遞增,這是 CPU 的初始值。
35從堆疊回復 esi 的值 ,。
36將標籤 relocated 的新位址存入 eax,。
37跳到新的 relocated 標籤位址,準備進行核心解壓縮。

1.2.2 核心映像解壓縮

執行到標籤 relocated 時,程式是已經被複製到重定址記憶體上,並進行解壓縮程序。 解壓縮後的核心映像會寫到記憶體的前端,可能是 0x000100000??。 接著,跳躍到解壓縮後的核心映像起始處,運行 LINUX 核心。

linux/arch/x86/boot/compressed/head_32.S
01     .text
02 relocated:
03     xorl   %eax, %eax
04     leal   _bss(%ebx), %edi
05     leal   _ebss(%ebx), %ecx
06     subl   %edi, %ecx
07     shrl   $2, %ecx
08     rep    stosl
09     leal   _got(%ebx), %edx
10     leal   _egot(%ebx), %ecx
11 1:  cmpl   %ecx, %edx
12     jae    2f
13     addl   %ebx, (%edx)
14     addl   $4, %edx
15     jmp    1b
16 2:  leal   z_extract_offset_negative(%ebx), %ebp
17     pushl  %ebp
18     pushl  $z_input_len
19     leal   input_data(%ebx), %eax
20     pushl  %eax
21     leal   boot_heap(%ebx), %eax
22     pushl  %eax
23     pushl  %esi
24     call   decompress_kernel
25     addl   $20, %esp
26     leal   z_output_len(%ebp), %edi
27     movl   %ebp, %ebx
28     subl   $LOAD_PHYSICAL_ADDR, %ebx
29     jz     2f
30 1:  subl   $4, %edi
31     movl   (%edi), %ecx
32     testl  %ecx, %ecx
33     jz     2f
34     addl   %ebx, -__PAGE_OFFSET(%ebx, %ecx)
35     jmp    1b
36 2:
37     xorl   %ebx, %ebx
38     jmp    *%ebp
39     .bss
40     .balign 4
41 boot_heap:
42     .fill BOOT_HEAP_SIZE, 1, 0
43 boot_stack:
44     .fill BOOT_STACK_SIZE, 1, 0
45 boot_stack_end:

行號說明
01宣告後面內容為程式碼區段,。
02已重定址的符號,程式執行到此表示已經完成重定址,這是在重定址後的新執行位址。
03清除 eax 為 0,。
04將 _bss(%ebx) 的值存入 edi,這是 bss 的開始位址。
05將 _ebss(%ebx) 的值存入 ecx,這是 bss 的結束位址。
06ecx=ecx-edi,這是 bss 的體積,單位為雙字組。
07將 ecx 轉換成位元組為單位,即 ecx=ecx*4。
08複製資料,。
09將 _got(%ebx) 的值存入 edi,這是 got 的開始位址。
10將 _egot(%ebx) 的值存入 ecx,這是 got 的結束位址。
11~15當 _got 等於 _egot,表示 got 不存在。此時重新計算 _got 的位址值,再回到判斷式,重新比較一次,應該會過關。
16取得解壓縮的負偏移位址,存入 ebp。此值應該是 -0x50D000。
17將解壓縮的負偏移位址(ebp)存入堆疊,這是解壓縮函式的第五個參數。
18將解壓縮長度 4769350 存入堆疊,這是解壓縮函式的第四個參數。
19~20取得輸入資料的位址,就是壓縮核心 vmlinux.bin.gz 的起始位址,存入堆疊,這是解壓縮函式的第三個參數。
21~22取得 boot_heap 位址值,存入堆疊,這是解壓縮函式的第二個參數。這是 HEAP 位址值。
23將 esi 存入堆疊,esi 指向 setup.bin 的 boot_params 的位址,這是解壓縮函式的第一個參數。
24呼叫解壓縮函式,進行核心解壓縮程序。
25回復堆疊到解壓縮參數存入之前的位址,即前面五個參數之前的堆疊位址。
26取得解壓縮後的映像體積值的變數位址,此變數值是 9996376,定義在 linux/arch/x86/boot/compressed/piggy.S。
27把 ebp 的值設定給 ebx,此時的 ebp 為 -0x50D000??。
28~29當 ebx 是 0x100000,解壓縮後的核心不需要重定址,可以直接啟動核心。LOAD_PHYSICAL_ADDR 定義在 linux/arch/x86/include/asm/boot.h。
30將位址減四,指向 z_output_len ??,9996372。
31將 z_output_len 9996372 存入 ecx。??
32~33當 ecx 的值為 0,跳躍到下一個標籤 "2:"。
34???。看不懂,再查一下。感覺是要把核心位址(ebx)透過分頁功能調整到 0x100000??。
35跳回上一個標籤 "1:",即第 30 行,繼續執行重定址程序。
37清除 ebx 的值。
38跳躍到 ebp 所指向的位址,即解壓縮後的 LINUX 核心起始位址。這是執行 LINUX 核心前的最後一個指令。核心啟動參數怎麼傳進到 LINUX 核心??。
39宣告為 bss 區段。
40設定對齊 16 位元組的邊界,。
41~42HEAP區段的體積值為 BOOT_HEAP_SIZE。
43~44堆疊區域體積為 BOOT_STACK_SIZE。
45堆疊頂端符號 boot_stack_end。

1.3 核心壓縮映像檔

mkpiggy 從核心壓縮檔 vmlinux.bin.gz 取得資訊,用以產生 piggy.S。 piggy.S 再將壓縮檔和相關資訊形成一個核心壓縮資料檔 piggy.o。

linux/arch/x86/boot/compressed/piggy.S
01 .section ".rodata..compressed","a",@progbits
02 .globl z_input_len
03 z_input_len = 4769350
04 .globl z_output_len
05 z_output_len = 9996376
06 .globl z_extract_offset
07 z_extract_offset = 0x50d000
08 .globl z_extract_offset_negative
09 z_extract_offset_negative = -0x50d000
10 .globl input_data, input_data_end
11 input_data:
12 .incbin "arch/x86/boot/compressed/vmlinux.bin.gz"
13 input_data_end:

行號說明
01宣告區段名稱 .rodata..compressed。a 的意義是 alocatable,會紀錄在 區段檔頭的 sh_flags。progbits 表示區段內容的格式和意義由程式決定。
02~03將符號 z_input_len 引出,z_input_len 是壓縮檔的體積,為 4769350 位元組。
04~05將符號 z_output_len 引出,z_output_len 是解壓縮後的體積,為 9996376 位元組。
06~07解壓縮位址偏移值,0x50d000 這是用來計算壓縮檔的重定址位址。
08~09負解壓縮偏移位址,-0x50d000,是解壓縮位置偏移值的負值。
10~13引出 input_data 和 input_data_end,input_data 是壓縮檔的起始位址,input_data_end 是壓縮檔的結束位址。

1.4 核心解壓縮函式

將核心映像從壓縮檔,解成 ELF 檔。再從 ELF 格式映像轉成二進位映像。

linux/arch/x86/boot/compressed/misc.c
01 asmlinkage void decompress_kernel(void *rmode, memptr heap,
02                                   unsigned char *input_data,
03                                   unsigned long input_len,
04                                   unsigned char *output){
05     real_mode = rmode;
06     if (cmdline_find_option_bool("quiet")) quiet = 1;
07     if (cmdline_find_option_bool("debug")) debug = 1;
08     if (real_mode->screen_info.orig_video_mode == 7) {
09         vidmem = (char *) 0xb0000;
10         vidport = 0x3b4;
11     } else {
12         vidmem = (char *) 0xb8000;
13         vidport = 0x3d4;
14     }
15     lines = real_mode->screen_info.orig_video_lines;
16     cols = real_mode->screen_info.orig_video_cols;
17     console_init();
18     if (debug) putstr("early console in decompress_kernel\n");
19     free_mem_ptr     = heap;
20     free_mem_end_ptr = heap + BOOT_HEAP_SIZE;
21     if ((unsigned long)output & (MIN_KERNEL_ALIGN - 1))
22         error("Destination address inappropriately aligned");
23     if (heap > ((-__PAGE_OFFSET-(128<<20)-1) & 0x7fffffff))
24         error("Destination address too large");
25     if (!quiet) putstr("\nDecompressing Linux... ");
26     decompress(input_data, input_len, NULL, NULL, output, NULL, error);
27     parse_elf(output);
28     if (!quiet) putstr("done.\nBooting the kernel.\n");
29     return;
30 }

行號說明
05real_mode 指向 setup.bin 的 boot_params 結構,。
06當命令列的參數出現 quiet,設定 quiet 為一,表示要抑制警告訊息。
07當命令列的參數出現 debug,設定 debug 為一,表示要致能偵錯。
08~14當啟動參數的螢幕資訊的原始視訊模式值為七,表示使用圖形模式,否則為文字模式。
圖形模式的視訊記憶體範圍是 0xb0000~0xcffff,文字模式的記憶體範圍是 0xb8000~0xbffff。
15取得視訊列數。
16取得視訊行數。
17初始化操控台,用來顯示偵錯訊息。
18當 debug 為一,顯示解壓縮核心的早期操控台訊息。
19設定自由的記憶體指標,指向 heap。
20設定自由的記憶體的尾端指標,指向 heap 的最頂端。
21~22當輸出的負偏移位址值沒有對齊 MIN_KERNEL_ALIGN,顯示目的位址沒有對齊的訊息。
23~24當自由記憶體的起始位址 heap 大於 128MB,顯示目的位址太大訊息。
25核心解壓縮。
26呼叫解壓縮函式,將輸入資料 vmlinux.bin.gz 解壓縮到輸出位址。這個函式由 linux/lib/decompress_inflate.c 提供。
27將解壓縮後的核心映像檔解析成二進位檔。因為核心映像檔是一個 ELF 檔,而執行作業系統需要二進位檔。
28當 quiet 為 0,顯示核心啟動訊息。

1.5 GZIP 解壓縮函式

解壓縮函式名稱 decompress 會轉譯成 gunzip,這是由 linux/lib/decompress_inflate。

linux/lib/x86/decompress_inflate.c
01 #define decompress gunzip

gunzip 是解壓縮函式,解壓縮演算法是 gzip。 輸入參數是解壓縮資料起始位址、解壓縮資料長度、資料輸出位址。 malloc 函式由 linux/include/linux/decompress/mm.h 提供。

linux/lib/decompress_inflate.c
01 STATIC int INIT gunzip(unsigned char *buf, int len,
02                        int(*fill)(void*, unsigned int),
03                        int(*flush)(void*, unsigned int),
04                        unsigned char *out_buf,
05                        int *pos,
06                        void(*error)(char *x)) {
07     u8 *zbuf;
08     struct z_stream_s *strm;
09     int rc;
10     size_t out_len;
11 
12     rc = -1;
13     if (flush) {
14         out_len = 0x8000;
15         out_buf = malloc(out_len);
16     } else {
17         out_len = 0x7fffffff;
18     }
19     if (!out_buf) {
20         error("Out of memory while allocating output buffer");
21         goto gunzip_nomem1;
22     }
23     if (buf) zbuf = buf;
24     else {
25         zbuf = malloc(GZIP_IOBUF_SIZE);
26         len = 0;
27     }
28     if (!zbuf) {
29         error("Out of memory while allocating input buffer");
30         goto gunzip_nomem2;
31     }
32     strm = malloc(sizeof(*strm));
33     if (strm == NULL) {
34         error("Out of memory while allocating z_stream");
35         goto gunzip_nomem3;
36     }
37     strm->workspace = malloc(flush ? zlib_inflate_workspacesize():sizeof(struct inflate_state));
38     if (strm->workspace == NULL) {
39         error("Out of memory while allocating workspace");
40         goto gunzip_nomem4;
41     }
42     if (!fill) fill = nofill;
43     if (len == 0) len = fill(zbuf, GZIP_IOBUF_SIZE);
44     if (len < 10 || zbuf[0] != 0x1f || zbuf[1] != 0x8b || zbuf[2] != 0x08) {
45         if (pos) *pos = 0;
46         error("Not a gzip file");
47         goto gunzip_5;
48     }
49     strm->next_in = zbuf + 10;
50     strm->avail_in = len - 10;
51     if (zbuf[3] & 0x8) {
52         do {
53             if (strm->avail_in == 0) {
54                 error("header error");
55                 goto gunzip_5;
56             }
57             --strm->avail_in;
58         } while (*strm->next_in++);
59     }
60     strm->next_out = out_buf;
61     strm->avail_out = out_len;
62     rc = zlib_inflateInit2(strm, -MAX_WBITS);
63     if (!flush) {
64         WS(strm)->inflate_state.wsize = 0;
65         WS(strm)->inflate_state.window = NULL;
66     }
67     while (rc == Z_OK) {
68         if (strm->avail_in == 0) {
69             len = fill(zbuf, GZIP_IOBUF_SIZE);
70             if (len < 0) {
71                 rc = -1;
72                 error("read error");
73                 break;
74             }
75             strm->next_in = zbuf;
76             strm->avail_in = len;
77         }
78         rc = zlib_inflate(strm, 0);
79         if (flush && strm->next_out > out_buf) {
80             int l = strm->next_out - out_buf;
81             if (l != flush(out_buf, l)) {
82                 rc = -1;
83                 error("write error");
84                 break;
85             }
86             strm->next_out = out_buf;
87             strm->avail_out = out_len;
88         }
89         if (rc == Z_STREAM_END) {
90             rc = 0;
91             break;
92         } else if (rc != Z_OK) {
93             error("uncompression error");
94             rc = -1;
95         }
96     }
97     zlib_inflateEnd(strm);
98     if (pos) *pos = strm->next_in - zbuf+8;
99 gunzip_5:
100     free(strm->workspace);
101 gunzip_nomem4:
102     free(strm);
103 gunzip_nomem3:
104     if (!buf) free(zbuf);
105 gunzip_nomem2:
106     if (flush) free(out_buf);
107 gunzip_nomem1:
108     return rc;
109 }

行號說明
12設定回返值為 -1,-1 是解壓縮失敗的回返值。
13~18flush 是空指標,輸出緩衝區已經指定,。
19~22當輸出緩衝區記憶體指標為空指標,表示記憶體耗盡,跳至 gunzip_nomem1,預備回返。這種狀況不會發生。
23~27取得壓縮資料緩衝區,。
28~31壓縮資料緩衝區記憶體指標為空指標,表示記憶體耗盡,跳至 gunzip_nomem2,預備回返。
32取得壓縮串流結構記憶體,。
33~36壓縮串流結構記憶體指標為空指標,表示記憶體耗盡,跳至 gunzip_nomem3,預備回返。
37取得壓縮串流的工作區記憶體,。
38~41壓縮串流的工作區記憶體指標為空指標,表示記憶體耗盡,跳至 gunzip_nomem4,預備回返。
42當 fill 指標為空指標,設定 fill 為 nofill,nofill 只會回返負值。
43~48檢驗 zip 檔特徵值,不符合時,跳至錯誤處理程序。
49~50設定剩餘的壓縮檔位址與體積,。
51~59處理壓縮檔的檔名,同時調整剩餘的壓縮檔位址與體積。
60~61設定輸出緩衝區的位址與體積,。
62設定串流結構的 window 相關變數,。
63~66flush 指標為空指標,清除 window size 和 window 指標。
67~96解壓縮迴圈,。
97解壓縮完成函式,筆者認為這一行沒有用處。因為作者沒有對此函式的回返值做處理,而此函式也只是回返狀態值。
98因為 pos 是個空指標,此行略過。
99~108錯誤處理程序,依照不同的錯誤標籤,釋放記憶體,最後函式回返。

1.6 轉檔

將 ELF 格式映像轉成二進位映像。 LINUX 核心映像有許多區段,這些程式區段可以由 linux/arch/x86/kernel/vmlinux.lds 描述。 ELF 解析函式 parse_elf 將這些區段從 ELF 映像檔中分離出來,重組成一個二進位映像,即可直接由 CPU 執行的 LINUX 核心映像。

linux/arch/x86/boot/compressed/misc.c
01 static void parse_elf(void *output){
02     Elf32_Ehdr ehdr;
03     Elf32_Phdr *phdrs, *phdr;
04     void *dest;
05     int i;
06 
07     memcpy(&ehdr, output, sizeof(ehdr));
08     if (ehdr.e_ident[EI_MAG0] != ELFMAG0 ||
09         ehdr.e_ident[EI_MAG1] != ELFMAG1 ||
10         ehdr.e_ident[EI_MAG2] != ELFMAG2 ||
11         ehdr.e_ident[EI_MAG3] != ELFMAG3) {
12         error("Kernel is not a valid ELF file");
13         return;
14     }
15     if (!quiet) putstr("Parsing ELF... ");
16     phdrs = malloc(sizeof(*phdrs) * ehdr.e_phnum);
17     if (!phdrs) error("Failed to allocate space for phdrs");
18     memcpy(phdrs, output + ehdr.e_phoff, sizeof(*phdrs) * ehdr.e_phnum);
19     for (i = 0; i < ehdr.e_phnum; i++) {
20         phdr = &phdrs[i];
21         switch (phdr->p_type) {
22             case PT_LOAD:
23                 dest = output;
24                 dest += (phdr->p_paddr - LOAD_PHYSICAL_ADDR);
25                 memcpy(dest,
26                        output + phdr->p_offset,
27                        phdr->p_filesz);
28                 break;
29             default: break;
30         }
31     }
32 }

行號說明
07從解壓縮資料複製檔頭到 ehdr,。
08~14檢驗 elf 檔頭,0x7F 加上 ELF。不符合的話,顯示錯誤的 ELF 格式訊息。
15當 quiet 為 0,顯示 elf 解析訊息。
16取得程式區段表所需要的記憶體,即區段表數乘以區段檔頭體積。當記憶體取得失敗,函式回返。
17取得 elf 映像檔的程式區段表內容,。
18區段表解析迴圈,只處理載入性質的區段,將區段內容載入記憶體。
19~31取得區段檔頭,。
23~24計算載入位址,輸出資料位址加上區段實體位址,減去 LOAD_PHYSICAL_ADDR。LOAD_PHYSICAL_ADDR 的值是 0x1000000。
25~27將區段內容複製到記憶體,逐步完成 LINUX 核心二進位映像。