makefile:简单小结

使用gcc编译的时候每次都会重新编译,对于较大的linux文件而言每次都重新编译较为繁琐;

所以引入makefile,只编译修改的文件,提高效率,方便编译;

1 makefile实例

  1.1 makefile格式

#makefile格式:
目标文件 :依赖文件集合
    shell命令
其他目标文件1 :依赖文件集合1
    shell命令
其他目标文件2 :依赖文件集合2
    shell命令
......

#makefile文件是由一系列规则构成的,每个makefile文件的目的都是为了生成第一条规则的目标文件;
#makefile中的其他目标文件没有排列顺序;
#如果第一条规则依赖的依赖文件没有创建或有待更新,则去其他目标文件中查找执行 > 生成该依赖文件的其他目标文件;
#make命令会把那些依赖文件集合被更新的所有目标文件重新编译,不会把整个项目都编译一遍,这是makefile的属性;

  makefile中的首行缩进为TAB键缩进;

  1.2 makefile实例

main : main.o input.o add.o                   #想要生成目标文件main,需要的依赖文件有main.o input.o add.o文件;   
    gcc -o main main.o input.o add.o          #当前目标文件执行的操作
main.o : main.c              #如果第一个目标文件的依赖文件main.o没有,则会查找执行当前目标文件
    gcc -c main.c            #当前目标文件执行的操作
input.o : input.c            #如果第一个目标文件的依赖文件input.o没有,则会查找执行当前目标文件
    gcc -c input.c           #当前目标文件执行的操作
add.o : add.c                #如果第一个目标文件的依赖文件add.o没有,则会查找执行当前目标文件
    gcc -c add.c             #当前目标文件执行的操作

clean:
    rm *.o
    rm main

  1.2.1 下面的makefile实例等价于上面的makefile实例;具体可见下面的 2 部分

#makefile实例
objects = main.o input.o add.o
main : $(objects)
    gcc -o main $(objects)
%.o : %.c
    gcc -c $<
clean:
    rm *.o
    rm main

#其中gcc -c $< 中的 $< 表示匹配当前依赖文件中匹配的文件

 2 makefile简单语法

  2.1 makefile变量:$( )

#makefile变量举例
objects = main.o input.o add.o
main : $(objects)
    gcc -o main $(objects)

#定义了一个变量objects,makefile的变量只有字符串变量;
#变量的引用为 $(变量)

  2.2 Makefile 追加变量 :+=

objects = main.o input.o add.o
#以上等式等价于下面的等式
objects = main.o input.o
objects+=add.o

  2.2 makefile打印:@echo

#makefile文件相关内容:
print:
    @echo  here is output from echo

#终端执行语句:
    make print

#以下是终端会输出的内容:
here is output from echo

   2.3 makefile赋值符

#赋值符:    =
favfruit = apple
favorite=$(favfruit)
favfruit = banana
#此时的favorite变量的取值为makefile文件最后出现的取值:banana
 
#赋值符:   :=
favfruit = banana
favfruit =apple
favorite := $(favfruit)
favfruit = papaya
#此时的favorite变量的取值为:=表达式前最近的值:apple

#赋值符:   ?=
favfruit =apple
favorite ?= $(favfruit)
favfruit = papaya
#如果favorite之前没有定义过,则使用当前的值,如果定义过则使用之前的值;
#此处的favorite值为papaya;

  2.4 伪目标 .PHONY

#此处第一行将clean目标文件声明为伪目标;
.PHONY :clean
clean:
    rm *.o
    rm main

#由于有些目标文件比如此处的clean是不会生成目标文件的;
#如果当前目录下刚好有一个名为clean的文件,对于不会生成目标文件的clean命令而言,make工具会认为目录下的clean文件始终是最新的文件,
#如果没有第一行代码,由于刚好有clean文件的存在,则make clean命令就不会执行clean目标文件;

#所以我们需要使用第一行代码将其声明为伪目标,这样,每次执行make clean命令,都会执行clean目标文件,即使当前目录下有clean文件也不例外;

 3 内核模块实例

  工具:kbuild system,linux内核使用的编译系统,内核是按照kbuild系统的格式编写的Makefile文件;

  前提:需要依赖已经编译过的内核源码进行编译;

  3.1 Makefile :编译内核模块的时候需要依赖内核源码进行编译,为了便于理解以及简化配置,本节示例依赖ubuntu系统内核源码;

  因为外部模块常常会加在内核中编译,外部模块单独编译时使用makefile工具,编译内核时使用kbuild工具,所以我们可以通过一些定义在内核最顶层的变量来判断当前模块是否是外部模块,应该使用什么命令来编译;

ifneq ($(KERNELRELEASE),)             #if KERNELRELEASE not euqal null,即如果之前定义过;
obj-m := hello.o                      #obj-m 表示编译生成.ko可加载模块;

else
PWD := $(shell pwd)                   #当前工作目录的绝对路径
KVER ?= $(shell uname -r)             #ubuntu的版本号
KDIR :=/lib/modules/$(KVER)/build     #ubuntu内核源码的绝对路径,手持机的内核源码绝对路径是.../kernel-3.10

all:
    make -C $(KDIR) M=$(PWD) modules  #-C表示kernel源文件目录,-M表示当前模块所在目录 modules表示编译成模块;
clean:
    make -C $(KDIR) M-$(PWD) clean 

endif

make -C $KDIR M=$PWD
#-C $KDIR 表示kbuild会在编译的时候跳转到内核源码的绝对路径KDIR执行,然后执行完之后返回;
#M=$PWD表示告诉kbuild,执行的命令为单独编译模块,模块的路径为PWD;

clean表示清除模块目录下的所有生成文件;

    3.1.1 条件判断 ifneq

ifneq( <参数1> ,<参数2>)
    #if 参数1 not equal to 参数2 ;
    #执行此处的makefile代码;
else
    #否则执行此处的代码;
endif

    3.1.2 KERNELRELEASE

#KERNELRELEASE为内核源码顶层的makefile中定义的一个变量
#如果是系统编译的话,会从顶层Makefile开始编译,那么KERNELRELEASE变量就会定义; #顶层的Makefile配置过的许多变量,以及未修改的.o和.ko之类的文件没必要重新编译; #如果是单个模块编译的话,KERNELRELEASE变量对于单个模块的Makefile而言,是没有定义过的变量; #那么就需要配置内核源文件目录等参数;目的是编译单个.ko模块;

    3.1.3 Makefile编译命令

obj -y  +=  hello.o 
#obj-y:直接编译进内核;

obj-m += hello.o
#obj-m:将hello.o作为模块编译,执行 make -C XX/kerneldir M=XX/moduledir modules  才会编译;

obj-$(CONFIG_HELLO) += hello.o
#obj-$(CONFIG_HELLO):根据.config决定是否将.o文件编译进内核;

obj-y    +=chardev_test.o
chardev_test-y    :=chardev.o test.o
#第二行表示第一行的chardev_test.o是由chardev.c 和 test.c一起编译成的;

obj-$(CONFIG_CHARDEV_TEST)    +=chardev_test.o
chardev_test-objs    :=chardev.o test.o
#第二行表示第一行的chardev_test.o是由chardev.c 和 test.c一起编译成的;

obj-m  :=chardev_test.o
chardev_test-y  :=chardev.o test.o
#
第二行表示第一行的chardev_test.o是由chardev.c 和 test.c一起编译成的;

    3.1.4 对于内核的Makefile,可以拆成Kbuild和Makefile来进行编写,也可以通过KERNELRELEASE变量将kbuild和makefile合并在一起;

#filename: Kbuild
obj-m  := 8123.o
8123-y := 8123_if.o 8123_pci.o 8123_bin.o

#filename: Makefile
ifneq ($(KERNELRELEASE),)
include Kbuild
else
KDIR ?= /lib/modules/`uname -r`/build
default:
    $(MAKE) -C $(KDIR) M=$$PWD

    3.1.5 Kbuild支持同时编译多个模块,以下举例为编译了foo.ko和bar.ko模块;

obj-m := foo.o bar.o
foo-y := <foo_srcs>
bar-y := <bar_srcs>

  3.2 hello.c模块文件

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
static int __init chardev_init(void)
{
    printk("chardev_init,hello! 
");
    return 0;
}

static void __exit chardev_exit(void)
{
    printk("chardev_exit,byebye! 
");
}
module_init(chardev_init);
module_exit(chardev_exit);
MODULE_LICENSE("GPL");

  3.3 模块命令

sudo insmod hello.ko     #加载hello.ko模块到ubuntu系统;
sudo rmmod hello.ko      #卸载hello.ko模块从ubuntu系统;
lsmod            #查看当前系统已经加载的模块信息;
dmesg            #查看内核日志,可以查看模块加载和卸载时的printk日志;
dmesg | tail -6  #查看内核最近输出的6条日志;        
原文地址:https://www.cnblogs.com/caesura-k/p/12627835.html