1. 程式人生 > >Linux 環境下編譯 0.11版本核心 kernel

Linux 環境下編譯 0.11版本核心 kernel

原文地址:http://chfj007.blog.163.com/blog/static/173145044201132523034138/
最近在看《linux核心0.11完全註釋》一書,由於書中涉及組合語言的地方眾多,本人在大學時組合語言學得一塌糊塗,所以實在看不下去了,頭都大了只好匆匆看了個頭尾(前面幾章和最後一章)。看來即使有《九陰真經》這樣的武功祕籍,內功不夠也是修煉不出來神馬來的。於是索性下了個0.11版本的kernel下來嘗試編譯一把。

      下面開始工作:

1、   tar  xvfz  linux-0.11.tar.gz

2、   cd  linux-0.11

3、   make

      make: as86: Command not finded

      make: ***

出錯原因:as86 彙編器未安裝

解決辦法:

   yum install dev86* (請務必保證網路暢通,如果使用Debian Linux系統命令改為 apt-get install dev86*)

4、  make

    gas -c -o boot/head.o boot/head.s

    make: gas: Command not finded

    make: *** [boot/head.o] Error 127

出錯原因:gas 彙編器未安裝

解決辦法:

   yum install binutils* (請確保網路正常)


   下載binutils-2.20.tar.gz安裝      下載地址http://ftp.gnu.org/gnu/binutils/


    tar xvfz binutils-2.20.tar.gz
    ./configure
    make
    make install

    確認 Gnu assembler 已經安裝
    which as
    /usr/local/bin/as

5、 make

   gas -c -o boot/head.o boot/head.s
   make: gas: Command not found
   make: *** [boot/head.o] Error 127

出錯原因:gas、gld 的名稱已經過時,現在GNU assembler的名稱是 as

解決辦法:

   修改主 Makefile 檔案

   將 AS =gas 修改為 AS =as
   將 LD =gld 修改為 LD =ld

6、make

   as -c -o boot/head.o boot/head.s
   as: unrecognized option '-c'
   make: *** [boot/head.o] Error 1

出錯原因:as 語法和1991年的時候有了一些變化

解決辦法:

   修改 Makefile 檔案

   將 $(AS) -c -o $*.o $<  修改為 $(AS) -o $*.o $<

7、make

   as -o boot/head.o boot/head.s
   boot/head.s: Assembler messages:
   boot/head.s:231: Error: alignment not a power of 2
   make: *** [boot/head.o] Error 1

出錯原因:.align 2 是組合語言指示符,其含義是指儲存邊界對齊調整;
          “2”表示把隨後的程式碼或資料的偏移位置調整到地址值最後2位元位為零的位置(2^2),即按4位元組對齊記憶體地址。
          不過現在GNU as直接是寫出對齊的值而非2的次方值了。

          .align 2 應該改為 .align 4
          .align 3 應該改為 .align 8

解決辦法:

   修改 boot/head.s 檔案

   將   .align 2 應該改為 .align 4
         .align 3 應該改為 .align 8

8、make

   cc1: error: unrecognized command line option "-mstring-insns"
   cc1: error: unrecognized command line option "-fcombine-regs"
   make: *** [init/main.o] Error 1

解決辦法:

   修改 Makefile 檔案

   將 -fcombine-regs -mstring-insns 刪除或者註釋掉

9、make

   In file include from init/main.c:9:
include/unistd.h:207: warning: function return types not compatible due to 'volatile'
include/unistd.h:208: warning: function return types not compatible due to 'volatile'
init/main.c:24: error: static declaration of 'fork' follows non-static declaration
init/main.c:26: error: static declaration of 'pause' follows non-static declaration
include/unistd.h:224: note: previous declaration of 'pause' was here
init/main.c:30: error: static declaration of 'sync' follows non-static declaration
include/unistd.h:235: note: previous declaration of 'sync' was here
init/main.c:108: warning: return type of 'main' is not 'int'
make: *** [init/main.o] Error 1

解決辦法:

   修改 init/main.c 檔案

   將 static inline _syscall0(int,fork)   修改為 inline _syscall0(int,fork)
      static inline _syscall0(int,pause)  修改為 inline _syscall0(int,pause)
      static inline _syscall1(int,setup,void *,BIOS)  修改為 inline _syscall1(int,setup,void *,BIOS)
      static inline _syscall0(int,sync)  修改為 inline _syscall0(int,sync)

10、make

  (cd kernel; make)
make[1]: Entering directory '***/linux-0.11/kernel'
gcc -Wall -O -fstrength-reduce -fomit-frame-pointer -fcombine-regs -finline-functions -mstring-insns -nostdinc -I../include \
 -c -o sched.o sched.c
cc1: error: unrecognized command line option "-mstring-insns"
cc1: error: unrecognized command line option "-fcombine-regs"
make[1]: *** [sched.o] Error 1
make[1]: Leaving directiory '***/linux-0.11/kernel'
make: *** [kernel/kernel.o] Error 2

解決辦法:

   修改 kernel目錄下 Makefile 檔案

   將 -fcombine-regs -mstring-insns 刪除或者註釋掉

11、make

gas -c -o system_call.o system_call.s
make[1]: gas: Command not found
make[1]: *** [system_call.o] Error 127
make[1]: Leaving directory '***/linux-0.11/kernel'
make: *** [kernel/kernel.o] Error 2

解決辦法:

    修改 kernel目錄下 Makefile 檔案
   
    將 AS =gas 修改為 AS =as
    將 LD =gld 修改為 LD =ld
    將 $(AS) -c -o $*.o $< 修改為 $(AS) -o $*.o $<

12、make

fork.c:In function 'copy_process':
fork.c:121: warning: suggest parentheses around assignment used as truth value
fork.c:In function 'copy_mem':
fork.c:54: error: can't find a register in class 'DREG' while reloading 'asm'
fork.c:55: error: can't find a register in class 'DREG' while reloading 'asm'
fork.c:46: error: 'asm' operand has impossible constraints
fork.c:47: error: 'asm' operand has impossible constraints
fork.c:54: error: 'asm' operand has impossible constraints
fork.c:55: error: 'asm' operand has impossible constraints
make[1]: *** [fork.o] Error 1
make[1]: Leaving directory '***/linux-0.11/kernel'
make: *** [kernel/kernel.o] Error 2

在 kernel/fork.c 第 54 行:

可以看到這樣的呼叫 set_base(p->ldt[1],new_code_base);
在 include/linux/sched.h 第 188 —— 211 行:

可以看到 set_base 的實現

#define set_base(ldt,base) _set_base( ((char *)&(ldt)) , base )

#define _set_base(addr,base) \
__asm__("movw %%dx,%0\n\t" \
 "rorl $16,%%edx\n\t" \
 "movb %%dl,%1\n\t" \
 "movb %%dh,%2" \
 ::"m" (*((addr)+2)), \
   "m" (*((addr)+4)), \
   "m" (*((addr)+7)), \
   "d" (base) \
 :"dx")

於是這個時候比較工具 Beyond Compare 就派上用場了。

將 #define _set_base(addr,base) \     修改為   #define _set_base(addr,base) \ 
      __asm__("movw %%dx,%0\n\t" \                 __asm__("push %%edx\n\t" \   
       "rorl $16,%%edx\n\t" \                                                "movw %%dx,%0\n\t" \       
       "movb %%dl,%1\n\t" \                                                 "rorl $16,%%edx\n\t" \      
       "movb %%dh,%2" \                                                     "movb %%dl,%1\n\t" \        
       ::"m" (*((addr)+2)), \                                                    "movb %%dh,%2\n\t" \            
         "m" (*((addr)+4)), \                                                    "pop %%edx" \               
         "m" (*((addr)+7)), \                                                    ::"m" (*((addr)+2)), \      
         "d" (base) \                                                                  "m" (*((addr)+4)), \      
       :"dx")                                                                              "m" (*((addr)+7)), \      
                                                                                              "d" (base) )

 將 #define switch_to(n) {\
       struct {long a,b;} __tmp; \
       __asm__("cmpl %%ecx,_current\n\t" \
                       "je 1f\n\t" \
                       "movw %%dx,%1\n\t" \
                       "xchgl %%ecx,_current\n\t" \
                       "ljmp %0\n\t" \ ------------------------------這裡修改為 "ljmp *%0\n\t" \
                       "cmpl %%ecx,_last_task_used_math\n\t" \
                       "jne 1f\n\t" \
                       "clts\n" \
                        "1:" \
                         ::"m" (*&__tmp.a),"m" (*&__tmp.b), \
                         "d" (_TSS(n)),"c" ((long) task[n])); \
       }               

  將 #define _get_base(addr) ({\              修改為   static inline unsigned long _get_base(char * addr)
       unsigned long __base; \                                  {                                            
       __asm__("movb %3,%%dh\n\t" \                       unsigned long __base;                                               
   movb %2,%%dl\n\t" \                                              __asm__("movb %3,%%dh\n\t" \
   shll $16,%%edx\n\t" \                                                              "movb %2,%%dl\n\t" \
   movw %1,%%dx" \                                                                  "shll $16,%%edx\n\t" \
   "=d" (__base) \                                                                       "movw %1,%%dx" \
   "m" (*((addr)+2)), \                                                                  :"=&d" (__base) \
   "m" (*((addr)+4)), \                                                                  :"m" (*((addr)+2)), \
   "m" (*((addr)+7))); \                                                                  "m" (*((addr)+4)), \
        __base;})                                                                            "m" (*((addr)+7)));
                                                                                   return __base; 
                                                                                 }

注意:
    由於GNU as彙編器不斷進化的原因,需要將 *.s 檔案中
    類似
    .globl _idt,_gdt,_pg_dir,_tmp_floppy_area
    修改為
    .globl idt,gdt,pg_dir,tmp_floppy_area

    不然雖然編譯通過,但是連線的時候將出現類似這樣的錯誤
    boot/head.o: In function 'setup_idt':
    (.text+0x87): undefined reference to '_idt'
    boot/head.o: In function 'idt_descr':
    (.text+0x54ac): undefined reference to '_idt'
    kernel/kernel.o: In function 'sched_init':
    (.text+0x37c): undefined reference to '_idt'
                                                                                                

--------------------------------------------------------------------------------------------------------------------------------------------------

彙編知識(來自網際網路)

GCC中基本的內聯彙編:__asm____volatile__("InstructionList");

__asm__(
"movl $1,%eax\r\t"
"xor %ebx,%ebx\r\t"
"int $0x80"
);

帶有C/C++表示式的內聯彙編:

__asm__ __volatile__("InstructionList"
                                    :Output
                                    :Input
                                    :Clobber/Modify);

這4個部分都不是必須的,任何一個部分都可以為空,其規則為:

1、如果Clobber/Modify  為空,則其前面的冒號(:)必須省略。

2、如果Output,Input,Clobber/Modify都為空,Output,Input之前的冒號(:)既可以省略,也可以不省略。

3、如果Input,Clobber/Modify為空,但Output不為空,Input前的冒號(:)既可以省略,也可以不省略。

4、如果後面的部分不為空,而前面的部分為空,則前面的冒號(:)都必須保留,否則無法說明不為空的部分究竟是第幾部分。

每一個Input和Output表示式都必須指定自己的操作約束Operation Constraint,這裡將討論在80386平臺上所可能使用的操作約束。

當前的輸入或輸出需要藉助一個暫存器時,需要為其指定一個暫存器約束,可以直接指定一個暫存器的名字。

常用的暫存器約束的縮寫 
約束        意義
r            表示使用一個通用暫存器,由 GCC  在%eax/%ax/%al,%ebx/%bx/%bl,%ecx/%cx/%cl,%edx/%dx/%dl中選取一個GCC認為合適的。
g           表示使用任意一個暫存器,由GCC在所有的可以使用的暫存器中選取一個GCC認為合適的。
q           表示使用一個通用暫存器,和約束r的意義相同。
a           表示使用%eax/%ax/%al
b           表示使用%ebx/%bx/%bl
c           表示使用%ecx/%cx/%cl
d           表示使用%edx/%dx/%dl
D          表示使用%edi/%di
S           表示使用%esi/%si
f            表示使用浮點暫存器
t            表示使用第一個浮點暫存器
u           表示使用第二個浮點暫存器

如果一個Input/Output  操作表示式的C/C++表示式表現為一個記憶體地址,不想借助於任何暫存器,則可以使用記憶體約束。比如:
__asm__("lidt%0":"=m"(__idt_addr));
__asm__("lidt%0"::"m"(__idt_addr));

修飾符     輸入/輸出      意義
=                 O               表示此Output操作表示式是Write-Only的。
+                 O               表示此Output操作表示式是Read-Write的。
&                 O               表示此Output操作表示式獨佔為其指定的暫存器。
%                I                 表示此Input  操作表示式中的C/C++表示式可以和下一 個Input操作表示式中的C/C++表示式互換

--------------------------------------------------------------------------------------------------------------------------------------------------------------

 13、make

    In file included from stat.c:13:
    ../include/asm/segment.h: Assembler messages:
    ../include/asm/segment.h:27: Error: bad register name '%sil'
    make[1]: *** [stat.o] Error 1
    make[1]: Leaving directory '***/linux-0.11/fs'
    make: *** [fs/fs.o] Error 2

出錯原因:

    fs 目錄下的 Makefile 中編譯選項使用了 -O 優化選項導致暫存器錯誤


解決方法:

    將fs目錄下的Makefile 檔案中的
    CFLAGS =-Wall -O -fstrength-reduce -fomit-frame-pointer \
    修改為
    CFLAGS =-Wall -fstrength-reduce -fomit-frame-pointer \

14、make

    tools/build.c: In function 'main':
    tools/build.c:75: warning: implicit declaration of function 'MAJOR'
    tools/build.c:76: warning: implicit declaration of function 'MINOR'
    tmp/ccsMKTAS.o: In function 'main':
    build.c:(.text+0xe1): undefined reference to 'MAJOR'
    build.c:(.text+0xf7): undefined reference to 'MINOR'
    collect2: ld returned 1 exit status    
    
出錯原因:'MAJOR' 和 'MINOR' 未定義

解決辦法:   
 
    我們可以在 include/linux/fs.h 檔案中找到
   
    #define MAJOR(a) (((unsigned)(a))>>8)
    #define MINOR(a) ((a)&0xff)   

    而在 tools/build.c 中也有包含 #include <linux/fs.h>
    那麼再看第一層目錄中的主 Makefile 檔案

    tools/build: tools/build.c
 $(CC) $(CFLAGS) \
 -o tools/build tools/build.c

    好象確實沒有引用標頭檔案

    簡單的新增 -Iinclude
    重新編譯後出現一堆報標準C庫標頭檔案的錯誤

    再新增 -nostdinc
    又報 stderr fprintf 之類的錯誤

    沒折,只好將
    #define MAJOR(a) (((unsigned)(a))>>8)
    #define MINOR(a) ((a)&0xff)
    新增到 tools/build.c 檔案中,然後刪除 #include <linux/fs.h>

15、make

    make[1]: Leaving directory '***/linux-0.11/lib'
    ld -s -x -M boot/head.o init/main.o \
 kernel/kernel.o mm/mm.o fs/fs.o \
 kernel/blk_drv/blk_drv.a kernel/chr_drv/chr_drv.a \
 kernel/math/math.a \
 lib/lib.a \
 -o tools/system > System.map
    ld: warning: cannot find entry symbol _start; defaulting to 08048a0
    gcc -Wall -O -fstrength-reduce -fomit-frame-pointer \
 -o tools/build tools/build.c
    tools/build boot/bootsect boot/setup tools/system /dev/hd6 > Image
    /dev/hd6: No such file or directory
    Couldn't stat root device.
    make: *** [Image] Error 1

解決辦法:

    將第一層主 Makefile 檔案中的

    tools/system: boot/head.o init/main.o \
  $(ARCHIVES) $(DRIVERS) $(MATH) $(LIBS)
 $(LD) $(LDFLAGS) boot/head.o init/main.o \
 $(ARCHIVES) \
 $(DRIVERS) \
 $(MATH) \
 $(LIBS) \
 -o tools/system > System.map

    修改為

    tools/system: boot/head.o init/main.o \
  $(ARCHIVES) $(DRIVERS) $(MATH) $(LIBS)
 $(LD) $(LDFLAGS) boot/head.o init/main.o \
 $(ARCHIVES) \
 $(DRIVERS) \
 $(MATH) \
 $(LIBS) \
 -o tools/system
 nm tools/system | grep -v '\(compiled\)\|\(\.o$$\)\|\( [aU] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)'| sort > System.map

 nm命令將目標檔案中的各種符號列出來。


 ROOT_DEV=/dev/hd6  修改為 ROOT_DEV=

16、make

    /DISCARD/
     *(.note.GNU-stack)
     *(.gnu_debuglink)
     *(.gnu.lto_*)
    OUTPUT(tools/system elf32-i386)
    nm tools/system | grep -v '\(compiled\)\|\(\.o$$\)\|\( [aU] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)'| sort > System.map
    nm:  tools/system: no symbols
    gcc -Wall -O -fstrength-reduce -fomit-frame-pointer \
 -o tools/build tools/build.c
    tools/build boot/bootsect boot/setup tools/system > Image
    Root device is (3, 6)
    Boot sector 512 bytes.
    Setup is 312 bytes.
    Non-Gcc header of 'system'
    make: *** [Image] Error 1

解決辦法:

    將第一層主 Makefile 檔案中的
   
    LDFLAGS =-s -x -M 
    修改為 
    LDFLAGS =-m elf_i386 -Ttext 0 -e startup_32


 Image: boot/bootsect boot/setup tools/system tools/build
 tools/build boot/bootsect boot/setup tools/system $(ROOT_DEV) > Image
 sync
    修改為
  Image: boot/bootsect boot/setup tools/system tools/build
 objcopy -O binary -R .note -R .comment tools/system tools/kernel
 tools/build boot/bootsect boot/setup tools/kernel $(ROOT_DEV) > Image
 rm tools/kernel -f
 sync

    objcopy命令能複製和轉化目標檔案    
    objcopy -O binary -R .note -R .comment tools/system tools/kernel
    -O binary tools/system tools/kernel將 tools/system 生成二進位制檔案 tools/kernel
    -R .note -R .comment 刪除 .note段 和 .comment 段
    

    將 tools/build.c 檔案中的
    if (((long *) buf)[5] != 0)
         die("Non-GCC header of 'system'");
    這段程式碼註釋掉
    //if (((long *) buf)[5] != 0)
    //  die("Non-GCC header of 'system'");

17、make

    ld -m elf_i386 -Ttext 0 -e startup_32 boot/head.o init/main.o \
     kernel/kernel.o mm/mm.o fs/fs.o \
     kernel/blk_drv/blk_drv.a kernel/chr_drv/chr_drv.a \
     kernel/math/math.a \
     lib/lib.a \
     -o tools/system
    nm tools/system | grep -v '\(compiled\)\|\(\.o$$\)\|\( [aU] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)'| sort > System.map
    gcc -Wall -O -fstrength-reduce -fomit-frame-pointer \
     -o tools/build tools/build.c
    objcopy -O binary -R .note -R .comment tools/system tools/kernel
    tools/build boot/bootsect boot/setup tools/system > Image
    Root device is (3, 6)
    Boot sector 512 bytes.
    Setup is 312 bytes.
    System is 128323 bytes.
    rm tools/kernel -f
    sync

    終於編譯 linux 核心 0.11 版本成功了!

       最後也可以利用趙炯博士在http://www.oldlinux.org/Linux.old/kernel/0.1x/ 這裡提供了修改 linux-0.11-060618-gcc4.tar.gz  好的 0.11版本的核心進行編譯,只要修改以下 Makefile 裡 -mcpu=i386 為 -march=i386 還需要將 kernel/blk_drv/blk.h 檔案第87行 將 #elif 修改為 #else 就可以編譯通過了。

總結:編譯需要一個過程,學習也是同樣需要一個過程。雖然可以利用趙博士修改好的 kernel-0.11版快速的編譯核心,但是那樣就不會遇到太多有價值的編譯問題,而解決這些問題就是一個學習過程,相信趙博士在編譯0.11版本核心的時候也遇到了這些問題。這樣我想起了高中解數學難題的時候,高手解體時總是省略了一些因式分解的過程,而對於菜鳥來說這些省略的過程是非常重要的。