In the previous two chapters, we specifically tried compiling a .c source file into a built-in driver and a module driver. We found extensive use of Makefile files and did some simple writing. Now let's explain Makefile syntax and the make tool in detail.
1. What is a Makefile?
- Makefile is an ordinary text file that contains rules and commands for compiling programs.
- When we use the
makecommand, themaketool automatically reads theMakefileand executes the instructions step by step to compile the code for us. - Using
Makefilesaves us from typing long compilation commands manually each time, which is very convenient especially when there are many source files.
2. What is the make tool?
The make tool is a compilation assistant. It solves the problem of being very tedious to compile projects using commands.
- Reads the
Makefilefile in the current directory, then automatically completes:- Compilation
- Linking
- Cleanup (deleting temporary files).
- It only recompiles if source files have changed, otherwise it does nothing.
- Compiling kernel drivers and many large projects relies on
make+Makefilefor automated compilation.
3. What is the relationship between make and Makefile?
- Makefile: A "specification document" that clearly states:
- What files I want to generate (targets)
- Which source files these files depend on
- What commands to use to generate them
- make: A "worker that executes the specification"
- It doesn't guess how to compile on its own
- It only reads the Makefile
- Then executes the commands inside step by step
Common usage:
make # By default executes the first target in the Makefile
make clean # Executes the target named clean in the Makefile2
4. Most Basic Structure of Makefile
Let's look at the minimum version of Makefile structure first:
target: dependency1 dependency2 ...
<Tab>command1
<Tab>command22
3
Key explanations:
target: The filename you want to generate, or a "task name"dependency: Files/targets that must be prepared before generating this targetcommand: The actual shell command to execute Note: This must be preceded by a Tab key, not spaces!
A super simple example:
hello: hello.c
gcc hello.c -o hello2
Meaning:
- target:
hello - dependency:
hello.c - command:
gcc hello.c -o hello
Run it:
make # Will generate hello according to the Makefile
./hello # Run the program2
5. Writing a Simple Makefile from Scratch (User Space Program)
Create a simple file main.c and write content:
#include <stdio.h>
int main(void)
{
printf("Hello, Makefile!\n");
return 0;
}2
3
4
5
6
7
5.1 Create Makefile
Create a new file in the same directory named Makefile.
Write:
# 1. Define variables
CC = gcc # Specify the compiler to use
TARGET = main # Generated executable filename
# 2. Compilation rules
$(TARGET): main.c
$(CC) main.c -o $(TARGET)
# 3. Cleanup command
clean:
rm -f $(TARGET)2
3
4
5
6
7
8
9
10
11
Line-by-line explanation:
CC = gcc: Defines a variable CC with valuegccTARGET = main: Variable TARGET represents the generated program name$(TARGET): main.c:- target is
main - dependency is
main.c
- target is
$(CC) main.c -o $(TARGET):- Actual execution is:
gcc main.c -o main
- Actual execution is:
clean::- Defines a "task" called
clean rm -f $(TARGET)deletes the generated program
- Defines a "task" called
5.2 Hands-on Practice
In the terminal:
# First compilation
make
# Run the program
./main
# Cleanup
make clean2
3
4
5
6
7
8
You will see:
makeautomatically callsgcc main.c -o mainmake cleandeletes themainfile
6. Multiple Source Files
Create three files and write content:
main.cadd.cadd.h
main.c calls the add() function:
// main.c
#include <stdio.h>
#include "add.h"
int main(void)
{
int result = add(3, 5);
printf("3 + 5 = %d\n", result);
return 0;
}2
3
4
5
6
7
8
9
10
// add.c
#include "add.h"
int add(int a, int b)
{
return a + b;
}2
3
4
5
6
7
// add.h
int add(int a, int b);2
3
6.1 Traditional Compilation Commands (Manual)
You might do it this way:
gcc -c main.c # Generate main.o
gcc -c add.c # Generate add.o
gcc main.o add.o -o main2
3
4
5
Having to retype commands for every small change is annoying.
6.2 Using Makefile for Management
Write a more complete Makefile:
# Specify compiler
CC = gcc
# Compilation options
CFLAGS = -Wall -g
# Target program name
TARGET = main
# Source file list
SRCS = main.c add.c
# Automatically generate .o file list from source files
OBJS = $(SRCS:.c=.o)
# Default target
all: $(TARGET)
# Linking stage: Link all .o into final program
$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $(TARGET)
# Compilation stage: Compile .c to .o
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# Cleanup
clean:
rm -f $(OBJS) $(TARGET)2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Simple understanding:
SRCS = main.c add.c: All.cfilesOBJS = $(SRCS:.c=.o): Convertmain.c add.ctomain.o add.oall: $(TARGET): Default execution of all target, i.e., generate main$(TARGET): $(OBJS): First ensure all.ofiles are generated, then link%.o: %.c:- This is a "pattern rule"
- Any
xxx.ccan use this rule to producexxx.o $<represents the first dependency file (here the source file.c)$@represents the current target (here the.ofile)
Operation flow:
make # Automatically complete compilation + linking
./main # Run the executable
make clean # Cleanup files2
3
4
5
7. Kernel Driver Related Makefile
In previous chapters, you've seen driver Makefiles like this:
export ARCH=arm64
# Cross compiler absolute path prefix
export CROSS_COMPILE=/home/lckfb/TaishanPi-3-Linux/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
# Consistent with source file name
obj-m += hello_world.o
# Kernel source directory
KDIR := /home/lckfb/TaishanPi-3-Linux/kernel-6.1
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Let's break it down piece by piece.
7.1 Explanation of obj-m += hello_world.o?
- The kernel uses a build system called Kbuild
- For modules,
obj-mmeans "object file to be compiled as a module" hello_world.owill be compiled intohello_world.komodule
In other words, just write:
obj-m += hello_world.oAnd Kbuild knows you want to compile a kernel module named hello_world.
Tip:
If you have multiple modules, you can write:
obj-m += hello_world.o led_drv.o key_drv.o
7.2 Explanation of ARCH and CROSS_COMPILE?
export ARCH=arm64
# Cross compiler absolute path prefix
export CROSS_COMPILE=/home/lckfb/TaishanPi-3-Linux/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-2
3
4
ARCH: Target architecture, here it'sarm64, meaning we are compiling the kernel module for the arm64 platform If it's x86_64, it's usuallyARCH=x86_64.CROSS_COMPILE: Cross compiler prefix For example, the actual compiler being called will be:aarch64-none-linux-gnu-gccaarch64-none-linux-gnu-ld- …
export means: export these two variables as environment variables so that when make -C $(KDIR) is executed later, the kernel's Kbuild can also see them and use the correct compiler and architecture.
7.3 What do KDIR and PWD mean?
# Kernel source directory
KDIR := /home/lckfb/TaishanPi-3-Linux/kernel-6.1
PWD ?= $(shell pwd)2
3
KDIR: Specifies the kernel source/build directory path- The kernel source you configured on that development machine is in this directory
PWD: Current directory where the module code is located$(shell pwd)means execute thepwdcommand to get the current path?=means "if PWD is not defined outside, use this value"
Later we pass these two variables to make:
-C $(KDIR): First switch to the kernel source directory, then executemakeM=$(PWD): Tell the kernel: the module source code I want to compile is in the current directory
7.4 The all target
all:
make -C $(KDIR) M=$(PWD) modules2
Equivalent to manually typing in the terminal:
make -C /home/lckfb/TaishanPi-3-Linux/kernel-6.1 M=$(pwd) modulesMeaning:
-C $(KDIR): Switch to the kernel source directory/home/lckfb/TaishanPi-3-Linux/kernel-6.1and executemakethereM=$(PWD): Tell Kbuild the current module code directorymodules: Tell the kernel build system to only compile "external modules"
You can think of it as:
Wrapping "a bunch of commands for compiling kernel modules" into a simple
make. In the future, in the module directory, you only need to typemaketo complete module compilation and generatehello_world.ko.
7.5 The clean target
clean:
make -C $(KDIR) M=$(PWD) clean2
This command means:
- Still switch to the kernel source directory
$(KDIR) - Let Kbuild, according to
M=$(PWD), go to your module directory - Automatically clean up current module-related intermediate files and target files, such as:
hello_world.ohello_world.mod.chello_world.ko.tmp_versions/etc.
In actual use, you only need to type
make cleanin the module directory to clean up all previously compiled files, restoring the directory to "source code only" state.
8. Exercises
Exercise 1: Single File Program
- Write a
hello.cthat prints a sentence:
#include <stdio.h>
int main() {
printf("Hello, Makefile!\n");
return 0;
}2
3
4
5
6
- Write the simplest
Makefile:
hello: hello.c
gcc hello.c -o hello
clean:
rm -f hello2
3
4
5
- Operations:
make
./hello
make clean2
3
Exercise 2: Multi-File Program
Create three files and write content:
main.cadd.cadd.h
main.c calls the add() function:
// main.c
#include <stdio.h>
#include "add.h"
int main(void)
{
int result = add(3, 5);
printf("3 + 5 = %d\n", result);
return 0;
}2
3
4
5
6
7
8
9
10
// add.c
#include "add.h"
int add(int a, int b)
{
return a + b;
}2
3
4
5
6
7
// add.h
int add(int a, int b);2
3
Write the Makefile:
# Specify compiler
CC = gcc
# Compilation options: -Wall enables common warnings, -g for debugging (optional)
CFLAGS = -Wall -g
# Final executable filename
TARGET = main
# Source files
SRCS = main.c add.c
# Convert .c list to .o list, e.g., main.c -> main.o
OBJS = $(SRCS:.c=.o)
# Default target: when running make without specifying another target, execute all first
all: $(TARGET)
# Linking: Generate final executable from multiple .o files
$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $(TARGET)
# Pattern rule: any xxx.c compiles to xxx.o
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# Run target: first ensure $(TARGET) exists, then execute it
run: $(TARGET)
./$(TARGET)
# Cleanup target: delete intermediate files and final program
clean:
rm -f $(OBJS) $(TARGET)2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- Added a
runtarget in theMakefile, for example:
run: $(TARGET)
./$(TARGET)2
This allows you to: make run to complete compilation + running in one step.
Of course, you can also make first then ./main to run:
Exercise 3: Simple Kernel Module
We directly refer to the explanation in the chapter 【Writing Your First Driver】:
Copy hello_world.ko to the development board:
Run the following command to load the driver module:
sudo insmod hello_world.koSome users may notice that nothing happens after loading. This is because logs are automatically classified and hidden by the system. Run the following command to view logs related to hello_world:
dmesg | grep -E 'hello'At this point, our first hello world driver is complete.
9. Common Pitfalls
- Using spaces instead of Tab before commands
- Result:
makereports an error, or promptsmissing separator - Solution: Ensure the command line is preceded by a Tab character
- Result:
- Misspelled filename/target name
- Result:
No rule to make target 'xxx' - Solution: Check if the target and dependency names in the Makefile match the actual files
- Result:
- Forgetting clean
- Modified code but the old executable/module is still there
- Habit: When needing a complete rebuild, execute
make cleanonce
- Case sensitivity issues
- Linux file systems are case-sensitive
- Both
Makefileandmakefilecan be recognized, but it's recommended to consistently useMakefile
10. Summary
- Makefile = Compilation specification, tells make what the target is, what the dependencies are, and how to compile.
- make = Automated execution tool, helps you complete tedious commands according to Makefile instructions.
- Basic syntax has only one rule:
target: dependencies+ next line starting with Tab for commands. - Practice more:
- Start from one
.cfile - Then multiple
.cfiles - Then simple kernel modules
- Start from one
- When encountering incomprehensible Makefiles, don't be afraid:
- First find: target, dependencies, commands
- Understand line by line