系统环境:Fedora 13 + gcc-4.4.5
最近在看《linux内核0.11完全注释》一书,由于书中涉及汇编语言的地方众多,本人在大学时汇编语言学得一塌糊涂,所以实在看不下去了,头都大了只 好匆匆看了个头尾(前面几章和最后一章)。看来即使有《九阴真经》这样的武功秘籍,内功不够也是修炼不出来神马来的。于是索性下了个0.11版本的 kernel下来尝试编译一把。
linux-0.11.tar.gz 下载地址:ftp://ftp.kernel.org/pub/linux/kernel/Historic/old-versions/
下面开始工作:
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*)
或
下载dev86-0.16.3-8.i386.rpm安装 下载地址 http://www.oldlinux.org/Linux.old/study/tools/
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")
因为这里涉及到汇编的知识,哥卡在这里一直没解决并且郁闷了好久,最后只好看回《linux内核0.11完全注释》一书。赵炯博士在http://www.oldlinux.org/Linux.old/kernel/0.1x/ 这里提供了修改 linux-0.11-060618-gcc4.tar.gz 好的 0.11版本的内核。
于是这个时候比较工具 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版本内核的时候也遇到了这些问 题。这样我想起了高中解数学难题的时候,高手解体时总是省略了一些因式分解的过程,而对于菜鸟来说这些省略的过程是非常重要的。