最近在学习Linux内核。看linux内核的第一件事情自然就是看Makefile文件。内核通过make,进行编译,make命令就是根据Makefile中的依赖关系,对内核中代码进行编译。
下面通过对Linux0.12内核中的Makefile文件的注释讲解,来介绍Linux0.12内核中Makefile的知识点和Linux内核的组织结构,剩下没讲到的Makefile基本上都是照样画葫芦,大家可以自行阅读分析。
一般分析内核,都是从主Makefile开始的。了解了这个Makefile,基本上对内核的结构算是了解了一半。
这里的结构最为复杂。
#
# if you want the ram-disk device, define this to be the
# size in blocks.
#
# 如果你要使用RAM盘(RAMDISK)设备的话就定义块大小。这里默认RAMDISK没有定义(注释掉了),
# 否则gcc编译时会带来有选项 ’-DRAMDISK=512‘, 参见第13行。
RAMDISK = #-DRAMDISK=512
AS86 =as86 -0 -a #8086汇编编译器和连接器。后带的参数含义分别是:-0生成8086目标程序; -a生成与gas和gld部分兼容的代码。
LD86 =ld86 -0 #LD86和AS86主要用在boot/setup和boot/bootsect两个程序的编译链接。
AS =gas #GUN汇编编译器和连接器,除了上面列出的两个程序以外,其他的程序都是由GUN汇编编译器编译链接的。
LD =gld
#下面是GUN链接器gld运行时用到的选项。
#含义是:
#-s输出文件中省略所有的符号信息;
#-x删除所有局部符号;
#-M表示需要在标准输出设备(显示器)上打印链接映像(link map),是指由链接程序
#产生的一种内存地址映像,其中列出了程序段装入到内存中的位置信息。具体来讲有如下信息:
# + 目标文件及符号信息映射到内存中的位置;
# + 公共符号如何放置
# + 连接中包含的所有文件成员及其引用的符号
LDFLAGS =-s -x -M
#gcc是GUN C程序编译器。makefile为unix类的脚本文件,对于UNIX类的脚本(script)程序而言,
#在引用定义的标识符时,需在前面加上$符号,并用括号括住标识符。简单的makefile语法。
#想要学习makefile语法和规则,参考:http://blog.csdn.net/daiyibo123/article/details/50333111
CC =gcc $(RAMDISK)
#下面指定gcc使用的选项,也就是和上一行的CC配合使用。这个CFLAGS包括两行,前一行最后的"\"符号表示下一行的续行。
#选项含义为:
#-Wall: 答应所有警告信息;
#-O: 对代码进行优化,例如当printf函数中没有参数的时候,代码会优化为puts函数等;
#'-f标志': 指定与机器无关的编译标志。下面来展开介绍:
# -fstrength-reduce: 用于优化循环语句
# -fcombine-regs: 用于指明编译器在组合编译阶段把复制一个寄存器到另一个寄存器的指令组合在一起
# -fomit-frame-pointer: 用于指明对于无需帧指针的函数不要把帧指针保留在寄存器中,这样在函数中可以避免帧指针的操作和维护
#-mstring-insns: 是linus在学习gcc编辑器时为gcc增加的选项,用于gcc-1.40在复制结构等操作时使用386 CPU的字符串指令,可以去掉。
CFLAGS =-Wall -O -fstrength-reduce -fomit-frame-pointer \
-fcombine-regs -mstring-insns
#下面的cpp是gcc的前(预)处理器程序。前处理器用于进行程序中的宏替换处理,条件编辑处理以及包
#含进指定文件的内容,即把使用"#include"指定的文件包含进来。源程序文件中所有以符号"#"开始的行均由前处理器进行处理。
#程序中所有"#define"定义的宏都会使用其定义部分替换掉。程序中所有"#if","#ifdef","#ifndef"和"#endif"等条件判别行用于确定是否包含其指定范围中的语句。
#"-nostdinc -Iinclude"含义是不要搜索标准头文件目录中的文件,即不用系统/usr/include/目录下的头文件,而是使用"-I"选项指定目录或者是在当前目录里搜索头文件
CPP =cpp -nostdinc -Iinclude
#
# ROOT_DEV specifies the default root-device when making the image.
# This can be either FLOPPY, /dev/xxxx or empty, in which case the
# default of /dev/hd6 is used by 'build'.
#
#ROOT_DEV指定在创建内核映像(image)文件时所使用的默认根文件系统所在设备,这可以是软盘(FLOPPY),/dev/xxxx或者空着。空着时
#build程序(tools/目录中)就使用默认值/dev/hd6。
#
#这里/dev/hd6对应第2个硬盘的第1个分区。这是linus开发linux内核时自己在机器上根文件系统所在的分区位置。/dev/hd2表示把第1个硬盘的第2个分区用作交换分区。
ROOT_DEV=/dev/hd6
SWAP_DEV=/dev/hd2
#makefile变量赋值
ARCHIVES=kernel/kernel.o mm/mm.o fs/fs.o
DRIVERS =kernel/blk_drv/blk_drv.a kernel/chr_drv/chr_drv.a
MATH =kernel/math/math.a
LIBS =lib/lib.a
#下面是makefile老式的隐式后缀规则。在该行指示的make利用下面的命令将所有的".c"文件编译生成".s"汇编程序,":"表示下面是该规则的命令。
#关于makefile老式的隐式后缀规则,可以参考此网站:http://wiki.ubuntu.org.cn/%E8%B7%9F%E6%88%91%E4%B8%80%E8%B5%B7%E5%86%99Makefile:%E9%9A%90%E5%90%AB%E8%A7%84%E5%88%99
#下面一句表示让gcc采用前面CFLAGS所指定的选项产生汇编代码文件。
.c.s:
$(CC) $(CFLAGS) \
-nostdinc -Iinclude -S -o $*.s $<
#表示将所有.s汇编程序文件编译成.o目标文件。整句表示使用gas编译器将汇编程序编译成.o目标文件。
# -c表示只编译或汇编,不进行连接操作。
.s.o:
$(AS) -c -o $*.o $<
#类似上面,*.c文件->*.o文件。整句话表示使用gcc将c语言文件编译成目标文件,但是不连接。
.c.o:
$(CC) $(CFLAGS) \
-nostdinc -Iinclude -c -o $*.o $<
#这个是makefile中,对顶层的目标。键入make,默认执行此目标。这里生成的Image文件,即是引导启动盘映象文件bootimage。
all: Image
#说明目标(Image文件)是由bootsect,setup,system,build文件组成。
#第一条命令表示:使用tool中刚刚生成的build工具程序将bootsect,setup和system文件以及
# ROOT_DEV(根文件磁盘)和SWAP_DEV(交换磁盘)组装成内核映像文件Image。
#第二条命令表示:使用sync同步命令,迫使缓冲块数据立即写盘。
Image: boot/bootsect boot/setup tools/system tools/build
tools/build boot/bootsect boot/setup tools/system $(ROOT_DEV) \
$(SWAP_DEV) > Image
sync
#表示disk这个目标要由Image产生。
# dd为UNIX标准命令:复制一个文件,根据选项进行转换和格式化。bs=表示一次读/写的字节数;if=表示输入的文件;of=表示输入到的文件。
# 这里的/dev/PS0是指第一个软盘驱动器(设备文件)。在linux系统下使用/dev/fd0。
disk: Image
dd bs=8192 if=Image of=/dev/PS0
tools/build: tools/build.c #由tools目录下的build.c程序生成执行程序build。
$(CC) $(CFLAGS) \
-o tools/build tools/build.c #编译生成执行程序build的命令。
boot/head.o: boot/head.s #利用上面给出的.s.o规则生成head.o目标文件。
# 表示tools目录中的system文件要由冒号右边所列的元素生成。下面命令将所有的系统需要用到的目标文件连接成一个可执行文件。最后生成一个tools/system文件。
# 最后的 >System.map 表示gld需要将连接映像重定向存放在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 > System.map
kernel/math/math.a: #调用子makefile,数学协处理函数文件
(cd kernel/math; make)
kernel/blk_drv/blk_drv.a: #调用子makefile,块设备库文件
(cd kernel/blk_drv; make)
kernel/chr_drv/chr_drv.a: #调用子makefile,字符设备文件
(cd kernel/chr_drv; make)
kernel/kernel.o: #调用子makefile,内核目标模块
(cd kernel; make)
mm/mm.o: #调用子makefile,内存管理模块
(cd mm; make)
fs/fs.o: #调用子makefile,文件系统目标模块
(cd fs; make)
lib/lib.a: #调用子makefile,库函数模块
(cd lib; make)
boot/setup: boot/setup.s #使用8086汇编编译器和连接器生成程序
$(AS86) -o boot/setup.o boot/setup.s
$(LD86) -s -o boot/setup boot/setup.o
boot/setup.s: boot/setup.S include/linux/config.h #执行C语言预处理,替代*.S文件中的宏定义生成对应的*.s文件
$(CPP) -traditional boot/setup.S -o boot/setup.s
boot/bootsect.s: boot/bootsect.S include/linux/config.h #执行C语言预处理,替代*.S文件中的宏定义生成对应的*.s文件
$(CPP) -traditional boot/bootsect.S -o boot/bootsect.s
boot/bootsect: boot/bootsect.s #使用8086汇编编译器和连接器生成程序
$(AS86) -o boot/bootsect.o boot/bootsect.s
$(LD86) -s -o boot/bootsect boot/bootsect.o
#当执行"make clean"时,就会执行下面命令,去除所有编译连接生成的文件
#"rm"是文件删除命令,选项-f 含义是忽略不存在的文件,并且不显示删除信息。
clean:
rm -f Image System.map tmp_make core boot/bootsect boot/setup \
boot/bootsect.s boot/setup.s
rm -f init/*.o tools/system tools/build boot/*.o
(cd mm;make clean) #进入 mm/目录;执行该目录Makefile文件中的clean规则。
(cd fs;make clean)
(cd kernel;make clean)
(cd lib;make clean)
#该规则将首先执行上面的clean规则,然后对linux/目录进行压缩,生成"backup.Z"压缩文件。
#"cd .."表示退到linux/的上一级(父)目录;"tar cf - linux"表示对linux/目录执行tar归档程序。
#"-cf"表示需要创建的归档文件 "| compress -"表示将tar程序的执行通过管道操作("|")
#传递给压缩程序compress,并将压缩程序的输出存成backup.Z
backup: clean
(cd .. ; tar cf - linux | compress - > backup.Z)
sync #同步到磁盘
#该目标或规则用于产生各文件之间的依赖关系。创建这些依赖关系是为了让make命令用它们来确定
#是否需要重建一个目标对象。比如当某个头文件被改动过后,make就能通过生成依赖关系,重新编译与该头文件有关的所有*.c文件。具体方法如下:
# +使用字符串编辑程序sed对Makefile文件(这里既是本文件)进行处理,输出为删除了Makefile
# +文件中"### Dependencies"行后面的所有行,并生成一个临时文件tmp make(即144行的作用)
# +然后对指定目录下(init/)的每一个C文件(其实只有一个文件main.c)执行gcc预处理操作。
# +标志"-M"告诉预处理程序会输出一个规则,其结果形式就是相应源程序文件的目标文件名加上其依赖关系,
# +即该源文件中包含的所有头文件列表。然后把预处理结果都添加到临时文件tmp_make中,最后将该临时文件复制成新的Makefile文件。
# +shell中for循环上面的 "$$i" 表示访问shell程序内定义的一个变量,在这句话中就是访问前面的.c文件。
dep:
sed '/\#\#\# Dependencies/q' < Makefile > tmp_make
(for i in init/*.c;do echo -n "init/";$(CPP) -M $$i;done) >> tmp_make
cp tmp_make Makefile
(cd fs; make dep)
(cd kernel; make dep)
(cd mm; make dep)
### Dependencies:
init/main.o : init/main.c include/unistd.h include/sys/stat.h \
include/sys/types.h include/sys/time.h include/time.h include/sys/times.h \
include/sys/utsname.h include/sys/param.h include/sys/resource.h \
include/utime.h include/linux/tty.h include/termios.h include/linux/sched.h \
include/linux/head.h include/linux/fs.h include/linux/mm.h \
include/linux/kernel.h include/signal.h include/asm/system.h \
include/asm/io.h include/stddef.h include/stdarg.h include/fcntl.h \
include/string.h
下面是文件系统的Makefile,一个内核,单独存在,是没有用的。需要相应的文件系统的配合。用户通过调用shell来和内核进行通讯,通过init()初始化进程,这些程序都是存在于文件系统中的。
把文件系统Makefile贴出来,读者可以查看主Makefile和子Makefile(文件系统Makefile)来分析他们的区别和了解他们之前的相互调用。
在主Makefile中,调用文件系统Makefile,有许多比较多的例子,下面随便举个例子,方便大家参考:
fs/fs.o: #调用子makefile,文件系统目标模块
(cd fs; make)
AR =gar #设置二进制压缩工具
AS =gas #GUN汇编编译器
CC =gcc #gcc是GUN C程序编译器
LD =gld #GUN连接器
#下面指定gcc使用的选项,也就是和上一行的CC配合使用。
#这个选项里面的一些参数,我们在前面已经介绍过了。下面就针对不同参数(-fno-defer-pop)进行讲解:
#-fno-defer-pop: 一旦函数返回,参数就立即弹出。对于那些调用函数后必须弹出参数的机器,编译器一般
# 情况下让好几次函数调用的参数堆积在栈上,然后一次全部弹出。
CFLAGS =-Wall -O -fstrength-reduce -fcombine-regs -fomit-frame-pointer \
-fno-defer-pop -mstring-insns -nostdinc -I../include
#-E参数介绍: 在gcc处理源代码文件,停留在预处理那一步
CPP =gcc -E -nostdinc -I../include
#和主makefile文件中一样,为makefile老式的隐式后缀规则
.c.s:
$(CC) $(CFLAGS) \
-S -o $*.s $<
.c.o:
$(CC) $(CFLAGS) \
-c -o $*.o $<
.s.o:
$(AS) -o $*.o $<
#定义变量
OBJS= open.o read_write.o inode.o file_table.o buffer.o super.o \
block_dev.o char_dev.o file_dev.o stat.o exec.o pipe.o namei.o \
bitmap.o fcntl.o ioctl.o truncate.o select.o
#根据依赖关系,编译出上面的目标代码,将代码连接成相应总目标文件
fs.o: $(OBJS)
$(LD) -r -o fs.o $(OBJS)
#清楚指令
clean:
rm -f core *.o *.a tmp_make
for i in *.c;do rm -f `basename $$i .c`.s;done
#建立依赖关系,在部分代码更新时,选择更新的文件,部分编译。
dep:
sed '/\#\#\# Dependencies/q' < Makefile > tmp_make
(for i in *.c;do $(CPP) -M $$i;done) >> tmp_make
cp tmp_make Makefile
#依赖关系
### Dependencies:
bitmap.o : bitmap.c ../include/string.h ../include/linux/sched.h \
../include/linux/head.h ../include/linux/fs.h ../include/sys/types.h \
../include/linux/mm.h ../include/linux/kernel.h ../include/signal.h \
../include/sys/param.h ../include/sys/time.h ../include/time.h \
../include/sys/resource.h
block_dev.o : block_dev.c ../include/errno.h ../include/linux/sched.h \
../include/linux/head.h ../include/linux/fs.h ../include/sys/types.h \
../include/linux/mm.h ../include/linux/kernel.h ../include/signal.h \
../include/sys/param.h ../include/sys/time.h ../include/time.h \
../include/sys/resource.h ../include/asm/segment.h ../include/asm/system.h
后面依赖关系省略~~~
转载于《linux内核完全剖析》